Aller au contenu

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

flowchart TD A[Event reliability.analysis.requested] --> B[Acquisition du lock] B --> C[Chargement transcription + segments] C --> D[Étape 1 : Extraction des candidats] D --> E[Étape 2 : Récupération des entités canoniques] E --> F[Étape 3 : Détermination du mode] F --> G[Étape 4 : Enrichissement RAG des candidats] G --> H{Étape 5 : Gating} H -->|match canonique fort| Z[Skip LLM] H -->|rejets historiques| Z H -->|sinon| I[Étape 6 : Raisonnement LLM contraint] I --> J["Étape 7 : Validation géographique
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, …) : un LOC
    ou ORG précédé d'un honorifique strict est retypé en person.
  • 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 :

  1. llm_decision == "abstain"REJECT(llm_abstain) — veto absolu.
  2. fused_score non fini (NaN / Inf) → REJECT(invalid_fused_score).
  3. fused_score >= RELIABILITY_V5_GATE_THRESHOLD
    HUMAN_REVIEW(accepted).
  4. 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 :

|segment_ref|start_pos|end_pos|normalized_original|normalized_suggested|entity_type|

Le statut initial dépend de la confiance :

  • confidence >= RELIABILITY_HIGH_CONFIDENCE_THRESHOLD (ou …CONTEXT_FREE si
    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 est CONTEXT_FREE,
  • INSERT dans reliability_score_snapshots uniquement si les compteurs ou
    les scores ont changé par rapport au dernier snapshot (idempotence).