feat(backend): 835 ERA parser with BPR/CLP/SVC balancing, CO Medicaid config, CLI, and FastAPI route
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
"""Tests for the 835 ERA parser orchestrator."""
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from cyclone.parsers.exceptions import CycloneParseError
|
||||
from cyclone.parsers.parse_835 import parse
|
||||
from cyclone.parsers.payer import PayerConfig835
|
||||
|
||||
FIXTURE = Path(__file__).parent / "fixtures" / "minimal_835.txt"
|
||||
|
||||
|
||||
def test_parse_minimal_fixture_returns_one_claim():
|
||||
text = FIXTURE.read_text()
|
||||
result = parse(text, PayerConfig835.co_medicaid_835())
|
||||
assert len(result.claims) == 1
|
||||
c = result.claims[0]
|
||||
assert c.payer_claim_control_number == "CLM001"
|
||||
assert c.status_code == "1"
|
||||
assert c.status_label == "Processed as Primary"
|
||||
assert c.total_charge == 100.00
|
||||
assert c.total_paid == 85.00
|
||||
assert c.patient_responsibility == 15.00
|
||||
assert c.claim_filing_indicator == "MC"
|
||||
assert c.facility_type == "12"
|
||||
assert c.frequency_code == "1"
|
||||
|
||||
|
||||
def test_parse_envelope_captured():
|
||||
result = parse(FIXTURE.read_text(), PayerConfig835.co_medicaid_835())
|
||||
assert result.envelope is not None
|
||||
assert result.envelope.control_number == "991102984"
|
||||
assert result.envelope.transaction_date == date(2026, 6, 17)
|
||||
assert result.envelope.sender_id == "COMEDASSISTPROG"
|
||||
assert result.envelope.implementation_guide == "005010X221A1"
|
||||
|
||||
|
||||
def test_parse_financial_info():
|
||||
result = parse(FIXTURE.read_text(), PayerConfig835.co_medicaid_835())
|
||||
f = result.financial_info
|
||||
assert f.handling_code == "C"
|
||||
assert f.paid_amount == 85.00
|
||||
assert f.credit_debit_flag == "C"
|
||||
assert f.payment_method == "ACH"
|
||||
assert f.payer_tax_id == "81-1725341"
|
||||
assert f.payment_date == date(2026, 6, 17)
|
||||
|
||||
|
||||
def test_parse_trace():
|
||||
result = parse(FIXTURE.read_text(), PayerConfig835.co_medicaid_835())
|
||||
t = result.trace
|
||||
assert t.trace_type_code == "1"
|
||||
# X12 standard: TRN02 = Reference Identification (trace number),
|
||||
# TRN03 = Originating Company Identifier. The CO guide labels
|
||||
# TRN03 as the Payer Tax ID, so for CO Medicaid 835s the
|
||||
# originating_company_id is "81-1725341" and the trace number is
|
||||
# the provider's TPID.
|
||||
assert t.trace_number == "TPID001"
|
||||
assert t.originating_company_id == "81-1725341"
|
||||
assert t.payer_id is None # TRN04 is not present in this fixture
|
||||
|
||||
|
||||
def test_parse_payer_and_payee():
|
||||
result = parse(FIXTURE.read_text(), PayerConfig835.co_medicaid_835())
|
||||
p = result.payer
|
||||
assert p.name == "CO_TXIX"
|
||||
assert p.id == "7912900843"
|
||||
assert p.contact_url == "hcpf.colorado.gov"
|
||||
assert p.address is not None
|
||||
assert p.address.city == "DENVER"
|
||||
pe = result.payee
|
||||
assert pe.name == "TOC, Inc."
|
||||
assert pe.npi == "1881068062"
|
||||
assert pe.address is not None
|
||||
assert pe.address.zip == "814014063"
|
||||
|
||||
|
||||
def test_parse_claim_has_two_service_lines():
|
||||
result = parse(FIXTURE.read_text(), PayerConfig835.co_medicaid_835())
|
||||
c = result.claims[0]
|
||||
assert len(c.service_payments) == 2
|
||||
s1, s2 = c.service_payments
|
||||
assert s1.line_number == 1
|
||||
assert s1.procedure_code == "S5130"
|
||||
assert s1.procedure_qualifier == "HC"
|
||||
assert s1.modifiers == ["U2"]
|
||||
assert s1.payment == 50.00
|
||||
assert s1.service_date == date(2026, 6, 2)
|
||||
assert s2.line_number == 2
|
||||
assert s2.payment == 35.00
|
||||
assert s2.service_date == date(2026, 6, 3)
|
||||
|
||||
|
||||
def test_parse_summary_counts():
|
||||
result = parse(FIXTURE.read_text(), PayerConfig835.co_medicaid_835())
|
||||
assert result.summary.total_claims == 1
|
||||
assert result.summary.input_file == ""
|
||||
# The orchestrator sets passed/failed from the validation report.
|
||||
# Without running validation it remains 0 — the CLI / API does that.
|
||||
assert result.summary.passed == 0
|
||||
|
||||
|
||||
def test_parse_preserves_raw_segments():
|
||||
result = parse(FIXTURE.read_text(), PayerConfig835.co_medicaid_835())
|
||||
c = result.claims[0]
|
||||
assert any(s[0] == "CLP" for s in c.raw_segments)
|
||||
assert any(s[0] == "SVC" for s in c.raw_segments)
|
||||
|
||||
|
||||
def test_parse_uses_generic_config():
|
||||
"""Generic config shouldn't suppress parser errors but accepts any payer."""
|
||||
result = parse(FIXTURE.read_text(), PayerConfig835.generic_835())
|
||||
assert len(result.claims) == 1
|
||||
|
||||
|
||||
def test_parse_no_bpr_raises():
|
||||
text = (
|
||||
"ISA*00* *00* *ZZ*COMEDASSISTPROG*ZZ*TPID001 *260617*1937*^*00501*991102990*1*P*:~"
|
||||
"ST*835*991102990*005010X221A1~"
|
||||
"SE*1*991102990~"
|
||||
"GE*1*991102990~"
|
||||
"IEA*1*991102990~"
|
||||
)
|
||||
with pytest.raises(CycloneParseError, match="No BPR"):
|
||||
parse(text, PayerConfig835.co_medicaid_835())
|
||||
Reference in New Issue
Block a user