Aller au contenu

Scoring & décision

Cette page décrit comment le pipeline fusionne ses signaux puis prend la
décision finale HUMAN_REVIEW ou REJECT.

Diagramme

flowchart LR A[Signal RAG] --> M[Multi-Signal Scorer] B[Signal Geo] --> M C[Signal LLM] --> M C2[LLM abstain ?] -->|oui| M M --> D[Score fusionné] M --> V[Variance des signaux] V -->|stdev > seuil| P[Pénalité -0.10] P --> D D --> G[Decision Gate] G -->|abstain veto| R1[REJECT llm_abstain] G -->|score non fini| R2[REJECT invalid_fused_score] G -->|score >= seuil| H[HUMAN_REVIEW accepted] G -->|score < seuil| R3[REJECT low_fused_score]

Multi-Signal Scorer

Module : MultiSignalScorer
(src/services/reliability/multi_signal_scorer.py).

Entrées

Pour chaque candidat, le scorer reçoit jusqu'à trois signaux normalisés dans
[0, 1] :

Signal Provenance Disponibilité
rag Similarité fuzzy avec les entités canoniques de la transcription Toujours présent, peut valoir 0.
geo geo_score issu de Validation géographique Type place uniquement.
llm confidence du raisonneur contraint Sauf si decision = "abstain".

Pondération par type d'entité

Les poids par défaut sont :

Type rag geo llm
person 0.40 0.30 0.30
place 0.20 0.60 0.20
org 0.50 0.00 0.50
acronym 0.20 0.00 0.80
typo 0.30 0.00 0.70

Chaque poids est surchargeable via la variable
RELIABILITY_V5_WEIGHT_{TYPE}_{SIGNAL} (15 variables au total — voir
Configuration).

Renormalisation

Quand un signal est indisponible (typiquement geo hors-place, ou llm
abstenu), les poids restants sont renormalisés pour que leur somme vaille 1.

Exemple, candidat person avec llm abstenu :

poids_effectifs = {rag: 0.40 / (0.40 + 0.30) = 0.571,
                   geo: 0.30 / 0.70           = 0.429}

Pénalité de variance

Lorsque les signaux disponibles donnent des réponses contradictoires, une
pénalité réduit la confiance dans la fusion. La règle est :

si stdev(signaux_disponibles) >= RELIABILITY_V5_VARIANCE_THRESHOLD (0.25)
   fused_score -= RELIABILITY_V5_VARIANCE_PENALTY (0.10)

La pénalité n'est appliquée qu'à partir de deux signaux disponibles.

Score fusionné

fused_score = clamp(0, 1,
    Σ (poids_renormalisé[s] × signal[s]) - pénalité_variance
)

Boost Wikidata

Quand le validateur géographique a obtenu un wikidata_qid et que le
libellé Wikidata correspond — après normalisation NFKD — à la valeur suggérée
par le LLM, un boost additif est appliqué au signal llm avant fusion :

boost = RELIABILITY_V5_WIKIDATA_BOOST (défaut 0.05, max)
llm_signal = clamp(0, 1, llm_signal + boost)

Métrique : reliability_v5_wikidata_boost_applied{outcome=…}.

Decision Gate

Module : DecisionGate (src/services/reliability/decision_gate.py).

La décision est strictement binaire : HUMAN_REVIEW ou REJECT.

Ordre de priorité

1. llm_decision == "abstain"        → REJECT(llm_abstain)
2. fused_score non fini (NaN, Inf)  → REJECT(invalid_fused_score)
3. fused_score >= threshold         → HUMAN_REVIEW(accepted)
4. sinon                            → REJECT(low_fused_score)

L'abstention LLM est un veto absolu : même un fused_score élevé ne peut
contourner une décision d'abstention. Cette règle garantit que le LLM peut
toujours refuser explicitement de prendre position.

Seuil

Le seuil unique est RELIABILITY_V5_GATE_THRESHOLD (défaut 0.70).

Sortie

GateDecision(
    action: "HUMAN_REVIEW" | "REJECT",
    reason: "accepted" | "low_fused_score" | "llm_abstain" | "invalid_fused_score",
    fused_score: float,
    threshold: float,
    distance: float,   # fused_score - threshold
)

La distance est exportée en histogramme Prometheus
reliability_v5_gate_threshold_distance pour calibrer le seuil au cours du
temps.

Calcul du score par transcription

Une fois toutes les suggestions de la transcription persistées, le score
agrégé est recalculé.

Sous-scores par type

Pour chaque type d'entité présent, un sous-score est calculé à partir des
compteurs (pending, validated, ignored, auto_applied) :

  • Une suggestion pending pénalise le score (incertitude résiduelle).
  • Une suggestion validated neutralise la pénalité (l'utilisateur a tranché).
  • Une suggestion ignored neutralise également (faux positif identifié).
  • Une suggestion auto_applied est traitée comme une correction implicite
    validée.

Score global

Moyenne pondérée des sous-scores présents, avec renormalisation des poids
sur les types effectivement observés dans la transcription :

poids_par_défaut = {place: 0.30, person: 0.25, acronym: 0.15,
                    org:   0.15, typo:   0.15}

Surchargables via RELIABILITY_WEIGHT_PLACES, …_PERSONS, etc.

Plafond CONTEXT_FREE

En mode CONTEXT_FREE, le score global est plafonné à
RELIABILITY_HIGH_CONFIDENCE_THRESHOLD_CONTEXT_FREE (défaut 0.85). Cette
règle traduit le fait qu'en l'absence de contexte canonique, la confiance
maximale légitime ne peut pas atteindre 100 %.

Snapshot idempotent

Le snapshot n'est inséré dans reliability_score_snapshots que si au moins
un compteur ou un sous-score a changé par rapport au snapshot précédent. Cette
règle évite la prolifération de lignes identiques lors de réanalyses.