Files
Rpa_Skills/rpa-dashboard-ai/templates/web/src/ai.ts
T
VasanthandClaude Opus 5.5 9dcdd8f572 AI cost/ROI page: pricing table, cost per call, Cost tab, ROI endpoint
- templates/web/pricing.py: pricing table with source and as-of date; cost_for_call
  (input rate + (output + thinking) at output rate, long-context above threshold;
  unknown model raises naming the known models).
- Cost tab: coverage pill, total/this-month/tokens tiles, month-by-month table,
  per-run tokens and cost ("not priced", never $0), by-hand panel with editable
  assumptions labelled as such.
- Contract: RoiRun and GET /roi; optional GET /models and POST /runs/{id}/retry.
- SKILL.md: cost page wiring, model picker and retry-on-other-model as optional sections.
- Tests: test_pricing.py self-check, page_check_ai/test_ai_cost.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:23:52 +05:30

72 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** 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
}
/** One run on the cost page. total_cost is null when the run recorded no token usage (unpriced). */
export type RoiRun = {
job_id: string
document: string
created_at: string // ISO, UTC
model: string
input_tokens: number | null
output_tokens: number | null
thinking_tokens: number | null
total_cost: number | null // USD
}
export type Pricing = { source: string; as_of: string }
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' }),
roi: () => req<{ runs: RoiRun[]; pricing: Pricing }>('/roi'),
documentUrl: (id: string) => `${API_BASE}/runs/${id}/document`,
}