# `ui/` — AI usage evaluator UI shell Self-contained Gradio front-end. Runs standalone (no backend / ML deps) so it can be developed and deployed independently of the `prompt_card` workstream. ## Run ```bash python -m ui.app # serves the Gradio app ``` ## Layout ``` ui/ app.py # gr.Blocks, event wiring, the staged-reveal generator theme.py # design tokens, dark CSS, CDN head (fonts + Chart.js + flip/radar JS) data.py # CardData contract + stub provider ← shared data shape components/card.py# render CardData -> flip-card HTML + accordion bodies screens/ # intake / processing / result column builders parsing/ # Task 2: ChatGPT+Claude .json/.zip parser + zero-dep language gate scoring/ # Task 3: Scorer protocol + DummyScorer (heuristic placeholder) ``` ## The three adoption seams (where the real backend plugs in) 1. **Parsing** — `ui/parsing/parser.py::parse_export(path) -> ParsedExport`. Mirrors the real export shapes; the backend's `prompt_card.adapters` are the reuse target when the workstreams merge. 2. **Scoring** — `ui/scoring/interface.py::Scorer`. Implement `score(parsed) -> ScoreResult` with a real model and pass it where `DummyScorer()` is used in `app.py`. The UI is unchanged. 3. **Card data** — `ui/data.py::CardData` is the stable render contract. `score_to_card` maps a `ScoreResult` onto it. ## Status (stubbed vs real) - Real: export parsing, language split, processing facts (counts/date range/busiest slot). - **Scoring ADOPTED** (`ui/scoring/observable.py::ObservableScorer`): the 5 axis scores, overall/tier, and per-axis confidence (measured Cohen's κ) now come from the backend `prompt_card.observable_pipeline` (base MiniCPM4.1-8B + the locked per-category LoRA hybrid). `app.py` calls `get_scorer()`, which returns the real scorer when `OPENBMB_BASE_URL`/`OPENBMB_TOKEN` are set, else falls back to `DummyScorer` (so the UI still runs standalone with zero ML deps). A live scoring failure degrades to the dummy mid-demo. - Now REAL too: per-axis **evidence quotes** are the actual user turns where each axis fired (pipeline `data["evidence"]`; falls back to a representative turn only when an axis had zero positives), and the **critical breakdown** shows true per-type **counts** (pipeline `data["critical_type_counts"]`). - Still heuristic/static: the per-axis **tips** and the **improvement** line (advice, not measurements).