9.4 KiB
Signal Readiness Model — Design Brief for Pi
Date: 2026-06-23 Status: Design input for Pi. Pi produces the build design; then Claude builds. Inputs: Session 2026-06-23 product decisions + end-to-end simulation + independent cto-architect review (folded in).
1. What Signal is
DMEPOS CGM documentation-readiness tracker. CSV in produces a per-patient stoplight worklist flagging documentation gaps before claim/PA submission. Signal identifies; supplier staff act. No PHI beyond a client-supplied patient_id (random UUID). Stack: FastAPI (Railway) + Supabase + Vite/React (Vercel) + Clerk.
2. Locked product decisions (authoritative; in the knowledge graph)
- Signal NEVER determines, guesses, or extrapolates the payer plan type. Client supplies plan type as a REQUIRED mapped field. Missing means "plan type needed", grade what we can, never guess.
- Signal does NOT determine eligibility or coverage. Remove that language from content. Rename "coverage" code to "readiness".
- Every determination CITES its basis: the rule that fired + the client-supplied input.
- Worklist unit = ONE record per patient UUID; all open items grouped; device detail nested.
- Multi-device is real (Dexcom CGM + Omnipod pump). CGM-only graded now; non-CGM preserved/displayed but NOT graded; other DMEPOS categories later.
- Monthly resupplies = separate dates of service, NOT duplicates. True duplicate = same patient + same device + same date of service.
- Validated PA-vs-claim: visit, SWO, PECOS, diagnosis are claim/coverage requirements (universal per LCD L33822). PA is the only prior-auth item, payer-dependent. Medicare FFS requires no PA. Medicaid heaviest (initial + renewal). Medicaid uses STATE enrollment, not PECOS.
3. Recommended data model (5 entities, from the architecture review)
Smallest model that satisfies the locked decisions.
- Patient (rollup unit): patient_id (client UUID, only identifier, hashed in logs), org_id, coverage_lines[], rollup_status (DERIVED, never stored as truth).
- CoverageLine (the gradeable unit, the keystone): key (patient_id, device_type, plan_id). plan_type REQUIRED + client-supplied/mapped: medicare | medicare_advantage | medicaid | commercial | UNKNOWN. gradeable:bool (true only for CGM this phase; pumps/other = false). shipments[], doc_items[], line_status (derived), timing_flag (existing CoverageFlag, per line).
- Shipment (history under a line): key (patient_id, device_type, date_of_service). quantity, component, order_number, hcpcs. is_current:bool (exactly one per line = latest DOS).
- DocItem (one of the 5, per line): doc_type (swo|visit|pecos|pa|diagnosis), required_state (REQUIRED|NOT_REQUIRED|NOT_EVALUATED, computed from plan_type+rules), input_state (SUPPLIED|ABSENT), value, source (csv|staff_override|computed), citation.
- Override (staff action, persisted): key (org_id, patient_hash, device_type, doc_type). status, status_date, expiry_date, confirmed_by, confirmed_at.
plan_type lives on CoverageLine, NOT Patient. Grade at the line; display rolls up to the patient. (Checked against per-patient-single-plan: dual-eligible and multi-device patients would mis-grade, credibility-ending in a documentation tool. One extra grouping key, not a new subsystem.)
4. The verdict rule (kills the false green)
Separate two axes the current code conflates into one string:
- required_state: does this plan need this item? (from plan_type + rules, independent of CSV contents)
- input_state: did the client supply data? (SUPPLIED | ABSENT)
Per DocItem:
- NOT_REQUIRED + any input -> satisfied (green-eligible)
- REQUIRED + SUPPLIED + good -> satisfied
- REQUIRED + SUPPLIED + bad -> gap (At Risk / Action Needed)
- REQUIRED + ABSENT -> NOT_EVALUATED, blocks green, never satisfied
Rule: a line is "Clear to Ship" only if EVERY required item is satisfied. One required-but-absent item forces at least Action Needed. Green is earned by positive evidence on every required item, never granted by absence of contradicting evidence.
plan_type = UNKNOWN: cannot compute required_state, so the line is "Plan Type Needed" and structurally cannot be green. Still grade the universal items that do not depend on plan (SWO, visit, diagnosis per LCD L33822); PECOS and PA sit NOT_EVALUATED.
Patient rollup: light = worst gradeable line. Hard floor: any gradeable line in "Plan Type Needed" caps the patient no greener than Action Needed. Severity: Plan Type Needed / At Risk > Action Needed > On Track > Clear to Ship. Ungraded non-CGM lines excluded from the light and tab counts. Tabs count patients, not rows.
5. Cascade with citation
Cascade entry = flat typed record (not a plain string, not event sourcing):
- doc_type, severity (blocker|alert = Type A/B), reason, rule_id (names a key in payer_rules.json), client_input ("you marked plan type = Medicaid"), requirement, basis, rules_version. Display string is RENDERED from these fields, not stored as prose. Prerequisite: rules become addressable data. Move the _PECOS_REQUIRED / _PA_REQUIRED / _PA_NOT_REQUIRED logic OUT of Python sets and INTO payer_rules.json with stable rule IDs, so the citation points at the same data that drove the decision. Also fixes a latent bug: the Python sets and the JSON can currently disagree.
6. Override + recompute + rollup
- Override keyed (org_id, patient_hash, device_type, doc_type). Frontend cache keyed
${patient_id}:${device_type}. Without device_type an override leaks across a patient's devices (present bug). - Scope: VISIT is patient-scoped (the visit is the person; fans out to all that patient's gradeable lines). SWO / PA / diagnosis / PECOS are line-scoped. Current code treats everything as patient-scoped, which is why confirm-visit works and doc-status leaks.
- Recompute returns { line: updated line, rollup: new patient status }. Recompute the overridden line, then re-roll-up the patient. No full batch rebuild per click.
7. Dedup / aggregation
- Group by patient UUID; keep per-device detail.
- True-duplicate key = (patient_id, device_type, date_of_service). SMALL OPEN DECISION: the current code's dedup key also includes order_number. If DOS-level dedup is the contract, drop order_number from the key and keep it as a displayed attribute. Recommended default: drop it, treat same patient+device+DOS as one, keep latest. Pi/Kisa to confirm.
- Split-shipment tiebreaker: same patient+device+DOS with different quantities is not a duplicate; define (sum quantities, or order by ingestion).
- Grade the CURRENT shipment only (latest DOS per line); keep prior shipments as collapsed history. Today calculate runs per record, so 6 months of history = 6 worklist lines (the row-vs-patient bug).
8. Live bugs in the current code (this pass exists to fix these)
- Worklist is ROW-keyed, not patient-keyed end to end (rowKey = patient_id + index; localDocStates keyed by bare patient_id; one result per shipment). Decisions 4/5/6 assume a patient-grouped model the code does not implement. This is the core work.
- Plan-type GUESSING is still live: _normalize_payer keyword-matches the payer name, and a
defaultpayer config + raw-string fallback can grade an unknown plan and show green. Both violate decision #1. Consume the client-mapped plan_type field; use the payer NAME only for display; route unmatched/unknown to "Plan Type Needed", notdefault. Remove the default grade path. - Override leak across a patient's devices (see section 6).
9. The 5 tracked items + rules engine
doc_state_machine.py grades SWO, qualifying visit (6-month), PECOS (Medicare/MA only), PA (MA/Medicaid/Commercial; not Medicare FFS), diagnosis. Rules in payer_rules.json. Medicaid uses state enrollment not PECOS; the Medicaid state-enrollment item is DEFERRED for the pilot (PECOS shows N/A for Medicaid), documented as a known gap, not an omission.
10. YAGNI / pilot scope (hold the line)
- Grade CGM only; non-CGM lines exist + display but gradeable=false. No multi-category grading.
- Six-field flat cascade entry; no event sourcing, no per-entry history (the audit log exists).
- No Medicaid state-enrollment item this phase.
- Override scope = the two facts staff change: visit (patient-scoped) + SWO/PA (line-scoped). No general override framework.
- Recompute returns {line, rollup}; no full re-batch per click.
11. Original blockers + trust fixes (mapping)
- Blocker 1 (MA misclassification) -> DELETE the name-guesser + require plan type + remove
defaultgrade path. - Blocker 2 (duplicate patient_id) -> patient-grouped aggregation with per-device detail + DOS-level dedup.
- Blocker 3 (override does not recompute) -> device-keyed override + recompute + re-roll-up; persist override on next upload.
- Trust: retention 6 years (fix 2 stale "7 year" lines); privacy BAA wording attorney-gated (not built by us); audit-log persistence to DB (verify; part of override path).
12. Proposed build sequence (after Pi's design)
Rename coverage->readiness -> extract rules to addressable config (IDs) -> patient/line/shipment grouping + required-vs-input verdict -> device-keyed override + recompute + rollup -> require-plan-type + client value-mapping + remove guesser/default -> frontend nesting + "plan type needed" state. code-auditor reviews each before it ships.
13. The ask for Pi
Take a stab at the build design from this model. Resolve the small dedup order_number decision (section 7), the frontend nesting/UX for patient -> device -> doc plus the "plan type needed" state, and the client plan-type value-mapping UX at import. Keep it pilot-scoped. Hand back; then Claude builds.