Spaces:
Runtime error
Runtime error
| """The data contract for the card UI — and the STUB provider that fills it with fake data. | |
| THIS IS THE BACKEND ADOPTION SEAM. The UI renders whatever `CardData` it is handed; it does | |
| not care where the numbers came from. To go live, the backend replaces `get_stub_card()` with | |
| a real builder that returns a `CardData`, and feeds `ProcessingFacts` from real progress. | |
| Nothing in `screens/` or `components/` needs to change. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| # Canonical axis order. Display name -> used everywhere (radar labels, bars, accordions). | |
| AXES = ["Focus", "Technique", "Critical", "Interaction", "Input Quality"] | |
| # Tier ramp: (min_inclusive, letter, hex color). Checked high-to-low. | |
| TIERS = [ | |
| (8.5, "S", "#A78BFA"), | |
| (7.0, "A", "#E8B84B"), | |
| (5.5, "B", "#C7CDD6"), | |
| (4.0, "C", "#B08D57"), | |
| (0.0, "D", "#6B7280"), | |
| ] | |
| class Axis: | |
| """One scored axis. `confidence` is one of 'high' | 'medium' | 'low'.""" | |
| name: str | |
| score: float # 0-10 | |
| confidence: str # high | medium | low | |
| evidence: list[str] # quoted user turns shown in the drilldown accordion | |
| tip: str # one-line improvement hint | |
| class CriticalBreakdown: | |
| """Counts behind the Critical axis, shown on the card back.""" | |
| skepticism: int | |
| source_req: int | |
| rebuttal: int | |
| verify: int | |
| re_ask: int | |
| def as_pairs(self) -> list[tuple[str, int]]: | |
| return [ | |
| ("skepticism", self.skepticism), | |
| ("source-req", self.source_req), | |
| ("rebuttal", self.rebuttal), | |
| ("verify", self.verify), | |
| ("re-ask", self.re_ask), | |
| ] | |
| class CardData: | |
| """Everything the result screen needs to render one card.""" | |
| name: str | |
| axes: list[Axis] | |
| critical: CriticalBreakdown | |
| improvement: str | |
| # scoring provenance for the honesty tag: "placeholder" (DummyScorer) | "real-base" (MiniCPM-8B) | |
| # | "real-lora" (MiniCPM-8B + the locked LoRA hybrid). Set by app.py from the active scorer. | |
| provenance: str = "placeholder" | |
| # True when scored from a single pasted conversation (quick demo) vs a full export. The card-design | |
| # session keys "Sample analysis" framing off this; the result screen shows a scope disclaimer. | |
| single_conversation: bool = False | |
| def overall(self) -> float: | |
| return round(sum(a.score for a in self.axes) / len(self.axes), 1) | |
| def tier(self) -> tuple[str, str]: | |
| """Returns (letter, hex_color) for the current overall.""" | |
| ovr = self.overall | |
| for minimum, letter, color in TIERS: | |
| if ovr >= minimum: | |
| return letter, color | |
| return TIERS[-1][1], TIERS[-1][2] | |
| def radar_scores(self) -> list[float]: | |
| return [a.score for a in self.axes] | |
| class ProcessingFacts: | |
| """Fake facts revealed one at a time on the processing screen. | |
| Backend swap: feed real values (or stream real progress events) instead of these stubs. | |
| """ | |
| export_label: str = "ChatGPT export detected" | |
| turns_total: int = 1204 | |
| date_range: str = "Mar 2024 – Jun 2026" | |
| english_scored: int = 1050 | |
| other_unscored: int = 154 | |
| highlight: str = "Most active on Tuesday nights" | |
| # -------------------------------------------------------------------------------------- | |
| # STUB DATA — fake, fixed. Replace these two providers to integrate the real backend. | |
| # -------------------------------------------------------------------------------------- | |
| def get_stub_card(name: str = "") -> CardData: | |
| """Fixed fake card. Scores: Focus 8 / Technique 6 / Critical 9 / Interaction 5 / | |
| Input Quality 7 -> mean 7.0 -> tier A.""" | |
| return CardData( | |
| name=(name or "").strip() or "Anonymous", | |
| axes=[ | |
| Axis( | |
| "Focus", 8, "low", | |
| ['"let\'s keep this thread strictly about the migration script"', | |
| '"ignore the earlier tangent — back to the failing test"'], | |
| "Open a fresh chat per task to keep each thread on one goal.", | |
| ), | |
| Axis( | |
| "Technique", 6, "high", | |
| ['"act as a senior reviewer and critique this diff"', | |
| '"give me 3 options, then we pick"'], | |
| "Try few-shot examples for formatting-heavy asks.", | |
| ), | |
| Axis( | |
| "Critical", 9, "high", | |
| ['"that benchmark looks cherry-picked — what\'s the baseline?"', | |
| '"cite the source before I trust that number"'], | |
| "Keep pushing back; maybe verify claims against a second source.", | |
| ), | |
| Axis( | |
| "Interaction", 5, "medium", | |
| ['"can you walk me through why, step by step?"'], | |
| "Ask follow-ups that build on the model\'s reasoning, not just redo it.", | |
| ), | |
| Axis( | |
| "Input Quality", 7, "medium", | |
| ['"context: Python 3.12, FastAPI, here\'s the failing trace …"', | |
| '"constraints: no new deps, must stay under 400 lines"'], | |
| "Lead with role + constraints + a concrete example for richer prompts.", | |
| ), | |
| ], | |
| critical=CriticalBreakdown(skepticism=14, source_req=6, rebuttal=9, verify=4, re_ask=3), | |
| improvement="Biggest lever: tighten focus — split multi-topic chats into separate threads.", | |
| ) | |
| def get_stub_facts() -> ProcessingFacts: | |
| return ProcessingFacts() | |