Pipeline de traitement¶
Cette page décrit les étapes du pipeline, dans l'ordre d'exécution, depuis
la réception d'un événement jusqu'à la persistance d'une suggestion.
Diagramme global¶
type=place uniquement"] J --> K[Étape 8 : Scoring multi-signal] K --> L[Étape 9 : Decision Gate] L -->|HUMAN_REVIEW| M[Étape 10 : Validation déterministe] L -->|REJECT| Z M --> N[Étape 11 : Persistance idempotente] N --> O[Étape 12 : Recalcul du score transcription]
Étape 1 — Extraction des candidats¶
Module : EntityExtractor (src/services/entity_extractor.py).
Objectif : générer la liste exhaustive des candidats d'entités sans aucune
décision sémantique. Le composant est volontairement un générateur pur.
Détecteurs en cascade, anti-chevauchement :
| Type émis | Détecteur | Technologie | Confiance par défaut |
|---|---|---|---|
place, person, org |
NER | spaCy modèle fr_core_news_lg |
0.80 (0.92 si un honorifique précède un person) |
acronym |
Regex contiguës et pointées | Expressions régulières strictes | 0.70 |
typo |
Spell-checker FR | pyspellchecker distance ≤ 2 |
0.55 |
Filtres transverses :
- Liste noire (
reliability_blacklist) appliquée à la source. - Déduplication intra-transcription par empreinte SHA-256 de
(valeur normalisée, type). - Anti-chevauchement spaCy : si deux entités de types différents se recouvrent,
la plus longue est conservée (prioritéPER>ORG>LOC/GPE). - Honorifiques stricts (
M,Mme,Dr,Pr,Me,Maître, …) : unLOC
ouORGprécédé d'un honorifique strict est retypé enperson. - Filtres anti-bruit sur les typos : longueur ≥ 4, fréquence ≤ 10 dans la
transcription, exclusion des tokens déjà capturés par NER ou regex, exclusion
des préfixes d'élision française (d',qu',l', …).
Sortie : List[CandidateEntity] triée par (segment_index, start_pos).
Étape 2 — Récupération des entités canoniques¶
Module : ReliabilityRagEnricher.fetch_canonical_entities.
Objectif : construire un dictionnaire des entités déjà connues pour la
transcription depuis les chunks Qdrant produits par l'enrichissement.
Sortie :
{
"persons": List[str],
"orgs": List[str],
"places": List[str],
"acronyms": List[str],
"keywords": List[str],
}
Chaque liste est plafonnée à RELIABILITY_RAG_CANONICAL_MAX_ITEMS éléments et
triée par fréquence décroissante.
Étape 3 — Détermination du mode¶
Module : ReliabilityService._determine_mode.
Le mode est calculé une seule fois par transcription, à partir du dictionnaire
canonique.
| Règle (mode strict) | Détail |
|---|---|
total >= MIN_TOTAL |
Somme des entités canoniques toutes catégories confondues. |
non_empty >= MIN_DIVERSITY |
Nombre de catégories non vides. |
Si les deux conditions sont satisfaites → RAG_ENHANCED, sinon CONTEXT_FREE.
Les valeurs MIN_TOTAL et MIN_DIVERSITY sont configurables (voir
Configuration).
Étape 4 — Enrichissement RAG des candidats¶
Module : ReliabilityRagEnricher.enrich_candidates.
Pour chaque candidat, applique un bonus de confiance basé sur la similarité
floue (token_set_ratio via rapidfuzz) entre la valeur originale et les
tokens fréquents extraits des chunks RAG de la transcription.
Formule :
bonus = (score_rapidfuzz - FUZZY_THRESHOLD) / (100 - FUZZY_THRESHOLD)
* (MAX_BONUS - 0.05)
nouvelle_confiance = min(1.0, ancienne_confiance + bonus)
Le bonus n'est appliqué qu'au-dessus du seuil FUZZY_THRESHOLD (par défaut 85).
Étape 5 — Gating¶
Avant tout appel LLM, deux gates court-circuitent les candidats
manifestement inutiles.
Gate S2 — Correspondance canonique forte¶
Si la valeur normalisée du candidat est strictement présente dans la liste
canonique de son type (persons, orgs, …), aucune correction n'est nécessaire
et l'appel LLM est sauté.
Gate S1 — Rejets historiques¶
Pour le couple (user_id, type, valeur_normalisée), un compteur de rejets est
maintenu dans un hash Redis reliability:identity_rejections:{user_id}. Si ce
compteur dépasse RELIABILITY_S1_MIN_REJECTS, le LLM est sauté.
Le TTL du hash est rafraîchi à chaque écriture (RELIABILITY_S1_TTL_DAYS).
Étape 6 — Raisonnement LLM contraint¶
Module : ConstrainedLlmReasoner.
Le LLM reçoit en entrée :
- la valeur originale du candidat et son type,
- la fenêtre locale (± 2 segments autour du segment courant),
- la liste canonique du type concerné (plafonnée à
RELIABILITY_V5_CANONICAL_LIST_CAP), - en mode
RAG_ENHANCED, des instructions privilégiant la liste canonique
comme source de vérité.
Le LLM est obligé de répondre selon le schéma JSON strict suivant :
{
"decision": "correct" | "abstain",
"suggested_value": "string ou null",
"confidence": 0.0,
"reason": "string"
}
Décisions et codes :
| Décision | Sens |
|---|---|
correct |
Le LLM propose une correction avec un niveau de confiance. |
abstain |
Refus explicite ; raison structurée parmi insufficient_evidence, json_schema_violation, conflict. |
Provider ordering : RELIABILITY_LLM_PRIMARY (défaut qwen), fallback
automatique sur RELIABILITY_LLM_FALLBACK (défaut openai). L'inversion est
purement opérationnelle, aucun changement de code requis.
Étape 7 — Validation géographique¶
Pour les candidats place uniquement, le sous-pipeline géographique est invoqué
en parallèle du raisonnement LLM. Le détail est documenté dans
Validation géographique.
Sortie : GeoResult contenant un geo_score ∈ [0, 1], optionnellement enrichi
d'un identifiant Wikidata.
Étape 8 — Scoring multi-signal¶
Module : MultiSignalScorer.
Fusion pondérée par type d'entité de trois signaux :
| Signal | Source | Disponibilité |
|---|---|---|
rag |
Similarité fuzzy du candidat dans le contexte RAG | Toujours (peut être 0) |
geo |
Score Photon + ranking composite | Uniquement type place |
llm |
Confiance du raisonneur contraint | Sauf si décision = abstain |
Voir Scoring & décision pour la pondération
exhaustive et la pénalité de variance.
Étape 9 — Decision Gate¶
Module : DecisionGate.
Décision binaire selon l'ordre de priorité strict :
llm_decision == "abstain"→REJECT(llm_abstain)— veto absolu.fused_scorenon fini (NaN / Inf) →REJECT(invalid_fused_score).fused_score >= RELIABILITY_V5_GATE_THRESHOLD→
HUMAN_REVIEW(accepted).- sinon →
REJECT(low_fused_score).
Une décision HUMAN_REVIEW conduit à la persistance ; une décision REJECT
est journalisée pour audit mais aucune suggestion n'est créée.
Étape 10 — Validation déterministe¶
Module : ReliabilityValidator.
Dernière ligne de défense avant écriture. Une chaîne de règles ordonnée filtre
les suggestions résiduelles :
| Règle | Vérifie |
|---|---|
TypeKnownRule |
Type ∈ {place, person, org, acronym, typo}. |
IdentityRule |
La valeur corrigée diffère bien de l'originale. |
BlacklistRule |
La valeur corrigée n'est pas dans la liste noire. |
ConfidenceMinRule |
Confiance ≥ seuil propre au mode (0.75 en RAG_ENHANCED, 0.55 en CONTEXT_FREE). |
LevenshteinRule |
Distance d'édition raisonnable selon le type (Jaro-Winkler ou ratio). |
AcronymInitialsRule (type acronym seulement) |
Initiales de la forme étendue ≈ acronyme original. |
TypoLevenshteinRule (type typo seulement) |
La correction est dans la liste de candidats du spell-checker ou dans les entités canoniques. |
Toute règle qui rejette produit un compteur Prometheus
suggestions_filtered_by_{rule_name} et la suggestion est abandonnée.
Étape 11 — Persistance idempotente¶
Module : ReliabilityService._upsert_nonplace_suggestion.
Une suggestion validée est insérée ou mise à jour dans la table
reliability_suggestions via un UPSERT sur la contrainte unique
(transcript_id, dedupe_key, analysis_version).
La dedupe_key est une empreinte SHA-256 de la signature stable de la
suggestion :
Le statut initial dépend de la confiance :
confidence >= RELIABILITY_HIGH_CONFIDENCE_THRESHOLD(ou…CONTEXT_FREEsi
applicable) →auto_applied.- sinon →
pending.
L'evidence_json contient l'audit trail complet : signaux, poids, seuil,
décision LLM, identifiant Wikidata éventuel.
Étape 12 — Recalcul du score transcription¶
Module : ReliabilityService.recalc_transcript_score.
Agrégation par type et par statut, puis :
- calcul des sous-scores par type :
_calculate_score_from_counts(pending, validated, ignored, auto_applied), - calcul du score global comme moyenne pondérée des sous-scores présents
(poids configurables, renormalisés sur les types présents), - application du plafond
RELIABILITY_HIGH_CONFIDENCE_THRESHOLD_CONTEXT_FREE
(85 % par défaut) si le mode estCONTEXT_FREE, - INSERT dans
reliability_score_snapshotsuniquement si les compteurs ou
les scores ont changé par rapport au dernier snapshot (idempotence).