feat: device-keyed staff overrides + recompute contract (plan 02, P4)

Ends the override leak: an SWO/PA save now applies to one device's line, not
every device the patient has. doc_status gains device_type ('' = legacy
patient-scoped row, applies until re-saved); the old unique key is dropped by
introspected name and replaced with the 4-col scope key. Staff overrides now
FEED THE VERDICT (device-scoped, staff wins over the CSV cell) instead of
display-only; visit overrides keep their patient-scoped fan-out via
confirmed_visits. PUT /api/doc-status and /api/confirm-visit accept an
optional echo of the patient's lines and return {lines, rollup_status}
recomputed through the same engine (stateless, no batch rebuild). Frontend
minimal fix: doc-status state and writes keyed patient:device. Independent
audit: SHIP (migration reproduced empirically on both old-key shapes; guard
scoped per its nit). 162 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kisa 2026-07-07 05:36:26 -04:00
parent 5ba31fac03
commit a53728e424
8 changed files with 2000 additions and 35 deletions

File diff suppressed because it is too large Load diff

View file

@ -35,6 +35,11 @@ from core.persistence import (
load_doc_statuses_for_org, load_doc_statuses_for_org,
) )
from api.normalizer import normalize_csv from api.normalizer import normalize_csv
from core.overrides import (
Override,
OverrideKey,
overrides_from_saved,
)
from core.worklist_readiness import ( from core.worklist_readiness import (
build_readiness_index, build_readiness_index,
merged_to_record, merged_to_record,
@ -349,22 +354,129 @@ VALID_DOC_STATUSES = {
"pa": {"not_required", "requested", "approved", "denied"}, "pa": {"not_required", "requested", "approved", "denied"},
} }
DOC_STATUS_LABELS = { # Single source of truth for stored-status labels lives with the override
"pending": "Pending", # engine (the same labels feed grading and display).
"requested": "Requested", from core.overrides import STORED_STATUS_LABELS as DOC_STATUS_LABELS # noqa: E402
"on_file": "On File",
"not_required": "Not Required",
"approved": "Approved", def _recompute_patient(
"denied": "Denied", patient_id: str,
} echo_lines: list["EchoLine"],
saved_doc_statuses: dict,
confirmed_visit: Optional[date] = None,
):
"""Stateless patient recompute (P4): rebuild the patient's lines from the
echoed CSV-truth inputs, apply ALL persisted staff overrides (the write
that triggered this call is already persisted) plus the confirmed visit,
re-grade through the same engine, and roll up through the same truth
table. Returns (lines_out, rollup_status)."""
from core.rollup import rollup_patient
from core.worklist_readiness import build_readiness_index as _bri
records = []
for el in echo_lines:
try:
ship = (
date_type.fromisoformat(el.shipment_date)
if el.shipment_date
else date.today()
)
except ValueError:
ship = date.today()
csv_visit = None
if el.csv_visit_date:
try:
csv_visit = date_type.fromisoformat(el.csv_visit_date)
except ValueError:
csv_visit = None
records.append(
ShipmentRecord(
patient_id=patient_id,
device_type=el.device_type,
shipment_date=ship,
quantity=1,
payer="",
csv_swo_status=el.csv_swo_status,
csv_visit_date=csv_visit,
csv_pecos_verified=el.csv_pecos_verified,
csv_pa_status=el.csv_pa_status,
csv_diagnosis_on_file=el.csv_diagnosis_on_file,
plan_type=(el.plan_type or "").strip().lower() or None,
)
)
patient_hash = hashlib.sha256(patient_id.encode()).hexdigest()
confirmed = (
{patient_hash: confirmed_visit} if confirmed_visit else None
)
overrides = overrides_from_saved(
{patient_hash: saved_doc_statuses.get(patient_hash, {})}
)
index = _bri(records, confirmed_visits=confirmed, overrides=overrides)
lines_out = []
for line_key, line in index.lines.items():
verdict = index.by_line.get(line_key)
lines_out.append(
LineRecomputeOut(
device_type=line.device_type,
plan_type=None if line.plan_type == "unknown" else line.plan_type,
line_status=line.line_status,
doc_items=_readiness_items_out(verdict),
)
)
rollup = rollup_patient(list(index.lines.values()))
return lines_out, (
rollup.rollup_status.value if rollup.rollup_status else None
)
class EchoLine(BaseModel):
"""One coverage line echoed back for a stateless patient recompute.
Carries CSV-truth grading inputs only; the server applies all persisted
staff overrides and confirmed visits on top, then re-grades."""
device_type: str
gradeable: bool = True
plan_type: Optional[str] = None
shipment_date: Optional[str] = None # ISO; defaults to today (doc axis
# grading does not depend on it)
csv_swo_status: Optional[str] = None
csv_visit_date: Optional[str] = None
csv_pecos_verified: Optional[str] = None
csv_pa_status: Optional[str] = None
csv_diagnosis_on_file: Optional[str] = None
class LineRecomputeOut(BaseModel):
device_type: str
plan_type: Optional[str] = None
line_status: Optional[str] = None
doc_items: Optional[list["ReadinessItemOut"]] = None
class DocStatusRequest(BaseModel): class DocStatusRequest(BaseModel):
patient_id: str patient_id: str
doc_type: str doc_type: str
status: str status: str
# Device scope (P4). '' or omitted writes a legacy patient-scoped row
# that applies to all devices until a device-scoped row is saved.
device_type: str = ""
status_date: Optional[str] = None status_date: Optional[str] = None
expiry_date: Optional[str] = None expiry_date: Optional[str] = None
# Optional echo of the patient's lines for the {line, rollup} recompute.
lines: Optional[list[EchoLine]] = None
def _saved_for_device(
doc_statuses: dict, patient_hash: str, device_type: str
) -> dict:
"""Resolve the device-scoped saved statuses for one record: legacy ''
(patient-scoped) rows apply to all devices; a device-scoped row wins."""
by_pat = doc_statuses.get(patient_hash, {})
merged = dict(by_pat.get("", {}))
merged.update(by_pat.get(device_type, {}))
return merged
def _readiness_items_out(readiness) -> Optional[list[ReadinessItemOut]]: def _readiness_items_out(readiness) -> Optional[list[ReadinessItemOut]]:
@ -683,8 +795,11 @@ async def upload_csv(
# Readiness index (Phase 2a: additive, augments the legacy scoring — # Readiness index (Phase 2a: additive, augments the legacy scoring —
# replaces nothing). Row-to-line membership comes from the dedup output # replaces nothing). Row-to-line membership comes from the dedup output
# itself, never re-derived from a row's own plan_type. # itself, never re-derived from a row's own plan_type. Staff overrides
index = build_readiness_index(records, confirmed_visits) # (P4) feed the grading inputs, device-scoped.
index = build_readiness_index(
records, confirmed_visits, overrides=overrides_from_saved(doc_statuses)
)
out = [] out = []
used_lines = {} used_lines = {}
@ -718,8 +833,10 @@ async def upload_csv(
confirmed_visit_date=confirmed_visits.get( confirmed_visit_date=confirmed_visits.get(
hashlib.sha256(r.patient_id.encode()).hexdigest() hashlib.sha256(r.patient_id.encode()).hexdigest()
), ),
saved_doc_statuses=doc_statuses.get( saved_doc_statuses=_saved_for_device(
hashlib.sha256(r.patient_id.encode()).hexdigest(), {} doc_statuses,
hashlib.sha256(r.patient_id.encode()).hexdigest(),
r.device_type,
), ),
readiness=verdict, readiness=verdict,
) )
@ -883,6 +1000,10 @@ class ConfirmVisitRequest(BaseModel):
# Client-mapped plan type echoed from the upload — never guessed. None # Client-mapped plan type echoed from the upload — never guessed. None
# keeps the recompute honest: the verdict reads Plan Type Needed. # keeps the recompute honest: the verdict reads Plan Type Needed.
plan_type: str | None = None plan_type: str | None = None
# Optional echo of ALL the patient's lines: the confirmed visit fans out
# to every line (the visit is the person), so the P4 recompute re-grades
# each and returns the re-rolled patient alongside the legacy record.
lines: Optional[list[EchoLine]] = None
@app.post("/api/confirm-visit") @app.post("/api/confirm-visit")
@ -985,9 +1106,23 @@ async def confirm_visit(
db_conn=db_conn, db_conn=db_conn,
) )
return _to_record_out( record_out = _to_record_out(
result, record=record, confirmed_visit_date=confirmed, readiness=verdict result, record=record, confirmed_visit_date=confirmed, readiness=verdict
) )
if not body.lines:
return record_out
# P4: re-grade the patient's echoed lines with the new confirmed date
# (visit fans out to all lines) and return the re-rolled patient. All
# legacy RecordOut fields stay present.
saved = load_doc_statuses_for_org(org_id)
lines_out, rollup_status = _recompute_patient(
body.patient_id, body.lines, saved, confirmed_visit=confirmed
)
payload = record_out.model_dump()
payload["lines"] = [l.model_dump() for l in lines_out]
payload["rollup_status"] = rollup_status
return payload
@app.put("/api/doc-status") @app.put("/api/doc-status")
@ -1039,7 +1174,13 @@ async def update_doc_status(
patient_hash = hashlib.sha256(body.patient_id.encode()).hexdigest() patient_hash = hashlib.sha256(body.patient_id.encode()).hexdigest()
success = upsert_doc_status( success = upsert_doc_status(
org_id, patient_hash, body.doc_type, body.status, status_date, expiry_date org_id,
patient_hash,
body.doc_type,
body.status,
status_date,
expiry_date,
device_type=body.device_type or "",
) )
if not success: if not success:
raise HTTPException(status_code=503, detail="Failed to save doc status.") raise HTTPException(status_code=503, detail="Failed to save doc status.")
@ -1057,9 +1198,24 @@ async def update_doc_status(
db_conn=db_conn, db_conn=db_conn,
) )
return { response = {
"patient_id": body.patient_id, "patient_id": body.patient_id,
"doc_type": body.doc_type, "doc_type": body.doc_type,
"status": body.status, "status": body.status,
"device_type": body.device_type or "",
"display": DOC_STATUS_LABELS.get(body.status, body.status), "display": DOC_STATUS_LABELS.get(body.status, body.status),
} }
# P4 recompute contract: when the client echoes the patient's lines,
# re-grade them (the write above is already persisted, so a fresh load
# picks it up) and return {lines, rollup} from the same engine.
if body.lines:
saved = load_doc_statuses_for_org(org_id)
confirmed = load_confirmed_visits_for_org(org_id).get(patient_hash)
lines_out, rollup_status = _recompute_patient(
body.patient_id, body.lines, saved, confirmed_visit=confirmed
)
response["lines"] = [l.model_dump() for l in lines_out]
response["rollup_status"] = rollup_status
return response

