126 lines
4.2 KiB
Python
126 lines
4.2 KiB
Python
"""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([], []) == []
|