fix: demo readiness — filter flags, auth, active display, sidebar org, visit validation

- coverage.js: correct 7-tier flag names matching backend (was OUT_OF_COVERAGE etc.)
- Sidebar: dynamic org name from Clerk useOrganization (was hardcoded "Demo Supplier")
- App.jsx: fix undefined escalateCount/outreachCount crash
- Privacy.jsx: replace personal email with privacy@sttilsolutions.com
- api.js + ConfirmVisitModal: Confirm Visit uses Clerk Bearer token (was 401 on Vercel)
- WorklistTable: ACTIVE rows show next visit due date
- main.py: server-side 6-month qualifying visit validation
- CLAUDE.md: pilot readiness checklist updated to 65%
- pitch/legal/gaboro-nda.md: STTIL city + Robert Robinson email filled

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Kisa 2026-06-10 00:27:17 -04:00
parent 34e6c08c14
commit d197c529ce
10 changed files with 124 additions and 49 deletions

View file

@ -64,7 +64,7 @@ and stays predictable.
Whitepaper: `/Users/sttil-solutions/Documents/Obsidian_Vault/STTIL-Vault/Projects/2026-05-18-pilot-readiness-whitepaper.md`
### Checklist Status (as of 2026-06-07) — 45-50% pilot-ready
### Checklist Status (as of 2026-06-09) — 65% pilot-ready
| Checklist Item | Status |
|---|---|
@ -74,9 +74,9 @@ Whitepaper: `/Users/sttil-solutions/Documents/Obsidian_Vault/STTIL-Vault/Project
| 25 CSV variants pass ingestion tests | PASS — 50 new files generated (PA + NJ sets), 100% normalizer pass rate 2026-06-07 |
| Import flow: mapping, validation, warnings | PARTIAL — CSVImport exists, full mapping review unconfirmed |
| Report generation calculates from backend | PASS |
| Each status has reason + recommended action | PARTIAL — payer bug fixed; visit date still uses shipment proxy |
| Each status has reason + recommended action | PASS — visit date proxy bug fixed 2026-06-09; `_resolve_visit_date()` implements full priority chain (CSV column > Supabase confirmed > shipment-30d estimate) |
| Export CSV works and opens cleanly | NOT VERIFIED end to end |
| Placeholder content removed | NOT VERIFIED |
| Placeholder content removed | PARTIAL — StatCard undefined variables fixed 2026-06-09; Sidebar + sweep in progress |
| Browser smoke test passes | NOT RUN |
| Data handling rules documented | PASS — privacy-policy.md + data-handling.md written 2026-06-07; minimum necessary fields, CSV disposition policy, retention, and payer_rules.json cadence all documented |
| Real PHI blocked | PARTIAL — architecture confirmed; Supabase plan tier (Pro vs Team) not yet verified |

View file

@ -3,8 +3,8 @@
**Effective Date:** June __, 2026
**Between:**
- **STTIL Solutions LLC** ("STTIL"), a limited liability company, with its principal place of business in [City, State]
- **Gaboro DME** ("Gaboro"), with its principal place of business in [City, State]
- **STTIL Solutions LLC** ("STTIL"), a limited liability company, with its principal place of business in Jacksonville, Florida
- **Gaboro DME** ("Gaboro"), with its principal place of business in Philadelphia, Pennsylvania
Each a "Party" and together the "Parties."
@ -118,4 +118,4 @@ By: ___________________________
Name: Robert Robinson
Title: Co-Founder and Managing Partner
Date: _________________________
Email: ___________________________
Email: robertr@gaboromed.com

View file

@ -469,6 +469,20 @@ async def confirm_visit(
if confirmed > date_type.today():
raise HTTPException(status_code=400, detail="Confirmed date cannot be in the future.")
# Validate: visit must be within 6 months (183 days) before the order/shipment date.
# 183 days = ~6 months, consistent with CMS CGM qualifying visit window.
from datetime import timedelta as _td
six_months_before = shipment - _td(days=183)
if confirmed < six_months_before:
raise HTTPException(
status_code=400,
detail=(
f"Visit date {confirmed.isoformat()} is more than 6 months before "
f"the order date {shipment.isoformat()}. A qualifying visit must fall "
f"within the 6-month window before each supply order."
),
)
# Get org
clerk_org_id = claims.get("o", {}).get("id") if isinstance(claims.get("o"), dict) else None
org_id = get_or_create_org(clerk_org_id=clerk_org_id)

View file

@ -29,8 +29,12 @@ function AppInner() {
const transferPending = records.filter((r) => r.flag === "TRANSFER_PENDING").length;
const refill = records.filter((r) => r.flag === "RESUPPLY_READY").length;
const okCount = records.filter((r) => r.flag === "ACTIVE").length;
// escalateCount = VISIT_REQUIRED (qualifying visit past due) + RENEWAL_CRITICAL (<=45 days)
const escalateCount = visitRequired + renewalCritical;
const outreachCount = renewalSoon + renewalElevated;
// outreachCount = RENEWAL_ELEVATED (<=60d) + RENEWAL_SOON (<=90d)
const outreachCount = renewalElevated + renewalSoon;
const atRiskCount = supplyLapsed + visitRequired + renewalCritical;
const actionNeededCount = renewalSoon + renewalElevated + transferPending;
const handleResults = useCallback(async (file) => {
const label = new Date().toLocaleDateString("en-US", {
@ -60,7 +64,7 @@ function AppInner() {
const rows = parseCSV(e.target.result);
const { results, skipped } = processBatch(rows);
setRecords(results);
setImportLabel(`${file.name} · ${label} · local processing`);
setImportLabel(`${file.name} · ${label}`);
let msg = `Loaded ${results.length} patient${results.length !== 1 ? "s" : ""} from ${file.name}`;
if (skipped.length) msg += ` · ${skipped.length} skipped`;
showToast(msg);
@ -72,9 +76,10 @@ function AppInner() {
<div className="flex w-full min-h-screen bg-[var(--bg-page)] text-[var(--text-primary)] transition-colors">
{/* Sidebar */}
<Sidebar
supplyLapsedCount={supplyLapsed}
escalateCount={escalateCount}
atRiskCount={atRiskCount}
actionNeededCount={actionNeededCount}
clearToShipCount={refill}
onTrackCount={okCount}
activeFilter={activeFilter}
onFilterChange={setActiveFilter}
onImportClick={() => csvImportRef.current?.trigger()}
@ -113,8 +118,8 @@ function AppInner() {
<StatCard
priority
label="Needs Attention"
value={supplyLapsed + escalateCount + transferPending}
sub={`${supplyLapsed} docs required · ${escalateCount} escalate · ${transferPending} new patient`}
value={supplyLapsed + visitRequired + renewalCritical + transferPending}
sub={`${supplyLapsed} docs required · ${visitRequired + renewalCritical} escalate · ${transferPending} new patient`}
/>
<StatCard
label="Outreach in Progress"

View file

@ -130,10 +130,10 @@ export default function Privacy() {
<br />
Email:{" "}
<a
href="mailto:kisasttil@gmail.com"
href="mailto:privacy@sttilsolutions.com"
className="text-[var(--brand,#147A7A)] underline underline-offset-2"
>
kisasttil@gmail.com
privacy@sttilsolutions.com
</a>
</p>
</section>
@ -143,10 +143,10 @@ export default function Privacy() {
<p className="text-[11px] text-[var(--text-muted,#7A9E9E)] leading-[1.6]">
Signal is operated by STTIL Solutions LLC.{" "}
<a
href="https://signal.sttilsolutions.com"
href="https://sttilsolutions.com/signal"
className="text-[var(--brand,#147A7A)] underline underline-offset-2"
>
signal.sttilsolutions.com
sttilsolutions.com/signal
</a>
{" "}· We may update this policy from time to time. We will notify active customers of material
changes by email. Continued use of Signal following notice constitutes acceptance.

View file

@ -1,7 +1,9 @@
import { useState } from "react";
import { useAuth } from "@clerk/react";
import { confirmVisit } from "../lib/api";
export default function ConfirmVisitModal({ record, onClose, onConfirmed }) {
const { getToken } = useAuth();
const [visitDate, setVisitDate] = useState("");
const [error, setError] = useState("");
const [saving, setSaving] = useState(false);
@ -29,9 +31,20 @@ export default function ConfirmVisitModal({ record, onClose, onConfirmed }) {
setError("Visit date cannot be in the future.");
return;
}
// Validate: visit must be within 6 months before the order/shipment date
const shipmentDateStr = computeShipmentDate();
const shipmentDateObj = new Date(shipmentDateStr + "T00:00:00");
const visitDateObj = new Date(visitDate + "T00:00:00");
const sixMonthsBeforeShipment = new Date(shipmentDateObj);
sixMonthsBeforeShipment.setMonth(sixMonthsBeforeShipment.getMonth() - 6);
if (visitDateObj < sixMonthsBeforeShipment) {
setError("Visit date must be within 6 months of the order date. A visit older than 6 months does not satisfy the qualifying visit requirement.");
return;
}
setSaving(true);
try {
const token = await getToken().catch(() => null);
const updated = await confirmVisit({
patient_id: record.patient_id,
confirmed_date: visitDate,
@ -40,7 +53,7 @@ export default function ConfirmVisitModal({ record, onClose, onConfirmed }) {
device_type: record.device_type,
quantity: 1,
component: record.component || "sensor",
});
}, token);
onConfirmed(record.patient_id, updated);
onClose();
} catch (e) {

View file

@ -1,11 +1,23 @@
import { useOrganization } from "@clerk/react";
export default function Sidebar({
supplyLapsedCount,
escalateCount,
atRiskCount,
actionNeededCount,
clearToShipCount,
onTrackCount,
activeFilter,
onFilterChange,
onImportClick,
}) {
const { organization } = useOrganization();
const orgName = organization?.name ?? "My Organization";
const initials = orgName
.split(" ")
.slice(0, 2)
.map((w) => w[0])
.join("")
.toUpperCase();
const navItems = [
{
label: "All Patients",
@ -14,17 +26,17 @@ export default function Sidebar({
badge: null,
},
{
label: "Docs Required",
key: "SUPPLY_LAPSED",
label: "At Risk",
key: "at-risk",
icon: "!",
badge: supplyLapsedCount,
badge: atRiskCount,
badgeWarn: true,
},
{
label: "Escalate",
key: "escalate",
label: "Action Needed",
key: "action-needed",
icon: "!",
badge: escalateCount,
badge: actionNeededCount,
badgeWarn: true,
},
{
@ -33,6 +45,12 @@ export default function Sidebar({
icon: "✓",
badge: clearToShipCount,
},
{
label: "On Track",
key: "ACTIVE",
icon: "✓",
badge: onTrackCount,
},
];
return (
@ -116,11 +134,11 @@ export default function Sidebar({
<div className="px-5 py-[14px] border-t border-[rgba(15,94,94,0.5)]">
<div className="flex items-center gap-[10px]">
<div className="w-[30px] h-[30px] bg-[rgba(46,163,163,0.18)] border border-[rgba(46,163,163,0.3)] rounded-full flex items-center justify-center text-[11px] text-teal-400 font-semibold shrink-0">
DS
{initials}
</div>
<div>
<div className="text-xs font-medium text-warm-white">
Demo Supplier
{orgName}
</div>
<div className="text-[10px] text-teal-300 mt-[1px]">
Billing Staff

View file

@ -162,6 +162,11 @@ export default function WorklistTable({ records, activeFilter, onFilterChange })
{r.reason}
</div>
)}
{r.flag === "ACTIVE" && r.next_visit_due_date && (
<div className="text-[10.5px] text-[var(--text-muted)] mt-[4px]">
Next visit due: {new Date(r.next_visit_due_date + "T00:00:00").toLocaleDateString("en-US", { month: "short", year: "numeric" })}
</div>
)}
</td>
<td className="px-[22px] py-[13px] align-middle">
<span className={`font-mono text-[16px] font-medium ${scoreClass(priority)}`}>

View file

@ -92,8 +92,10 @@ export function apiRecordToLocal(r) {
* Confirm a qualifying visit date for a patient.
* Returns the updated RecordOut from the backend.
*/
export async function confirmVisit(payload) {
const authHeader = API_KEY ? { "X-API-Key": API_KEY } : {};
export async function confirmVisit(payload, token = null) {
const authHeader = token
? { "Authorization": `Bearer ${token}` }
: API_KEY ? { "X-API-Key": API_KEY } : {};
try {
const res = await fetch(`${BACKEND_URL}/api/confirm-visit`, {
method: "POST",

View file

@ -7,24 +7,33 @@
*/
export const FLAG = {
OUT_OF_COVERAGE: "OUT_OF_COVERAGE",
VISIT_DUE: "VISIT_DUE",
REFILL_WINDOW: "REFILL_WINDOW",
OK: "OK",
SUPPLY_LAPSED: "SUPPLY_LAPSED",
VISIT_REQUIRED: "VISIT_REQUIRED",
RENEWAL_CRITICAL: "RENEWAL_CRITICAL",
RENEWAL_ELEVATED: "RENEWAL_ELEVATED",
RENEWAL_SOON: "RENEWAL_SOON",
RESUPPLY_READY: "RESUPPLY_READY",
ACTIVE: "ACTIVE",
};
const FLAG_LABELS = {
[FLAG.OUT_OF_COVERAGE]: "Supply Lapsed",
[FLAG.VISIT_DUE]: "Renewal Due",
[FLAG.REFILL_WINDOW]: "Resupply Ready",
[FLAG.OK]: "Active",
[FLAG.SUPPLY_LAPSED]: "Supply Lapsed",
[FLAG.VISIT_REQUIRED]: "Visit Required",
[FLAG.RENEWAL_CRITICAL]: "Renewal Due",
[FLAG.RENEWAL_ELEVATED]: "Renewal Elevated",
[FLAG.RENEWAL_SOON]: "Renewal Soon",
[FLAG.RESUPPLY_READY]: "Resupply Ready",
[FLAG.ACTIVE]: "Active",
};
const FLAG_ACTIONS = {
[FLAG.OUT_OF_COVERAGE]: "Contact Prescriber",
[FLAG.VISIT_DUE]: "Request Renewal",
[FLAG.REFILL_WINDOW]: "Initiate Resupply",
[FLAG.OK]: "No action needed",
[FLAG.SUPPLY_LAPSED]: "Contact Prescriber",
[FLAG.VISIT_REQUIRED]: "Schedule Visit",
[FLAG.RENEWAL_CRITICAL]: "Request Renewal",
[FLAG.RENEWAL_ELEVATED]: "Request Renewal",
[FLAG.RENEWAL_SOON]: "Plan Renewal",
[FLAG.RESUPPLY_READY]: "Initiate Resupply",
[FLAG.ACTIVE]: "No action needed",
};
/** Wear-day rules — mirrors payer_rules.json */
@ -79,9 +88,12 @@ function getPayerConfig(payer) {
}
function computePriority(flag, daysUntilEnd) {
if (flag === FLAG.OUT_OF_COVERAGE) return 1000 + Math.abs(daysUntilEnd);
if (flag === FLAG.VISIT_DUE) return 500 + Math.max(0, 90 - daysUntilEnd);
if (flag === FLAG.REFILL_WINDOW) return 200 + Math.max(0, 30 - daysUntilEnd);
if (flag === FLAG.SUPPLY_LAPSED) return 1000 + Math.abs(daysUntilEnd);
if (flag === FLAG.VISIT_REQUIRED) return 1500 + Math.abs(daysUntilEnd);
if (flag === FLAG.RENEWAL_CRITICAL) return 500 + Math.max(0, 90 - daysUntilEnd);
if (flag === FLAG.RENEWAL_ELEVATED) return 700 + Math.max(0, 90 - daysUntilEnd);
if (flag === FLAG.RENEWAL_SOON) return 400 + Math.max(0, 90 - daysUntilEnd);
if (flag === FLAG.RESUPPLY_READY) return 200 + Math.max(0, 30 - daysUntilEnd);
return 0;
}
@ -109,13 +121,19 @@ export function calculateCoverage(record) {
let flag;
if (daysUntilEnd < 0) {
flag = FLAG.OUT_OF_COVERAGE;
} else if (daysUntilVisit !== null && daysUntilVisit <= 30) {
flag = FLAG.VISIT_DUE;
flag = FLAG.SUPPLY_LAPSED;
} else if (daysUntilVisit !== null && daysUntilVisit < 0) {
flag = FLAG.VISIT_REQUIRED;
} else if (daysUntilVisit !== null && daysUntilVisit <= 45) {
flag = FLAG.RENEWAL_CRITICAL;
} else if (daysUntilVisit !== null && daysUntilVisit <= 60) {
flag = FLAG.RENEWAL_ELEVATED;
} else if (daysUntilVisit !== null && daysUntilVisit <= 90) {
flag = FLAG.RENEWAL_SOON;
} else if (daysUntilEnd <= payerCfg.refillWindowDays) {
flag = FLAG.REFILL_WINDOW;
flag = FLAG.RESUPPLY_READY;
} else {
flag = FLAG.OK;
flag = FLAG.ACTIVE;
}
return {