AI skill: SKILL.md, AI contract, scrubbed references, eval fixture (d)

- rpa-dashboard-ai/SKILL.md: trigger description and the shared question flow;
  points at the no-AI frontend.md, themes and palette instead of copying them.
- templates/contract.md: runs, review rows, endpoints and the server approve gate.
- Superbill extracts: patient names and the client name removed, every file
  carries an EXAMPLE ONLY banner. tests/test_reference_scan.py checks both skills
  for patient data, internal IPs, credentials and keys.
- tests/fixtures/d-ai-extraction and its eval entry, which runs page_check_ai
  on the built folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
VasanthandClaude Opus 5.5 committed 2026-10-16 15:13:55 +05:30
1 parent 91495935b9
commit 8951ac05a5
30 files changed
+372 -26

No files matched your search

+126
View File
@@ -0,0 +1,126 @@
---
name: rpa-dashboard-ai
description: Builds a working dashboard for an RPA bot or service that uses AI (an LLM or OCR extraction call). It has everything from rpa-dashboard-no-ai (sidebar, top bar with dark toggle and status dot, KPI cards, batch donut, run panel with steps and live log, report table with search and export) plus confidence bands, a review split pane (source document beside the extracted fields, band filters, draft edits, Verify) and an approve gate the server enforces. Web path is React + Vite + Tailwind with a sample-data server stub. Use when the user asks for an "AI RPA dashboard", an "extraction dashboard", an "LLM/OCR review dashboard", or any RPA/bot dashboard in a project that calls an LLM or OCR to extract fields. Needs rpa-dashboard-no-ai installed next to it.
---
# RPA dashboard (AI)
Everything this skill needs is bundled. Never read the original source projects.
Shared files live in the no-AI skill, installed next to this one: `<no-ai>` below
means `../rpa-dashboard-no-ai/templates/`.
| File | What it is |
|---|---|
| `templates/web/` | The React + Vite + Tailwind starter: shell, KPI, donut, run panel, report, **band legend, review split pane, approve gate**, sample data. |
| `templates/web/server_stub.py` | Sample-data backend for runs, review rows and the approve gate. Standard library only. |
| `templates/web/bands.py` | The band cut points and rules. The one source of truth: the backend imports it. |
| `templates/contract.md` | The AI contract: runs, review rows, endpoints and the gate. Extends `<no-ai>contract.md`. |
| `templates/reference/` | Superbill Review extracts, marked EXAMPLE ONLY. Use them to port extra polish, never copy their project names. |
| `<no-ai>frontend.md`, `<no-ai>detect_frontend.py` | Existing frontend: detect, report, keep or restyle. |
| `<no-ai>themes.md`, `<no-ai>palette.py` | Theme presets and the custom-colour steps. |
| `<no-ai>contract.md` | The no-AI contract the AI one extends. |
The questions are the same as the no-AI skill's, in the same order. Ask one at a
time and wait for the answer. If the user's request already answers a question,
don't ask it again.
## 1. Scan the project
Before asking anything:
- **Frontend:** run `python <no-ai>detect_frontend.py <project root>` and read
`<no-ai>frontend.md` section 1.
- **AI call:** find the LLM or OCR extraction (for example `genai`, `openai`,
`anthropic`, Textract or Tesseract calls): which fields it returns, which are
mandatory, and whether it returns per-word scores.
- **Bot or backend:** the entry point, what it prints, and any web framework
(FastAPI, Flask, Django).
If the project has no AI call at all, say so and suggest `rpa-dashboard-no-ai`.
## 2. Report the frontend, then ask keep or restyle
Follow `<no-ai>frontend.md` sections 2 to 5.
- **No frontend:** say "No existing frontend found." and go to step 3.
- **Frontend found:** say "Frontend found at `<path>`, stack `<stack>`, colours
`<main tokens>`." Then ask: **keep its look**, or **restyle** it?
## 3. Ask for colours
Skip this if the user chose keep. Offer:
1. **Tebra Sky**: blue glass cards on a sky gradient.
2. **Superbill Ember**: flat surfaces with a red-orange brand gradient.
3. **Custom**: the user gives one brand hex colour.
## 4. Ask desktop or web
Ask only if there is no frontend. If there is one, use its stack. If the project
uses PyQt, choose desktop and say so.
- **Web:** the React starter in `templates/web/`, served by the backend.
- **Desktop:** the plain-HTML AI sections for the PyQt page aren't bundled yet.
Tell the user, and offer the web path or the no-AI desktop page
(`rpa-dashboard-no-ai`) without the AI sections.
## 5. Ask which optional sections to build
Always built: the no-AI shell and sections, plus the band legend, the review split
pane and the approve gate. Ask which extras to add (choose any, or none):
**checkpoint tracker**, **admin/roles screen**, **login page**. Build only the
chosen ones at their `OPTIONAL:` markers, and delete the markers.
## 6. Build (web)
1. Copy `templates/web/` into the project as `dashboard/` (an existing frontend:
see `<no-ai>frontend.md` section 3; in an existing React app, port the
`src/` components into it). Don't copy `node_modules/` or `dist/`.
2. Fill every `{{...}}` in `dashboard/index.html`'s CONFIG block:
| Placeholder | Value |
|---|---|
| `APP_NAME`, `APP_SUBTITLE` | The project's name and a short tagline. |
| `LOGO_SRC` | The logo's path, or empty. |
| `STEPS_JSON` | The run steps as a JSON list, for example `["Initializing","Extract","Review","Completed"]`. |
| `REPORT_COLUMNS_JSON` | `[{"key":..., "label":...}]`, with a `status` column. |
| `API_BASE` | `/api` unless the backend mounts the dashboard API elsewhere. Keep it equal to `API` in `server_stub.py`. |
3. Apply the theme in `dashboard/src/index.css` only: replace the `body{...}` and
`body.dark{...}` token blocks with the preset from `<no-ai>themes.md` (for Ember,
add its font `<link>` to `index.html`'s `<head>`), or with
`python <no-ai>palette.py "<hex>"` tokens for a custom colour, showing the user
its contrast table. For keep, fill them from the project's tokens
(`<no-ai>frontend.md` section 4). Never change the band colour block.
4. Search the output for `{{`. None may be left.
## 7. Wire the data
The contracts: `<no-ai>contract.md` for the shell, `templates/contract.md` for runs,
review rows and the gate.
- **Sample data (no backend for these endpoints yet):** keep `SAMPLE_DATA = true` in
`src/store.ts` and use `server_stub.py`. Replace its `SAMPLE_FIELDS` with the
fields the project's extraction really returns (name, a sample value, word scores,
mandatory). Tell the user which endpoints their backend must serve later.
- **Backend exists:** add the endpoints from `templates/contract.md` to it. Copy
`bands.py` next to it and band every field with `field_band(word_scores, value,
mandatory)`. The approve endpoint must refuse (409 with `detail`) while any flagged
field is unverified: the page never decides that. For the shell, set
`SAMPLE_DATA = false` and serve the no-AI endpoints too.
- Never invent fields, model names or prices the project doesn't have.
## 8. Verify
```
cd dashboard
npm install
npm run build
python server_stub.py # page + sample API on http://127.0.0.1:8000
```
Open the page and check, in light, dark (the toggle) and a window 820px wide or less:
- the dashboard, Run and Report tabs show sample data;
- on Review: the band legend counts and the caveat, band filters, a typed edit marked
unsaved until Save, a refused Approve while flagged fields are unverified, and
"page X of Y" on a running document;
- no `{{` is left and the console shows no script errors.
If you can't run npm here, say so and give the user these commands. Finish by
telling the user where the files are, which theme and sections were built, and
whether the review data is sample data or the backend's.
+52
View File
@@ -0,0 +1,52 @@
# 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 }
```
- `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`.
## 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}`. |
| `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}`. |
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.
- **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.
@@ -1,3 +1,4 @@
# 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.
INCLUDE_THINKING_IN_COST = True # thinking tokens bill at output rate
# ═════════════════════════════════════════════════════════════════════════════
@@ -1,3 +1,4 @@
<!-- 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. -->
---
name: Superbill Extraction and Automation Tool
description: Extract billing fields from a scan, verify them against the page, post to eClinicalWorks.
@@ -1,3 +1,4 @@
/* 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 type {
AuditEntry, Batch, BatchStage, DemographyRecord, DemographyRun, InsuranceMasterRow, LastRun,
ManagedUser,
@@ -84,7 +85,7 @@ export const api = {
for (const f of files) body.append('files', f)
// `project` is which board received the drop. Nothing has detected a
// template yet, and the board a batch appears on is derived from that, so
// without it a Gulf Coast upload surfaces under ECW and reads as a failed
// without it a Example Client upload surfaces under ECW and reads as a failed
// upload on the Tebra board.
return req<Batch>(`/api/batches?${new URLSearchParams({ model, project })}`, {
method: 'POST',
@@ -1,3 +1,4 @@
/* 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. */
/**
* The confidence bands, and the exact colours the workbook paints.
*
@@ -1,3 +1,4 @@
/* 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 { ThemeSwitcher } from '../theme'
import { ROLE_LABEL } from '../auth'
@@ -1,3 +1,4 @@
/* 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. */
/**
* A proportional ring.
*
@@ -1,3 +1,4 @@
/* 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 { useCallback, useEffect, useRef, useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { api } from '../api'
@@ -1,3 +1,4 @@
/* 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 { useEffect, useMemo, useRef, useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { api } from '../api'
@@ -12,7 +13,7 @@ const isOfficeLocation = (t: string | null) =>
// One review card per record on a page. An ECW roster can list the same
// person twice (two Sleep Study encounters for one patient) — line_index
// splits those into separate cards. A Gulf Coast ticket spreads its charges
// splits those into separate cards. A Example Client ticket spreads its charges
// over several line_index values and must stay ONE card, so it is excluded.
// The name stays in the key so runs stored before line_index existed (every
// row LineIndex 0) still group exactly as before.
@@ -100,7 +101,7 @@ type Patient = {
type SaveArgs = { row: ReviewRow; draft: string; verified: boolean }
// ── Office Location ────────────────────────────────────────────────────────
// A Gulf Coast ticket whose yellow note the Office Location rules read. The
// A Example Client ticket whose yellow note the Office Location rules read. The
// backend stores the parsed note on the ticket's first line under this field
// (src/superbill/extraction/office_location.py, ParsedNote); a ticket without
// it is a run from before the rules existed and keeps the flat layout.
@@ -191,11 +192,11 @@ export function ReviewPane({
}) {
const [index, setIndex] = useState(0)
// Drafts live here, not in FieldRow — a correction is no longer committed on
// blur (that is exactly how four ARMSTRONG fields got saved mid-keystroke:
// "ARMSTRONG, CHARLES T" as "\ONG, CHARLES T", "99291" as "992"). Text typed
// blur (that is exactly how four fields of one record got saved mid-keystroke:
// "Doe, Jane A" as "\e, Jane A", "99291" as "992"). Text typed
// into a field is held until an explicit Save, keyed by row_id rather than
// bare field name — a bare name repeats once a page can carry more than one
// row per field (Gulf Coast's repeated CPT lines), and DemographyTab.tsx
// row per field (Example Client's repeated CPT lines), and DemographyTab.tsx
// hit exactly this before switching its own drafts to row_id.
const [drafts, setDrafts] = useState<Record<number, string>>({})
const qc = useQueryClient()
@@ -238,7 +239,7 @@ export function ReviewPane({
// PDF order: page, then where the patient sits on it. line_index is each
// record's position on its page, set by build_field_rows. Sorting by name
// here used to put ANDERSON above the first patient on the sheet, so the
// here used to put a later surname above the first patient on the sheet, so the
// list no longer matched the document the reviewer had open beside it.
// Name is only the tie-break, for runs stored before line_index existed.
return [...byKey.values()].sort(
@@ -254,7 +255,7 @@ export function ReviewPane({
map.get(key)!.push(row)
}
for (const rows of map.values()) {
// Gulf Coast (Template 7) records keep the order the API sent — the flat
// Example Client (Template 7) records keep the order the API sent — the flat
// 20-column superbill layout, Ticket# through Units. Re-sorting by band
// would scatter it (that is what put ICD11 above ICD2 and CPT at the
// bottom). Every other template keeps worst-band-last, which is what the
@@ -280,7 +281,7 @@ export function ReviewPane({
// A field that was never queued (needs_review === false) has no review
// row until this moment on most templates — build_field_rows only emits
// a row when a value exists or the field is billing-critical, so a
// genuinely blank optional field (e.g. Gulf Coast's From DOS / To DOS /
// genuinely blank optional field (e.g. Example Client's From DOS / To DOS /
// Units, always null today) is addressed by the cell it occupies,
// line_index included, rather than by row_id.
if (row.needs_review === false) {
@@ -363,7 +364,7 @@ export function ReviewPane({
const discardAll = () => setDrafts(settledMap)
// Leaving a patient with typed-but-unsaved edits used to be silent — the old
// blur handler saved automatically, which is what produced the ARMSTRONG
// blur handler saved automatically, which is what produced the truncated-name
// corruption. Now nothing commits without Save, so leaving instead has to
// ask, or the edit is just lost with no signal either way.
const guardLeave = (): boolean => {
@@ -711,7 +712,7 @@ function PatientView({
<div className="flex flex-col gap-[9px]">
{/* Keyed on row_id — a real database identity from /all-fields
(SPB_Extraction.RowId), unique across every field on every page.
A page-and-field key would collide on Gulf Coast's repeated CPT
A page-and-field key would collide on Example Client's repeated CPT
lines, where two rows share the same (page, field). */}
{/* No entry animation. Rows are keyed by row_id, so every Next
remounts them all, and the old staggered fade (opacity 0 over
@@ -802,7 +803,7 @@ function PatientView({
* lost by making it editable.
*
* Nothing here commits on its own. An earlier version saved on blur, which is
* exactly how "ARMSTRONG, CHARLES T" got recorded as "\ONG, CHARLES T" — the
* exactly how "Doe, Jane A" got recorded as "\e, Jane A" — the
* moment focus left the box mid-keystroke, whatever half-typed text was there
* became the correction. Typing only updates the shared draft in ReviewPane;
* the patient-level Save bar (here and in PdfPane) is the one place an edit is
@@ -835,7 +836,7 @@ function FieldRow({
const corrected = row.corrected_value != null && row.corrected_value !== row.value
// While being edited the value takes its own full-width line. A DX field is
// five comma-separated codes and a provider name runs past twenty
// characters; in the shared row they were truncated to "ARMSTRONG, C", so a
// characters; in the shared row they were truncated to "Doe, J", so a
// reviewer was correcting a value they could not fully see. Expanding only
// on focus keeps the scan-many-fields-fast layout for reading.
const expanded = focused || dirty
@@ -938,7 +939,7 @@ function FieldRow({
/**
* A Gulf Coast ticket laid out the way its yellow note is: the note itself,
* A Example Client ticket laid out the way its yellow note is: the note itself,
* the page-level rules it fired, the visit, then one tab per encounter.
*
* Hovering a field lights the note line it came from. The page preview is an
@@ -1,3 +1,4 @@
/* 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 { useMemo, useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { api } from '../api'
@@ -95,7 +96,7 @@ type Filter = 'all' | 'priced' | 'unmeasured' | 'failed'
/**
* api.roi() has no project filter — it totals every document. The ROI board
* is per-project (Gulf Coast's spend is not eClinicalWorks' spend), so the
* is per-project (Example Client's spend is not eClinicalWorks' spend), so the
* totals are recomputed here from the already-fetched runs instead of a new
* query param. Every field is a plain sum or count over RoiRun — nothing
* server-side is being second-guessed, just re-scoped to fewer rows.
@@ -138,7 +139,7 @@ export function RoiTab({
}: {
/** Absent on the demography board: its runs have no charge review to open. */
onOpenRun?: (jobId: string) => void
/** Which project's spend this board reports — Gulf Coast tickets and
/** Which project's spend this board reports — Example Client tickets and
* eClinicalWorks documents never share a total. */
project: Project
/** The Demography ROI (ticket 05): its own payload, one Gemini call per PDF
@@ -1,3 +1,4 @@
/* 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'
@@ -1,3 +1,4 @@
/* 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 { useState } from 'react'
import type { ReactElement } from 'react'
import type { BatchStage, BoardTab } from '../types'
@@ -14,7 +15,7 @@ import { IconSpend, IconPage, IconLoop, IconIdCard, IconPatients } from './icons
* icons are" step from every navigation.
*
* Two projects, one rail: ECW's boards (templates 1-6) and Tebra's (template 7
* / Gulf Coast), each under its own group header. See project.ts for how a
* / Example Client), each under its own group header. See project.ts for how a
* batch or run is assigned to one or the other.
*
* Selection and drag-and-drop-to-move-stage behaviour are unchanged — only the
@@ -136,7 +137,7 @@ export function Sidebar({
tab="tebra-roi"
icon={IconSpend}
label="ROI Dashboard"
hint="What the Gulf Coast pipeline has cost, and what that bought"
hint="What the Example Client pipeline has cost, and what that bought"
active={tab === 'tebra-roi'}
onSelect={onTab}
/>
@@ -146,8 +147,8 @@ export function Sidebar({
dropStage="extraction"
dropTab="tebra-extraction"
icon={IconPage}
label="Gulf Coast Charge Extraction"
hint="Gulf Coast tickets: extract and clear the flagged fields"
label="Example Client Charge Extraction"
hint="Example Client tickets: extract and clear the flagged fields"
active={tab === 'tebra-extraction'}
n={counts.tebra.extraction}
onSelect={onTab}
@@ -159,8 +160,8 @@ export function Sidebar({
dropStage="automation"
dropTab="tebra-automation"
icon={IconLoop}
label="Gulf Coast Charge Automation"
hint="Gulf Coast batches that finished extraction and are ready to post"
label="Example Client Charge Automation"
hint="Example Client batches that finished extraction and are ready to post"
active={tab === 'tebra-automation'}
n={counts.tebra.automation}
onSelect={onTab}
@@ -1,3 +1,4 @@
/* 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 { useCallback, useEffect, useRef, useState } from 'react'
const KEY = 'superbill.split'
@@ -1,3 +1,4 @@
/* 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 { BAND_VAR } from '../bands'
import type { Run } from '../types'
@@ -1,3 +1,4 @@
/* 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 { useEffect, useLayoutEffect, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
@@ -1,3 +1,4 @@
/* 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. */
/**
* The icon set.
*
@@ -1,3 +1,4 @@
/* 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 'tailwindcss';
/* Manual dark mode: Tailwind v4 defaults `dark:` to the OS preference, but this
@@ -1662,7 +1663,7 @@ textarea,
.sb-brand-name { background-position: 0 0; }
}
/* ── Office Location (a Gulf Coast ticket's yellow note) ──────────────────────
/* ── Office Location (a Example Client ticket's yellow note) ──────────────────────
The note is amber because it IS amber on the page; everything else stays on
the neutral surfaces. No brand ramp here -- it is reserved for its six
things (DESIGN.md, "The One Loud Colour Rule"). */
@@ -1,4 +1,5 @@
<!doctype html>
<!-- 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. -->
<html lang="en">
<head>
<meta charset="UTF-8" />
@@ -1,3 +1,4 @@
/* 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 { createContext, useContext, useEffect, useState } from 'react'
/**
@@ -1,3 +1,4 @@
/* 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 type { Band } from './bands'
/**
@@ -21,7 +22,7 @@ export type Project = 'ecw' | 'tebra'
* stage a card can be dropped into.
*
* Two projects live in one rail: the ECW boards (templates 1-6) and the Tebra
* boards (template 7 / Gulf Coast). Tebra ids are prefixed because a bare
* boards (template 7 / Example Client). Tebra ids are prefixed because a bare
* 'extraction' can no longer identify a board on its own — see project.ts for
* the tab -> (project, stage) mapping.
*/
@@ -118,7 +119,7 @@ export interface ReviewRow {
patient: string | null
/**
* 0 for every template except one that can carry more than one row per
* (page, patient, field) — Gulf Coast's repeated CPT lines, one per coder
* (page, patient, field) — Example Client's repeated CPT lines, one per coder
* annotation on the same ticket.
*/
line_index: number