6.5 KiB
6.5 KiB
Signal Quality Review — 2026-06-11
Read-only review of signal-ui and python-backend by background agent. No fixes applied yet. Execution plan: surgical pass = 2 CRITICAL + 8 HIGH first, then label unification (see decision note below).
Label decision (Kisa, 2026-06-11): All user-facing status labels are exactly four, consistent everywhere without fail: At Risk, Action Needed, On Track, Clear to Ship. Mapping: SUPPLY_LAPSED/VISIT_REQUIRED/RENEWAL_CRITICAL → At Risk; RENEWAL_ELEVATED/RENEWAL_SOON/TRANSFER_PENDING/NO_RECENT_SHIPMENT → Action Needed; RESUPPLY_READY → Clear to Ship; ACTIVE → On Track. Define once in backend; Badge.jsx, exports, and coverage.js consume it. Supersedes FLAG_LABELS in api/main.py:117-127 and coverage.js:20-29. This resolves findings 15 and 16.
CRITICAL
- Confirm Visit sends fabricated shipment date (today) and hardcoded quantity 1. ConfirmVisitModal.jsx:16-23 derives shipment = today; :58 hardcodes quantity 1. Backend recompute (main.py:538-546) then yields wrong Days Left/coverage end/flag; visit validation (main.py:514-523) anchors wrong. Fix: add last_shipment_date and quantity to RecordOut and pass through.
- Privacy page claims an executed BAA that does not exist. Privacy.jsx:104-110; also :97 says 7-year audit retention vs audit_logger.py 6 years. Fix: rewrite HIPAA section to actual de-identified posture; align retention number.
HIGH
- NO_RECENT_SHIPMENT unreachable — coverage_calculator.py:269-288 returns SUPPLY_LAPSED before the 12-month check at :312-328; same in coverage.js:127-154. Fix: check days_since_shipment >= 365 before supply-lapsed early return.
- Tricare → commercial. normalizer.py:202-208 normalizes first (no Tricare entries, default commercial), making coverage_calculator._normalize_payer TFL logic dead code; raw payer display string also destroyed. Fix: normalize in one place, keep raw string for display, add Tricare to normalizer.
- Frontend export drops order_number, hcpcs, recommended_next_step_code. api.js:62-89 omits them; export rows get blanks + NO_ACTION_NEEDED (main.py:170-172 defaults). Fix: map all three in apiRecordToLocal.
- Fallback priority math inverted. coverage.js:93-102: RENEWAL_CRITICAL 500 < RENEWAL_ELEVATED 700; SUPPLY_LAPSED 1000 vs backend 2000; uses daysUntilEnd vs backend days_until_visit_due. Fix: copy backend _compute_priority exactly.
- Confirmed visits never reflected in doc state; confirming regresses checklist. main.py:262 passes confirmed_visit_date=None; /api/confirm-visit rebuild (main.py:538-545) loses csv_* doc fields, frontend merge (WorklistTable.jsx:71-77) overwrites row with regressed state. Fix: pass confirmed date into compute_doc_state; echo existing doc fields through confirm payload.
- _recommended_next_step_code precedence bug. main.py:245 guard is constantly True (DocStateOut has no cascade attr); diagnosis "Missing" never checked → READY_TO_BILL with open diagnosis gap. Fix: drop broken guard, add diagnosis check.
- "refill" in user-facing text. main.py:136 "30-day refill window" → "resupply window". (Internal refill_window_days config keys non-urgent.)
- Frontend FLAG enum missing TRANSFER_PENDING; payer config drift. coverage.js:9-18 (8 vs 9 flags), no csv_visit_date support; getPayerConfig gives medicaid/commercial visitRenewalDays null vs payer_rules.json 180; visit due = shipment+180 vs backend (shipment−30)+180. Fix: mirror backend flag set, payer table, 30-day offset.
MEDIUM
- Backend failures silently swallowed; weak local fallback runs (api.js:21-34, App.jsx:61-74); real CSVs can show "Loaded 0 patients" with no error. Surface backend errors; fall back only on network failure.
- Audit entries never reach WORM audit_log table — audit_logger.py:147-148 needs db_conn, no caller passes one. Add Supabase REST sink in log_event.
- Audit calls hardcode "demo_user"/"0.0.0.0" (main.py:359, 381, 467, 549). Thread claims + request IP through.
- Org resolution reads only "o" claim (main.py:371, 526); org_id-shaped JWTs silently land in shared gaboro-pilot org. Read o.id with org_id fallback; fail closed otherwise.
- Three conflicting status vocabularies — RESOLVED by label decision above.
- DocStatusBar.jsx:6-24 maps dead status strings; real values fall through to amber. Align keys with doc_state_machine.py values.
- Duplicate patient_ids collapse in dict lookups (main.py:378, persistence.py:114). Key by (patient_id, shipment_date, component) or index-align.
- Mapping review Override dropdown is a no-op (CSVImport.jsx:117-123); example_value (:52) never sent by backend. Wire it or remove until wired; checklist counts it PASS — correct the checklist if not wiring now.
- Blank/unrecognized payer silently becomes commercial (normalizer.py:208). Surface warning in skipped_reasons/mapping_summary.
- VITE_SIGNAL_API_KEY path would ship API key in public bundle if ever set (api.js:8). Remove frontend API-key path; Clerk token covers production.
- No explicit PHI-column guard in normalizer; bare "patient"/"id" aliases broad (normalizer.py:20-26). Add rejection list (name/dob/ssn/phone/address) that fails upload with clear message; drop bare "patient" alias. Checklist claims "PHI blocked at normalizer" — currently implicit only.
- Client/backend disagree on 6-month window: setMonth(-6) (ConfirmVisitModal.jsx:39-41) vs 183 days (main.py:514). Use 183 days both sides.
LOW
- Stale flag "OK" in _no_action_flags — persistence.py:153.
- Unused/redundant imports — main.py:25, 499, 513.
- catch(e){throw e} — api.js:116-118; misleading warning text — coverage_calculator.py:376.
- escalateCount unused — App.jsx:34.
- Two parallel toast systems — Toast.jsx + WorklistTable.jsx:45-53, 330-350.
- expandedRow localStorage key auto-expands wrong row after re-import — WorklistTable.jsx:41, 139, 199.
- parseCSV naive comma split breaks quoted fields (fallback only) — coverage.js:169-182.
- EmptyState hardcodes light-theme colors — EmptyState.jsx:8, 22.
- /health/db leaks raw exception text — main.py:339.
- "now()" string literal to timestamptz — persistence.py:193.
- SUPPLY_LAPSED priority adds abs(days_until_visit) even when visit far future — coverage_calculator.py:209-211.
- Sidebar hardcodes "Billing Staff" role + dead Settings link — Sidebar.jsx:126-130, 143-145.
Clean
- Auth: /api/upload, /api/export, /api/confirm-visit all behind require_auth; frontend passes Clerk Bearer tokens. Only /health endpoints open (see 31).
- PHI in audit log: all resource_ids hashed before storage.