cascade: show all doc gaps; export: add order_number, hcpcs, recommended_next_step_code, diagnosis/pecos; supabase: confirm pro plan
- doc_state_machine: remove early returns from _build_cascade, surface all 5 doc checks, pecos gated to medicare/MA, pa denied routes to appeal alert - main.py: add _recommended_next_step_code(), extend RecordOut and export CSV with new columns - normalizer: accept order_number and hcpcs as pass-through fields - coverage_calculator: add order_number/hcpcs to ShipmentRecord - CLAUDE.md: supabase pro plan confirmed 2026-06-10 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
63093990d2
commit
ff657f4765
5 changed files with 85 additions and 10 deletions
|
|
@ -118,7 +118,7 @@ This checklist tracks compliance gates separately from build readiness. Update i
|
|||
|---|---|
|
||||
| Privacy Policy published at stable public URL | PASS — privacy-policy.md written 2026-06-07; Privacy.jsx deployed to https://signal-ui-xi.vercel.app/privacy |
|
||||
| FDA CDS exemption memo written and attorney-reviewed | NOT DONE |
|
||||
| Supabase plan tier confirmed (Pro vs Team) and PHI posture documented | NOT VERIFIED |
|
||||
| Supabase plan tier confirmed (Pro vs Team) and PHI posture documented | PASS — Pro plan confirmed 2026-06-10 ($25/mo). HIPAA available as paid add-on on Team ($599/mo) — deferred to post-funding per Real PHI gate. Pro is correct posture for pilot. |
|
||||
| Pilot LOI reviewed by attorney | PARTIAL — template ready; Bittinger/Nixon review pending |
|
||||
| NDA template reviewed by attorney | PARTIAL — template ready; Bittinger/Nixon review pending |
|
||||
| Pilot LOI executed with supplier | NOT DONE |
|
||||
|
|
|
|||
|
|
@ -167,6 +167,9 @@ class RecordOut(BaseModel):
|
|||
is_transfer: bool = False
|
||||
doc_state: Optional[DocStateOut] = None
|
||||
cascade: list[str] = []
|
||||
order_number: Optional[str] = None
|
||||
hcpcs: Optional[str] = None
|
||||
recommended_next_step_code: str = "NO_ACTION_NEEDED"
|
||||
|
||||
|
||||
class UploadResponse(BaseModel):
|
||||
|
|
@ -226,6 +229,24 @@ def _normalize_payer_type(payer: str) -> str:
|
|||
return normalized
|
||||
|
||||
|
||||
def _recommended_next_step_code(flag_val: str, doc: Optional[DocStateOut]) -> str:
|
||||
if doc and doc.pa == "Denied":
|
||||
return "PA_APPEAL"
|
||||
if doc and doc.pa == "Required — Not Started":
|
||||
return "INITIATE_PA"
|
||||
if doc and "Not Confirmed" in (doc.visit or ""):
|
||||
return "AWAITING_VISIT_CONFIRMATION"
|
||||
if doc and doc.swo in ("Pending", "Expired"):
|
||||
return "REQUEST_SWO_UPDATE"
|
||||
if doc and doc.pecos == "Not Verified":
|
||||
return "VERIFY_PRESCRIBER_PECOS"
|
||||
if doc and doc.pa == "Pending":
|
||||
return "PA_FOLLOW_UP"
|
||||
if flag_val in ("RESUPPLY_READY", "ACTIVE") and (not doc or not doc.cascade if hasattr(doc, "cascade") else True):
|
||||
return "READY_TO_BILL"
|
||||
return "NO_ACTION_NEEDED"
|
||||
|
||||
|
||||
def _to_record_out(r, record=None) -> RecordOut:
|
||||
flag_val = r.flag.value if hasattr(r.flag, "value") else str(r.flag)
|
||||
|
||||
|
|
@ -269,6 +290,9 @@ def _to_record_out(r, record=None) -> RecordOut:
|
|||
is_transfer=getattr(r, "is_transfer", False),
|
||||
doc_state=doc_state_out,
|
||||
cascade=cascade,
|
||||
order_number=getattr(record, "order_number", None) if record else None,
|
||||
hcpcs=getattr(record, "hcpcs", None) if record else None,
|
||||
recommended_next_step_code=_recommended_next_step_code(flag_val, doc_state_out),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -393,10 +417,13 @@ async def export_work_queue(
|
|||
output = io.StringIO()
|
||||
writer = csv.writer(output)
|
||||
writer.writerow([
|
||||
"Order Number",
|
||||
"HCPCS",
|
||||
"Patient ID",
|
||||
"Device",
|
||||
"Payer",
|
||||
"Status",
|
||||
"Recommended Next Step",
|
||||
"Priority Score",
|
||||
"Days Until Resupply End",
|
||||
"Next Visit Due",
|
||||
|
|
@ -407,14 +434,19 @@ async def export_work_queue(
|
|||
"SWO Status",
|
||||
"Visit Status",
|
||||
"PA Status",
|
||||
"Diagnosis Status",
|
||||
"PECOS Status",
|
||||
])
|
||||
for r in records:
|
||||
doc = r.doc_state
|
||||
writer.writerow([
|
||||
r.order_number or "",
|
||||
r.hcpcs or "",
|
||||
r.patient_id,
|
||||
r.device_display,
|
||||
r.payer,
|
||||
r.status_label,
|
||||
r.recommended_next_step_code,
|
||||
r.priority_score,
|
||||
r.days_until_coverage_end,
|
||||
r.next_visit_due_date or "",
|
||||
|
|
@ -425,6 +457,8 @@ async def export_work_queue(
|
|||
doc.swo if doc else "",
|
||||
doc.visit if doc else "",
|
||||
doc.pa if doc else "",
|
||||
doc.diagnosis if doc else "",
|
||||
doc.pecos if doc else "",
|
||||
])
|
||||
|
||||
output.seek(0)
|
||||
|
|
|
|||
|
|
@ -76,6 +76,15 @@ HEADER_MAP: dict[str, list[str]] = {
|
|||
"transfer_from", "transfer from", "previous_supplier", "previous supplier",
|
||||
"prior_supplier", "prior supplier", "transfer_status", "transferred_from",
|
||||
],
|
||||
"order_number": [
|
||||
"order_number", "order number", "order_no", "order no", "order#",
|
||||
"claim_number", "claim number", "brightree_order", "order_id",
|
||||
"rx_number", "rx number", "rx#", "dispense_number",
|
||||
],
|
||||
"hcpcs": [
|
||||
"hcpcs", "hcpcs_code", "hcpcs code", "procedure_code", "procedure code",
|
||||
"billing_code", "billing code", "item_code", "item code", "hcpc",
|
||||
],
|
||||
}
|
||||
|
||||
DEVICE_MAP: dict[str, str] = {
|
||||
|
|
@ -284,6 +293,8 @@ def normalize_csv(text: str) -> tuple[list[ShipmentRecord], list[str], dict]:
|
|||
csv_diagnosis_on_file = mapped.get("csv_diagnosis_on_file") or None
|
||||
transfer_raw = mapped.get("csv_transfer_from", "").strip()
|
||||
csv_transfer_from = transfer_raw if transfer_raw else None
|
||||
order_number = mapped.get("order_number") or None
|
||||
hcpcs = mapped.get("hcpcs") or None
|
||||
|
||||
records.append(ShipmentRecord(
|
||||
patient_id=patient_id,
|
||||
|
|
@ -298,6 +309,8 @@ def normalize_csv(text: str) -> tuple[list[ShipmentRecord], list[str], dict]:
|
|||
csv_pa_status=csv_pa_status,
|
||||
csv_diagnosis_on_file=csv_diagnosis_on_file,
|
||||
csv_transfer_from=csv_transfer_from,
|
||||
order_number=order_number,
|
||||
hcpcs=hcpcs,
|
||||
))
|
||||
|
||||
return records, skipped, mapping_summary
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ class ShipmentRecord:
|
|||
csv_pa_status: Optional[str] = None # "Approved" | "Pending" | "Denied" | "Not Required"
|
||||
csv_diagnosis_on_file: Optional[str] = None # "Yes" | "No"
|
||||
csv_transfer_from: Optional[str] = None # prior supplier name if transfer
|
||||
order_number: Optional[str] = None # pass-through — billing system order/claim number
|
||||
hcpcs: Optional[str] = None # pass-through — HCPCS code from supplier CSV
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
|
|
@ -60,23 +60,49 @@ def _build_cascade(
|
|||
payer_type: str,
|
||||
) -> list:
|
||||
"""
|
||||
Build the downstream consequence chain for missing/at-risk doc items.
|
||||
Only add cascade steps that are actually blocked.
|
||||
Build the full consequence chain for all documentation gaps.
|
||||
No early returns — every unfulfilled requirement is surfaced.
|
||||
Type A = doc gaps Signal can help close.
|
||||
Type B = route-to-action alerts for denials.
|
||||
"""
|
||||
cascade = []
|
||||
|
||||
# Type A: Documentation gaps
|
||||
if not visit_confirmed:
|
||||
cascade.append("Visit date not on file")
|
||||
cascade.append("Device shipment at risk")
|
||||
return cascade
|
||||
cascade.append(
|
||||
"Qualifying visit not on file — confirm with prescriber before scheduling shipment"
|
||||
)
|
||||
|
||||
if swo in ("Pending", "Expired"):
|
||||
cascade.append("SWO not on file")
|
||||
cascade.append("Device shipment at risk")
|
||||
return cascade
|
||||
label = "not on file" if swo == "Pending" else "expired"
|
||||
cascade.append(
|
||||
f"Written order {label} — obtain from prescriber before claim submission"
|
||||
)
|
||||
|
||||
if diagnosis == "Missing":
|
||||
cascade.append("Diagnosis not on file")
|
||||
cascade.append(
|
||||
"Diagnosis code not on file — obtain from prescriber records before billing"
|
||||
)
|
||||
|
||||
if payer_type in _PECOS_REQUIRED and pecos == "Not Verified":
|
||||
cascade.append(
|
||||
"Ordering provider PECOS enrollment not verified — confirm NPI is active before claim submission"
|
||||
)
|
||||
|
||||
if payer_type not in _PA_NOT_REQUIRED and pa == "Required — Not Started":
|
||||
cascade.append(
|
||||
"Prior authorization not started — obtain before scheduling shipment"
|
||||
)
|
||||
|
||||
# Type B: Route-to-action alert
|
||||
if pa == "Denied":
|
||||
cascade.append(
|
||||
"Prior authorization denied — file Level 1 appeal within 65 days. "
|
||||
"Approximately 80% of first-level Medicare Advantage appeals succeed."
|
||||
)
|
||||
|
||||
# Consequence line — only appended when at least one gap exists
|
||||
if cascade:
|
||||
cascade.append("Device shipment at risk")
|
||||
|
||||
return cascade
|
||||
|
|
|
|||
Loading…
Reference in a new issue