# 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)
1. 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.
2. Signal does NOT determine eligibility or coverage. Remove that language from content. Rename "coverage" code to "readiness".
3. Every determination CITES its basis: the rule that fired + the client-supplied input.
4. Worklist unit = ONE record per patient UUID; all open items grouped; device detail nested.
5. Multi-device is real (Dexcom CGM + Omnipod pump). CGM-only graded now; non-CGM preserved/displayed but NOT graded; other DMEPOS categories later.
6. Monthly resupplies = separate dates of service, NOT duplicates. True duplicate = same patient + same device + same date of service.
7. 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 `default` payer 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", not `default`. 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 `default` grade 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.
