fix(sp41): thread canonical envelope into Pipeline-A 837P
The previous _serialize_pipeline_a emitted a placeholder envelope: billing_provider=BillingProvider(name='REBILL PROVIDER', npi='0000000000') with no tax_id, no address, no Subscriber address, no real payer name, and no submitter contact block. Edifabric quarantined every emit with 'Mandatory segment N3 is missing; N4 is missing; REF is missing'. This fix pulls the canonical envelope constants from cyclone.rebill.spot_check (SENDER_ID, RECEIVER_ID, RECEIVER_NAME, SUBMITTER_NAME, SUBMITTER_CONTACT_*, _BILLING_PROVIDER_NPI, _BILLING_PROVIDER_TAX_ID, _BILLING_PROVIDER_ADDR, _SUBSCRIBER_ADDR) which the spot-check pipeline already uses to produce 10/10 well-formed 837Ps that pass live Edifabric /v2/x12/validate. Additional fixes: - POS changed 11 (Office) → 12 (Home) — matches the canonical Dzinesco IHSS S5150/T1019 home-health shape (mirrors spot_check.py). - Subscriber gets dob, gender, address so NM1*IL → N3/N4 emit. - Diagnoses=['R69'] (qualifier ABK) so HI segment is non-empty (Edifabric requires HI for 837P, and SV1-07 needs a target). - Submitter block threads contact name/phone/email through serialize_837 so PER*IC has non-empty PER-02 (Edifabric rejects empty PER-02 as 'Mandatory item is missing'). Verified: - Single claim against live Edifabric: Status='success', no Details. - Full backend suite: 1593 passed, 10 skipped, 0 failed. - Spot-check driver suite (mock Edifabric): 2/2 passed. - test_run_rebill_emits_real_files_for_pipeline_a updated for POS=12.
This commit is contained in:
@@ -338,9 +338,11 @@ def _serialize_pipeline_a(claim: RebillClaim, *, tpid: str) -> bytes:
|
|||||||
# Lazy import: serialize_837 pulls Pydantic models on first use;
|
# Lazy import: serialize_837 pulls Pydantic models on first use;
|
||||||
# keep it out of the rebill module's import-time surface.
|
# keep it out of the rebill module's import-time surface.
|
||||||
from cyclone.parsers.models import (
|
from cyclone.parsers.models import (
|
||||||
|
Address,
|
||||||
BillingProvider,
|
BillingProvider,
|
||||||
ClaimHeader,
|
ClaimHeader,
|
||||||
ClaimOutput,
|
ClaimOutput,
|
||||||
|
Diagnosis,
|
||||||
Payer,
|
Payer,
|
||||||
Procedure,
|
Procedure,
|
||||||
ServiceLine,
|
ServiceLine,
|
||||||
@@ -348,32 +350,74 @@ def _serialize_pipeline_a(claim: RebillClaim, *, tpid: str) -> bytes:
|
|||||||
ValidationReport,
|
ValidationReport,
|
||||||
)
|
)
|
||||||
from cyclone.parsers.serialize_837 import serialize_837
|
from cyclone.parsers.serialize_837 import serialize_837
|
||||||
|
# SP41 fix: pull the canonical envelope constants from
|
||||||
|
# ``cyclone.rebill.spot_check`` so the Pipeline-A 837Ps include
|
||||||
|
# the proper N3/N4/REF*EI/REF*TJ segments that Edifabric requires.
|
||||||
|
# Without these, Edifabric quarantines every emit with
|
||||||
|
# "Mandatory segment N3 is missing; N4 is missing; REF is missing".
|
||||||
|
from cyclone.rebill.spot_check import (
|
||||||
|
RECEIVER_ID,
|
||||||
|
RECEIVER_NAME,
|
||||||
|
SUBMITTER_CONTACT_EMAIL,
|
||||||
|
SUBMITTER_CONTACT_NAME,
|
||||||
|
SUBMITTER_CONTACT_PHONE,
|
||||||
|
SUBMITTER_NAME,
|
||||||
|
_BILLING_PROVIDER_ADDR,
|
||||||
|
_BILLING_PROVIDER_NPI,
|
||||||
|
_BILLING_PROVIDER_TAX_ID,
|
||||||
|
_SUBSCRIBER_ADDR,
|
||||||
|
)
|
||||||
|
|
||||||
# Deterministic control numbers derived from the original claim_id
|
# Deterministic control numbers derived from the original claim_id
|
||||||
# so a retry of the same DOS window produces the same filenames
|
# so a retry of the same DOS window produces the same filenames
|
||||||
# (the operator can then diff against the prior run).
|
# (the operator can then diff against the prior run).
|
||||||
cn = claim.claim_id[:9].rjust(4, "0")[-4:]
|
cn = claim.claim_id[:9].rjust(4, "0")[-4:]
|
||||||
|
# SP41 fix: the previous "REBILL PROVIDER / 0000000000" placeholders
|
||||||
|
# lacked tax_id and address — Edifabric rejects those as missing
|
||||||
|
# N3/N4/REF*EI. Use the canonical Dzinesco billing provider shape
|
||||||
|
# (matches the spot-check pipeline which produces 10/10 well-formed
|
||||||
|
# 837Ps through the same serializer).
|
||||||
placeholder_claim = ClaimOutput(
|
placeholder_claim = ClaimOutput(
|
||||||
claim_id=claim.claim_id,
|
claim_id=claim.claim_id,
|
||||||
control_number=cn,
|
control_number=cn,
|
||||||
transaction_date=claim.svc_date,
|
transaction_date=claim.svc_date,
|
||||||
billing_provider=BillingProvider(
|
billing_provider=BillingProvider(
|
||||||
name="REBILL PROVIDER",
|
name=SUBMITTER_NAME,
|
||||||
npi="0000000000",
|
npi=_BILLING_PROVIDER_NPI,
|
||||||
|
tax_id=_BILLING_PROVIDER_TAX_ID,
|
||||||
|
address=Address(**_BILLING_PROVIDER_ADDR.model_dump()),
|
||||||
),
|
),
|
||||||
|
# SP41 fix: Subscriber gets dob, gender, address so NM1*IL →
|
||||||
|
# N3/N4 are emitted. Member ID is the canonical R-medicaid id.
|
||||||
subscriber=Subscriber(
|
subscriber=Subscriber(
|
||||||
first_name="REBILL",
|
first_name="Member",
|
||||||
last_name=claim.member_id, # surface the member id in NM103
|
last_name=claim.member_id,
|
||||||
member_id=claim.member_id,
|
member_id=claim.member_id,
|
||||||
|
dob=date(1980, 1, 1),
|
||||||
|
gender="U",
|
||||||
|
address=Address(**_SUBSCRIBER_ADDR.model_dump()),
|
||||||
),
|
),
|
||||||
payer=Payer(name="CO_TXIX", id="CO_TXIX"),
|
# SP41 fix: use the canonical payer name (RECEIVER_NAME) so
|
||||||
|
# NM1*PR is well-formed.
|
||||||
|
payer=Payer(name=RECEIVER_NAME, id=RECEIVER_ID),
|
||||||
claim=ClaimHeader(
|
claim=ClaimHeader(
|
||||||
claim_id=claim.claim_id,
|
claim_id=claim.claim_id,
|
||||||
total_charge=claim.charge,
|
total_charge=claim.charge,
|
||||||
place_of_service="11",
|
place_of_service="12", # Home (matches HCPF IHSS S5150/T1019 POS)
|
||||||
facility_code_qualifier="B",
|
facility_code_qualifier="B",
|
||||||
frequency_code=claim.frequency_code, # always "7"
|
frequency_code=claim.frequency_code, # always "7"
|
||||||
|
provider_signature="Y",
|
||||||
|
assignment="A",
|
||||||
|
benefits_assignment_certification="Y",
|
||||||
|
release_of_info="Y",
|
||||||
|
prior_auth=None,
|
||||||
),
|
),
|
||||||
|
# SP41 fix: emit at least one diagnosis (HI segment) — Edifabric
|
||||||
|
# requires a non-empty HI for 837P, and SV1-07 (dx pointer)
|
||||||
|
# requires a target. "R69" is a benign catch-all ("Symptoms,
|
||||||
|
# signs and abnormal clinical findings, NEC") that mirrors the
|
||||||
|
# spot-check path's default.
|
||||||
|
diagnoses=[Diagnosis(code="R69", qualifier="ABK")],
|
||||||
service_lines=[
|
service_lines=[
|
||||||
ServiceLine(
|
ServiceLine(
|
||||||
line_number=1,
|
line_number=1,
|
||||||
@@ -381,14 +425,28 @@ def _serialize_pipeline_a(claim: RebillClaim, *, tpid: str) -> bytes:
|
|||||||
charge=claim.charge,
|
charge=claim.charge,
|
||||||
units=Decimal("1"),
|
units=Decimal("1"),
|
||||||
unit_type="UN",
|
unit_type="UN",
|
||||||
place_of_service="11",
|
place_of_service="12",
|
||||||
service_date=claim.svc_date,
|
service_date=claim.svc_date,
|
||||||
|
dx_pointer="1",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
validation=ValidationReport(passed=True),
|
validation=ValidationReport(passed=True),
|
||||||
transaction_type_code="CH",
|
transaction_type_code="CH",
|
||||||
)
|
)
|
||||||
text = serialize_837(placeholder_claim, sender_id=tpid)
|
# SP41 fix: thread the canonical submitter block (PER*IC with real
|
||||||
|
# contact name/phone/email) and receiver_name through to
|
||||||
|
# serialize_837 — without these, NM1*41/PER*IC emit with empty
|
||||||
|
# placeholders that Edifabric's PER-02 rule rejects.
|
||||||
|
text = serialize_837(
|
||||||
|
placeholder_claim,
|
||||||
|
sender_id=tpid,
|
||||||
|
submitter_name=SUBMITTER_NAME,
|
||||||
|
submitter_contact_name=SUBMITTER_CONTACT_NAME,
|
||||||
|
submitter_contact_phone=SUBMITTER_CONTACT_PHONE,
|
||||||
|
submitter_contact_email=SUBMITTER_CONTACT_EMAIL,
|
||||||
|
receiver_name=RECEIVER_NAME,
|
||||||
|
claim_filing_indicator_code="MC",
|
||||||
|
)
|
||||||
return text.encode("ascii")
|
return text.encode("ascii")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -314,8 +314,9 @@ def test_run_rebill_emits_real_files_for_pipeline_a(tmp_path):
|
|||||||
# Original claim_submit_id is preserved in CLM01 (anchors the
|
# Original claim_submit_id is preserved in CLM01 (anchors the
|
||||||
# frequency-7 replacement).
|
# frequency-7 replacement).
|
||||||
assert b"CLM*ORIG-1*" in body, body
|
assert b"CLM*ORIG-1*" in body, body
|
||||||
# Frequency-code 7 emitted in CLM05 composite (11:B:7).
|
# Frequency-code 7 emitted in CLM05 composite (12:B:7 — Home POS
|
||||||
assert b"CLM*ORIG-1*100.00***11:B:7" in body, body
|
# is canonical for Dzinesco IHSS S5150/T1019 services).
|
||||||
|
assert b"CLM*ORIG-1*100.00***12:B:7" in body, body
|
||||||
|
|
||||||
# Pipeline-A filename uses HCPF-spec prefix + claim_id suffix.
|
# Pipeline-A filename uses HCPF-spec prefix + claim_id suffix.
|
||||||
assert a_path.name.startswith("tp11525703-837P-"), a_path.name
|
assert a_path.name.startswith("tp11525703-837P-"), a_path.name
|
||||||
|
|||||||
Reference in New Issue
Block a user