Skip to content

armaangulati1/ChartExtract-UI

Repository files navigation

ChartExtract-UI

Armaan Gulati

A single-screen review console where a person checks, corrects, and approves the structured oncology facts that an AI pulled out of a clinical note.

screenshot

Live demo: https://armaangulati1.github.io/ChartExtract-UI/

Parent project (the extraction API this sits on top of): https://github.com/armaangulati1/chartextract

What it does, in 60 seconds

Clinical notes are messy free text. A separate service, ChartExtractor, reads a note and returns structured variables: primary site, histology, stage, biomarkers, ECOG status, line of therapy, date of diagnosis, and treatment regimen. Each value comes with a confidence score and, when available, the exact quote from the note that supports it.

This front-end is the human side of that loop. A reviewer:

  • picks a note (five bundled synthetic examples, or pastes their own in Live API mode),
  • runs the extraction,
  • sees one card per field with its value, a confidence bar, the source, and any validator flags,
  • hovers a card to light up the supporting quote inside the note,
  • approves each field or corrects it with a typed editor, and
  • exports the approved and corrected values as a gold-label JSON file.

Low-confidence fields are pushed to the top and visually marked as needing review, so attention goes where the model is least sure.

Scope note

This is a time-boxed curiosity project, built in a day, exploring what the human-review surface on top of ChartExtractor should look and feel like. It is deliberately one screen with a fixed feature set.

Not a medical device

All notes here are fictional and synthetic. Nothing in this project is a medical device and it must not be used for any clinical decision.


For engineers

Stack

JavaScript and JSX only (no TypeScript), Vite, React 18 with functional components and hooks. The review workflow runs on a single useReducer. Styling is plain CSS with a small clinical-blue palette. Tests are Vitest plus React Testing Library. Lint is ESLint (flat config) plus Prettier.

Data flow

                 demo mode (default)
                 +-----------------------------+
  note text ---> | lib/demo.js                 |
                 |   load bundled fixture JSON  |
                 |   applyThreshold(threshold)  |---+
                 +-----------------------------+   |
                                                   v
  note text ---> +-----------------------------+   ExtractionOutput
   live mode     | lib/api.js  POST /extract   |-->  { extract, fields,
                 |   real ChartExtractor API   |      needs_review, usage,
                 +-----------------------------+      run_metrics }
                                                   |
                                                   v
                 +-----------------------------------------------+
                 | reviewReducer (EXTRACT_SUCCESS)               |
                 |   result + fresh per-field review (pending)  |
                 +-----------------------------------------------+
                                                   |
              selectOrderedFields (needs-review first) + selectCounts
                                                   v
                 +-----------------------------------------------+
                 | ResultsPanel -> FieldCard per field           |
                 |   approve / correct (typed editor) / reset    |
                 +-----------------------------------------------+
                                                   |
                             buildCorrectionExport -> gold-label JSON

Reducer design

src/lib/reviewReducer.js owns the whole session: the active mode (demo or live), the review threshold, the selected note, request status (idle / loading / ready / error), the current result (ExtractionOutput), and a review map of { status, value } per field where status is pending, approved, or corrected.

Keeping this pure and framework-free is intentional. Every workflow transition (extract, approve, correct, reset a field, reset the whole review, change note, re-route on a new threshold) is a plain action, and the selectors (selectCounts, selectOrderedFields, buildCorrectionExport) are pure functions of state. That makes the workflow easy to unit test without rendering React.

Demo and live adapters

Both sources return the same ExtractionOutput shape, so the rest of the app does not care which one ran:

  • lib/api.js calls the real API at POST /extract using fetch and an AbortController timeout. Errors (non-200, timeout, network, bad JSON) are normalized into a single ApiError.
  • lib/demo.js replays a bundled fixture. The fixtures in src/fixtures/*.json are real responses captured from the live API by curling each of the five synthetic notes (values vary slightly per run, since the pipeline uses an LLM; the captures are saved verbatim). Demo mode is the default so the console works instantly and stays usable when the free-tier API is asleep.

Demo mode only replays fixtures for the five bundled notes. Pasting your own note is a Live API feature: in demo mode the paste box disables Extract and shows an inline note pointing you to Live API. This is deliberate. Pairing an arbitrary pasted note with a canned response would be dishonest and would corrupt any gold-label export built from it, so extractDemo also throws if it is ever asked to resolve an unknown note id, as a last line of defense behind the UI guard.

How needs-review routing works, end to end

The API decides needs_review server-side: a field is routed to review when its confidence is below review_threshold (see schema.py in the parent repo). The UI reads result.needs_review and selectOrderedFields sorts those fields to the top, where FieldCard renders them in a distinct amber state.

The threshold slider drives this from the front end:

  • In live mode, changing the slider changes the value sent as review_threshold on the next extract, and the server recomputes routing.
  • In demo mode, the fixtures have fixed confidences, so lib/routing.js applyThreshold recomputes needs_review client-side using the same rule (confidence < threshold). This keeps the slider honest and interactive without a network call. Raising the threshold on a bundled note visibly moves cards into the needs-review state.

The default threshold differs by mode on purpose. Live mode defaults to 0.75 to match the API server default in schema.py. Demo mode defaults to 0.90 because the captured fixture confidences cluster around 0.85, so a 0.75 cutoff would show zero flagged fields and hide the review feature on the very first extract. Both defaults live in DEFAULT_THRESHOLD in reviewReducer.js, and switching mode resets the threshold to that mode's default.

How to run

npm install
npm run dev      # http://localhost:5173/ChartExtract-UI/
npm test         # vitest
npm run lint     # eslint
npm run build    # optimized bundle into dist/

Tests and CI

Vitest and React Testing Library cover the API client (mocked fetch, including error and timeout paths), the review reducer (approve, correct, reset, threshold change, re-route), needs-review sorting, the correction-export shape, demo-mode fixture loading, evidence highlighting, value formatting, and field-card render states including the needs-review state.

Two GitHub Actions workflows are included and activate once the repo is pushed: ci.yml runs install, lint, format check, test, and build on every push and PR; deploy.yml builds and publishes to GitHub Pages via actions/deploy-pages (Vite base is set to /ChartExtract-UI/ for project-site hosting). The deploy workflow calls actions/configure-pages@v5 with enablement: true, so it turns Pages on for the repo on its first run rather than failing before Pages has been enabled in settings.

The README screenshot is regenerated with a committed dev utility, scripts/capture-screenshot.mjs, which drives a local Chrome via puppeteer-core against a running preview server. puppeteer-core is intentionally not a dependency; install it transiently with npm i --no-save puppeteer-core before running the script (see the script header for the exact steps).

Honest limitations

  • Notes are synthetic and fictional. There is no real PHI anywhere.
  • No authentication. Anyone with the URL can use the demo.
  • No history browsing. Each session reviews one note at a time; exported corrections are downloaded, not persisted anywhere.
  • Single screen by design. No routing, no multi-note batch view.
  • Confidence values from the current API cluster around 0.85, so the review threshold becomes interesting mainly above about 0.85. Demo mode defaults to 0.90 for that reason (see the routing section).

Future work

  • Migrate to TypeScript for stronger schema typing at the boundary.
  • A /history browser backed by the API's existing history endpoint.
  • Gold-set aggregation: collect exported corrections into an evaluation set and measure model accuracy against reviewer decisions over time.

About

Human-in-the-loop review console for ChartExtractor: React front-end with per-field confidence, evidence provenance, and reviewer corrections

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors