From b4f57079376be26363ff1cfa7b0204b1ceadea9a Mon Sep 17 00:00:00 2001 From: Kisa Date: Tue, 7 Jul 2026 06:15:27 -0400 Subject: [PATCH] fix: engine-dropped rows surface in the skipped list (failed-data honesty) Kisa ruling 2026-07-07: failed CSV info must be visible and Signal never acts on rows whose required information was not provided. Rows that survive normalization but die in the scoring engine (no coverage rule for the component) previously vanished from the response; they now append to skipped_reasons before persistence so the stored count matches what the supplier saw. Auditor SHIP; its MEDIUM (persist-order) fixed in-commit. Known bound (auditor LOW, tracked): a dropped component inside an otherwise scored same-DOS group is covered by the scored line and not separately reported. 169 tests green. Co-Authored-By: Claude Fable 5 --- python-backend/api/main.py | 21 +++++++++++++++++++++ tests/test_worklist_readiness.py | 6 ++++++ 2 files changed, 27 insertions(+) diff --git a/python-backend/api/main.py b/python-backend/api/main.py index 163abfa..0f1592b 100644 --- a/python-backend/api/main.py +++ b/python-backend/api/main.py @@ -858,6 +858,27 @@ async def upload_csv( ) ) + # Failed-data honesty (Kisa 2026-07-07): a row the scoring engine drops + # (e.g. a component with no coverage rule) must appear in the skipped + # list, never vanish — and BEFORE persistence so the stored batch record + # matches the count the supplier saw. Signal never acts on rows whose + # required information was not provided; it also never hides them. + from core.dedup import _as_date as _asd + + processed_groups = { + (r.patient_id, r.device_type, _asd(r.last_shipment_date)) + for r in results + } + _reported_drops = set() + for rec in records: + g = (rec.patient_id, rec.device_type, _asd(rec.shipment_date)) + if g not in processed_groups and g not in _reported_drops: + _reported_drops.add(g) + skipped_reasons.append( + f"{rec.patient_id}: {rec.device_type} ({rec.component}) has " + "no coverage rule for this component — row not scored" + ) + # Use real user ID and db_conn where possible if we implement it, hardcoding # IP as 0.0.0.0 for now unless req object available from core.supabase_client import get_client diff --git a/tests/test_worklist_readiness.py b/tests/test_worklist_readiness.py index 282a394..bc541d3 100644 --- a/tests/test_worklist_readiness.py +++ b/tests/test_worklist_readiness.py @@ -513,6 +513,12 @@ def test_upload_skipped_record_leaves_no_phantom_line_in_stats(): pids = {r["patient_id"] for r in body["records"]} assert pids == {"PT-OK-1"} # transmitter row skipped by the engine assert body["readiness_stats"]["lines_total"] == 1 + # Failed-data honesty: the dropped row is visible in the skipped list, + # never silently vanished (Kisa ruling 2026-07-07). + assert body["skipped"] >= 1 + assert any( + "PT-TX-1" in r and "not scored" in r for r in body["skipped_reasons"] + ) def test_upload_non_gradeable_rows_carry_no_verdict():