From 30d13876cd116503535fc11fc594b6a5b1fb9e83 Mon Sep 17 00:00:00 2001 From: Nora Date: Tue, 7 Jul 2026 20:24:40 -0600 Subject: [PATCH] feat(sp41): 120-day timely-filing gate with per-batch override --- backend/src/cyclone/rebill/timely_filing.py | 37 +++++++++++++++++++ backend/tests/test_rebill_timely_filing.py | 39 +++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 backend/src/cyclone/rebill/timely_filing.py create mode 100644 backend/tests/test_rebill_timely_filing.py diff --git a/backend/src/cyclone/rebill/timely_filing.py b/backend/src/cyclone/rebill/timely_filing.py new file mode 100644 index 0000000..b1aa995 --- /dev/null +++ b/backend/src/cyclone/rebill/timely_filing.py @@ -0,0 +1,37 @@ +"""120-day timely-filing gate for Colorado Medicaid. + +Per HCPF's Colorado Medical Assistance Program provider manual, claims must +be filed within 120 days from the date of service. The gate is HARD by +default — past-window visits are surfaced as EXCLUDED_TIMELY_FILING in the +summary CSV and not emitted by Pipeline B. + +The operator may pass override=True per batch to relax the gate (e.g. for +good-cause appeal visits). The decision is recorded in the summary CSV +either way so the audit trail shows the override. +""" +from dataclasses import dataclass +from datetime import date + +FILING_WINDOW_DAYS = 120 + + +@dataclass(frozen=True) +class TimelyFilingDecision: + dos: date + as_of: date + days_old: int + within_window: bool + override: bool + rebillable: bool + + +def timely_filing_decision( + dos: date, as_of: date, override: bool +) -> TimelyFilingDecision: + days_old = (as_of - dos).days + within_window = days_old <= FILING_WINDOW_DAYS + return TimelyFilingDecision( + dos=dos, as_of=as_of, days_old=days_old, + within_window=within_window, override=override, + rebillable=within_window or override, + ) diff --git a/backend/tests/test_rebill_timely_filing.py b/backend/tests/test_rebill_timely_filing.py new file mode 100644 index 0000000..c8aeb87 --- /dev/null +++ b/backend/tests/test_rebill_timely_filing.py @@ -0,0 +1,39 @@ +"""120-day DOS-age gate. CO Medicaid's timely-filing window is 120 days from DOS.""" +from datetime import date +from decimal import Decimal +from cyclone.rebill.timely_filing import timely_filing_decision, FILING_WINDOW_DAYS + + +def test_visit_within_window_is_rebillable(): + decision = timely_filing_decision( + dos=date(2026, 6, 1), as_of=date(2026, 7, 7), override=False + ) + assert decision.rebillable is True + + +def test_visit_past_window_excluded_by_default(): + decision = timely_filing_decision( + dos=date(2026, 1, 1), as_of=date(2026, 7, 7), override=False + ) + assert decision.rebillable is False + assert decision.days_old == 187 + + +def test_visit_at_exact_120_days_inclusive(): + decision = timely_filing_decision( + dos=date(2026, 3, 9), as_of=date(2026, 7, 7), override=False + ) + # 2026-03-09 → 2026-07-07 = 120 days + assert decision.days_old == 120 + assert decision.rebillable is True + + +def test_override_flag_relaxes_the_gate(): + decision = timely_filing_decision( + dos=date(2026, 1, 1), as_of=date(2026, 7, 7), override=True + ) + assert decision.rebillable is True + + +def test_window_constant_is_120(): + assert FILING_WINDOW_DAYS == 120