AI web starter: bands, review split pane, approve gate, server stub (checkpoint)
React + Vite + Tailwind starter with the no-AI shell, band legend, review split pane with drafts and verify, and a server stub that refuses approval while flagged fields are unverified. Band self-check and AI page check. Both page checks share one Chromium via tests/conftest.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3eb3fdf00e
commit
d4d3f83fb1
24 files changed
+3429
-73
No files matched your search
@@ -0,0 +1,57 @@
|
||||
/** The AI contract additions (see contract.md): runs, review rows, the approve gate. */
|
||||
export type Band = 'green' | 'amber' | 'red' | 'blank' | 'unscored'
|
||||
export type RunStatus = 'pending' | 'queued' | 'running' | 'done' | 'failed'
|
||||
export type Run = {
|
||||
job_id: string
|
||||
document: string
|
||||
status: RunStatus
|
||||
model: string
|
||||
pages_done: number
|
||||
pages_total: number
|
||||
summary: Record<Band, number> | null // null until the run is done
|
||||
approved: boolean
|
||||
error: string | null
|
||||
}
|
||||
export type ReviewRow = {
|
||||
row_id: number
|
||||
field: string
|
||||
value: string | null
|
||||
band: Band
|
||||
score: number | null
|
||||
reason: string | null
|
||||
verified: boolean
|
||||
corrected_value: string | null
|
||||
}
|
||||
|
||||
export const BUSY: RunStatus[] = ['queued', 'running'] // polled every 1.5s while in one of these
|
||||
|
||||
/** Legend order, label, and the colour token. Band colours are semantic: never on buttons or headings. */
|
||||
export const BANDS: { band: Band; label: string; colour: string }[] = [
|
||||
{ band: 'green', label: 'Clean ≥0.95', colour: 'var(--clean)' },
|
||||
{ band: 'amber', label: 'Check 0.90–0.95', colour: 'var(--verify)' },
|
||||
{ band: 'red', label: 'Review <0.90', colour: 'var(--review)' },
|
||||
{ band: 'blank', label: 'Missing (required)', colour: 'var(--absent)' },
|
||||
{ band: 'unscored', label: 'Unscored', colour: 'var(--absent)' },
|
||||
]
|
||||
export const CAVEAT = 'Colour reflects print clarity, not correctness. A cleanly printed value can still be misread.'
|
||||
|
||||
async function req<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const res = await fetch(API_BASE + path, { ...init, headers: { 'Content-Type': 'application/json' } })
|
||||
if (!res.ok) {
|
||||
// The server says why it refused (the approve gate answers 409 with the count).
|
||||
const body = await res.json().catch(() => null)
|
||||
throw new Error(body?.detail ?? `${res.status} ${res.statusText}`)
|
||||
}
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export const ai = {
|
||||
runs: () => req<{ runs: Run[] }>('/runs'),
|
||||
run: (id: string) => req<Run>(`/runs/${id}`),
|
||||
rows: (id: string) => req<{ rows: ReviewRow[] }>(`/runs/${id}/review`),
|
||||
patchRow: (rowId: number, body: { corrected_value?: string | null; verified: boolean }) =>
|
||||
req<ReviewRow>(`/review/${rowId}`, { method: 'PATCH', body: JSON.stringify(body) }),
|
||||
verifyRemaining: (id: string) => req<{ verified: number }>(`/runs/${id}/verify-remaining`, { method: 'POST' }),
|
||||
approve: (id: string) => req<{ ok: boolean }>(`/runs/${id}/approve`, { method: 'POST' }),
|
||||
documentUrl: (id: string) => `${API_BASE}/runs/${id}/document`,
|
||||
}
|
||||
Reference in new issue
Block a user