"""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