CLAIR DocsCLAIR Docs
  • clair.vote
  • GitHub
  • Nous soutenir
Nous soutenir
  • Accueil
  • Plateforme & Guide
  • Données & Base de Données
  • Explorateur API
CLAIR

Plateforme citoyenne de transparence politique. Données 100% publiques, 0% d'opinion.

Explorer

  • Députés
  • Sénateurs
  • Groupes politiques
  • Sujets
  • Dossiers législatifs
  • Scrutins
  • Agenda
  • Lobbying

Ressources

  • Nous soutenir
  • À propos
  • Méthodologie
  • Contact
  • Comprendre
  • Guide pratique
  • API ouverte

Légal

  • Mentions légales
  • Confidentialité
  • CGU

© 2026 CLAIR. Sources : Assemblée nationale · Sénat · HATVP · Ministère de l'Intérieur · DILA

Vue d'ensembleCommandes CLIEnrichissement IAModèle PrismaOrchestrateur d'IngestionQualité des DonnéesSchémas ERDSources Open DataSuivi des Modifications
Données & Base de Données

Qualité des Données & Contrôles d'Intégrité

Trois garde-fous vérifient la donnée. Ils sont à lancer après toute modification du code d'ingestion, de rattachement ou d'enrichissement, avant de fusionner :

TerminalCode
pnpm --filter @clair/ingestion test # tests unitaires (Vitest) pnpm --filter @clair/ingestion check-quality # contrôles de la base pnpm --filter @clair/ingestion exec tsx src/cli.ts check-ia-quality # contrôles des résumés IA

Les deux check-* interrogent la base pointée par DATABASE_URL et sortent avec le code 1 en cas d'échec. Ne pas les faire passer dans un pipe (| tail, | grep) : le code de sortie serait celui de la dernière commande.


🛡️ check-quality

Code : services/ingestion/src/checks/data-quality.ts. Le contrôle réussit si tous les invariants et tous les seuils passent.

Invariants (valeur attendue : 0)

Contrôle
Votes sans scrutin valide
Scrutins sans aucun vote (hors Congrès, voir plus bas)
Doublons de scrutins (numéro + chambre + session)
Doublons d'amendements (uid)
Amendements en double sous une même clé canonique
Prises de parole du Sénat empilées sur un même rang de séance
Tables sans updated_at tenu par la base (colonne ou trigger manquant, voir Suivi des modifications)
Liaisons qui ne datent pas leur parent (cosignataires, amendement ↔ scrutin, secteurs, participants)
Parlementaires actifs sans groupe
Personnes à la fois député et sénateur en cours de mandat
Fiches portant des mandats de l'autre chambre sans son identifiant (homonymes fusionnés : separer-homonyme ; vrai changement de chambre : lier-identifiant)
Mandats d'organe AN ouverts sans mandat de député ouvert
Liens scrutin → dossier entre chambres différentes
Liens scrutin → dossier entre législatures différentes (AN)
Scrutins rattachés à une procédure sans vote : rapport ou mission d'information, ou 49.3 hors motion de censure
Scrutins AN rattachés à un autre dossier que celui qui les référence (voteRefs)
Motions de censure rattachées à un dossier qui ne les référence pas
Liens scrutin ↔ amendement entre législatures différentes (AN)
Scrutins AN liés à un amendement déposé après le vote
Prises de parole rattachées au dossier d'une autre législature (AN)
Amendements rattachés au dossier d'une autre législature (AN)
Amendements AN rattachés à un autre dossier que celui de leur texte
Sous-amendements AN rattachés à un amendement d'un autre texte
Amendements placés dans une discussion sans identifiant de discussion
Interventions dont la date contredit celle de leur séance
Scrutins du Sénat dont la date porte un décalage de fuseau
Résumés IA contredisant le statut du sujet
Événements à date imprécise dont le mois est passé
Candidatures sans nom
Circonscriptions aux listes de tailles inégales (art. L. 300 du code électoral)
Ruptures d'alternance femme-homme sur les listes (art. L. 300)
Binômes titulaire-suppléant de même sexe (art. L. 299)
Sortants déclarés par le fichier du ministère mais non rattachés à une personne
Index LEGI jamais chargé, ou pas vérifié contre les archives de la DILA depuis plus de 2 jours
Amendements avec dispositif, en base depuis plus de 2 jours, dont les renvois au droit en vigueur n'ont pas été calculés par la version courante de la détection
Gouvernements qui se chevauchent au-delà du jour de passation
Écart à un seul gouvernement en cours
Postes ministériels hors des dates de leur gouvernement (un sortant peut finir au plus tard le jour où commence le gouvernement suivant : il expédie les affaires courantes)
Postes ministériels qui finissent avant de commencer
Gouvernements sans exactement un Premier ministre (poste de rang protocolaire 1) à leurs dates
Membres du gouvernement sans poste
Membres du gouvernement rattachés à une fiche qui ne leur correspond plus (identifiant AN ou état civil)
Fonctions gouvernementales encore dans mandats (elles viennent de postes_ministeriels et presidences)
Réponses de membres du Gouvernement typées « question » (elles entreraient dans le compteur de questions posées de leur fiche)
Votes exprimés pendant un poste ministériel (un membre du Gouvernement ne vote pas : un tel vote trahit une fiche mal liée ou des dates de poste fausses)
Présidences qui se chevauchent au-delà du jour de passation
Écart à une seule présidence en cours
Gouvernements qui ne commencent pendant aucune présidence
Postes ministériels sans domaine : un intitulé nouveau, à ajouter à domaines-ministeriels.ts
Présidences de l'Assemblée qui se chevauchent au-delà du jour de passation
Écart à une seule présidence de l'Assemblée en cours

