From c4bc11855749613c713bde8909eae39648fad30c Mon Sep 17 00:00:00 2001 From: Nora Date: Thu, 2 Jul 2026 17:06:25 -0600 Subject: [PATCH] feat(sp32): wire rendering_provider_npi and service_provider_npi through ORM builders --- backend/src/cyclone/store/orm_builders.py | 8 ++++ backend/tests/test_reconcile.py | 54 +++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/backend/src/cyclone/store/orm_builders.py b/backend/src/cyclone/store/orm_builders.py index b3a38b0..714a308 100644 --- a/backend/src/cyclone/store/orm_builders.py +++ b/backend/src/cyclone/store/orm_builders.py @@ -53,6 +53,10 @@ def _claim_837_row(claim: ClaimOutput, batch_id: str) -> Claim: service_date_to=d_to, charge_amount=Decimal(claim.claim.total_charge or 0), provider_npi=claim.billing_provider.npi, + # SP32: wire NM1*82 (Loop 2420A) rendering provider NPI from + # ClaimOutput (T3 parser extraction) into the typed ORM column + # (T1 migration 0019). Falls back to None if absent. + rendering_provider_npi=claim.rendering_provider_npi, payer_id=claim.payer.id, state=ClaimState.SUBMITTED, raw_json=json.loads(claim.model_dump_json()), @@ -89,6 +93,10 @@ def _remittance_835_row(cp: ClaimPayment, batch_id: str) -> Remittance: total_paid=Decimal(cp.total_paid or 0), patient_responsibility=cp.patient_responsibility, adjustment_amount=adjustment, + # SP32: wire NM1*1P (Loop 2100) service provider NPI from + # ClaimPayment (T2 parser extraction) into the typed ORM column + # (T1 migration 0019). Falls back to None if absent. + rendering_provider_npi=cp.service_provider_npi, received_at=received_at, service_date=service_date, is_reversal=cp.status_code in ("21", "22"), diff --git a/backend/tests/test_reconcile.py b/backend/tests/test_reconcile.py index 63b2424..d4827ba 100644 --- a/backend/tests/test_reconcile.py +++ b/backend/tests/test_reconcile.py @@ -792,3 +792,57 @@ def test_reconcile_run_no_match_leaves_remit_unlinked(db_session, make_claim, ma assert claim.state == ClaimState.SUBMITTED # unchanged db_session.refresh(remit) assert remit.claim_id is None # unchanged + + +# --- SP32 Task 4: ORM builders wire rendering_provider_npi ----------------- + + +def test_claim_837_row_includes_rendering_provider_npi(): + """SP32: Claim.rendering_provider_npi is populated from ClaimOutput in ORM builder.""" + from pathlib import Path + from cyclone.parsers.parse_837 import parse + from cyclone.parsers.payer import PayerConfig + from cyclone.store.orm_builders import _claim_837_row + + text = Path("tests/fixtures/co_medicaid_837p_with_renderer.txt").read_text() + parsed = parse(text, PayerConfig.co_medicaid()) + # Find the claim whose rendering NPI was extracted. + targets = [c for c in parsed.claims if c.rendering_provider_npi == "1234567893"] + assert len(targets) == 1 + row = _claim_837_row(targets[0], batch_id="test-batch") + assert row.rendering_provider_npi == "1234567893" + + +def test_remittance_835_row_includes_service_provider_npi(): + """SP32: Remittance.rendering_provider_npi is populated from ClaimPayment in ORM builder.""" + from cyclone.parsers.parse_835 import parse as parse_835 + from cyclone.store.orm_builders import _remittance_835_row + + text = ( + "ISA*00* *00* *ZZ*RECEIVERID *ZZ*SENDERID *250101*1200*^*00501*000000001*0*P*:~" + "GS*HP*RECEIVER*SENDER*20250101*1200*1*X*005010X221A1~" + "ST*835*0001~" + "BPR*I*85.40*C*NON*****01*021000021*DA*123456*1511111**01*031302955*DA*9876543~" + "TRN*1*1511111*1511111~" + "DTM*405*20250101~" + "N1*PR*COLORADO MEDICAID*XV*CO MEDICAID~" + "N3*PO BOX 1100*~" + "N4*DENVER*CO*80203~" + "REF*2U*12345~" + "PER*BL*SUPPORT*TE*8005551212~" + "N1*PE*ACME CLINIC*XX*1111111111~" + "LX*1~" + "CLP*CLM001*1*85.40*85.40*0.00*MC*CLM001*11*1~" + "CAS*PR*1*0.00~" + "NM1*1P*2*RENDERING PROVIDER*****XX*2222222222~" + "SVC*HC:99213*85.40*85.40**1~" + "DTM*472*20250101~" + "SE*18*0001~" + "GE*1*1~" + "IEA*1*000000001~" + ) + parsed = parse_835(text, payer_config=None) # type: ignore[arg-type] + assert len(parsed.claims) == 1 + assert parsed.claims[0].service_provider_npi == "2222222222" + row = _remittance_835_row(parsed.claims[0], batch_id="test-batch") + assert row.rendering_provider_npi == "2222222222"