Commit Graph

6 Commits

Author SHA1 Message Date
Nora d8841834dc fix(sp27): Claim.patient_control_number populated from CLM01 (claim_id), not 2010BA NM109 (member_id)
The 837 ingest path (_claim_837_row in store.py:194) populated
Claim.patient_control_number from claim.subscriber.member_id — the
subscriber's 2010BA NM109 Medicaid ID — instead of from
claim.claim_id (CLM01, the claim submitter's identifier the 837
actually sent).

That silently broke every downstream join that uses this column as a
cross-reference key:

  * reconcile.match() (reconcile.py:74) — joins
    Claim.patient_control_number against
    Remittance.payer_claim_control_number (which is parsed from CLP01,
    the 835's echo of CLM01 per X12 spec). Member_id never matches
    CLP01, so auto-match always fails.
  * apply_999_rejections — same lookup, same broken key.
  * apply_277ca_rejections — same lookup, same broken key.
  * scoring.score_pair — same broken key.

Live DB probe (1,739 remits, 337 claims):
  * Claim.id == Remit.payer_claim_control_number    → 0 matches
  * Claim.patient_control_number == Remit.payer_claim_control_number
    → 9 matches (substring coincidences; synthetic PCN strings share
    alphanumeric characters with the human-readable member_ids)
  * Claim.matched_remittance_id NOT NULL             → 0 claims

This commit changes _claim_837_row to write
Claim.patient_control_number = claim.claim_id (= CLM01). The
reconcile matcher's existing join now hits the row the 835 echoes
back. Companion migration 0017 backfills the 337 pre-fix rows so
they're on equal footing with new ingests (UPDATE claims SET
patient_control_number = id WHERE patient_control_number IS DISTINCT
FROM id — idempotent).

Tests:
  * 2 new RED→GREEN tests in test_store_reconcile.py:
    - test_837_ingest_populates_patient_control_number_from_claim_id
      pins the field semantics directly
    - test_837_then_835_with_echoed_pcn_auto_pairs proves the full
      end-to-end auto-match now fires
  * 3 existing manual_match tests that relied on the bug (had setup
    using member_id != claim_id to deliberately prevent auto-match)
    updated to use distinct PCNs explicitly so the tests still
    exercise the manual-match path with a real orphan pair.
  * 3 store_claim_detail / api_gets tests adjusted for the same
    reason.

Verified: 1,176 / 1,177 backend tests pass; the one failure is the
pre-existing flake in test_provider_extended_response.py noted before
this work started.

Caveat for the existing dev DB: the 1,739 remits already in the DB
were ingested from 835 fixtures whose CLP01 is a different synthetic
identifier than the 837's CLM01 (the test fixtures never echoed
CLM01 — a fixture-data limitation, not a code bug). After this fix,
new 837+835 ingest pairs whose payer echoes CLM01 in CLP01 will
auto-match as expected. The pre-existing 1,739 remits will continue
to land in the unmatched bucket; that can only be fixed by
regenerating the test fixtures (out of scope for this SP).
2026-06-29 14:32:10 -06:00
Nora f5d119fbe7 feat(sp27): pin Claim↔Remit matched-pair invariant + startup drift audit
Add check_matched_pair_drift() at module level in store.py — read-only
audit of the Claim.matched_remittance_id ↔ Remittance.claim_id FK pair.
Logs WARNING on drift with up to 5 examples of each of two cases
(claim-side and remit-side), returns the count of drifted rows. Wired
into api.py::lifespan right after db.init_db() and ensure_clearhouse_seeded(),
wrapped in try/except so a query failure logs but doesn't crash boot.

The symmetric-write + symmetric-clear invariants on
manual_match / manual_unmatch were already correct; this commit pins
them with focused regression tests in test_store_match_invariant.py
(8 tests covering both directions, both happy-path, rollback pin, and
drift-check unit behavior). PCN asymmetry in the fixture prevents the
auto-match inside CycloneStore.add (Task 10) from pre-pairing, so
manual_match is the only writer.

Migration 0016 adds the missing ix_claims_matched_remittance_id index —
the drift check scans WHERE matched_remittance_id IS NOT NULL on every
boot and would become a full-table scan past ~10k claims. Symmetric
with ix_remittances_claim_id (added in 0007). Migration tests bumped
from head=15 to head=16.
2026-06-29 12:21:34 -06:00
Tyler 2194d35ea1 fix(sp22): correct test fixtures in SP22 migration tests
- test_migration_latest_idempotent_on_fresh_db now uses the pytest
  tmp_path fixture instead of a literal /tmp path that did not exist,
  so sqlite can create the test DB.
- test_drop_claims_unique_constraint_migration now inserts the NOT
  NULL batches.kind / input_filename / parsed_at columns when seeding
  the parent row for the two same-PCN claims.
2026-06-23 16:18:01 -06:00
Tyler 4fd55dc33e feat(sp22): migration 0015 — drop inline UNIQUE on claims, add tests
Adds migration 0015_drop_claims_unique_constraint.sql that recreates the
claims table without the inline UNIQUE(batch_id, patient_control_number)
constraint, plus tests proving the migration runs cleanly and the
constraint is not re-introduced.
2026-06-23 16:11:42 -06:00
Tyler 934d623d3a feat(backend): harden db_migrate — rollback test, non-sql filter test, doc convention 2026-06-19 21:25:15 -06:00
Tyler 1bd2334e40 feat(backend): add PRAGMA user_version migration runner + 0001_initial 2026-06-19 21:19:43 -06:00