File size: 1,691 Bytes
dc9f530
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
"""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,
    )