View file

@ -0,0 +1,130 @@
"""
overrides.py
Signal STTIL Solutions
Device-keyed staff overrides (build plan 02, P4; phase-2 design Item 4).
Pilot scope is LOCKED to two override kinds:
- visit: patient-scoped (the visit is the person). It rides the existing
confirmed_visits path, which already fans out to every line of the patient
by construction (evaluate_readiness receives the confirmed date per
patient). This module does not duplicate that mechanism.
- swo / pa: line-scoped. They apply only to lines whose device_type matches;
device_type '' marks a legacy patient-scoped row and matches all devices
until staff re-saves it device-scoped.
Overrides feed the GRADING inputs (staff knowledge wins over the CSV cell,
matching the existing display precedence), then affected lines re-grade.
PHI CONTRACT: overrides carry patient_id_hash only (the storage key). Lines
carry raw patient_id; matching hashes it. Nothing here logs patient data.
"""
from __future__ import annotations
import hashlib
from dataclasses import dataclass
from datetime import date, datetime
from typing import Optional
# Stored status -> display/grading label. Single source of truth; the API's
# display map imports this. The labels feed the readiness vocabularies
# ("On File" grades GOOD, "Denied" grades BAD, "Requested" grades PENDING).
STORED_STATUS_LABELS = {
"pending": "Pending",
"requested": "Requested",
"on_file": "On File",
"not_required": "Not Required",
"approved": "Approved",
"denied": "Denied",
}
# Legacy patient-scoped rows carry an empty device_type and match all devices.
LEGACY_ALL_DEVICES = ""
OVERRIDABLE_DOC_TYPES = ("swo", "pa")
@dataclass(frozen=True)
class OverrideKey:
org_id: str
patient_hash: str
device_type: str # '' = legacy patient-scoped, applies to all devices
doc_type: str # "swo" | "pa" (visit rides confirmed_visits)
@dataclass
class Override:
key: OverrideKey
status: str # stored value, e.g. "on_file" | "approved" | "denied"
status_date: Optional[date] = None
expiry_date: Optional[date] = None
confirmed_by: str = "staff"
confirmed_at: Optional[datetime] = None
def _hash_pid(patient_id: str) -> str:
return hashlib.sha256(patient_id.encode()).hexdigest()
def grading_value(override: Override) -> str:
"""The label fed to the readiness vocabularies for this override."""
return STORED_STATUS_LABELS.get(override.status, override.status)
def overrides_from_saved(saved: dict) -> list[Override]:
"""Build Override objects from the persistence load shape:
patient_hash -> device_type -> doc_type -> {status, status_date, ...}."""
out: list[Override] = []
for patient_hash, by_device in (saved or {}).items():
for device_type, by_doc in by_device.items():
for doc_type, row in by_doc.items():
if doc_type not in OVERRIDABLE_DOC_TYPES:
continue
out.append(
Override(
key=OverrideKey(
org_id="",
patient_hash=patient_hash,
device_type=device_type,
doc_type=doc_type,
),
status=row.get("status", ""),
)
)
return out
def apply_overrides(lines: dict, overrides: list[Override]) -> set[str]:
"""Apply swo/pa overrides onto the grading inputs of matching lines.
Mutates each matching line's current_shipment csv fields (staff knowledge
wins over the CSV cell). Device-scoped rows win over legacy '' rows.
Returns the raw patient_ids of touched lines. Grading (or re-grading)
happens in the caller build_readiness_index grades after application.
"""
if not overrides:
return set()
by_patient: dict[str, list[Override]] = {}
for o in overrides:
by_patient.setdefault(o.key.patient_hash, []).append(o)
touched: set[str] = set()
for line in lines.values():
patient_overrides = by_patient.get(_hash_pid(line.patient_id))
if not patient_overrides:
continue
# Legacy '' first so a device-scoped row applied after it wins.
for o in sorted(
patient_overrides, key=lambda o: o.key.device_type != LEGACY_ALL_DEVICES
):
if o.key.device_type not in (LEGACY_ALL_DEVICES, line.device_type):
continue
label = grading_value(o)
if o.key.doc_type == "swo":
line.current_shipment.csv_swo_status = label
elif o.key.doc_type == "pa":
line.current_shipment.csv_pa_status = label
touched.add(line.patient_id)
return touched

