northlake-health/src/screens/QueueScreen.tsx
Yashas b4bff014b3 feat: review coverage as rows of facts, not a paragraph
Ava now writes the work statement as four labelled lines (REVIEWED / FOUND /
WATCHED / CLEAR, items separated by ';'); the case page renders each as a row
of short chips under 'Review coverage'. Older prose falls back to sentence
bullets. Charter also asks for a <=25-word first sentence and plain English —
no column names or SQL in prose.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 22:53:27 +05:30

168 lines
7.0 KiB
TypeScript

import { useNavigate, useParams } from 'react-router-dom';
import { Brain, ChevronRight, Radar } from 'lucide-react';
import { useQuery, useZino } from '../api/provider';
import { FINDING_KIND, QUEUES, RECORD_VIEW, STAGE_TONE } from '../api/config';
import { Badge, Button, Empty, ErrorNote, Spinner } from '../components/core';
import type { Row } from '../api/types';
import { dateTime, headline, money, num, prose, sentences, str, workStatement } from '../format';
/**
* A queue, as a morning briefing.
*
* The first thing on the page is not a list: it is one strip that says what
* Ava did and what is waiting — "reviewed everything, one thing needs you,
* worth $1.09M". A manager who reads only that line has the day's status.
* The cases follow, each led by its one-sentence verdict and the money.
*
* POLLS, because cases move on their own — Investigating → Awaiting Review with
* no user action to hang a refresh off. A stale list looks like a stuck system.
*/
export function QueueScreen() {
const { q } = useParams();
const navigate = useNavigate();
const { client } = useZino();
const queue = QUEUES.find((x) => x.path === q) ?? QUEUES[0];
const rows = useQuery(
() =>
client.rows(RECORD_VIEW, {
limit: 100,
sortBy: 'updated_at',
sortDir: 'desc',
filters: queue.stage ? [{ field_key: 'current_state_name', value: queue.stage, data_type: 'text' }] : [],
}),
[queue.path],
true,
8000,
);
// The briefing needs the whole picture, whichever queue is open.
const all = useQuery(() => client.rows(RECORD_VIEW, { limit: 100, sortBy: 'updated_at', sortDir: 'desc' }), [], true, 15000);
const list = rows.data ?? [];
return (
<div className="mx-auto max-w-5xl space-y-4">
<Briefing rows={all.data ?? []} onRun={() => navigate('/review')} />
<header className="flex flex-wrap items-end justify-between gap-3 pt-2">
<div>
<h1 className="text-lg font-semibold text-ink">{queue.label}</h1>
<p className="mt-0.5 text-sm text-muted">{queue.hint}</p>
</div>
{!rows.loading && <Badge tone="slate">{list.length} case{list.length === 1 ? '' : 's'}</Badge>}
</header>
{rows.error && <ErrorNote>{rows.error}</ErrorNote>}
{rows.loading && !rows.data && <Spinner label="Loading cases…" />}
{rows.data && list.length === 0 && (
<Empty>No cases in this queue.</Empty>
)}
<div className="space-y-3">
{list.map((r) => (
<CaseCard key={str(r.instance_id)} row={r} onOpen={() => navigate(`/c/${str(r.instance_id)}`)} />
))}
</div>
</div>
);
}
/** The one strip a manager reads. */
function Briefing({ rows, onRun }: { rows: Row[]; onRun: () => void }) {
const byState = (s: string) => rows.filter((r) => str(r.current_state_name) === s);
const waiting = byState('Awaiting Review');
const working = [...byState('Investigating'), ...byState('Monitoring')];
const needsHuman = byState('Needs Human');
const recoverable = waiting.reduce((t, r) => t + (num(r.recoverable_amount) ?? 0), 0);
const latest = rows[0];
return (
<section className="lift rounded-xl border border-line bg-panel">
<div className="flex flex-wrap items-center gap-4 px-5 py-4">
<span className="rounded-full bg-ai-soft p-2.5 text-ai ring-1 ring-ai-edge ring-inset">
<Brain className="h-5 w-5" />
</span>
<div className="min-w-0 flex-1">
<p className="text-[11px] font-semibold tracking-wide text-faint uppercase">
Daily review{latest ? ` · last run ${dateTime(latest.updated_at)}` : ''}
</p>
<p className="mt-0.5 text-[19px] leading-snug font-semibold text-ink">
{waiting.length === 0 && working.length === 0 && needsHuman.length === 0
? 'No cases awaiting decision.'
: [
waiting.length > 0 &&
`${waiting.length} case${waiting.length === 1 ? '' : 's'} awaiting decision${
recoverable > 0 ? ` · ${money(recoverable)} recoverable` : ''
}`,
working.length > 0 && `${working.length} review${working.length === 1 ? '' : 's'} in progress`,
needsHuman.length > 0 && `${needsHuman.length} escalated`,
]
.filter(Boolean)
.join(' · ')}
</p>
<p className="mt-0.5 text-xs text-muted">
Automated review of all payers, departments and front desks. A case is opened only when a change clears the materiality threshold.
</p>
</div>
<Button kind="primary" onClick={onRun}>
<Radar className="h-4 w-4" /> Run review
</Button>
</div>
</section>
);
}
/** A case, led by its verdict and its money. */
function CaseCard({ row, onOpen }: { row: Row; onOpen: () => void }) {
const state = str(row.current_state_name);
const summary = prose(row.summary);
const scanned = prose(row.scanned_summary);
const flagged = num(row.flagged_count);
const kind = str(row.finding_kind);
const recoverable = num(row.recoverable_amount);
const aiOwned = state === 'Investigating' || state === 'Monitoring';
const quiet = flagged === 0 && !summary;
const waiting = state === 'Awaiting Review';
return (
<button
onClick={onOpen}
className={`lift block w-full rounded-xl border bg-panel px-5 py-4 text-left transition-colors hover:border-brand/60 focus-visible:outline focus-visible:outline-2 focus-visible:outline-brand ${
waiting ? 'border-warn-edge' : 'border-line'
}`}
>
<div className="flex flex-wrap items-center gap-2">
<Badge tone={quiet ? 'slate' : (STAGE_TONE[state] ?? 'slate')} dot={aiOwned}>
{quiet ? 'No findings' : waiting ? 'Awaiting decision' : state}
</Badge>
{kind && FINDING_KIND[kind] && <Badge tone="violet">{FINDING_KIND[kind]}</Badge>}
<span className="ml-auto text-[11px] text-faint">
#{str(row.instance_id)} · {dateTime(row.updated_at)}
</span>
</div>
<div className="mt-2.5 flex items-start gap-4">
<p className="min-w-0 flex-1 text-[16px] leading-snug font-semibold text-ink">
{summary
? headline(summary, 160)
: quiet
? 'No material change detected across payers, departments or front desks.'
: 'Review in progress…'}
</p>
{recoverable != null && recoverable > 0 && (
<span className="shrink-0 text-right">
<span className="tnum block text-[22px] leading-none font-bold text-good">{money(recoverable)}</span>
<span className="block text-[11px] text-faint">recoverable</span>
</span>
)}
<ChevronRight className="mt-1 h-4 w-4 shrink-0 text-faint" />
</div>
{quiet && scanned && (
<p className="mt-1.5 line-clamp-2 text-xs text-muted">
{(workStatement(scanned)?.find((r) => r.key === 'REVIEWED')?.items ?? sentences(scanned).slice(0, 1)).join(' · ')}
</p>
)}
</button>
);
}