northlake-health/src/screens/RunReviewScreen.tsx
Yashas b2377b4645 feat: Ava stops waiting to be asked — the daily review
The primary action is now "Run daily review": no form, no question. It
starts the new INIT door dni-act-review, which is the same activity a
scheduler performs at 6am; the button exists so a room does not have to
wait until 6am. Ava sweeps every payer, department and front desk against a
materiality bar and, if nothing clears it, records "nothing to report" and
closes the case herself — it never reaches a desk. "Ask a question" stays,
demoted to a ghost button, for the follow-up path.

A second button replays a quiet morning: the demo data is static, so every
live sweep would flag the same August spike. Anchoring one run on
2026-07-26 lets it genuinely come back empty, and the contrast — one quiet
morning beside one $1.09M morning — is the pitch.

Queue cards and the case page now carry the sweep outcome: what she
reviewed, how many patterns she flagged, and what kind (payer rule change,
front desk, expiring appeals, …). A closed quiet case reads "Nothing to
report" rather than "Closed", because those are different outcomes.

Backend counterpart: sm2/custom-apps/denials-investigator/08_daily_review.sql
and the sweep-first charter in 05_ai_employee.sql.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 13:12:40 +05:30

105 lines
4.3 KiB
TypeScript

import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Brain, Database, Moon, Radar } from 'lucide-react';
import { useZino } from '../api/provider';
import { ACT, LAKEHOUSE, QUIET_MORNING_AS_OF, WORKFLOW } from '../api/config';
import { Button, Card, ErrorNote } from '../components/core';
/**
* Run the daily review, now.
*
* There is no form. This is the same activity a scheduler performs at 6am;
* the button exists so a room does not have to wait until 6am. Nothing is
* asked of Ava — she decides what, if anything, is worth a person's time.
*
* The second button anchors the sweep on a date before the August spike. The
* demo data is static, so without it every run flags the same thing; with it,
* one run can genuinely come back "nothing to report" — which is what most
* mornings look like, and the contrast is the point.
*/
export function RunReviewScreen() {
const navigate = useNavigate();
const { client } = useZino();
const [busy, setBusy] = useState<'now' | 'quiet' | null>(null);
const [err, setErr] = useState<string | null>(null);
async function run(asOf?: string) {
setBusy(asOf ? 'quiet' : 'now');
setErr(null);
try {
// The activity's one field is optional; omit it entirely for a live run
// so the workflow sees a genuinely empty submission.
const res = await client.startInstance(
WORKFLOW,
ACT.REVIEW,
asOf ? { review_as_of__review: asOf } : {},
);
if (res.instance_id) navigate(`/c/${res.instance_id}`);
else navigate('/q/investigating');
} catch (e) {
setErr((e as { message?: string })?.message ?? 'Could not start the review');
setBusy(null);
}
}
return (
<div className="mx-auto max-w-2xl space-y-4">
<header>
<h1 className="text-xl font-semibold text-ink">Daily review</h1>
<p className="mt-0.5 text-sm text-muted">
Ava looks at every payer, department and front desk, and only opens a case if
something clears the bar. Most mornings she reports nothing.
</p>
</header>
{err && <ErrorNote>{err}</ErrorNote>}
<Card className="px-4 py-4">
<div className="flex items-start gap-3">
<span className="rounded-full bg-ai-soft p-2 text-ai ring-1 ring-ai-edge ring-inset">
<Brain className="h-4 w-4" />
</span>
<div className="min-w-0 flex-1 text-sm text-muted">
<p className="font-semibold text-ink">What she checks</p>
<ul className="mt-1.5 list-disc space-y-0.5 pl-4">
<li>That the claims data reconciles with the finance report</li>
<li>Denial rate vs baseline — by payer, reason code, department, front desk, week</li>
<li>Money about to fall out of an appeal window unfiled</li>
</ul>
<p className="mt-2 text-xs text-faint">
The bar: a denied count up more than 40% on meaningful volume, or more than $100K
expiring. Below it, she watches and says nothing.
</p>
<p className="mt-2 inline-flex items-center gap-1.5 text-xs text-faint">
<Database className="h-3.5 w-3.5" />
Read-only against {LAKEHOUSE.platform} · {LAKEHOUSE.catalog}.{LAKEHOUSE.schema}
</p>
</div>
</div>
<div className="mt-5 flex flex-wrap items-start gap-3 border-t border-line pt-4">
<div>
<Button kind="primary" onClick={() => run()} busy={busy === 'now'} disabled={busy !== null}>
<Radar className="h-4 w-4" />
Run the review now
</Button>
<p className="mt-1 max-w-[16rem] text-[11px] text-faint">
Normally fires at 6am on its own. This is the same activity.
</p>
</div>
<div>
<Button kind="secondary" onClick={() => run(QUIET_MORNING_AS_OF)} busy={busy === 'quiet'} disabled={busy !== null}>
<Moon className="h-4 w-4" />
Replay a quiet morning
</Button>
<p className="mt-1 max-w-[16rem] text-[11px] text-faint">
Anchored on {QUIET_MORNING_AS_OF}, before anything went wrong — to see what
&ldquo;nothing to report&rdquo; looks like.
</p>
</div>
</div>
</Card>
</div>
);
}