View file

@ -227,9 +227,12 @@ def upsert_doc_status(
status: str, status: str,
status_date: "date | None" = None, status_date: "date | None" = None,
expiry_date: "date | None" = None, expiry_date: "date | None" = None,
device_type: str = "",
) -> bool: ) -> bool:
""" """
Insert or update a doc status (swo or pa) for a patient. Insert or update a doc status (swo or pa), scoped to a device.
device_type '' writes a legacy patient-scoped row (applies to all of the
patient's devices until a device-scoped row is saved).
Returns True on success, False if Supabase unavailable. Returns True on success, False if Supabase unavailable.
""" """
client = get_client() client = get_client()
@ -241,6 +244,7 @@ def upsert_doc_status(
"patient_id_hash": patient_id_hash, "patient_id_hash": patient_id_hash,
"doc_type": doc_type, "doc_type": doc_type,
"status": status, "status": status,
"device_type": device_type or "",
"updated_at": "now()", "updated_at": "now()",
} }
if status_date is not None: if status_date is not None:
@ -248,7 +252,7 @@ def upsert_doc_status(
if expiry_date is not None: if expiry_date is not None:
payload["expiry_date"] = expiry_date.isoformat() payload["expiry_date"] = expiry_date.isoformat()
client.table("doc_status").upsert( client.table("doc_status").upsert(
payload, on_conflict="org_id,patient_id_hash,doc_type" payload, on_conflict="org_id,patient_id_hash,doc_type,device_type"
).execute() ).execute()
return True return True
except Exception as e: except Exception as e:
@ -258,23 +262,24 @@ def upsert_doc_status(
def load_doc_statuses_for_org(org_id: str) -> dict: def load_doc_statuses_for_org(org_id: str) -> dict:
""" """
Load all SWO and PA statuses for an org. Load all SWO and PA statuses for an org, device-scoped.
Returns dict: patient_id_hash -> {doc_type: {status, status_date, expiry_date}} Returns dict: patient_id_hash -> device_type -> {doc_type: {status,
status_date, expiry_date}}. The '' device bucket holds legacy
patient-scoped rows (they apply to every device until re-saved).
""" """
client = get_client() client = get_client()
if not client: if not client:
return {} return {}
try: try:
result = client.table("doc_status") \ result = client.table("doc_status") \
.select("patient_id_hash,doc_type,status,status_date,expiry_date") \ .select("patient_id_hash,device_type,doc_type,status,status_date,expiry_date") \
.eq("org_id", org_id) \ .eq("org_id", org_id) \
.execute() .execute()
out: dict = {} out: dict = {}
for row in (result.data or []): for row in (result.data or []):
h = row["patient_id_hash"] h = row["patient_id_hash"]
if h not in out: device = row.get("device_type") or ""
out[h] = {} out.setdefault(h, {}).setdefault(device, {})[row["doc_type"]] = {
out[h][row["doc_type"]] = {
"status": row["status"], "status": row["status"],
"status_date": row.get("status_date"), "status_date": row.get("status_date"),
"expiry_date": row.get("expiry_date"), "expiry_date": row.get("expiry_date"),

View file

@ -82,18 +82,27 @@ def _confirmed_for(
def build_readiness_index( def build_readiness_index(
records: list[ShipmentRecord], records: list[ShipmentRecord],
confirmed_visits: Optional[dict[str, date]] = None, confirmed_visits: Optional[dict[str, date]] = None,
overrides: Optional[list] = None,
) -> ReadinessIndex: ) -> ReadinessIndex:
"""Dedup records into coverage lines, grade each gradeable line, and """Dedup records into coverage lines, apply staff overrides to the
record the group->line membership map so row-level resolution never grading inputs (P4: swo/pa device-scoped; staff knowledge wins over the
re-derives it. CSV cell), grade each gradeable line, and record the group->line
membership map so row-level resolution never re-derives it.
Grades the CURRENT shipment only (latest DOS per line locked rule); Grades the CURRENT shipment only (latest DOS per line locked rule);
prior shipments are collapsed history. Non-gradeable lines are skipped. prior shipments are collapsed history. Non-gradeable lines are skipped.
Visit overrides arrive via confirmed_visits (patient-scoped fan-out by
construction).
""" """
confirmed_visits = confirmed_visits or {} confirmed_visits = confirmed_visits or {}
merged = dedup(records) merged = dedup(records)
lines = assign_to_coverage_lines(merged) lines = assign_to_coverage_lines(merged)
if overrides:
from core.overrides import apply_overrides
apply_overrides(lines, overrides)
index = ReadinessIndex() index = ReadinessIndex()
for key, line in lines.items(): for key, line in lines.items():
line_key = (key.patient_id, key.device_type, key.plan_type_id) line_key = (key.patient_id, key.device_type, key.plan_type_id)

View file

@ -347,22 +347,22 @@ export default function WorklistTable({ records, activeFilter, onFilterChange, o
<div className="grid grid-cols-2 gap-y-[6px] gap-x-[20px] text-[12px]"> <div className="grid grid-cols-2 gap-y-[6px] gap-x-[20px] text-[12px]">
<DocItem <DocItem
label="SWO" label="SWO"
value={localDocStates[r.patient_id]?.swo ?? r.doc_state.swo} value={localDocStates[`${r.patient_id}:${r.device_type}`]?.swo ?? r.doc_state.swo}
patientId={r.patient_id} docType="swo" cycle={SWO_CYCLE} patientId={r.patient_id} deviceType={r.device_type} docType="swo" cycle={SWO_CYCLE}
getToken={getToken} getToken={getToken}
onUpdated={(type, label) => setLocalDocStates(prev => ({ onUpdated={(type, label) => setLocalDocStates(prev => ({
...prev, [r.patient_id]: { ...prev[r.patient_id], [type]: label } ...prev, [`${r.patient_id}:${r.device_type}`]: { ...prev[`${r.patient_id}:${r.device_type}`], [type]: label }
}))} }))}
/> />
<DocItem label="Qualifying Visit" value={r.doc_state.visit} /> <DocItem label="Qualifying Visit" value={r.doc_state.visit} />
<DocItem label="PECOS Enrollment" value={r.doc_state.pecos} /> <DocItem label="PECOS Enrollment" value={r.doc_state.pecos} />
<DocItem <DocItem
label="Prior Authorization" label="Prior Authorization"
value={localDocStates[r.patient_id]?.pa ?? r.doc_state.pa} value={localDocStates[`${r.patient_id}:${r.device_type}`]?.pa ?? r.doc_state.pa}
patientId={r.patient_id} docType="pa" cycle={PA_CYCLE} patientId={r.patient_id} deviceType={r.device_type} docType="pa" cycle={PA_CYCLE}
getToken={getToken} getToken={getToken}
onUpdated={(type, label) => setLocalDocStates(prev => ({ onUpdated={(type, label) => setLocalDocStates(prev => ({
...prev, [r.patient_id]: { ...prev[r.patient_id], [type]: label } ...prev, [`${r.patient_id}:${r.device_type}`]: { ...prev[`${r.patient_id}:${r.device_type}`], [type]: label }
}))} }))}
/> />
<DocItem label="Diagnosis on File" value={r.doc_state.diagnosis} /> <DocItem label="Diagnosis on File" value={r.doc_state.diagnosis} />
@ -492,7 +492,7 @@ function ActionCell({ flag, cascade, showConfirmVisit, onConfirmVisit, onExpand
return null; return null;
} }
function DocItem({ label, value, patientId, docType, cycle, getToken, onUpdated }) { function DocItem({ label, value, patientId, deviceType = "", docType, cycle, getToken, onUpdated }) {
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const isGood = value && ( const isGood = value && (
@ -514,7 +514,7 @@ function DocItem({ label, value, patientId, docType, cycle, getToken, onUpdated
const nextLabel = DOC_STATUS_LABELS[nextKey]; const nextLabel = DOC_STATUS_LABELS[nextKey];
setSaving(true); setSaving(true);
const token = getToken ? await getToken().catch(() => null) : null; const token = getToken ? await getToken().catch(() => null) : null;
const result = await updateDocStatus(patientId, docType, nextKey, null, null, token); const result = await updateDocStatus(patientId, docType, nextKey, null, null, token, deviceType);
setSaving(false); setSaving(false);
if (result && onUpdated) onUpdated(docType, nextLabel); if (result && onUpdated) onUpdated(docType, nextLabel);
}; };

View file

@ -141,7 +141,7 @@ export async function confirmVisit(payload, token = null) {
* @param {string|null} token - Clerk JWT * @param {string|null} token - Clerk JWT
* @returns {Promise<{patient_id, doc_type, status, display}|null>} * @returns {Promise<{patient_id, doc_type, status, display}|null>}
*/ */
export async function updateDocStatus(patientId, docType, status, statusDate = null, expiryDate = null, token = null) { export async function updateDocStatus(patientId, docType, status, statusDate = null, expiryDate = null, token = null, deviceType = "") {
const authHeader = token const authHeader = token
? { "Authorization": `Bearer ${token}` } ? { "Authorization": `Bearer ${token}` }
: {}; : {};
@ -153,6 +153,9 @@ export async function updateDocStatus(patientId, docType, status, statusDate = n
patient_id: patientId, patient_id: patientId,
doc_type: docType, doc_type: docType,
status, status,
// Device scope (P4): an SWO/PA update applies to this device's line
// only, never to every device the patient has.
...(deviceType && { device_type: deviceType }),
...(statusDate && { status_date: statusDate }), ...(statusDate && { status_date: statusDate }),
...(expiryDate && { expiry_date: expiryDate }), ...(expiryDate && { expiry_date: expiryDate }),
}), }),

311
tests/test_overrides.py Normal file
View file

@ -0,0 +1,311 @@
"""
Tests for device-keyed staff overrides (build plan 02, P4).
Locked semantics (phase-2 design Item 4 / brief section 6):
- visit overrides are patient-scoped and fan out to ALL the patient's lines
(they ride the confirmed_visits path by construction);
- swo/pa overrides apply only to lines whose device_type matches;
- device_type '' marks a legacy patient-scoped row and matches all devices;
- staff knowledge wins over the CSV cell;
- after a write, the recompute returns {lines, rollup} from the same engine.
PHI CONTRACT: synthetic patient_ids only; overrides carry hashes.
"""
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 # noqa: E402
from core.overrides import ( # noqa: E402
Override,
OverrideKey,
apply_overrides,
overrides_from_saved,
)
from core.worklist_readiness import build_readiness_index # noqa: E402
TODAY = date.today()
def _hash(pid: str) -> str:
return hashlib.sha256(pid.encode()).hexdigest()
def _record(**kwargs) -> ShipmentRecord:
defaults = dict(
patient_id="PT-OV-1",
device_type="dexcom_g7",
shipment_date=TODAY - timedelta(days=5),
quantity=3,
payer="Medicare Part B",
component="sensor",
plan_type="medicare",
csv_visit_date=TODAY - timedelta(days=30),
csv_pecos_verified="Yes",
csv_diagnosis_on_file="Yes",
)
defaults.update(kwargs)
return ShipmentRecord(**defaults)
def _swo_override(pid="PT-OV-1", device="dexcom_g7", status="on_file"):
return Override(
key=OverrideKey(
org_id="org", patient_hash=_hash(pid), device_type=device,
doc_type="swo",
),
status=status,
)
def test_override_swo_applies_only_to_one_device():
# Two devices, both missing SWO -> Action Needed. Override device A only.
g7 = _record(device_type="dexcom_g7")
libre = _record(device_type="freestyle_libre_2")
index = build_readiness_index(
[g7, libre], overrides=[_swo_override(device="dexcom_g7")]
)
assert index.by_line[("PT-OV-1", "dexcom_g7", "medicare")].line_status.value == "Clear to Ship"
assert index.by_line[("PT-OV-1", "freestyle_libre_2", "medicare")].line_status.value == "Action Needed"
def test_legacy_blank_device_row_applies_patient_wide():
g7 = _record(device_type="dexcom_g7")
libre = _record(device_type="freestyle_libre_2")
index = build_readiness_index(
[g7, libre], overrides=[_swo_override(device="")]
)
for key, verdict in index.by_line.items():
assert verdict.line_status.value == "Clear to Ship", key
def test_device_scoped_row_wins_over_legacy_row():
g7 = _record(device_type="dexcom_g7")
index = build_readiness_index(
[g7],
overrides=[
_swo_override(device="", status="on_file"),
_swo_override(device="dexcom_g7", status="pending"),
],
)
verdict = index.by_line[("PT-OV-1", "dexcom_g7", "medicare")]
swo = next(i for i in verdict.doc_items if i.doc_type == "swo")
assert swo.quality.value == "PENDING" # device-scoped Pending won
def test_override_triggers_recompute_to_better_status():
# SWO missing is the last open required item; the override flips the line.
rec = _record()
base = build_readiness_index([rec])
assert base.by_line[("PT-OV-1", "dexcom_g7", "medicare")].line_status.value == "Action Needed"
fixed = build_readiness_index([rec], overrides=[_swo_override()])
assert fixed.by_line[("PT-OV-1", "dexcom_g7", "medicare")].line_status.value == "Clear to Ship"
def test_override_visit_applies_to_all_patient_lines():
# Visit rides confirmed_visits: patient-scoped fan-out by construction.
g7 = _record(device_type="dexcom_g7", csv_visit_date=None,
csv_swo_status="On File")
libre = _record(device_type="freestyle_libre_2", csv_visit_date=None,
csv_swo_status="On File")
confirmed = {_hash("PT-OV-1"): TODAY - timedelta(days=10)}
index = build_readiness_index([g7, libre], confirmed_visits=confirmed)
for key, verdict in index.by_line.items():
visit = next(i for i in verdict.doc_items if i.doc_type == "visit")
assert visit.satisfied, key
def test_overrides_from_saved_shape():
saved = {
_hash("PT-OV-1"): {
"": {"swo": {"status": "on_file"}},
"dexcom_g7": {"pa": {"status": "approved"}},
}
}
overrides = overrides_from_saved(saved)
assert len(overrides) == 2
kinds = {(o.key.device_type, o.key.doc_type, o.status) for o in overrides}
assert kinds == {("", "swo", "on_file"), ("dexcom_g7", "pa", "approved")}
def test_apply_overrides_returns_touched_patients():
g7 = _record()
other = _record(patient_id="PT-OV-2")
index_lines = build_readiness_index([g7, other]).lines
touched = apply_overrides(index_lines, [_swo_override()])
assert touched == {"PT-OV-1"}
# --- API level ---------------------------------------------------------------
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 test_doc_status_put_accepts_device_and_returns_line_and_rollup(monkeypatch):
saved_writes = {}
def fake_upsert(org_id, patient_hash, doc_type, status,
status_date=None, expiry_date=None, device_type=""):
saved_writes[(patient_hash, doc_type, device_type)] = status
return True
def fake_load(org_id):
# Echo the persisted write back, as a fresh load would.
out = {}
for (ph, dt, dev), status in saved_writes.items():
out.setdefault(ph, {}).setdefault(dev, {})[dt] = {"status": status}
return out
monkeypatch.setattr(api_main, "get_or_create_org", lambda **kw: "org-test")
monkeypatch.setattr(api_main, "upsert_doc_status", fake_upsert)
monkeypatch.setattr(api_main, "load_doc_statuses_for_org", fake_load)
monkeypatch.setattr(
api_main, "load_confirmed_visits_for_org", lambda org: {}
)
resp = client.put(
"/api/doc-status",
json={
"patient_id": "PT-API-1",
"doc_type": "swo",
"status": "on_file",
"device_type": "dexcom_g7",
"lines": [
{
"device_type": "dexcom_g7",
"plan_type": "medicare",
"csv_visit_date": (TODAY - timedelta(days=20)).isoformat(),
"csv_pecos_verified": "Yes",
"csv_diagnosis_on_file": "Yes",
},
{
"device_type": "freestyle_libre_2",
"plan_type": "medicare",
"csv_visit_date": (TODAY - timedelta(days=20)).isoformat(),
"csv_pecos_verified": "Yes",
"csv_diagnosis_on_file": "Yes",
},
],
},
)
assert resp.status_code == 200
body = resp.json()
assert body["device_type"] == "dexcom_g7"
by_dev = {l["device_type"]: l for l in body["lines"]}
# The override satisfied G7's SWO; the Libre line is untouched (no leak).
assert by_dev["dexcom_g7"]["line_status"] == "Clear to Ship"
assert by_dev["freestyle_libre_2"]["line_status"] == "Action Needed"
assert body["rollup_status"] == "Action Needed" # worst line
def test_doc_status_put_without_lines_keeps_legacy_shape(monkeypatch):
monkeypatch.setattr(api_main, "get_or_create_org", lambda **kw: "org-test")
monkeypatch.setattr(
api_main, "upsert_doc_status", lambda *a, **kw: True
)
resp = client.put(
"/api/doc-status",
json={"patient_id": "PT-API-2", "doc_type": "pa", "status": "approved"},
)
assert resp.status_code == 200
body = resp.json()
assert body["display"] == "Approved"
assert "lines" not in body
def test_confirm_visit_with_lines_returns_rerolled_patient(monkeypatch):
monkeypatch.setattr(api_main, "get_or_create_org", lambda **kw: "org-test")
monkeypatch.setattr(
api_main, "upsert_confirmed_visit", lambda *a, **kw: True
)
monkeypatch.setattr(
api_main, "load_doc_statuses_for_org", lambda org: {}
)
ship = (TODAY - timedelta(days=5)).isoformat()
resp = client.post(
"/api/confirm-visit",
json={
"patient_id": "PT-API-3",
"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",
"lines": [
{
"device_type": "dexcom_g7",
"plan_type": "medicare",
"csv_swo_status": "On File",
"csv_pecos_verified": "Yes",
"csv_diagnosis_on_file": "Yes",
},
{
"device_type": "freestyle_libre_2",
"plan_type": "medicare",
"csv_swo_status": "On File",
"csv_pecos_verified": "Yes",
"csv_diagnosis_on_file": "Yes",
},
],
},
)
assert resp.status_code == 200
body = resp.json()
# Legacy record fields still present
assert body["readiness_status"] == "Clear to Ship"
# The confirmed visit fanned out to BOTH echoed lines
assert all(l["line_status"] == "Clear to Ship" for l in body["lines"])
assert body["rollup_status"] == "Clear to Ship"
def test_upload_grades_with_saved_device_scoped_override(monkeypatch):
"""An SWO override saved for device A feeds the verdict for A's line only."""
pid_hash = hashlib.sha256("PT-UP-1".encode()).hexdigest()
monkeypatch.setattr(
api_main,
"load_doc_statuses_for_org",
lambda org: {
pid_hash: {"dexcom_g7": {"swo": {"status": "on_file"}}}
},
)
monkeypatch.setattr(api_main, "get_or_create_org", lambda **kw: "org-test")
monkeypatch.setattr(
api_main, "load_confirmed_visits_for_org", lambda org: {}
)
ship = (TODAY - timedelta(days=5)).isoformat()
visit = (TODAY - timedelta(days=30)).isoformat()
header = (
"patient_id,device_type,shipment_date,quantity,payer,plan_type,"
"visit_date,pecos_verified,diagnosis_on_file"
)
csv_text = (
header
+ f"\nPT-UP-1,Dexcom G7,{ship},3,Medicare Part B,medicare,{visit},Yes,Yes"
+ f"\nPT-UP-1,Libre 2,{ship},1,Medicare Part B,medicare,{visit},Yes,Yes"
)
resp = client.post(
"/api/upload",
files={"file": ("f.csv", io.BytesIO(csv_text.encode()), "text/csv")},
)
assert resp.status_code == 200
recs = {r["device_type"]: r for r in resp.json()["records"]}
assert recs["dexcom_g7"]["readiness_status"] == "Clear to Ship"
assert recs["freestyle_libre_2"]["readiness_status"] == "Action Needed"
# Display follows the same device scope
assert recs["dexcom_g7"]["doc_state"]["swo"] == "On File"