"""Tests for the 835 ERA validator rules.""" from datetime import date from decimal import Decimal from pathlib import Path import pytest from cyclone.parsers.models import Address, BatchSummary, Envelope from cyclone.parsers.models_835 import ( ClaimAdjustment, ClaimPayment, FinancialInfo, ParseResult835, Payer835, Payee835, ReassociationTrace, ServicePayment, ) from cyclone.parsers.payer import PayerConfig835 from cyclone.parsers.validator_835 import validate # --------------------------------------------------------------------------- # # Builders # --------------------------------------------------------------------------- # def _build_result(**overrides) -> ParseResult835: """Build a passing CO Medicaid 835 result; override fields to break rules.""" financial = FinancialInfo( handling_code="C", paid_amount=Decimal("85.00"), credit_debit_flag="C", payment_method="ACH", payer_tax_id="81-1725341", payment_date=date(2026, 6, 17), ) trace = ReassociationTrace( trace_type_code="1", trace_number="TPID001", originating_company_id="TPID001", payer_id="81-1725341", ) payer = Payer835( name="CO_TXIX", id="7912900843", address=Address(line1="PO BOX 11000", city="DENVER", state="CO", zip="802110000"), ) payee = Payee835( name="TOC, Inc.", npi="1881068062", address=Address(line1="1100 East Main St", city="Montrose", state="CO", zip="814014063"), ) claim = ClaimPayment( payer_claim_control_number="CLM001", status_code="1", status_label="Processed as Primary", total_charge=Decimal("100.00"), total_paid=Decimal("85.00"), patient_responsibility=Decimal("15.00"), claim_filing_indicator="MC", original_claim_id="CLM001", facility_type="12", frequency_code="1", per_diem_covered_days=None, ref_benefit_plan=None, service_payments=[ ServicePayment( line_number=1, procedure_qualifier="HC", procedure_code="S5130", modifiers=["U2"], charge=Decimal("60.00"), payment=Decimal("50.00"), units=Decimal("7.73"), unit_type="UN", service_date=date(2026, 6, 2), adjustments=[], ref_benefit_plan=None, ), ServicePayment( line_number=2, procedure_qualifier="HC", procedure_code="S5130", modifiers=["U2"], charge=Decimal("40.00"), payment=Decimal("35.00"), units=Decimal("7.73"), unit_type="UN", service_date=date(2026, 6, 3), adjustments=[], ref_benefit_plan=None, ), ], raw_segments=[], ) summary = BatchSummary(input_file="f", total_claims=1, passed=1, failed=0) result = ParseResult835( envelope=Envelope( sender_id="COMEDASSISTPROG", receiver_id="TPID001", control_number="991102984", transaction_date=date(2026, 6, 17), ), financial_info=financial, trace=trace, payer=payer, payee=payee, claims=[claim], summary=summary, ) if overrides: result = result.model_copy(update=overrides) return result # --------------------------------------------------------------------------- # # Sanity: balanced claim passes # --------------------------------------------------------------------------- # def test_balanced_claim_passes_all_rules(): cfg = PayerConfig835.co_medicaid_835() report = validate(_build_result(), cfg) assert report.passed is True assert report.errors == [] # --------------------------------------------------------------------------- # # BPR rules # --------------------------------------------------------------------------- # def test_r835_bpr01_handling_code_disallowed(): cfg = PayerConfig835.co_medicaid_835() result = _build_result(financial_info=_build_result().financial_info.model_copy(update={"handling_code": "Z"})) report = validate(result, cfg) assert any(i.rule == "R835_BPR01_handling_code_allowed" for i in report.errors) assert report.passed is False def test_r835_bpr02_positive(): cfg = PayerConfig835.co_medicaid_835() result = _build_result(financial_info=_build_result().financial_info.model_copy(update={"paid_amount": Decimal("0.00")})) report = validate(result, cfg) assert any(i.rule == "R835_BPR02_positive" for i in report.errors) def test_r835_bal_bpr_vs_clp04_unbalanced(): """BPR02 (85.00) must equal sum of CLP04 (85.00). Off by 0.50 → error.""" cfg = PayerConfig835.co_medicaid_835() result = _build_result(financial_info=_build_result().financial_info.model_copy(update={"paid_amount": Decimal("85.50")})) report = validate(result, cfg) assert any(i.rule == "R835_BAL_BPR_vs_CLP04" for i in report.errors) assert report.passed is False def test_r835_bal_bpr_vs_clp04_within_tolerance_passes(): cfg = PayerConfig835.co_medicaid_835() result = _build_result(financial_info=_build_result().financial_info.model_copy(update={"paid_amount": Decimal("85.005")})) report = validate(result, cfg) # 0.005 difference is below tolerance (0.01) — no error. assert not any(i.rule == "R835_BAL_BPR_vs_CLP04" for i in report.errors) # --------------------------------------------------------------------------- # # CLP rules # --------------------------------------------------------------------------- # def test_r835_bal_clp04_vs_svc03_unbalanced(): cfg = PayerConfig835.co_medicaid_835() # The default fixture has CLP04=85.00 and SVC03 sum = 50+35 = 85.00. # Change one SVC payment to 40 → sum = 40+35 = 75, off by 10. base = _build_result() svc = base.claims[0].service_payments[0].model_copy(update={"payment": Decimal("40.00")}) result = base.model_copy(update={"claims": [base.claims[0].model_copy(update={"service_payments": [svc, base.claims[0].service_payments[1]]})]}) report = validate(result, cfg) assert any(i.rule == "R835_BAL_CLP04_vs_SVC03" for i in report.errors) assert "CLM001" in [i.message for i in report.errors if i.rule == "R835_BAL_CLP04_vs_SVC03"][0] def test_r835_clp01_present(): cfg = PayerConfig835.co_medicaid_835() result = _build_result(claims=[_build_result().claims[0].model_copy(update={"payer_claim_control_number": ""})]) report = validate(result, cfg) assert any(i.rule == "R835_CLP01_present" for i in report.errors) def test_r835_clp02_status_disallowed(): cfg = PayerConfig835.co_medicaid_835() result = _build_result(claims=[_build_result().claims[0].model_copy(update={"status_code": "9", "status_label": None})]) report = validate(result, cfg) assert any(i.rule == "R835_CLP02_status_allowed" for i in report.errors) def test_r835_clp03_present(): cfg = PayerConfig835.co_medicaid_835() result = _build_result(claims=[_build_result().claims[0].model_copy(update={"total_charge": Decimal("0.00")})]) report = validate(result, cfg) assert any(i.rule == "R835_CLP03_present" for i in report.errors) # --------------------------------------------------------------------------- # # Payer / payee rules # --------------------------------------------------------------------------- # def test_r835_payer_tax_id_mismatch_warning(): cfg = PayerConfig835.co_medicaid_835() result = _build_result(financial_info=_build_result().financial_info.model_copy(update={"payer_tax_id": "99-9999999"})) report = validate(result, cfg) assert any(i.rule == "R835_PAYER_TAX_ID_matches" and i.severity == "warning" for i in report.warnings) # Warning only — report still passes. assert report.passed is True def test_r835_payer_tax_id_skipped_when_set_empty(): cfg = PayerConfig835.co_medicaid_835() result = _build_result(financial_info=_build_result().financial_info.model_copy(update={"payer_tax_id": None})) report = validate(result, cfg) # When BPR10 is empty, the rule is skipped silently (per spec wording). assert not any(i.rule == "R835_PAYER_TAX_ID_matches" for i in report.errors + report.warnings) def test_r835_payer_health_plan_id_mismatch_warning(): cfg = PayerConfig835.co_medicaid_835() result = _build_result(payer=_build_result().payer.model_copy(update={"id": "12345"})) report = validate(result, cfg) assert any(i.rule == "R835_PAYER_HEALTH_PLAN_ID_matches" and i.severity == "warning" for i in report.warnings) assert report.passed is True def test_r835_payee_npi_format(): cfg = PayerConfig835.co_medicaid_835() result = _build_result(payee=_build_result().payee.model_copy(update={"npi": "12345"})) report = validate(result, cfg) assert any(i.rule == "R835_PAYEE_NPI_format" for i in report.errors) assert report.passed is False def test_r835_trn02_originating_company_id_present(): cfg = PayerConfig835.co_medicaid_835() result = _build_result(trace=_build_result().trace.model_copy(update={"originating_company_id": ""})) report = validate(result, cfg) assert any(i.rule == "R835_REGN01_trn02_present" for i in report.errors) # --------------------------------------------------------------------------- # # Optional: warnings don't fail the report # --------------------------------------------------------------------------- # def test_warnings_do_not_set_passed_false(): cfg = PayerConfig835.co_medicaid_835() # A wrong payer tax id is a warning; report should still pass. result = _build_result(financial_info=_build_result().financial_info.model_copy(update={"payer_tax_id": "BAD"})) report = validate(result, cfg) assert report.passed is True assert any(i.severity == "warning" for i in report.warnings)