feat(sp32): extract 837p NM1*82 (rendering provider NPI) per ClaimOutput
This commit is contained in:
@@ -142,6 +142,7 @@ class ClaimOutput(_Base):
|
|||||||
subscriber: Subscriber
|
subscriber: Subscriber
|
||||||
payer: Payer
|
payer: Payer
|
||||||
claim: ClaimHeader
|
claim: ClaimHeader
|
||||||
|
rendering_provider_npi: str | None = None # NM1*82 NM109 (Loop 2420A)
|
||||||
diagnoses: list[Diagnosis] = Field(default_factory=list)
|
diagnoses: list[Diagnosis] = Field(default_factory=list)
|
||||||
service_lines: list[ServiceLine] = Field(default_factory=list)
|
service_lines: list[ServiceLine] = Field(default_factory=list)
|
||||||
validation: ValidationReport
|
validation: ValidationReport
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ def _consume_claim(segments: list[list[str]], idx: int) -> tuple[ClaimOutput, in
|
|||||||
service_lines: list[ServiceLine] = []
|
service_lines: list[ServiceLine] = []
|
||||||
raw: list[list[str]] = [seg]
|
raw: list[list[str]] = [seg]
|
||||||
prior_auth: str | None = None
|
prior_auth: str | None = None
|
||||||
|
rendering_provider_npi: str | None = None
|
||||||
|
|
||||||
idx += 1
|
idx += 1
|
||||||
while idx < len(segments) and segments[idx][0] not in {"HL", "CLM", "SE"}:
|
while idx < len(segments) and segments[idx][0] not in {"HL", "CLM", "SE"}:
|
||||||
@@ -226,6 +227,11 @@ def _consume_claim(segments: list[list[str]], idx: int) -> tuple[ClaimOutput, in
|
|||||||
for code in parts[1:]:
|
for code in parts[1:]:
|
||||||
if code:
|
if code:
|
||||||
diagnoses.append(Diagnosis(code=code, qualifier=qualifier))
|
diagnoses.append(Diagnosis(code=code, qualifier=qualifier))
|
||||||
|
elif s[0] == "NM1" and len(s) > 1 and s[1] == "82":
|
||||||
|
# SP32: capture rendering provider NPI from Loop 2420A NM1*82.
|
||||||
|
# NM108 (idx 8) is the qualifier (typically "XX"), NM109 (idx 9) is the NPI.
|
||||||
|
if len(s) > 9 and s[8] == "XX" and len(s[9]) == 10 and s[9].isdigit():
|
||||||
|
rendering_provider_npi = s[9]
|
||||||
elif s[0] == "LX":
|
elif s[0] == "LX":
|
||||||
line_no = int(s[1]) if len(s) > 1 and s[1].isdigit() else len(service_lines) + 1
|
line_no = int(s[1]) if len(s) > 1 and s[1].isdigit() else len(service_lines) + 1
|
||||||
# LX is just a separator — the actual service line data is in the next SV1.
|
# LX is just a separator — the actual service line data is in the next SV1.
|
||||||
@@ -255,6 +261,7 @@ def _consume_claim(segments: list[list[str]], idx: int) -> tuple[ClaimOutput, in
|
|||||||
subscriber=Subscriber(first_name="", last_name="", member_id="", address=Address(line1="", city="", state="", zip="")),
|
subscriber=Subscriber(first_name="", last_name="", member_id="", address=Address(line1="", city="", state="", zip="")),
|
||||||
payer=Payer(name="", id=""),
|
payer=Payer(name="", id=""),
|
||||||
claim=claim_header,
|
claim=claim_header,
|
||||||
|
rendering_provider_npi=rendering_provider_npi,
|
||||||
diagnoses=diagnoses,
|
diagnoses=diagnoses,
|
||||||
service_lines=service_lines,
|
service_lines=service_lines,
|
||||||
validation=ValidationReport(passed=True, errors=[], warnings=[]),
|
validation=ValidationReport(passed=True, errors=[], warnings=[]),
|
||||||
@@ -296,7 +303,7 @@ def _consume_service_line(segments: list[list[str]], idx: int, line_no: int) ->
|
|||||||
provider_ref: str | None = None
|
provider_ref: str | None = None
|
||||||
|
|
||||||
idx += 1
|
idx += 1
|
||||||
while idx < len(segments) and segments[idx][0] not in {"LX", "HL", "CLM", "SE"}:
|
while idx < len(segments) and segments[idx][0] not in {"LX", "HL", "CLM", "SE", "NM1"}:
|
||||||
s = segments[idx]
|
s = segments[idx]
|
||||||
raw.append(s)
|
raw.append(s)
|
||||||
if s[0] == "DTP" and len(s) > 2 and s[1] == "472":
|
if s[0] == "DTP" and len(s) > 2 and s[1] == "472":
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
ISA*00* *00* *ZZ*11525703 *ZZ*COMEDASSISTPROG*260617*1937*^*00501*991102984*1*P*:~
|
||||||
|
GS*HC*11525703*COMEDASSISTPROG*20260617*193715*991102984*X*005010X222A1~
|
||||||
|
ST*837*991102984*005010X222A1~
|
||||||
|
BHT*0019*00*co-fixture-001*20260617*193715*CH~
|
||||||
|
NM1*41*2*Dzinesco*****46*11525703~
|
||||||
|
PER*IC*Tester*EM*tester@example.com~
|
||||||
|
NM1*40*2*COLORADO MEDICAL ASSISTANCE PROGRAM*****46*COMEDASSISTPROG~
|
||||||
|
HL*1**20*1~
|
||||||
|
PRV*BI*PXC*251E00000X~
|
||||||
|
NM1*85*2*TOC, Inc.*****XX*1881068062~
|
||||||
|
N3*1100 East Main St*Suite A~
|
||||||
|
N4*Montrose*CO*814014063~
|
||||||
|
REF*EI*721587149~
|
||||||
|
HL*2*1*22*0~
|
||||||
|
SBR*P*18*******MC~
|
||||||
|
NM1*IL*1*Balliache*Marianela****MI*P060946~
|
||||||
|
N3*1811 PAVILION DR APT 303~
|
||||||
|
N4*Montrose*CO*814016072~
|
||||||
|
DMG*D8*19590223*F~
|
||||||
|
NM1*PR*2*COHCPF*****PI*SKCO0~
|
||||||
|
CLM*t991102984o1c1d*85.40***12:B:1*Y*A*Y*Y~
|
||||||
|
REF*G1*3173~
|
||||||
|
HI*ABK:R69~
|
||||||
|
LX*1~
|
||||||
|
SV1*HC:T1019:U2*42.70*UN*6.00***1~
|
||||||
|
DTP*472*D8*20260602~
|
||||||
|
REF*6R*t991102984v769804d~
|
||||||
|
LX*2~
|
||||||
|
SV1*HC:T1019:U2*42.70*UN*6.00***1~
|
||||||
|
DTP*472*D8*20260603~
|
||||||
|
REF*6R*t991102984v770058d~
|
||||||
|
NM1*82*2*RENDERING PROVIDER*****XX*1234567893~
|
||||||
|
HL*3*1*22*0~
|
||||||
|
SBR*P*18*******MC~
|
||||||
|
NM1*IL*1*Barella*Victoria****MI*H582447~
|
||||||
|
N3*1900 Kellie DR~
|
||||||
|
N4*Montrose*CO*814019524~
|
||||||
|
DMG*D8*19570727*F~
|
||||||
|
NM1*PR*2*COHCPF*****PI*SKCO0~
|
||||||
|
CLM*t991102984o1c2d*155.76***12:B:1*Y*A*Y*Y~
|
||||||
|
REF*G1*3173~
|
||||||
|
HI*ABK:R69~
|
||||||
|
LX*1~
|
||||||
|
SV1*HC:S5130:U2*38.94*UN*6.00***1~
|
||||||
|
DTP*472*D8*20260602~
|
||||||
|
REF*6R*t991102984v769805d~
|
||||||
|
LX*2~
|
||||||
|
SV1*HC:S5130:U2*38.94*UN*6.00***1~
|
||||||
|
DTP*472*D8*20260603~
|
||||||
|
REF*6R*t991102984v770059d~
|
||||||
|
LX*3~
|
||||||
|
SV1*HC:S5130:U2*38.94*UN*6.00***1~
|
||||||
|
DTP*472*D8*20260604~
|
||||||
|
REF*6R*t991102984v770308d~
|
||||||
|
LX*4~
|
||||||
|
SV1*HC:S5130:U2*38.94*UN*6.00***1~
|
||||||
|
DTP*472*D8*20260605~
|
||||||
|
REF*6R*t991102984v770668d~
|
||||||
|
SE*45*991102984~
|
||||||
|
GE*1*991102984~
|
||||||
|
IEA*1*991102984~
|
||||||
@@ -68,3 +68,16 @@ def test_parse_uses_generic_config_when_requested():
|
|||||||
assert len(result.claims) == 1
|
assert len(result.claims) == 1
|
||||||
# No patient-loop rule on generic config
|
# No patient-loop rule on generic config
|
||||||
assert result.claims[0].validation.passed is True
|
assert result.claims[0].validation.passed is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_837_extracts_rendering_provider_npi_from_nm1_82():
|
||||||
|
"""SP32: NM1*82 (rendering provider) populates ClaimOutput.rendering_provider_npi."""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
text = Path("tests/fixtures/co_medicaid_837p_with_renderer.txt").read_text()
|
||||||
|
result = parse(text, PayerConfig.co_medicaid())
|
||||||
|
assert len(result.claims) >= 1
|
||||||
|
# Find the claim that has the rendering NPI (others may not).
|
||||||
|
matches = [c for c in result.claims if c.rendering_provider_npi is not None]
|
||||||
|
assert len(matches) == 1
|
||||||
|
assert matches[0].rendering_provider_npi == "1234567893"
|
||||||
|
|||||||
Reference in New Issue
Block a user