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():