feat(backend): add reconcile.match with date-window + multi-claim fallback
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
"""Tests for the pure reconciliation functions.
|
||||
|
||||
These tests use lightweight stand-in objects (namedtuples) rather than
|
||||
SQLAlchemy ORM instances, so the reconcile module has zero DB dependency.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
import pytest
|
||||
|
||||
from cyclone import reconcile
|
||||
|
||||
|
||||
# --- Stand-in types -----------------------------------------------------------
|
||||
|
||||
|
||||
@dataclass
|
||||
class FakeClaim:
|
||||
id: str
|
||||
patient_control_number: str
|
||||
service_date_from: Optional[date]
|
||||
state: str = "submitted"
|
||||
matched_remittance_id: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class FakeRemit:
|
||||
id: str
|
||||
payer_claim_control_number: str
|
||||
service_date: Optional[date]
|
||||
is_reversal: bool = False
|
||||
|
||||
|
||||
# --- match() ------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_match_same_pcn_within_window_matches():
|
||||
claims = [FakeClaim("CLM-1", "PCN-A", date(2026, 6, 1))]
|
||||
remits = [FakeRemit("CLP-1", "PCN-A", date(2026, 6, 2))]
|
||||
matches = reconcile.match(claims, remits, window_days=7)
|
||||
assert len(matches) == 1
|
||||
assert matches[0].claim.id == "CLM-1"
|
||||
assert matches[0].remittance.id == "CLP-1"
|
||||
assert matches[0].strategy == "auto"
|
||||
|
||||
|
||||
def test_match_dates_outside_window_unmatched():
|
||||
claims = [FakeClaim("CLM-1", "PCN-A", date(2026, 6, 1))]
|
||||
remits = [FakeRemit("CLP-1", "PCN-A", date(2026, 7, 1))] # 30d apart
|
||||
matches = reconcile.match(claims, remits, window_days=7)
|
||||
assert matches == []
|
||||
|
||||
|
||||
def test_match_different_pcn_unmatched():
|
||||
claims = [FakeClaim("CLM-1", "PCN-A", date(2026, 6, 1))]
|
||||
remits = [FakeRemit("CLP-1", "PCN-B", date(2026, 6, 1))]
|
||||
matches = reconcile.match(claims, remits)
|
||||
assert matches == []
|
||||
|
||||
|
||||
def test_match_multiple_claims_same_pcn_picks_closest_date():
|
||||
claims = [
|
||||
FakeClaim("CLM-1", "PCN-A", date(2026, 5, 1)),
|
||||
FakeClaim("CLM-2", "PCN-A", date(2026, 6, 1)), # closer
|
||||
]
|
||||
remits = [FakeRemit("CLP-1", "PCN-A", date(2026, 6, 2))]
|
||||
matches = reconcile.match(claims, remits)
|
||||
assert len(matches) == 1
|
||||
assert matches[0].claim.id == "CLM-2"
|
||||
|
||||
|
||||
def test_match_multiple_remits_same_pcn_each_gets_own_claim():
|
||||
claims = [
|
||||
FakeClaim("CLM-1", "PCN-A", date(2026, 6, 1)),
|
||||
FakeClaim("CLM-2", "PCN-A", date(2026, 6, 15)),
|
||||
]
|
||||
remits = [
|
||||
FakeRemit("CLP-1", "PCN-A", date(2026, 6, 2)),
|
||||
FakeRemit("CLP-2", "PCN-A", date(2026, 6, 16)),
|
||||
]
|
||||
matches = reconcile.match(claims, remits)
|
||||
assert len(matches) == 2
|
||||
claim_ids = {m.claim.id for m in matches}
|
||||
assert claim_ids == {"CLM-1", "CLM-2"}
|
||||
|
||||
|
||||
def test_match_skips_already_matched_claims():
|
||||
claims = [FakeClaim("CLM-1", "PCN-A", date(2026, 6, 1), matched_remittance_id="OLD")]
|
||||
remits = [FakeRemit("CLP-1", "PCN-A", date(2026, 6, 1))]
|
||||
matches = reconcile.match(claims, remits)
|
||||
assert matches == []
|
||||
|
||||
|
||||
def test_match_falls_back_to_first_claim_when_no_service_date():
|
||||
"""When the remit has no service_date, match the first claim for that PCN."""
|
||||
claims = [
|
||||
FakeClaim("CLM-1", "PCN-A", None),
|
||||
FakeClaim("CLM-2", "PCN-A", None),
|
||||
]
|
||||
remits = [FakeRemit("CLP-1", "PCN-A", None)]
|
||||
matches = reconcile.match(claims, remits)
|
||||
assert len(matches) == 1
|
||||
assert matches[0].claim.id == "CLM-1"
|
||||
|
||||
|
||||
def test_match_reversal_flag_propagates():
|
||||
claims = [FakeClaim("CLM-1", "PCN-A", date(2026, 6, 1))]
|
||||
remits = [FakeRemit("CLP-1", "PCN-A", date(2026, 6, 1), is_reversal=True)]
|
||||
matches = reconcile.match(claims, remits)
|
||||
assert matches[0].is_reversal is True
|
||||
Reference in New Issue
Block a user