4 Commits

Author SHA1 Message Date
Nora b19f43f448 fix(sp41): log dedup raise + TestClient coverage for api_routers special-case 2026-07-07 22:40:17 -06:00
Nora 2dfe213af0 feat(sp41): wire claim-id dedup into submit_file pre-flight
Inserts a per-claim-id check_duplicate() walk into submit_file between
the payer-mismatch guard and the BatchRecord837 DB write (Task 9).

If any CLM01 in the parsed 837P file was submitted within the 30-day
window recorded in the submission_dedup table, submit_file raises
DuplicateClaimError before any DB write or SFTP upload. The DB-first,
upload-second invariant is preserved: a duplicate blocks both the
BatchRecord837 write AND the paramiko upload.

The api_routers/submission.submit_batch per-file loop special-cases
DuplicateClaimError BEFORE the generic Exception handler so the
200-with-results status-code contract documented at the top of the
module is preserved (per-file failures stay in the body). The
exception remains 409-class by design — a future caller that
propagates it past the per-file loop will surface 409 at the FastAPI
layer.

Approach A per the Task 9 spec (raise, don't return a SubmitResult).

Tests:
- test_submission_dedup.test_submit_file_raises_on_duplicate_claim_id
  pre-populates the dedup table via record_submission('CLM001', ...),
  calls submit_file against minimal_837p.txt, expects DuplicateClaimError
  with structured claim_id / original_submission_at attributes, and
  pins the DB-write invariant (no Batch row landed) plus an
  AssertionError sentinel on the SFTP factory.
2026-07-07 22:33:42 -06:00
Nora 53df3f0684 refactor(sp41): align submission_dedup with cyclone.store conventions (main Base, db.SessionLocal, now(timezone.utc), exceptions.py) 2026-07-07 22:16:07 -06:00
Nora 0e130579b4 feat(sp41): claim-id dedup at SFTP pre-flight (30-day window, configurable) 2026-07-07 22:02:46 -06:00