"""The scorer interface — the single seam between the UI and any scoring backend. A `Scorer` turns a `ParsedExport` into a `ScoreResult` (5 axis scores 0-10, critical-type counts, per-axis confidence, evidence quotes, tips, one improvement line). `score_to_card` maps that result onto the UI's `CardData`. The real ML model implements `Scorer.score` with the same signature; nothing in the UI changes. """ from __future__ import annotations from dataclasses import dataclass from typing import Protocol from ..data import AXES, Axis, CardData, CriticalBreakdown from ..parsing import ParsedExport @dataclass class AxisScore: name: str score: float # 0-10 confidence: str # high | medium | low evidence: list[str] tip: str @dataclass class ScoreResult: axes: list[AxisScore] critical_counts: dict[str, int] # skepticism, source_req, rebuttal, verify, re_ask improvement: str class Scorer(Protocol): def score(self, parsed: ParsedExport, progress=None) -> ScoreResult: ... def score_to_card(result: ScoreResult, name: str) -> CardData: """Adapt a backend-agnostic ScoreResult into the UI's CardData.""" c = result.critical_counts return CardData( name=(name or "").strip() or "Anonymous", axes=[Axis(a.name, round(a.score, 1), a.confidence, a.evidence, a.tip) for a in result.axes], critical=CriticalBreakdown( skepticism=c.get("skepticism", 0), source_req=c.get("source_req", 0), rebuttal=c.get("rebuttal", 0), verify=c.get("verify", 0), re_ask=c.get("re_ask", 0), ), improvement=result.improvement, )