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:
parent
5ba31fac03
commit
a53728e424
8 changed files with 2000 additions and 35 deletions
1351
docs/build-plans/03-game-center-token-layer.md
Normal file
1351
docs/build-plans/03-game-center-token-layer.md
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||||
|
|
|
||||||
130
python-backend/core/overrides.py
Normal file
130
python-backend/core/overrides.py
Normal 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
|
||||||
|
|
@ -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"),
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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
311
tests/test_overrides.py
Normal 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"
|
||||||
Loading…
Reference in a new issue