Move all three skills into skills/; add root README; fix paths

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
VasanthandClaude Sonnet 5.5 committed 2026-10-16 17:04:38 +05:30
1 parent 312742cfef
commit d95e5d452d
84 files changed
+97 -31

No files matched your search

@@ -0,0 +1,278 @@
/* EXAMPLE ONLY: an extract from the Superbill Review prototype, kept as reference for porting. ECW/eClinicalWorks, Tebra, 'Example Client', SPB_* tables and patient/ticket wording are that project's examples. Don't copy them into a new project. */
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { api } from '../api'
import { IconDownload } from './icons'
import type { Run } from '../types'
// Each model carries its OWN free-tier daily allowance, so when one is spent —
// or overloaded — switching is the way through rather than waiting for a reset.
const MODELS = ['gemini-3.5-flash', 'gemini-2.5-flash'] as const
/** One compact strip: status, page progress, band counts, and the approve gate. */
export function RunHeader({
jobId,
onApproved,
}: {
jobId: string
/** Approving is the end of the human's job on this document, so the app
* follows through to the automation board rather than leaving the reviewer
* on a screen they are finished with. */
onApproved?: () => void
}) {
const { data: run } = useQuery({
queryKey: ['run', jobId],
queryFn: () => api.getRun(jobId),
// Poll only while the answer can still change. A finished run polled every
// second is a request that can never return anything new.
refetchInterval: (q) => {
const s = q.state.data?.status
return s === 'done' || s === 'failed' ? false : 1500
},
})
const qc = useQueryClient()
const approve = useMutation({
// Approving with fields still unverified used to prompt "approve anyway?",
// then fail: the gate is enforced server-side and returned 409, so OK did
// nothing and the refusal was easy to miss in the row. The prompt now
// states exactly what OK does, and OK does it — the remaining fields are
// signed off as extracted first (audited separately as `bulk_verified`),
// which satisfies the gate rather than pretending to bypass it.
mutationFn: async () => {
if ((run?.review?.remaining ?? 0) > 0) await api.verifyRemaining(jobId)
return api.approve(jobId)
},
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['run', jobId] })
qc.invalidateQueries({ queryKey: ['batches'] })
// Held briefly so the "Approved ✓" state is actually seen before the
// view changes underneath it — an instant jump reads as the click having
// done something unrelated.
setTimeout(() => onApproved?.(), 700)
},
})
const retry = useMutation({
mutationFn: (model?: string) => api.retryRun(jobId, model),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['run', jobId] })
qc.invalidateQueries({ queryKey: ['runs'] })
},
})
if (!run) return null
const left = run.review?.remaining ?? 0
// A spent daily allowance is per-model, so the same model will just fail
// again. Offer the switch right where the failure is shown.
const quotaSpent = /quota/i.test(run.error ?? '')
const other = MODELS.find((m) => m !== run.model) ?? MODELS[0]
return (
<div className="flex shrink-0 flex-wrap items-center gap-x-3 gap-y-2 border-b border-line bg-surface px-4 py-2">
<StatusPill run={run} />
{run.status === 'running' && (
<span className="tnum text-[12px] text-muted">
page {run.pages_done} of {run.pages_total || '?'}
</span>
)}
{run.summary && (
<div className="flex flex-wrap gap-1.5">
<Chip band="green" n={run.summary.green} label="clean" />
<Chip band="amber" n={run.summary.amber} label="sample" />
<Chip band="red" n={run.summary.red} label="review" />
<Chip band="blank" n={run.summary.blank_critical} label="missing code" />
</div>
)}
{run.error && (
<span className="min-w-0 flex-1 truncate text-[12px] text-review" title={run.error}>
{run.error}
</span>
)}
{/* Reads the same SPB_Roi + SPB_Extraction rows the review pane does —
nothing re-extracts, no API call happens. Gated on 'done' rather than
shown always: costs and every field are null until the run finishes,
so a report requested earlier would just be empty. */}
{run.status === 'done' && (
<a
href={`/api/runs/${jobId}/report.xlsx`}
download
title="Download a cost + extracted-values spreadsheet for this run"
className="sb-btn sb-btn-quiet sb-btn-sm shrink-0"
>
<IconDownload size={13} />
Report
</a>
)}
{run.status === 'failed' && (
<span className="flex shrink-0 items-center gap-1.5">
<button
onClick={() => retry.mutate(undefined)}
disabled={retry.isPending}
// A 503 spike or a per-minute window is weather, not a bad file. The
// uploaded PDF is still on the server, so retrying costs no upload.
title={`Run the same file again on ${run.model} — no re-upload needed`}
// min-h-[24px]: text-xs at this line-height renders at 17px tall,
// under the WCAG 2.5.8 24x24 minimum target size — the padding
// alone didn't clear it.
className="sb-btn sb-btn-quiet sb-btn-sm min-h-[24px]"
>
{retry.isPending ? 'Retrying…' : 'Retry'}
</button>
<button
onClick={() => retry.mutate(other)}
disabled={retry.isPending}
title={`Retry on ${other}, which has its own daily allowance`}
className={`sb-btn sb-btn-sm min-h-[24px] ${
quotaSpent ? 'sb-btn-primary' : 'sb-btn-quiet'
}`}
>
Retry on {other.replace('gemini-', '')}
</button>
</span>
)}
{run.review && run.review.total > 0 && (
<div className="ml-auto flex items-center gap-2.5">
{/* The bar alone, no "N left" readout beside it. The number was
redundant with the review pane's own "9/230 fields" and turned
into noise ("222 left") on a document nobody had started. The
proportion is what a glance actually needs. */}
<div className="sb-meter w-32">
<div
className="h-full w-full origin-left rounded-full transition-transform duration-500 ease-out"
style={{
transform: `scaleX(${run.review.verified / run.review.total})`,
background: 'var(--brand-grad)',
}}
/>
</div>
<ApproveButton
onApprove={() => approve.mutate()}
pending={approve.isPending}
approved={approve.isSuccess}
remaining={left}
/>
</div>
)}
{run.review?.total === 0 && (
<div className="ml-auto flex items-center gap-2.5">
<span className="sb-pill sb-pill-ok">Nothing flagged</span>
<ApproveButton
onApprove={() => approve.mutate()}
pending={approve.isPending}
approved={approve.isSuccess}
remaining={0}
/>
</div>
)}
{/* Full-width so a refused approve cannot hide at the end of a crowded
row — the previous inline span was the only signal that the click had
failed, and it was routinely missed. */}
{approve.error && (
<p
role="alert"
className="w-full rounded-card border border-review/40 bg-review/10 px-3 py-1.5 text-[12px] font-medium text-review"
>
Not approved — {(approve.error as Error).message}
</p>
)}
</div>
)
}
/**
* Approving releases a charge, so it asks first rather than firing on a single
* click. The button is never disabled: hiding the gate behind a dead control
* left a reviewer with a greyed-out button and no statement of what was
* blocking it. Now the confirm says exactly how many fields are still
* unverified, and the server still refuses (409) if they try anyway — the gate
* stays where it is enforced, not where it is merely displayed.
*/
function ApproveButton({
onApprove,
pending,
approved,
remaining,
}: {
onApprove: () => void
pending: boolean
approved: boolean
remaining: number
}) {
if (approved) {
return (
<span className="sb-pulse sb-pill sb-pill-ok inline-flex items-center gap-1.5">
<span aria-hidden="true">✓</span> Approved
</span>
)
}
return (
<button
disabled={pending}
onClick={() => {
// States what OK actually does. The previous wording ("approve
// anyway?") promised something the server refuses, so OK appeared to
// do nothing at all.
const ok = window.confirm(
remaining > 0
? `${remaining} field(s) are still unverified.\n\n` +
`OK will sign them off as extracted, then approve this document ` +
`for posting. That is recorded as a bulk sign-off.\n\n` +
`Cancel to go back and check them individually.`
: 'Approve this document for posting?',
)
if (ok) onApprove()
}}
className="sb-btn sb-btn-primary sb-btn-sm"
>
{pending ? 'Approving…' : 'Approve'}
</button>
)
}
// Fixed, not accent-driven: these are STATE semantics (queued/in-progress/done/
// failed), read the same way a CI pipeline's status colors are read. Tying them
// to the switchable accent would make "failed" turn the same red as "confirm"
// whenever someone picks the red accent — a real confusion, not a style choice.
function StatusPill({ run }: { run: Run }) {
const tone = {
// Uploaded but not yet handed to the extractor — the batch's Run button
// has not been pressed. Deliberately the quietest of the five.
pending: 'sb-pill-mute opacity-70',
queued: 'sb-pill-mute',
running: 'sb-pill-warn',
done: 'sb-pill-ok',
failed: 'sb-pill-bad',
}[run.status]
return <span className={`sb-pill ${tone}`}>{run.status}</span>
}
function Chip({
band,
n,
label,
}: {
band: 'green' | 'amber' | 'red' | 'blank'
n: number
label: string
}) {
// "blank" is not a grade — nothing was read, so there is no clarity to score.
// It takes the neutral pill rather than one of the three grading colours.
const tone = {
green: 'sb-pill-ok',
amber: 'sb-pill-warn',
red: 'sb-pill-bad',
blank: 'sb-pill-mute',
}[band]
return (
<span className={`sb-pill ${tone}`}>
<b className="tnum">{n}</b> {label}
</span>
)
}