Signal/tests/test_worklist_readiness.py
Kisa d24340c47a feat: citation rule IDs on every documentation requirement (plan 02, P2)
core/rules.py format-tolerant readers + stable registry (R001-R004 plan-scoped,
R100-R102 universal per LCD L33822). payer_rules.json wrapped in the same
commit as both engine readers (coupling hazard closed). Every DocItem now
cites the rule that decided it; NOT_EVALUATED cites nothing. Independent
audit: SHIP (registry-drift test added per its L2 finding). 138 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 05:10:52 -04:00

611 lines
22 KiB
Python

"""
Tests for the readiness-model wiring (build plan 01).
Engine behavior is covered by test_dedup.py / test_readiness.py; these tests
target the SEAMS the wiring adds: the orchestration module, the /api/upload
augmentation, the /api/confirm-visit recompute, and back-compat invariants.
PHI CONTRACT: fixtures use synthetic patient_ids only. No names, DOBs, SSNs.
"""
import hashlib
import io
import sys
from datetime import date, timedelta
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent / "python-backend"))
from core.coverage_calculator import ShipmentRecord, calculate_batch # noqa: E402
from core.worklist_readiness import ( # noqa: E402
build_readiness_index,
merged_to_record,
readiness_by_line,
readiness_for_record,
readiness_stats,
resolve_group,
)
TODAY = date.today()
def _record(**kwargs) -> ShipmentRecord:
defaults = dict(
patient_id="PT-1001",
device_type="dexcom_g7",
shipment_date=TODAY - timedelta(days=5),
quantity=3,
payer="Medicare Part B",
component="sensor",
)
defaults.update(kwargs)
return ShipmentRecord(**defaults)
def _hash(pid: str) -> str:
return hashlib.sha256(pid.encode()).hexdigest()
# --- orchestration module (unit level) --------------------------------------
def test_readiness_by_line_full_evidence_is_clear_to_ship():
rec = _record(
plan_type="medicare",
csv_swo_status="On File",
csv_visit_date=TODAY - timedelta(days=30),
csv_pecos_verified="Yes",
csv_diagnosis_on_file="Yes",
)
verdicts = readiness_by_line([rec])
key = ("PT-1001", "dexcom_g7", "medicare")
assert key in verdicts
assert verdicts[key].line_status.value == "Clear to Ship"
def test_readiness_by_line_skips_non_gradeable_lines():
rec = _record(device_type="omnipod_5", plan_type="medicare")
verdicts = readiness_by_line([rec])
assert verdicts == {}
def test_no_plan_type_reads_plan_type_needed():
rec = _record(
csv_swo_status="On File",
csv_visit_date=TODAY - timedelta(days=30),
csv_pecos_verified="Yes",
csv_diagnosis_on_file="Yes",
)
verdicts = readiness_by_line([rec])
key = ("PT-1001", "dexcom_g7", "unknown")
assert verdicts[key].line_status.value == "Plan Type Needed"
by_type = {i.doc_type: i for i in verdicts[key].doc_items}
assert by_type["pecos"].required_state.value == "NOT_EVALUATED"
assert by_type["pa"].required_state.value == "NOT_EVALUATED"
def test_same_dos_different_order_numbers_is_one_line_verdict():
a = _record(order_number="ORD-1", plan_type="medicare")
b = _record(order_number="ORD-2", plan_type="medicare")
verdicts = readiness_by_line([a, b])
assert len(verdicts) == 1
def test_verdict_graded_from_latest_dos():
older = _record(
shipment_date=TODAY - timedelta(days=40),
plan_type="medicare",
csv_swo_status="On File",
csv_visit_date=TODAY - timedelta(days=45),
csv_pecos_verified="Yes",
csv_diagnosis_on_file="Yes",
)
# Latest DOS is missing PECOS -> the line verdict must reflect THIS row.
newer = _record(
shipment_date=TODAY - timedelta(days=5),
plan_type="medicare",
csv_swo_status="On File",
csv_visit_date=TODAY - timedelta(days=45),
csv_diagnosis_on_file="Yes",
)
verdicts = readiness_by_line([older, newer])
assert len(verdicts) == 1
verdict = verdicts[("PT-1001", "dexcom_g7", "medicare")]
assert verdict.line_status.value == "Action Needed"
def test_conflicting_plan_types_route_to_unknown_line():
a = _record(order_number="ORD-1", plan_type="medicare")
b = _record(order_number="ORD-2", plan_type="medicaid")
index = build_readiness_index([a, b])
assert set(index.by_line) == {("PT-1001", "dexcom_g7", "unknown")}
verdict, line_key, merged = resolve_group(
index, "PT-1001", "dexcom_g7", a.shipment_date
)
assert verdict is not None
assert verdict.line_status.value == "Plan Type Needed"
assert line_key == ("PT-1001", "dexcom_g7", "unknown")
assert merged is not None
def test_mixed_plan_population_resolves_to_merged_line():
"""Review finding (HIGH family): a group where only SOME rows carry
plan_type merges to the typed line; row resolution must follow the MERGE,
not any single row's own plan_type."""
blank = _record(
order_number="ORD-1",
csv_swo_status="On File",
csv_visit_date=TODAY - timedelta(days=30),
csv_pecos_verified="Yes",
csv_diagnosis_on_file="Yes",
)
typed = _record(
order_number="ORD-2",
plan_type="medicare",
csv_swo_status="On File",
csv_visit_date=TODAY - timedelta(days=30),
csv_pecos_verified="Yes",
csv_diagnosis_on_file="Yes",
)
index = build_readiness_index([blank, typed])
assert set(index.by_line) == {("PT-1001", "dexcom_g7", "medicare")}
verdict, line_key, _ = resolve_group(
index, "PT-1001", "dexcom_g7", blank.shipment_date
)
assert verdict is not None, "blank-plan row must inherit its merged line's verdict"
assert verdict.line_status.value == "Clear to Ship"
assert line_key == ("PT-1001", "dexcom_g7", "medicare")
def test_conflicted_dos_never_borrows_another_lines_verdict():
"""Review finding (HIGH): a conflicted newer DOS must read its own
unknown-line verdict, never the older clean line's green."""
old_clean = _record(
shipment_date=TODAY - timedelta(days=45),
plan_type="medicare",
csv_swo_status="On File",
csv_visit_date=TODAY - timedelta(days=50),
csv_pecos_verified="Yes",
csv_diagnosis_on_file="Yes",
)
new_a = _record(order_number="ORD-1", plan_type="medicare")
new_b = _record(order_number="ORD-2", plan_type="medicaid")
index = build_readiness_index([old_clean, new_a, new_b])
assert set(index.by_line) == {
("PT-1001", "dexcom_g7", "medicare"),
("PT-1001", "dexcom_g7", "unknown"),
}
old_verdict, _, _ = resolve_group(
index, "PT-1001", "dexcom_g7", old_clean.shipment_date
)
new_verdict, _, _ = resolve_group(
index, "PT-1001", "dexcom_g7", new_a.shipment_date
)
assert old_verdict.line_status.value == "Clear to Ship"
assert new_verdict.line_status.value == "Plan Type Needed"
def test_merged_to_record_matches_dedup_field_policy():
"""Review finding (MEDIUM): fallback record fields must come from the
dedup merge (latest-non-null, collected order numbers), so the legacy
doc_state cannot contradict the readiness verdict."""
first = _record(order_number="ORD-1", csv_pecos_verified="Yes")
second = _record(order_number="ORD-2", plan_type="medicare")
index = build_readiness_index([first, second])
_, _, merged = resolve_group(
index, "PT-1001", "dexcom_g7", first.shipment_date
)
rec = merged_to_record(merged)
assert rec.csv_pecos_verified == "Yes" # latest-non-null keeps it
assert rec.order_number == "ORD-1, ORD-2" # collected, not arbitrary
assert rec.plan_type == "medicare"
def test_confirmed_visit_counts_as_positive_evidence():
rec = _record(
plan_type="medicare",
csv_swo_status="On File",
csv_pecos_verified="Yes",
csv_diagnosis_on_file="Yes",
)
confirmed = {_hash("PT-1001"): TODAY - timedelta(days=20)}
verdicts = readiness_by_line([rec], confirmed_visits=confirmed)
verdict = verdicts[("PT-1001", "dexcom_g7", "medicare")]
assert verdict.line_status.value == "Clear to Ship"
visit = next(i for i in verdict.doc_items if i.doc_type == "visit")
assert visit.satisfied
def test_readiness_stats_counts_lines():
good = _record(
patient_id="PT-A",
plan_type="medicare",
csv_swo_status="On File",
csv_visit_date=TODAY - timedelta(days=30),
csv_pecos_verified="Yes",
csv_diagnosis_on_file="Yes",
)
unknown = _record(patient_id="PT-B")
stats = readiness_stats(readiness_by_line([good, unknown]))
assert stats["Clear to Ship"] == 1
assert stats["Plan Type Needed"] == 1
assert stats["lines_total"] == 2
def test_no_raw_patient_id_in_logs(caplog):
# Conflicting plan types force the dedup warning path.
a = _record(patient_id="RAW-SECRET-ID-42", order_number="1", plan_type="medicare")
b = _record(patient_id="RAW-SECRET-ID-42", order_number="2", plan_type="medicaid")
with caplog.at_level("WARNING"):
readiness_by_line([a, b])
assert caplog.text # the warning fired
assert "RAW-SECRET-ID-42" not in caplog.text
def test_readiness_for_record_single_path():
rec = _record(
plan_type="medicare",
csv_swo_status="On File",
csv_pecos_verified="Yes",
csv_diagnosis_on_file="Yes",
)
verdict = readiness_for_record(
rec, confirmed_visit_date=TODAY - timedelta(days=10)
)
assert verdict.line_status.value == "Clear to Ship"
# --- API level (FastAPI TestClient) ------------------------------------------
httpx = pytest.importorskip("httpx", reason="httpx required for TestClient")
from fastapi.testclient import TestClient # noqa: E402
import api.main as api_main # noqa: E402
client = TestClient(api_main.app)
def _upload(csv_text: str):
return client.post(
"/api/upload",
files={"file": ("fixture.csv", io.BytesIO(csv_text.encode()), "text/csv")},
)
# Dates engineered so the legacy timing flag is RESUPPLY_READY (coverage ends
# within the 30-day window) and no renewal flag fires (visit recent).
SHIP = (TODAY - timedelta(days=5)).isoformat()
VISIT = (TODAY - timedelta(days=30)).isoformat()
HEADER = (
"patient_id,device_type,shipment_date,quantity,payer,plan_type,"
"swo_status,visit_date,pecos_verified,pa_status,diagnosis_on_file"
)
def test_upload_false_green_blocked_at_api_layer():
"""Acceptance case 1: legacy label green-ish, readiness verdict honest."""
csv_text = (
HEADER
+ f"\nPT-FG-1,Dexcom G7,{SHIP},3,Medicare Part B,medicare,"
+ f"On File,{VISIT},,,Yes"
)
resp = _upload(csv_text)
assert resp.status_code == 200
rec = resp.json()["records"][0]
assert rec["flag"] == "RESUPPLY_READY"
assert rec["status_label"] == "Clear to Ship" # legacy timing label
assert rec["readiness_status"] == "Action Needed" # PECOS absent blocks green
def test_upload_without_plan_type_reads_plan_type_needed():
"""Acceptance case 2: today's default for every record — honest, never guessed."""
header = (
"patient_id,device_type,shipment_date,quantity,payer,"
"swo_status,visit_date,pecos_verified,pa_status,diagnosis_on_file"
)
csv_text = (
header
+ f"\nPT-PTN-1,Dexcom G7,{SHIP},3,Medicare Part B,"
+ f"On File,{VISIT},Yes,,Yes"
)
resp = _upload(csv_text)
assert resp.status_code == 200
body = resp.json()
graded = [r for r in body["records"] if r["readiness_status"] is not None]
assert graded, "expected at least one graded record"
assert all(r["readiness_status"] == "Plan Type Needed" for r in graded)
assert body["readiness_stats"]["Plan Type Needed"] == body[
"readiness_stats"
]["lines_total"]
def test_upload_clear_to_ship_reachable_end_to_end():
"""Acceptance case 3: full positive evidence + mapped plan type = true green."""
csv_text = (
HEADER
+ f"\nPT-GREEN-1,Dexcom G7,{SHIP},3,Medicare Part B,medicare,"
+ f"On File,{VISIT},Yes,,Yes"
)
resp = _upload(csv_text)
assert resp.status_code == 200
rec = resp.json()["records"][0]
assert rec["plan_type"] == "medicare"
assert rec["readiness_status"] == "Clear to Ship"
items = {i["doc_type"]: i for i in rec["readiness_items"]}
assert items["pa"]["required_state"] == "NOT_REQUIRED" # Medicare FFS
assert all(i["satisfied"] for i in items.values())
def test_upload_with_order_numbers_still_resolves_records():
"""Regression for the record_lookup miss: CoverageResult has no
order_number, so exact-key lookups miss for order-numbered CSVs; the
fallback must recover doc_state AND readiness fields."""
header = HEADER + ",order_number"
csv_text = (
header
+ f"\nPT-ORD-1,Dexcom G7,{SHIP},3,Medicare Part B,medicare,"
+ f"On File,{VISIT},Yes,,Yes,ORD-778899"
)
resp = _upload(csv_text)
assert resp.status_code == 200
rec = resp.json()["records"][0]
assert rec["order_number"] == "ORD-778899"
assert rec["doc_state"] is not None
assert rec["readiness_status"] == "Clear to Ship"
def test_upload_backcompat_legacy_fields_unchanged():
"""Acceptance case 6: the wiring is additive — every legacy field matches
an independent reconstruction from the engines for an order-less CSV
(where the exact record lookup must hit and byte-identity must hold)."""
csv_text = (
HEADER
+ f"\nPT-BC-1,Dexcom G7,{SHIP},3,Medicare Part B,medicare,"
+ f"On File,{VISIT},Yes,,Yes"
+ f"\nPT-BC-2,Libre 3,{(TODAY - timedelta(days=90)).isoformat()},1,"
+ "Aetna,commercial,Pending,,,Requested,Yes"
)
resp = _upload(csv_text)
assert resp.status_code == 200
body = resp.json()
from api.normalizer import normalize_csv
from core.doc_state_machine import compute_doc_state
records, _, _ = normalize_csv(csv_text)
by_pid = {r.patient_id: r for r in records}
expected = calculate_batch(records, as_of=TODAY, confirmed_visits={})
assert len(body["records"]) == len(expected)
for got, want in zip(body["records"], expected):
rec = by_pid[want.patient_id]
assert got["patient_id"] == want.patient_id
assert got["flag"] == want.flag.value
assert got["priority_score"] == want.priority_score
assert got["status_label"] == api_main.FLAG_LABELS.get(
want.flag.value, want.flag.value
)
assert got["payer"] == want.payer
assert got["days_until_coverage_end"] == want.days_until_coverage_end
assert got["coverage_end_date"] == want.coverage_end_date.isoformat()
assert got["rule_version"] == want.rule_version
assert got["action"] == api_main.FLAG_ACTIONS.get(
want.flag.value, "Review"
)
# Record-echo fields resolve through the exact lookup (order-less CSV)
assert got["quantity"] == rec.quantity
assert got["order_number"] == rec.order_number
assert got["csv_swo_status"] == rec.csv_swo_status
assert got["csv_pecos_verified"] == rec.csv_pecos_verified
assert got["csv_pa_status"] == rec.csv_pa_status
assert got["csv_diagnosis_on_file"] == rec.csv_diagnosis_on_file
# doc_state reconstructed independently from the same inputs
doc = compute_doc_state(
payer_type=api_main._normalize_payer_type(want.payer),
csv_swo_status=rec.csv_swo_status,
csv_visit_date=rec.csv_visit_date,
confirmed_visit_date=None,
csv_pecos_verified=rec.csv_pecos_verified,
csv_pa_status=rec.csv_pa_status,
csv_diagnosis_on_file=rec.csv_diagnosis_on_file,
is_transfer=bool(rec.csv_transfer_from),
)
assert got["doc_state"]["visit"] == doc.visit
assert got["doc_state"]["pecos"] == doc.pecos
assert got["doc_state"]["diagnosis"] == doc.diagnosis
assert got["cascade"] == doc.cascade
# Full legacy stats dict reconstructed independently from the flags
flags = [w.flag.value for w in expected]
assert body["stats"] == {
"total": len(expected),
"supply_lapsed": flags.count("SUPPLY_LAPSED"),
"visit_required": flags.count("VISIT_REQUIRED"),
"transfer_pending": flags.count("TRANSFER_PENDING"),
"renewal_critical": flags.count("RENEWAL_CRITICAL"),
"renewal_elevated": flags.count("RENEWAL_ELEVATED"),
"renewal_soon": flags.count("RENEWAL_SOON"),
"resupply_ready": flags.count("RESUPPLY_READY"),
"active": flags.count("ACTIVE"),
"no_recent_shipment": flags.count("NO_RECENT_SHIPMENT"),
"prescriber_action": (
flags.count("SUPPLY_LAPSED")
+ flags.count("VISIT_REQUIRED")
+ flags.count("RENEWAL_CRITICAL")
+ flags.count("RENEWAL_ELEVATED")
),
}
def test_upload_mixed_plan_group_both_rows_carry_merged_verdict():
"""Review finding (HIGH family), API level: duplicate order-numbered rows
where only one carries plan_type — both output rows must carry the merged
line's verdict, and stats must reconcile with the rows."""
header = HEADER + ",order_number"
csv_text = (
header
+ f"\nPT-MIX-1,Dexcom G7,{SHIP},3,Medicare Part B,,"
+ f"On File,{VISIT},Yes,,Yes,ORD-1"
+ f"\nPT-MIX-1,Dexcom G7,{SHIP},3,Medicare Part B,medicare,"
+ f"On File,{VISIT},Yes,,Yes,ORD-2"
)
resp = _upload(csv_text)
assert resp.status_code == 200
body = resp.json()
assert len(body["records"]) == 2
for rec in body["records"]:
assert rec["readiness_status"] == "Clear to Ship"
assert rec["plan_type"] == "medicare"
assert rec["order_number"] == "ORD-1, ORD-2"
assert body["readiness_stats"] == {"Clear to Ship": 1, "lines_total": 1}
def test_upload_conflicted_dos_reads_own_line_not_older_green():
"""Review finding (HIGH), API level: the false-green repro. A conflicted
newer DOS must read Plan Type Needed even when an older clean DOS on the
same patient+device graded Clear to Ship."""
header = HEADER + ",order_number"
old_ship = (TODAY - timedelta(days=45)).isoformat()
old_visit = (TODAY - timedelta(days=50)).isoformat()
csv_text = (
header
+ f"\nPT-CONF-1,Dexcom G7,{old_ship},3,Medicare Part B,medicare,"
+ f"On File,{old_visit},Yes,,Yes,ORD-OLD"
+ f"\nPT-CONF-1,Dexcom G7,{SHIP},3,Medicare Part B,medicare,"
+ f"On File,{VISIT},Yes,,Yes,ORD-N1"
+ f"\nPT-CONF-1,Dexcom G7,{SHIP},3,Medicaid - GA,medicaid,"
+ f"On File,{VISIT},Yes,,Yes,ORD-N2"
)
resp = _upload(csv_text)
assert resp.status_code == 200
body = resp.json()
by_ship = {}
for rec in body["records"]:
by_ship.setdefault(rec["last_shipment_date"], []).append(rec)
for rec in by_ship[old_ship]:
assert rec["readiness_status"] == "Clear to Ship"
for rec in by_ship[SHIP]:
assert rec["readiness_status"] == "Plan Type Needed"
assert body["readiness_stats"] == {
"Clear to Ship": 1,
"Plan Type Needed": 1,
"lines_total": 2,
}
def test_upload_skipped_record_leaves_no_phantom_line_in_stats():
"""Review finding (LOW): a record calculate_batch skips (G7 transmitter
has no wear-day rule) must not leave a phantom line in readiness_stats."""
header = HEADER + ",component"
csv_text = (
header
+ f"\nPT-OK-1,Dexcom G7,{SHIP},3,Medicare Part B,medicare,"
+ f"On File,{VISIT},Yes,,Yes,sensor"
+ f"\nPT-TX-1,Dexcom G7,{SHIP},1,Medicare Part B,medicare,"
+ f"On File,{VISIT},Yes,,Yes,transmitter"
)
resp = _upload(csv_text)
assert resp.status_code == 200
body = resp.json()
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
def test_upload_non_gradeable_rows_carry_no_verdict():
csv_text = (
HEADER
+ f"\nPT-POD-1,Omnipod 5,{SHIP},1,Medicare Part B,medicare,"
+ f"On File,{VISIT},Yes,,Yes"
)
resp = _upload(csv_text)
assert resp.status_code == 200
rec = resp.json()["records"][0]
assert rec["readiness_status"] is None
assert rec["readiness_items"] is None
def test_confirm_visit_recompute_flips_to_clear(monkeypatch):
"""Acceptance case 5: confirming the visit satisfies the last open
required item in the same response."""
monkeypatch.setattr(api_main, "get_or_create_org", lambda **kw: "org-test")
monkeypatch.setattr(
api_main, "upsert_confirmed_visit", lambda *a, **kw: True
)
# Upload path first: everything good except visit evidence.
csv_text = (
HEADER
+ f"\nPT-CV-1,Dexcom G7,{SHIP},3,Medicare Part B,medicare,"
+ "On File,,Yes,,Yes"
)
up = _upload(csv_text)
assert up.status_code == 200
assert up.json()["records"][0]["readiness_status"] == "Action Needed"
resp = client.post(
"/api/confirm-visit",
json={
"patient_id": "PT-CV-1",
"confirmed_date": (TODAY - timedelta(days=15)).isoformat(),
"shipment_date": SHIP,
"payer": "Medicare Part B",
"device_type": "dexcom_g7",
"quantity": 3,
"csv_swo_status": "On File",
"csv_pecos_verified": "Yes",
"csv_diagnosis_on_file": "Yes",
"plan_type": "medicare",
},
)
assert resp.status_code == 200
rec = resp.json()
assert rec["readiness_status"] == "Clear to Ship"
visit = next(i for i in rec["readiness_items"] if i["doc_type"] == "visit")
assert visit["satisfied"]
def test_confirm_visit_normalizes_plan_type(monkeypatch):
"""Review finding (LOW): both paths must return the same normalized
plan_type representation."""
monkeypatch.setattr(api_main, "get_or_create_org", lambda **kw: "org-test")
monkeypatch.setattr(
api_main, "upsert_confirmed_visit", lambda *a, **kw: True
)
resp = client.post(
"/api/confirm-visit",
json={
"patient_id": "PT-NORM-1",
"confirmed_date": (TODAY - timedelta(days=15)).isoformat(),
"shipment_date": SHIP,
"payer": "Medicare Part B",
"device_type": "dexcom_g7",
"quantity": 3,
"csv_swo_status": "On File",
"csv_pecos_verified": "Yes",
"csv_diagnosis_on_file": "Yes",
"plan_type": " Medicare ",
},
)
assert resp.status_code == 200
rec = resp.json()
assert rec["plan_type"] == "medicare"
assert rec["readiness_status"] == "Clear to Ship"
def test_upload_readiness_items_carry_rule_ids():
"""P2 contract W2: the serialized doc_items cite their rules."""
csv_text = (
HEADER
+ f"\nPT-RID-1,Dexcom G7,{SHIP},3,Medicare Part B,medicare,"
+ f"On File,{VISIT},Yes,,Yes"
)
resp = _upload(csv_text)
assert resp.status_code == 200
items = {i["doc_type"]: i for i in resp.json()["records"][0]["readiness_items"]}
assert items["pecos"]["rule_id"] == "R004"
assert items["pa"]["rule_id"] == "R003"
assert items["swo"]["rule_id"] == "R100"