Le Congrès n'a pas de votes nominatifs, c'est normal. Les scrutins du Congrès (députés et sénateurs réunis à Versailles) sont publiés par l'AN sous un uid VTCG… (CG pour Congrès, AN pour Assemblée) avec leurs totaux, mais sans aucune liste de votants. Ce n'est ni une perte à l'ingestion ni un scrutin cassé : il n'y a rien à réparer. Ces scrutins sont donc exclus de l'invariant « scrutins sans aucun vote » (filtre sur source_url contenant /scrutins/VTCG), ce qui ne peut masquer aucun scrutin de l'Assemblée ni du Sénat. Au 7 octobre 2026, la base en compte un seul : VTCGR5L16V1, la révision constitutionnelle sur l'IVG du 4 mars 2024 (780 pour, 72 contre).

Seuils (valeur minimale)

ContrôleMinimum
Parlementaires (toutes les fiches, en exercice ou non)900
Groupes politiques15
Scrutins5 000
Votes1 000 000
Amendements150 000
Interventions500 000
Liens débat → scrutin (Assemblée)130 000
Scrutins de l'Assemblée dont on montre le débat11 500
Scrutins du Sénat dont on montre le débat700
Explications de vote de l'Assemblée1 800
Interventions rattachées à un amendement (Assemblée)40 000
Lobbyistes3 500
Actions de lobbying400
Dossiers législatifs6 000
Liens amendement ↔ scrutin4 000
Taux de liaison scrutins → amendements, AN (scrutins de la législature courante dont le titre mentionne un amendement)80 %
Taux de liaison scrutins → amendements, Sénat (mêmes scrutins, sessions 2024 et suivantes)45 %
Taux de liaison scrutins → dossiers (Sénat, et AN de la législature courante)90 %
Taux de liaison amendements → dossiers40 %
Amendements du Sénat avec article visé90 %
Amendements de l'AN datés par la source (chronotag)99 %
Sous-amendements de l'AN dont le parent est retrouvé99 %
Commissions et organes2 900
Commissions et organes de l'Assemblée2 800
Commissions et organes du Sénat35
Président du Sénat en fonction (exactement 1)1
Scrutins solennels du Sénat (votes personnels déduits des délégations)30
Concordance des commissions du Sénat en cours avec l'annuaire du jour97 %
Réunions6 000
Taux de liaison réunions → commissions (AN)85 %
Index LEGI : versions de textes140 000
Index LEGI : versions d'articles680 000
Amendements calculés dont le dispositif cite au moins un texte ou article lié25 %
Gouvernements (depuis 2007)17
Postes ministériels766
Membres du gouvernement311
Membres du gouvernement rattachés à leur fiche145
Présidences de la République (depuis 2007)3
Présidences de l'Assemblée (depuis 2007)7

Le périmètre des taux de liaison au Sénat commence à la session 2024, parce que les amendements des sessions antérieures ne sont pas ingérés : les compter mesurerait le périmètre de l'ingestion, pas la qualité du rattachement.

Rapports informatifs (sans effet sur le code de sortie)

  • Intégrité des scrutins multi-amendements : pour chaque scrutin portant sur des « amendements identiques », les numéros extraits du titre sont comparés aux amendements rattachés (manquants, en trop).
  • Liens des sujets : couverture des liens « construction » et « contexte », et vérification HTTP d'un échantillon de 10 liens tirés au hasard (délai de 5 s ; une erreur réseau compte comme « non vérifié », pas comme un lien mort).

🤖 check-ia-quality

Code : services/ingestion/src/checks/ia-quality.ts. Il relit le champ enjeux des sujets et le résumé resume_ia des dossiers, et les confronte aux votes réels des groupes cités.

Défaut détectéSeuil maximal
Inversion pour/contre : un groupe décrit comme favorable alors qu'il a voté contre, ou l'inverse35
Abstention décrite comme un vote pour ou contre45
Position prêtée à un groupe qui n'a aucun vote sur l'entité20

Le signal « groupe pesant dans les votes mais jamais mentionné » est calculé et affiché, sans effet sur le résultat.

Les votes sont comptés sous le groupe du mandat à la date du scrutin, comme dans les prompts : à l'Assemblée, le mandat de la législature du scrutin ; au Sénat, le mandat en cours à sa date. Le générateur et le contrôle partagent le même fragment SQL (services/ingestion/src/utils/groupe-epoque.ts) : un résumé de 2018 qui cite LAREM ou LR est jugé sur les groupes de 2018, pas sur ceux d'aujourd'hui.

Les sigles successifs d'une même famille politique (FI, LFI-NUPES et LFI-NFP ; LR, UMP et DR…) sont rapprochés par leurs alias communs. LREM, le groupe du Sénat devenu RDPI, est rattaché à LAREM, car le modèle écrit « LREM » pour le groupe de l'Assemblée.

Les votes sont aussi comptés chambre par chambre, car un même sigle ou un même nom désigne deux groupes distincts : « SOC » est le groupe socialiste de l'Assemblée et celui du Sénat, « Les Républicains » le groupe LR de l'Assemblée et le groupe UMP du Sénat. Une phrase qui nomme une chambre est comparée aux votes de cette chambre, et une phrase qui nomme les deux est contrôlée dans les deux. Une phrase qui n'en nomme aucune, alors que le groupe désigné a voté dans le sens décrit dans l'une des deux, est ambiguë mais pas fausse : elle n'est pas comptée. L'intitulé d'un seul des deux groupes (« Socialistes et apparentés ») ou un sigle cité tel quel (« SOC-A ») n'est pas ambigu.

Les règles du contrôle suivent celles du prompt :

  • un groupe ne s'abstient majoritairement que si ses abstentions dépassent ses voix exprimées. À égalité, le prompt l'annonce favorable ou opposé, et le contrôle ne reproche pas au modèle de l'avoir écrit ;
  • un groupe cité comme l'exception d'une phrase (« tous les groupes, à l'exception du CRC, ont voté pour ») ne se voit pas prêter la position des autres ;
  • le passif décrit le sort d'un objet, pas le vote d'un groupe : « un amendement du groupe SOC a été rejeté » n'est pas une opposition du groupe.

La détection des inversions repose sur des expressions régulières appliquées au voisinage de chaque nom de groupe. Elle produit des faux positifs, et ses seuils sont documentés dans le code comme des dettes à ramener vers zéro plutôt que comme des tolérances.


🧪 Tests unitaires

Les tests de l'ingestion (*.test.ts à côté des fichiers testés) couvrent notamment les parsers de sources (syceron, comptes rendus de commission, avis de commission, agenda et vidéos du Sénat, candidatures), les rattachements (débats ↔ scrutins, amendement ↔ scrutin, article ↔ scrutin), la nature des scrutins, les prompts IA et la configuration des contrôles de qualité. L'API a ses propres tests Vitest dans apps/api/src. Le frontend n'en a qu'un, celui du proxy /api/v1 (app/api/v1/[...path]/route.test.ts) : chemins relayés vers l'API, chemins refusés.

La CI (GitHub Actions) lance le lint, les tests et le build à chaque push et pull request sur master. Elle ne lance ni check-quality ni check-ia-quality, qui ont besoin d'une base remplie.

Orchestrateur d'IngestionSchémas ERD
On this page
  • 🛡️ check-quality
    • Invariants (valeur attendue : 0)
    • Seuils (valeur minimale)
    • Rapports informatifs (sans effet sur le code de sortie)
  • 🤖 check-ia-quality
  • 🧪 Tests unitaires