feat(sp32): extract 835 NM1*1P (service provider NPI) per ClaimPayment
This commit is contained in:
@@ -160,6 +160,7 @@ class ClaimPayment(_Base):
|
|||||||
ref_benefit_plan: str | None = None # REF*CE per the CO guide
|
ref_benefit_plan: str | None = None # REF*CE per the CO guide
|
||||||
service_payments: list[ServicePayment] = Field(default_factory=list)
|
service_payments: list[ServicePayment] = Field(default_factory=list)
|
||||||
raw_segments: list[list[str]] = Field(default_factory=list)
|
raw_segments: list[list[str]] = Field(default_factory=list)
|
||||||
|
service_provider_npi: str | None = None # NM1*1P NM109 (Loop 2100 service provider)
|
||||||
|
|
||||||
@model_validator(mode="before")
|
@model_validator(mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -376,6 +376,7 @@ def _consume_claim_payment(segments: list[list[str]], idx: int) -> tuple[ClaimPa
|
|||||||
|
|
||||||
service_payments: list[ServicePayment] = []
|
service_payments: list[ServicePayment] = []
|
||||||
ref_benefit_plan: str | None = None
|
ref_benefit_plan: str | None = None
|
||||||
|
service_provider_npi: str | None = None
|
||||||
per_diem: Decimal | None = None
|
per_diem: Decimal | None = None
|
||||||
status_label = claim_status_label(status)
|
status_label = claim_status_label(status)
|
||||||
|
|
||||||
@@ -422,9 +423,16 @@ def _consume_claim_payment(segments: list[list[str]], idx: int) -> tuple[ClaimPa
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
per_diem = None
|
per_diem = None
|
||||||
elif s[0] == "NM1":
|
elif s[0] == "NM1":
|
||||||
# Patient (QC) / service-provider (1P) — captured in raw_segments.
|
# SP32: capture service-provider NPI from NM1*1P (Loop 2100).
|
||||||
# The 835 spec doesn't require a structured patient model in v1.
|
# NM108 (idx 8) carries the ID qualifier (typically "XX");
|
||||||
pass
|
# NM109 (idx 9) is the value. Some senders omit NM108; accept
|
||||||
|
# both forms but require a 10-digit ID.
|
||||||
|
if len(s) > 9 and s[1] == "1P":
|
||||||
|
if len(s) > 8 and s[8] == "XX" and s[9]:
|
||||||
|
if s[9].isdigit() and len(s[9]) == 10:
|
||||||
|
service_provider_npi = s[9]
|
||||||
|
elif s[9] and s[9].isdigit() and len(s[9]) == 10:
|
||||||
|
service_provider_npi = s[9]
|
||||||
elif s[0] == "DTM":
|
elif s[0] == "DTM":
|
||||||
# Claim-level dates — captured in raw_segments.
|
# Claim-level dates — captured in raw_segments.
|
||||||
pass
|
pass
|
||||||
@@ -446,6 +454,7 @@ def _consume_claim_payment(segments: list[list[str]], idx: int) -> tuple[ClaimPa
|
|||||||
ref_benefit_plan=ref_benefit_plan,
|
ref_benefit_plan=ref_benefit_plan,
|
||||||
service_payments=service_payments,
|
service_payments=service_payments,
|
||||||
raw_segments=raw,
|
raw_segments=raw,
|
||||||
|
service_provider_npi=service_provider_npi,
|
||||||
),
|
),
|
||||||
idx,
|
idx,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -178,3 +178,35 @@ def test_parse_service_payment_units_default_unit_type_to_un():
|
|||||||
# Default to UN when SVC04 missing but units present.
|
# Default to UN when SVC04 missing but units present.
|
||||||
assert s1.unit_type == "UN", s1.unit_type
|
assert s1.unit_type == "UN", s1.unit_type
|
||||||
assert s2.unit_type == "UN", s2.unit_type
|
assert s2.unit_type == "UN", s2.unit_type
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_835_extracts_service_provider_npi_from_nm1_1p():
|
||||||
|
"""SP32: NM1*1P in Loop 2100 populates ClaimPayment.service_provider_npi."""
|
||||||
|
from cyclone.parsers.parse_835 import parse as parse_835
|
||||||
|
|
||||||
|
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~"
|
||||||
|
)
|
||||||
|
result = parse_835(text, payer_config=None) # type: ignore[arg-type]
|
||||||
|
assert len(result.claims) == 1
|
||||||
|
assert result.claims[0].service_provider_npi == "2222222222"
|
||||||
|
|||||||
Reference in New Issue
Block a user