Skip to content

fix(seeder): make phase2 enrichment idempotent #312

@w7-mgfcode

Description

@w7-mgfcode

Problem

POST /seeder/phase2-enrichment (app/features/seeder/service.py phase2_enrichment(...); route at app/features/seeder/routes.py:313) inserts into replenishment_event, exogenous_signal, and sales_returns without conflict handling. A second call against an already-enriched database raises IntegrityError on uq_exogenous_signal_per_store (constraint name confirmed during the PRP-38 dogfood), and the unhandled exception surfaces as HTTP 500 instead of a clean RFC 7807 problem+json.

Repro

docker compose up -d
uv run alembic upgrade head
# Seed once, then enrich twice.
curl -s -X POST http://localhost:8123/seeder/generate -d '{"scenario":"showcase_rich","seed":42,"force":true}' -H 'Content-Type: application/json'
curl -s -X POST http://localhost:8123/seeder/phase2-enrichment -H 'Content-Type: application/json' -d '{}'   # → 200 (first call succeeds)
curl -s -X POST http://localhost:8123/seeder/phase2-enrichment -H 'Content-Type: application/json' -d '{}'   # → 500 (IntegrityError: uq_exogenous_signal_per_store)

Scope

The bug is reachable only on a manual second call. The showcase pipeline (POST /demo/run with scenario=showcase_rich) always seeds first inside the same run before calling phase2_enrichment, so the pipeline path never hits the duplicate state.

Fix options (~10-20 LOC)

  1. Idempotent insert. Add .on_conflict_do_nothing() to the three Phase-2 inserts (replenishment_event, exogenous_signal, sales_returns) so a repeat call is a no-op. Cleanest, matches the app/features/ingest/service.py upsert idiom.
  2. Clean 422. Catch IntegrityError in the service, raise UnprocessableEntityError("Phase 2 already enriched") → RFC 7807 422. Cheaper but explicit-failure UX (the operator must drop the existing enrichment to re-run).

Recommendation: option 1 plus a route test that calls the endpoint twice and asserts both responses are 200.

Notes

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfixBug fix

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions