Prototype de fact-checking en direct des discours TNT. Rien n’est publié sur X. Aucune attribution nominative : on dit « il a été affirmé à telle heure sur telle chaîne », pas « untel a dit ».
L’interface
Quatre pages, toujours accessibles en haut à gauche.
- Salle — écoute en direct, transcript, affirmations.
- Tableau de bord — archives des directs déjà enregistrés.
- Sources — liste blanche prioritaire pour Gemini.
- Prompts — consignes du preflight et du fact-check, éditables.
- Aide — cette page.
Salle de rédaction
C’est le poste de travail. Deux colonnes : le transcript à gauche, les affirmations à droite. Chaque colonne défile toute seule.
Choisir une source
Le menu Source a trois groupes :
- Direct TNT — chaînes live via l’API de Philippe. Après « Écouter », les premiers sous-titres mettent environ 30 secondes. Une seule personne à la fois peut lancer ou couper ce live (voir Direct partagé).
- Émissions sauvegardées — replays des directs déjà captés. Ils ne se réenregistrent pas.
Rien n’est présélectionné au chargement : il faut choisir une source à chaque fois. La sélection n’est conservée que si la liste se reconstruit pendant la session (arrêt, tuner occupé…).
LLM
Gemini fait le preflight et la recherche. Il faut une clé API_GEMINI dans env.local.
Transcript
Chaque ligne est un extrait horodaté. Le flux de Philippe envoie deux pistes :
- CC — sous-titres chaîne (souvent en retard, parfois incomplets).
- STT — transcription audio, en général plus utile pour le fact-check.
Le bouton CC affiche ou masque les sous-titres. Dès qu’un STT arrive, le moteur n’envoie plus le CC au buffer — on évite de fact-checker deux fois la même phrase.
Tu peux sélectionner des extraits consécutifs (glisser, ou Maj+clic) pour forcer un fact-check manuel, même si le preflight n’a rien sorti.
Affirmations
Une carte = une affirmation reformulée, puis le verdict et l’explication. En pied : heure, durée d’analyse, tokens in/out, coût.
- Cliquer une carte (hors boutons et liens) fait défiler le transcript jusqu’aux extraits concernés.
- Valider / Rejeter : le bouton choisi se remplit (vert ou rouge). L’autre reste cliquable si tu changes d’avis.
- Les pastilles en haut à droite comptent les cartes prêtes et celles encore en analyse. Tant que Gemini travaille sans carte, ça reste un simple « en analyse » — sans chiffre qui clignote.
Direct partagé
Le tuner TNT n’accepte qu’une captation à la fois. Sur ce site, le live est donc limité à un poste à la fois : celui qui clique Écouter sur une chaîne TNT prend le siège.
- Ce poste peut lancer, changer de chaîne et Couper.
- Les autres voient un bandeau « Le direct est déjà utilisé » (avec le nom de la chaîne). Ils peuvent suivre le transcript et les affirmations, mais pas lancer ni couper.
- Un replay d’émission sauvegardée reste possible tant que personne n’est en live. Pendant un live, personne d’autre ne peut démarrer un replay non plus (ça couperait la captation).
- Si le poste qui écoute ferme l’onglet ou perd la connexion, le siège se libère après environ 45 secondes. On peut alors relancer.
- Deux onglets du même navigateur comptent comme deux postes.
Tableau de bord
Vue d’ensemble des directs persistés : extraits, affirmations, tokens, coût. Ouvre une émission pour voir les cartes.
Les encarts de verdict (Tous, Vrai, Faux…) filtrent la liste en dessous. Tous est sélectionné par défaut.
Seuls les directs TNT sont enregistrés. Un replay d’archive ne crée pas une nouvelle ligne.
Sources
Catalogue des bases que Gemini doit chercher en priorité : INSEE, OCDE, Eurostat, ministères, Parlement, Cour des comptes, data.gouv.fr, Our World in Data, etc.
- Désactiver une source la retire du prompt, sans l’effacer.
- Les sources du catalogue ne se suppriment pas (sinon elles reviendraient au redémarrage). On les désactive.
- Tu peux en ajouter (presse, think tank, etc.). Celles-là, on peut les supprimer.
Ce n’est pas un mur étanche : si aucune source de la liste ne couvre le sujet, Gemini peut élargir. La presse peut contextualiser, elle ne doit pas être la source principale quand une base institutionnelle existe.
Prompts
Page Prompts : les deux consignes envoyées à Gemini. On peut les lire, les modifier, les enregistrer, ou revenir au texte d’origine.
- Preflight — « y a-t-il une affirmation vérifiable ? » Sans search. Variables :
{channel},{when},{transcript}. - Fact-check — vérifie la phrase reformulée avec Google Search. Variables :
{source_list},{claim},{context},{channel},{when}.
Un prompt enregistré est utilisé dès le prochain appel. Si tu supprimes une variable, l’enregistrement est refusé.
La technique
Moteur Python (FastAPI) dans src/factcheck/. UI locale, SQLite par défaut. L’objectif n’est pas un bus de messages : un SSE suffit pour le live.
Qui fait quoi
- Philippe — tuner TNT, CCExtractor, API de transcription, identification vocale (V1), bot X, hébergement.
- Thierry — ce moteur : buffer, preflight, fact-check, sources, salle de rédaction.
Le contrat V0 : le moteur renvoie un résultat structuré (affirmation, chaîne, heure, verdict, explication, sources, confiance). Plus tard, ça partira vers une URL média ; le bot X publiera. Aujourd’hui, ça s’arrête à l’écran.
Flux TNT
Base : https://beta.leselectronslibres.com
GET /channels— liste des chaînes.GET /transcribe?channel=<id>— SSE monouser. Événementsstart,end,cc_text,stt_text.
Le parseur (ingest.py) tolère plusieurs formes de JSON. Si l’API chaînes est down, l’UI bascule sur une liste figée (BFM, CNEWS, etc.) : le live peut quand même marcher.
Pipeline, de bout en bout
- Extraits SSE → affichage immédiat dans le transcript.
- Buffer local (pas de LLM) — recolle les extraits en fenêtres.
- Preflight —
gemini-3.5-flash-lite, sans search : 0 à 2 affirmations vérifiables, ou rien. - Dédoublonnage local — on jette les paraphrases déjà vues.
- Fact-check —
gemini-3.8-flash+ Google Search, seulement s’il reste une affirmation. - Carte à l’écran, et écriture SQLite si c’est un direct.
Deux appels LLM peuvent tourner en parallèle (sémaphore). Le transcript continue d’arriver pendant que Gemini travaille.
Buffer : recomposer des phrases
Le STT arrive en miettes. On n’envoie pas chaque miette au modèle. TranscriptBuffer agglomère sans inventer de ponctuation :
- minimum 3 extraits (pas 3 phrases dans une seule ligne) ;
- on coupe de préférence en fin de phrase (
. ? ! …) ; - on ne coupe pas si l’extrait commence par une continuation (« mais », « donc », « parce que », « du coup »…) ;
- prêt aussi si le texte dépasse 280 caractères tout en ayant 3 extraits ;
- filets : 720 caractères max, ou 6 secondes sans nouvel extrait ;
- pas de recouvrement entre fenêtres (
overlap = 0).
Ce n’est pas un parseur syntaxique. C’est un regroupement pour que le preflight voie un bout de discours, pas une miette.
Preflight Flash Lite
Avant tout search, gemini-3.5-flash-lite reçoit la fenêtre et répond à : ce texte est-il fact-checkable ?
Il ne doit sortir que des faits objectivement vérifiables : chiffres, dates, comparaisons mesurables. Il écarte opinions, slogans, promesses, questions de journalistes. Chaque affirmation est reformulée en une phrase autonome (pronoms résolus, STT corrigé, 15–30 mots). C’est cette phrase qui sera vérifiée, pas le brut.
Réponse vide = on s’arrête. Coût typique : 0,30 $ / M tokens in, 2,50 $ / M out. Pas de Google Search à cette étape.
Une sélection manuelle contourne le preflight : on envoie le texte choisi tel quel au fact-check.
Fact-check + search
gemini-3.8-flash avec l’outil Google Search. Le prompt lui donne la liste des sources actives et lui demande de les prioriser.
Gemini n’accepte pas JSON structuré et search en même temps. On laisse le modèle parler, on parse, et s’il dérape on relance un petit appel « réécris ça en JSON strict » (sans search).
Les URL du grounding sont fusionnées avec celles du JSON, les domaines de la liste blanche passent devant.
Tarif intro 3.8 Flash (jusqu’au 31/12/2026, thinking inclus) : 0,75 $ / M in, 3,75 $ / M out.
Dédoublonnage
Même chiffre répété dix fois à l’antenne ≠ dix cartes. ClaimMemory compare les phrases :
- égalité après normalisation (accents, casse) ;
- ou Jaccard des mots ≥ 0,68 ;
- ou mêmes chiffres et Jaccard ≥ 0,45.
La mémoire vit le temps de la session. Un nouveau direct repart de zéro.
Verdicts et relecture
Échelle : vrai, plutôt vrai, mixte, plutôt faux, faux, insuffisant.
Si le verdict est insuffisant ou si la confiance est sous 70 %, la carte passe en « à relire ». Valider / Rejeter est humain, local à la session en cours.
Persistance
SQLite dans data/factcheck.db (ou PostgreSQL si DATABASE_URL). Tables : recordings, segments, checks, data_sources.
Un direct ouvert reste ouvert tant que la session n’est pas coupée (ou le process tué). Relancer uvicorn coupe le live en mémoire ; les extraits déjà écrits restent.
Coûts affichés
Chaque carte montre tokens in/out et une estimation en dollars, thinking compris. Le tableau de bord agrège par émission. Le preflight Lite est beaucoup moins cher que le Flash+search : d’où le filtre avant d’ouvrir le grounding.
Limites de la V0
- Pas de publication X, pas de nom de locuteur.
- Le CC TNT a souvent ~10 s de retard ; le STT n’est pas parfait.
- Le search grounding n’est pas une preuve. Un LLM peut mal lire une source ou inventer une nuance.
- Une seule captation live à la fois (voir Direct partagé).
- Relancer le serveur = perdre la session courante (pas l’archive déjà écrite).
La V1 prévue côté Philippe : reconnaissance d’une dizaine de voix politiques, seulement si la confiance est suffisante. Les autres restent anonymes.