"""SP7 — pure-function tests for match_service_lines(). Spec §4. """ from dataclasses import dataclass from datetime import date from decimal import Decimal from cyclone.reconcile import ( LineMatch, match_service_lines, ) @dataclass class FakeLine: line_number: int procedure_code: str modifiers: list service_date: date | None units: Decimal | None payment: Decimal = Decimal("0") # 835 only @dataclass class FakeClaimLine(FakeLine): charge: Decimal = Decimal("0") # 837 only def _claim(*lines: FakeClaimLine) -> list: return list(lines) def _remit(*lines: FakeLine) -> list: return list(lines) def test_exact_match_on_all_four_criteria(): claim_lines = _claim(FakeClaimLine(1, "99213", [], date(2026, 6, 1), Decimal("1"))) remit_lines = _remit(FakeLine(1, "99213", [], date(2026, 6, 1), Decimal("1"), payment=Decimal("80"))) matches = match_service_lines(claim_lines, remit_lines) assert len(matches) == 1 assert matches[0].claim_line.line_number == 1 assert matches[0].service_payment.line_number == 1 assert matches[0].status == "matched" def test_procedure_code_mismatch_both_unmatched(): claim_lines = _claim(FakeClaimLine(1, "99213", [], None, None)) remit_lines = _remit(FakeLine(1, "99214", [], None, None)) matches = match_service_lines(claim_lines, remit_lines) assert len(matches) == 2 statuses = sorted(m.status for m in matches) assert statuses == ["unmatched_835_only", "unmatched_837_only"] def test_modifiers_set_equal_regardless_of_order(): claim_lines = _claim(FakeClaimLine(1, "99213", ["25", "59"], None, None)) remit_lines = _remit(FakeLine(1, "99213", ["59", "25"], None, None)) matches = match_service_lines(claim_lines, remit_lines) assert len(matches) == 1 assert matches[0].status == "matched" def test_service_date_mismatch(): claim_lines = _claim(FakeClaimLine(1, "99213", [], date(2026, 6, 1), None)) remit_lines = _remit(FakeLine(1, "99213", [], date(2026, 6, 2), None)) matches = match_service_lines(claim_lines, remit_lines) assert len(matches) == 2 statuses = sorted(m.status for m in matches) assert statuses == ["unmatched_835_only", "unmatched_837_only"] def test_units_mismatch(): claim_lines = _claim(FakeClaimLine(1, "99213", [], None, Decimal("1"))) remit_lines = _remit(FakeLine(1, "99213", [], None, Decimal("2"))) matches = match_service_lines(claim_lines, remit_lines) assert len(matches) == 2 statuses = sorted(m.status for m in matches) assert statuses == ["unmatched_835_only", "unmatched_837_only"] def test_procedure_code_case_normalized(): claim_lines = _claim(FakeClaimLine(1, "99213", [], None, None)) remit_lines = _remit(FakeLine(1, "99213", [], None, None)) # case irrelevant when both upper matches = match_service_lines(claim_lines, remit_lines) assert matches[0].status == "matched" def test_claim_has_more_lines_than_remit(): claim_lines = _claim( FakeClaimLine(1, "99213", [], None, None), FakeClaimLine(2, "99214", [], None, None), ) remit_lines = _remit(FakeLine(1, "99213", [], None, None)) matches = match_service_lines(claim_lines, remit_lines) statuses = sorted(m.status for m in matches) assert statuses == ["matched", "unmatched_837_only"] def test_remit_has_more_lines_than_claim(): claim_lines = _claim(FakeClaimLine(1, "99213", [], None, None)) remit_lines = _remit( FakeLine(1, "99213", [], None, None), FakeLine(2, "99214", [], None, None), ) matches = match_service_lines(claim_lines, remit_lines) statuses = sorted(m.status for m in matches) assert statuses == ["matched", "unmatched_835_only"] def test_empty_837_lines_no_reconciliation_rows(): matches = match_service_lines([], _remit(FakeLine(1, "99213", [], None, None))) assert len(matches) == 1 assert matches[0].status == "unmatched_835_only" def test_empty_835_lines_no_reconciliation_rows(): matches = match_service_lines(_claim(FakeClaimLine(1, "99213", [], None, None)), []) assert len(matches) == 1 assert matches[0].status == "unmatched_837_only" def test_both_empty(): assert match_service_lines([], []) == []