Files
Rpa_Skills/rpa-dashboard-ai/templates/contract.md
T
VasanthandClaude Opus 5.5 d89dce3c33 AI desktop path: plain-HTML Review and Cost tabs over the Qt bridge, gate in Python
ai_sections.html pastes into the no-AI dashboard.html; ai_bridge.py adds one
ai_request slot that hands every AI contract request (incl. GET /roi) to the
review backend's dispatch(). Desktop page check reruns the web review and cost
cases plus a tampered-page gate case. Eval fixture e-pyqt-ai added.

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

5.1 KiB
Raw Blame History

Dashboard data contract (AI)

Everything in the no-AI contract (../rpa-dashboard-no-ai/templates/contract.md) applies unchanged: the {event, data} envelope, its events, its actions and its rules. The AI dashboard adds extraction runs, review rows and the approve gate. These shapes come from the Superbill prototype, trimmed.

Shapes

Run       { job_id, document, status: pending|queued|running|done|failed, model,
            pages_done, pages_total, summary: { green, amber, red, blank, unscored } | null,
            approved, error }
ReviewRow { row_id, field, value, band: green|amber|red|blank|unscored,
            score, reason, verified, corrected_value }
RoiRun    { job_id, document, created_at, model, input_tokens, output_tokens,
            thinking_tokens, total_cost }
  • summary counts the run's rows per band. It is null until the run is done.
  • score is the field's worst word score, or null when it has none.
  • corrected_value is null until a reviewer saves a value different from value.
  • created_at is ISO 8601 in UTC. Months on the cost page are UTC months.
  • RoiRun token counts and total_cost (USD) are null when the run recorded no token usage. The page shows it as "not priced" and counts it in the coverage pill, never as $0.

Endpoints (web: {API_BASE} + path, JSON in and out)

Method and path What it does
GET /runs {runs: [Run]}. The page opens the first one.
GET /runs/{job_id} One Run.
GET /runs/{job_id}/review {rows: [ReviewRow]}. Empty until the run is done.
GET /runs/{job_id}/document The source document (PDF or image) shown beside the fields.
PATCH /review/{row_id} Body {corrected_value?, verified}. Patches one row, returns it.
POST /runs/{job_id}/verify-remaining Verifies every flagged row not yet verified. Returns {verified: n}.
GET /roi Fetch ROI. {runs: [RoiRun], pricing: {source, as_of}}, every run, newest first. The page sums spend, groups months and computes the by-hand panel itself.
POST /runs/{job_id}/approve The approve gate. 409 {detail} while the run isn't done or any flagged row is unverified, else 200 {ok: true}.

Optional, only when the user chose those sections:

Method and path What it does
GET /models {models: [name]}: the keys of the pricing table, so only priced models can be picked.
POST /runs/{job_id}/retry Body {model}. Re-queues a failed run on that model, returns the Run.

Errors answer with a non-2xx status and {detail: "<why>"}. The page shows detail.

Rules

  • Bands come from templates/web/bands.py and nowhere else: green ≥0.95, amber 0.90–0.95, red <0.90. A field takes its worst word score. A blank mandatory field is blank, whatever its score. No scores is unscored.
  • Flagged = every band but green. Each flagged row must be verified before approval.
  • The server enforces the gate. The page disables nothing that matters for safety: it asks, and shows the server's refusal.
  • Drafts: the page sends nothing while a reviewer types. Save sends one PATCH per edited row (with verified: true). Leaving with unsaved edits asks first.
  • Polling: while a run is queued or running, the page polls GET /runs/{job_id} every 1.5s and shows "page X of Y". It stops when the run settles.
  • Cost comes from templates/web/pricing.py and nowhere else: per million tokens, input × input rate + (output + thinking) × output rate, long-context rates above the model's threshold. An unknown model is an error naming the known models, never $0. The table records its source URL and as-of date; GET /roi returns both.
  • Assumptions (minutes per document, hourly rate) live only on the page, typed by the reader and labelled as assumptions. The backend never stores or guesses them.
  • Caveat: the legend always shows that colour reflects print clarity, not correctness.

templates/web/server_stub.py implements all of this on sample data, standard library only. A real backend serves the same endpoints with the same rules.

Desktop (PyQt, no web server)

The same endpoints and rules travel over the Qt bridge instead of HTTP:

  • Page to Python: one slot, ai_request(id, method, path, body). path is the endpoint path above without {API_BASE}, body is a JSON string ("{}" when there is none).
  • Python to page: one envelope, {event: "ai_response", data: {id, status, body}}, with the same status and JSON body the web endpoint would answer.
  • Every endpoint above uses this pair, the cost page's GET /roi included (the page sends ai_request(id, "GET", "/roi", "{}") and gets {runs: [RoiRun], pricing: {source, as_of}} back as the body). No other AI events or slots exist.
  • GET /runs/{job_id}/document answers an SVG string or a URL the page can load (for example a file:/// path to the PDF).

templates/desktop/ai_bridge.py adds that slot to the no-AI Bridge. It hands each request to the review backend's dispatch(method, path, body) (server_stub.py until the project has its own), so the approve gate is enforced in Python, never in the page.