From 39f578aa5ea6352b286cb760981ea4ac63278d7b Mon Sep 17 00:00:00 2001 From: Nora Date: Tue, 7 Jul 2026 21:37:48 -0600 Subject: [PATCH] fix(sp41): test has_overridden_visits + clarify pipeline_b forward-compat helper --- backend/src/cyclone/rebill/pipeline_b.py | 14 +++++++-- backend/tests/test_rebill_pipeline_b.py | 36 +++++++++++++++++++++++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/backend/src/cyclone/rebill/pipeline_b.py b/backend/src/cyclone/rebill/pipeline_b.py index 9d61996..ad49325 100644 --- a/backend/src/cyclone/rebill/pipeline_b.py +++ b/backend/src/cyclone/rebill/pipeline_b.py @@ -20,8 +20,11 @@ from cyclone.rebill.timely_filing import timely_filing_decision @dataclass(frozen=True) class MemberWeekBatch: """Pre-emission 837P shape for Pipeline B. The orchestrator's serializer - adapter converts this to a ClaimOutput for serialize_837 (one 837P - carrying every visit in `visits` as a CLM segment). + adapter converts this to a ClaimOutput for serialize_837. + + Note: frozen=True is shallow — `visits` is a mutable list, so + `mb.visits.append(x)` works even though `mb.visits = new_list` raises + FrozenInstanceError. Treat the batch as logically immutable. """ member_id: str @@ -78,7 +81,12 @@ def batch_visits_by_member_week( ISO-week) batches back down to a per-member visit list. This is the shape the orchestrator's summary aggregation expects. - NOTE: This collapses multiple weeks for the same member into one + PROVISIONAL: this helper is defined for the Task 12 orchestrator's + anticipated use, but has no callers in this branch. If Task 12's + actual needs differ, this function may be removed or replaced without + notice. + + This also collapses multiple weeks for the same member into one list. If the orchestrator needs week-aware per-member iteration, use build_pipeline_b_batches directly. """ diff --git a/backend/tests/test_rebill_pipeline_b.py b/backend/tests/test_rebill_pipeline_b.py index 86811d3..e2fb7d4 100644 --- a/backend/tests/test_rebill_pipeline_b.py +++ b/backend/tests/test_rebill_pipeline_b.py @@ -38,4 +38,38 @@ def test_override_relaxes_timely_filing(): out_default = build_pipeline_b_batches(visits, as_of=date(2026, 7, 7), override=False) out_overridden = build_pipeline_b_batches(visits, as_of=date(2026, 7, 7), override=True) assert out_default == [] - assert len(out_overridden) == 1 \ No newline at end of file + assert len(out_overridden) == 1 + + +def test_override_flag_set_on_past_window_visit_batch(): + # Same member "OLD" with one past-window visit (187 days old, ISO + # week 1) and one within-window visit (10 days old, ISO week 26). + # With override=True the past-window visit survives and the batch + # for week 1 must be flagged has_overridden_visits=True because the + # override saved an otherwise-excluded visit. + visits = [ + _v(date(2026, 1, 1), "OLD", "T1019", "2.32"), # 187 days old (past window, W01) + _v(date(2026, 6, 27), "OLD", "T1019", "2.32"), # 10 days old (within window, W26) + ] + out_overridden = build_pipeline_b_batches( + visits, as_of=date(2026, 7, 7), override=True, + ) + # Two batches: one per (member, ISO-week) — the past-window visit and + # the within-window visit land in different weeks. + assert len(out_overridden) == 2 + by_week = {b.iso_week: b for b in out_overridden} + assert by_week[1].member_id == "OLD" + assert by_week[1].has_overridden_visits is True # the override saved this batch + assert by_week[26].member_id == "OLD" + assert by_week[26].has_overridden_visits is False # no override needed here + + # With override=False the past-window visit is dropped. The surviving + # within-window batch (W26) must NOT carry the override flag, and the + # past-window batch (W01) is absent. + out_default = build_pipeline_b_batches( + visits, as_of=date(2026, 7, 7), override=False, + ) + assert len(out_default) == 1 + assert out_default[0].member_id == "OLD" + assert out_default[0].iso_week == 26 + assert out_default[0].has_overridden_visits is False