fix(sp41): reset member_id on new CLP + tighten status-preservation test

This commit is contained in:
sp41-bot
2026-07-07 19:53:04 -06:00
parent 52795adfb1
commit f8e2f0933e
3 changed files with 16 additions and 5 deletions
@@ -55,6 +55,7 @@ def parse_835_svc(path: str | Path) -> "Iterator[SvcRow]":
elif seg_name == "CLP": elif seg_name == "CLP":
current_claim_id = elems[1] if len(elems) > 1 else "" current_claim_id = elems[1] if len(elems) > 1 else ""
current_status = elems[2] if len(elems) > 2 else "" current_status = elems[2] if len(elems) > 2 else ""
current_member_id = ""
elif seg_name == "NM1" and len(elems) > 1 and elems[1] == "QC": elif seg_name == "NM1" and len(elems) > 1 and elems[1] == "QC":
current_member_id = elems[9] if len(elems) > 9 else "" current_member_id = elems[9] if len(elems) > 9 else ""
elif seg_name == "SVC" and current_claim_id: elif seg_name == "SVC" and current_claim_id:
+1 -1
View File
@@ -1 +1 @@
ISA*00* *00* *ZZ*CYCLONE *ZZ*GAINWELL *260101*1200*^*00501*000000001*0*P*:~GS*HC*CYCLONE*GAINWELL*20260101*1200*1*X*005010X221A1~ST*835*0001~BPR*I*100.00*C*ACH*CCP*01*021000021*DA*123456789*1512345678**01*021000021*DA*123456789*20260101~TRN*1*TRACE01*1512345678~DTM*405*20260118~N1*PR*COLORADO MEDICAL ASSISTANCE PROGRAM*XV*COMEDASSISTPROG~N3*PO BOX 1100~N4*DENVER*CO*80202~REF*2U*7912900843~LX*1~CLP*T1001*1*16.24*16.10**MC*2026029105200*11*1~NM1*QC*1*AALBUE*ERIC****MR*J813715~NM1*74*1*AALBUE*ERIC*W***C*J813715~DTM*232*20260118~DTM*233*20260124~SVC*HC:T1019:U2:SC:KX*2.32*2.30**.33~DTM*472*20260118~CAS*CO*45*.02~REF*G1*6252960154~REF*6R*T1001V001~AMT*B6*2.30~SVC*HC:T1019:U2:SC:KX*2.32*2.30**.33~DTM*472*20260119~CAS*OA*18*2.32~SVC*HC:T1019:U2:SC:KX*2.32*-2.30**.33~DTM*472*20260120~CAS*CO*45*-.02~SE*23*0001~GE*1*1~IEA*1*000000001~ ISA*00* *00* *ZZ*CYCLONE *ZZ*GAINWELL *260101*1200*^*00501*000000001*0*P*:~GS*HC*CYCLONE*GAINWELL*20260101*1200*1*X*005010X221A1~ST*835*0001~BPR*I*100.00*C*ACH*CCP*01*021000021*DA*123456789*1512345678**01*021000021*DA*123456789*20260101~TRN*1*TRACE01*1512345678~DTM*405*20260118~N1*PR*COLORADO MEDICAL ASSISTANCE PROGRAM*XV*COMEDASSISTPROG~N3*PO BOX 1100~N4*DENVER*CO*80202~REF*2U*7912900843~LX*1~CLP*T1001*1*16.24*16.10**MC*2026029105200*11*1~NM1*QC*1*AALBUE*ERIC****MR*J813715~NM1*74*1*AALBUE*ERIC*W***C*J813715~DTM*232*20260118~DTM*233*20260124~SVC*HC:T1019:U2:SC:KX*2.32*2.30**.33~DTM*472*20260118~CAS*CO*45*.02~REF*G1*6252960154~REF*6R*T1001V001~AMT*B6*2.30~SVC*HC:T1019:U2:SC:KX*2.32*2.30**.33~DTM*472*20260119~CAS*OA*18*2.32~SVC*HC:T1019:U2:SC:KX*2.32*-2.30**.33~DTM*472*20260120~CAS*CO*45*-.02~LX*2~CLP*T1002*4*16.24*0**MC*2026029105201*11*1~NM1*QC*1*OTHER-A*FIRST****MR*OTHER-MEMBER-A~SVC*HC:T1019*2.32*0**.33~DTM*472*20260121~CAS*CO*97*2.32~LX*3~CLP*T1003*22*16.24*-16.10**MC*2026029105202*11*1~NM1*QC*1*OTHER-B*SECOND****MR*OTHER-MEMBER-B~SVC*HC:T1019*2.32*-2.30**.33~DTM*472*20260122~SE*23*0001~GE*1*1~IEA*1*000000001~
+14 -4
View File
@@ -1,19 +1,28 @@
"""835 SVC-level parser with member_id at SVC scope.""" """835 SVC-level parser with member_id at SVC scope."""
from pathlib import Path from pathlib import Path
from collections import defaultdict
from decimal import Decimal from decimal import Decimal
from cyclone.rebill.parse_835_svc import parse_835_svc from cyclone.rebill.parse_835_svc import parse_835_svc
FIX = Path(__file__).parent / "fixtures" / "835_sample_svc_with_member.txt" FIX = Path(__file__).parent / "fixtures" / "835_sample_svc_with_member.txt"
def test_parse_835_svc_extracts_member_id(): def test_parse_835_svc_extracts_member_id():
"""NM1*QC NM109 at the CLP scope must propagate to every SVC row.""" """NM1*QC NM109 at the CLP scope must propagate to that CLP's SVC rows;
the multi-claim fixture confirms each CLP's NM1*QC lands on its own SVCs."""
rows = list(parse_835_svc(FIX)) rows = list(parse_835_svc(FIX))
assert len(rows) >= 1 assert len(rows) >= 1
by_claim: dict[str, list[str]] = defaultdict(list)
for r in rows: for r in rows:
assert r.member_id == "J813715" by_claim[r.claim_id].append(r.member_id)
assert r.procedure # non-empty assert r.procedure # non-empty
assert r.svc_date # non-empty assert r.svc_date # non-empty
assert r.charge > 0 assert r.charge > 0
# First CLP (T1001) -> original fixture member
assert all(mid == "J813715" for mid in by_claim["T1001"]), by_claim
# Second CLP (T1002, status 4) -> its own NM1*QC
assert all(mid == "OTHER-MEMBER-A" for mid in by_claim["T1002"]), by_claim
# Third CLP (T1003, status 22) -> its own NM1*QC
assert all(mid == "OTHER-MEMBER-B" for mid in by_claim["T1003"]), by_claim
def test_parse_835_svc_extracts_cas_reasons(): def test_parse_835_svc_extracts_cas_reasons():
"""CAS segments after DTM*472 must be captured (post-DTM*472 ordering).""" """CAS segments after DTM*472 must be captured (post-DTM*472 ordering)."""
@@ -22,7 +31,8 @@ def test_parse_835_svc_extracts_cas_reasons():
assert any("OA-18" in r.cas_reasons for r in rows) assert any("OA-18" in r.cas_reasons for r in rows)
def test_parse_835_svc_picks_up_status_22_reversals(): def test_parse_835_svc_picks_up_status_22_reversals():
"""Status 22 (reversal of previous payment) must be preserved.""" """Status 22 (reversal of previous payment) must be preserved, along with
status 1 (primary) and status 4 (denied)."""
rows = list(parse_835_svc(FIX)) rows = list(parse_835_svc(FIX))
statuses = {r.status for r in rows} statuses = {r.status for r in rows}
assert statuses & {"1", "4", "22"} # at least one of each present assert statuses == {"1", "4", "22"}