feat(sp7): match_service_lines() pure function + tests
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
"""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([], []) == []
|
||||
Reference in New Issue
Block a user