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

Modèle de Données Prisma

La base est une base PostgreSQL 17 décrite par un seul fichier : apps/api/prisma/schema.prisma. C'est la source de vérité ; cette page en donne une lecture d'ensemble. Les diagrammes sont sur la page Schémas ERD.

Le schéma compte 47 modèles, soit 47 tables, plus 2 tables de jointure créées implicitement par Prisma. Il n'y a ni comptes utilisateurs, ni tables d'authentification. La base de production ne contient aucune autre table que celles-ci, hors la table _prisma_migrations de Prisma (vérifié le 21 octobre 2026).


📐 Conventions

  • Noms : les modèles et champs Prisma sont en camelCase (DossierLegislatif.dateDepot) ; tables et colonnes SQL en snake_case (dossiers_legislatifs.date_depot), par @map et @@map.
  • Clés primaires : UUID stocké en text (@default(uuid())), sauf cible_types et action_descriptions (entier auto-incrémenté), secteurs (slug) et intervention_scrutin (clé composée).
  • Pas d'enum SQL : les valeurs fermées (chambre, position, sort, type…) sont des text validés par le code ; les valeurs admises sont listées en commentaire dans le schéma.
  • Chambre : chambre vaut 'assemblee' ou 'senat' sur toutes les tables qui la portent.
  • Législature : 15, 16 ou 17 pour l'Assemblée. Pour le Sénat, qui n'a pas de législature, la colonne vaut NULL sur scrutins, groupes_politiques et mandats_parlementaires (la période est portée par session ou mandature), et 0 sur dossiers_legislatifs et amendements, où elle est obligatoire.
  • Données brutes : source_data (JSON) conserve l'enregistrement reçu de la source sur parlementaires, mandats_parlementaires, dossiers_legislatifs, scrutins et lobbyistes.
  • Horodatage : toutes les tables portent created_at et updated_at (sauf les deux tables de jointure implicites). updated_at est tenu par la base, par un trigger, et ne bouge que si le contenu de la ligne change : une réécriture à l'identique le laisse en place. Voir Suivi des modifications.
  • Empreintes : scrutins.source_hash et amendements.source_hash résument ce que la synchronisation écrit ; une ligne dont l'empreinte n'a pas changé n'est pas réécrite. ia_content_hash joue le même rôle pour l'IA (voir Enrichissement IA).

💡 Points de modélisation à connaître

1. Une personne, plusieurs mandats

Il n'y a pas de table « députés » ni « sénateurs » : parlementaires contient des personnes, et mandats_parlementaires leurs sièges (une ligne par législature à l'Assemblée, par mandature au Sénat). Une personne peut avoir été députée puis sénatrice.

2. Changement de chambre

parlementaires.chambre et parlementaires.source_id désignent la chambre de la fiche et son identifiant dans cette chambre (PA… à l'Assemblée, matricule au Sénat). Quand un député est élu sénateur, sa fiche est conservée et passe au Sénat avec son matricule ; son identifiant de l'Assemblée est gardé dans parlementaires_identifiants, pour que les données de l'Assemblée publiées après son départ retrouvent leur auteur.

3. Le groupe politique appartient au mandat, pas à la personne

Le groupe, la circonscription, la commission permanente et la série sénatoriale d'une période sont sur mandats_parlementaires. parlementaires.groupe_id ne porte que le groupe actuel : l'utiliser pour un vote passé l'attribuerait au mauvais groupe. Pour situer un vote, prendre le mandat de la même législature que le scrutin (Assemblée) ou dont la période contient sa date (Sénat).

4. Deux tables de mandats distinctes

mandats_parlementaires est le siège de parlementaire. mandats contient les fonctions dans des organes (commission, délégation, groupe d'études, mission…), avec la qualité (membre, président, rapporteur…).

Les fonctions gouvernementales n'y sont pas : elles ont leurs propres tables (point 6). Les mandats GOUVERNEMENT et PRESREP de la source sont faux (appartenances jamais closes, présidence rattachée à l'organe d'un prédécesseur). Seules les missions confiées à un parlementaire (MINISTERE, qualité « en mission ») restent dans mandats.

5. Groupes politiques

À l'Assemblée, un groupe a une ligne par législature. Au Sénat, une ligne par sigle, valable dans le temps (legislature à NULL). nom contient le sigle tel que la source le code, figé à travers les renommages (UMP pour Les Républicains au Sénat) : c'est une clé de rapprochement, pas un libellé. Le libellé d'usage est nom_court ; l'API renvoie nom_court à la place de nom quand il existe.

6. Gouvernements et présidence

gouvernements, postes_ministeriels, membres_gouvernement et presidences viennent de l'archive AMO30 de l'Assemblée (organes GOUVERNEMENT, MINISTERE, PRESREP), depuis 2007. Un poste est un portefeuille tenu par une personne dans un gouvernement, entre deux dates : une personne peut en tenir plusieurs, successifs ou simultanés. Le Premier ministre est le poste de rang protocolaire 1. Les dates sont des dates civiles (DATE), telles que la source les publie : un ministre sortant peut finir le lendemain de la fin de son gouvernement, il expédie les affaires courantes.

Un membre du gouvernement n'est pas un parlementaire : membres_gouvernement.parlementaire_id le rattache à sa fiche quand elle existe, par son identifiant AN ou, pour un sénateur, par son état civil (liaison dit comment). Ce rattachement est recalculé à chaque synchronisation.

presidences_assemblee porte les présidences de l'Assemblée (mandats BUREAU « Président » de l'AMO30, depuis 2007), rattachées à la fiche par l'identifiant AN : l'AMO10 ne date que celle de la législature en cours. Celles du Sénat sont des mandats du bureau dans mandats (SENBUR-…). Ces périodes, comme les postes ministériels, sortent de la présence de la personne : un membre du Gouvernement ne vote pas, un président d'assemblée seulement par exception.

7. Statistiques pré-calculées

Elles sont calculées par le batch nocturne, jamais par l'API. La position majoritaire d'un groupe sur un scrutin n'existe que si une seule position est en tête : en cas d'égalité, le scrutin ne compte ni pour la loyauté (il sort du dénominateur), ni pour les alliances, ni pour les thématiques.

  • parlementaires.stats_* : mandat en cours (sert aux listes et classements) ;
  • parlementaires.stats_carriere_* : tous mandats cumulés ;
  • mandats_parlementaires.stats_* : par mandat ;
  • groupes_politiques.stats_*, groupes_alliances, groupes_thematiques : par groupe.

8. Scrutins, votes et amendements

Un scrutin peut porter sur plusieurs amendements et un amendement sur plusieurs scrutins : table implicite _AmendementToScrutin (A = amendement, B = scrutin). Les cosignataires sont dans _AmendementCosignataires (A = amendement, B = parlementaire). numero n'identifie un scrutin qu'avec chambre et session (contrainte unique numero, chambre, session). Un vote (votes.position) vaut pour, contre, abstention ou absent.

9. Sous-amendements et discussions

Un sous-amendement modifie un amendement, pas le texte : amendements.amendement_parent_id pointe vers l'amendement qu'il modifie, et amendement_parent_ref garde la référence lue à la source. Les amendements discutés ensemble en séance partagent un id_discussion_commune, les amendements identiques un id_discussion_identique, chacun unique au sein d'une chambre seulement.

ChampAssembléeSénat
Parent d'un sous-amendementamendementParentRef de l'archiveamdperid du dump AMELI
Discussions communes et identiquesdiscussionCommune, discussionIdentique, avec la place (Premier, Milieu, Dernier)discomid et ideid d'AMELI, sans place
Alinéalibellé et numéro de l'archivelibellé : colonne « Alinéa » du CSV ; numéro : AMELI
chronotag, tri_amendement, seance_discussion_refarchivenon publiés

Au Sénat, chaque colonne n'a qu'un écrivain (le CSV pour le libellé de l'alinéa, la passe AMELI pour le reste) : deux sources qui se disputeraient une colonne la réécriraient chaque nuit, et updated_at bougerait sans changement.

10. Interventions

interventions contient les prises de parole en séance (reunion_id à NULL) et en commission (reunion_id renseigné). parlementaire_id est vide pour les orateurs non parlementaires (ministres…), dont le nom reste dans orateur_nom. intervention_scrutin relie une prise de parole au scrutin qu'elle précède, avec la méthode du rattachement dans via : amendement et article dans les deux chambres ; ensemble (explications de vote), motion et finances (fascicule budgétaire) au Sénat ; fenetre (ce qui a été dit entre deux votes) à l'Assemblée.

11. Sujets

Un sujet regroupe les dossiers de l'Assemblée et du Sénat qui portent sur le même texte (dossiers_legislatifs.sujet_id).

12. Élections sénatoriales

Chaque ingestion aligne les candidatures sur le fichier du ministère : une liste se reconnaît à son source_uid, une candidature à sa liste, son rôle et son rang, et toutes deux gardent leur id d'une ingestion à l'autre. Les tables de résultats ne s'y rattachent pourtant pas par clé étrangère, mais par des identifiants de la source, pour ne pas dépendre de la vie des lignes de candidature :

  • resultats_listes (voix, sièges, élu) par liste_source_uid = candidatures_listes.source_uid ;
  • candidats_mandats_locaux par l'identité du candidat (scrutin, nom, prenom, date_naissance).

resultats_tours porte la participation par circonscription et par tour. L'absence de ligne signifie « pas encore publié », jamais « zéro ». Les colonnes candidatures.elu et candidatures.tour_election ne sont pas alimentées (elles valent false et NULL sur toutes les lignes) : les élus se lisent dans resultats_listes.elu (majoritaire) et resultats_listes.sieges (proportionnel).


📊 Catalogue des tables

Volumétrie mesurée en production le 21 octobre 2026.

Parlementaires et groupes

ModèleTableLignesContenu
Parlementaireparlementaires2 194Personnes, rangées par la chambre de leur fiche : 1 164 à l'Assemblée (dont 574 en exercice), 1 030 au Sénat (dont 348 en exercice). Identité, stats pré-calculées, fiche IA
ParlementaireIdentifiantparlementaires_identifiants9Identifiant conservé dans la chambre quittée (9 députés devenus sénateurs)
MandatParlementairemandats_parlementaires3 563Sièges : AN 15e (659), 16e (618), 17e (612) ; Sénat, mandatures 1998 à 2026 (178 sièges pour 2026)
GroupePolitiquegroupes_politiques47Groupes de l'Assemblée, une ligne par législature (13 en 15e, 11 en 16e, 12 en 17e), et du Sénat (11)
Circonscriptioncirconscriptions686577 circonscriptions législatives, 109 sénatoriales
Mandatmandats47 507Fonctions dans les organes
GroupeAlliancegroupes_alliances506Taux d'accord entre deux groupes
GroupeThematiquegroupes_thematiques752Position moyenne d'un groupe par thème
DeclarationHATVPdeclarations_hatvp4 943Déclarations d'intérêts et de patrimoine (liens vers la HATVP)

Gouvernements et présidence

Volumétrie de l'archive AMO30 du 21 octobre 2026.

ModèleTableLignesContenu
Gouvernementgouvernements17De Fillon I (mai 2007) à Lecornu II
PosteMinisterielpostes_ministeriels766Portefeuille tenu par une personne dans un gouvernement, avec ses dates (missions exclues)
MembreGouvernementmembres_gouvernement311Personnes ayant tenu au moins un poste ; 145 rattachées à leur fiche parlementaire
Presidencepresidences3Présidences de la République depuis 2007 (un seul organe pour les deux mandats d'Emmanuel Macron)
PresidenceAssembleepresidences_assemblee7Présidences de l'Assemblée depuis 2007

Travail législatif

ModèleTableLignesContenu
DossierLegislatifdossiers_legislatifs21 508Dossiers : AN 15e (4 982), 16e (2 794), 17e (3 149), législatures 8 à 14 (44), Sénat (10 539)
Scrutinscrutins22 215Scrutins publics, voir la couverture ci-dessous
Votevotes3 929 249Vote nominatif d'un parlementaire à un scrutin
Amendementamendements683 722Amendements : AN 15e (311 934), 16e (163 789), 17e (140 865), Sénat (67 134)
(implicite)_AmendementToScrutin14 607Liens scrutin ↔ amendement
(implicite)_AmendementCosignataires9 524 231Cosignatures
Interventioninterventions1 435 955Prises de parole, voir la couverture ci-dessous
InterventionScrutinintervention_scrutin350 202Liens prise de parole → scrutin
TexteArticletextes_articles5 185Texte d'un article dans une version donnée d'un texte (AN)
Sujetsujets1 785Regroupements de dossiers AN + Sénat, résumés IA
SujetLiensujet_liens4 709Liens sortants d'un sujet : documents officiels, vie-publique.fr, Wikipédia
LegiTextelegi_textes154 473Index de la base LEGI de la DILA : versions des textes cités comme du droit (codes, lois, ordonnances, décrets, arrêtés, Constitution…), avec leur titre et leurs dates. Compté au premier chargement en production, le 31 octobre 2026
LegiArticlelegi_articles734 625Index de la base LEGI : versions des articles de ces textes (hors arrêtés et circulaires) encore en vigueur en 2017 ou après. Identifiant Légifrance, numéro, dates ; aucun contenu. Compté au premier chargement en production, le 31 octobre 2026
AmendementRenvoisamendements_renvois531 547Renvois au droit en vigueur repérés dans le dispositif d'un amendement : positions dans le texte, article (LEGIARTI) ou texte (CID) visé dans l'index LEGI. Une ligne par amendement calculé, même sans renvoi ; 31 % en ont au moins un. Compté au premier calcul en production, le 1er novembre 2026

Commissions et agenda

ModèleTableLignesContenu
Commissioncommissions3 020Commissions et autres organes, AN et Sénat
Reunionreunions9 027Réunions de commission et séances publiques
ReunionParticipantreunion_participants151 741Présence d'un parlementaire à une réunion
AvisCommissionavis_commission3 529Avis rendu par une commission sur un amendement avant la séance (AN)
DossierCommissiondossier_commissions13 263Commission saisie d'un dossier, au fond ou pour avis
EvenementInstitutionnelevenements_institutionnels18Repères de l'agenda saisis à la main (élections, sessions, suspensions, budget)

Lobbying (HATVP)

ModèleTableLignesContenu
Lobbyistelobbyistes4 107Représentants d'intérêts
ActionLobbyactions_lobby102 244Actions déclarées
Secteursecteurs143Secteurs d'activité
LobbyisteSecteurlobbyistes_secteurs18 917Pivot lobbyiste ↔ secteur
ActionSecteuractions_secteurs184 038Pivot action ↔ secteur
CibleTypecible_types2 028Libellés normalisés des cibles d'action
ActionDescriptionaction_descriptions98 700Textes normalisés des descriptions d'action

Élections sénatoriales 2026

ModèleTableLignesContenu
CandidatureListecandidatures_listes483Unité de vote : une liste (proportionnel) ou un binôme (majoritaire)
Candidaturecandidatures1 936Candidats, rattachés à une personne connue quand l'identification est sûre
ResultatTourresultats_tours78Participation par circonscription et par tour (64 au 1er tour, 14 au 2nd)
ResultatListeresultats_listes559Voix d'une liste ou d'un candidat à un tour, sièges (proportionnel) ou élection (majoritaire) : 119 sièges au proportionnel et 59 élus au majoritaire, soit les 178 sièges renouvelés
CandidatMandatsLocauxcandidats_mandats_locaux1 091Mandats locaux des candidats, lus dans le Répertoire national des élus

Technique

ModèleTableContenu
SourceStatesource_statesÉtat de fraîcheur de chaque source (24 lignes, une par clé)
SyncLogsync_logsJournal des synchronisations
FeedbackfeedbacksRetours anonymes envoyés depuis le site

🗺️ Couverture

DonnéesPérimètre en base au 21 octobre 2026
Scrutins de l'Assemblée15e législature (4 417, du 4 juillet 2017 au 24 février 2022), 16e (4 105, du 11 juillet 2022 au 7 juin 2024), 17e (8 906, depuis le 8 octobre 2024)
Scrutins du Sénat4 787, depuis le 4 octobre 2006
Interventions en séanceAssemblée : 1 015 970 depuis juin 2017 ; Sénat : 307 271 depuis janvier 2017
Interventions en commissionAssemblée : 112 714 depuis juillet 2024
AmendementsAssemblée : 15e, 16e et 17e législatures (déposés depuis juin 2017) ; Sénat : déposés depuis janvier 2023
ÉlectionsSénatoriales du 27 septembre 2026 (série 2, 64 circonscriptions) : candidatures et résultats

🔒 Ce que l'API n'expose pas

L'API renvoie l'essentiel de la base, souvent la ligne entière sur les routes de détail (source_data compris pour les parlementaires, mandats, scrutins et lobbyistes). Voici ce qu'elle ne renvoie pas, hors clés de jointure (les objets liés sont imbriqués à la place) et dates techniques created_at / updated_at.

Tables jamais renvoyées

TableRaison
textes_articlesSert uniquement de matière aux résumés IA ; aucune route ne la sert pour l'instant
amendements_renvoisPréparation des liens vers Légifrance dans le dispositif des amendements ; aucune route ne la sert pour l'instant
legi_textes, legi_articlesIndex de travail pour lier les références des amendements à Légifrance. Ce sont des données de la DILA, à prendre à la source (echanges.dila.gouv.fr, Licence Ouverte)
parlementaires_identifiantsInterne au rattachement des données
source_states, sync_logsSuivi de l'ingestion ; la page d'accueil n'en lit que la date de la dernière synchronisation réussie
feedbacksRetours des visiteurs, données personnelles

Tables exposées en partie

TableCe qui manque
_AmendementCosignataires/parlementaires/:slug/amendements inclut les amendements cosignés par la personne, mais aucune route ne donne les cosignataires d'un amendement
intervention_scrutinLe lien est renvoyé (scrutinIds d'une prise de parole). Sa méthode via ne l'est pas ligne par ligne : /scrutins/:numero/interventions n'en donne que la plus fine du scrutin (precision)

Colonnes non renvoyées

TableColonnesRemarque
amendementsparlementaire_id, source_url, numero_ordre, uid_canonique, auteur_ref, groupe_ref, source_hash, amendement_parent_refL'auteur n'est donné que par auteur_libelle ; le parent d'un sous-amendement l'est par amendementParent
interventionssource_uid, seance_uid, ordre_absoluRepérage dans le compte rendu source
dossiers_legislatifssource_data, ia_content_hash, ia_generated_atL'API extrait de source_data la chronologie et le lien vers le Journal officiel, sans le renvoyer brut
sujetsia_content_hash, ia_generated_at, ia_groupe_amendement_hash, context_resolved_at, context_input_hashSuivi de l'enrichissement
sujet_liensprovenance
candidaturesdate_naissance, profession_code, sortant_declare, match_confiance, tour_electionLa date de naissance des candidats est volontairement tenue hors de l'API : seule l'année est publiée
candidatures_listesnumero_depot
candidats_mandats_locauxdate_naissance, confianceMême règle pour la date de naissance
gouvernementslibelle_sourceLibellé brut de la source (« VALLS 2 », « LECORNU II »), incohérent ; l'API renvoie à la place nom (« Valls II »)
membres_gouvernementslug, date_naissance, liaisonLa date de naissance ne sert qu'au rattachement des sénateurs
postes_ministerielsqualite quand qualite_accordee existeL'API renvoie la qualité accordée au titulaire (« Ministre déléguée ») sous le nom qualite
presidencesacteur_uid, liaison
presidences_assembleetoutesSert au calcul de la présence ; la fiche affiche la présidence parmi les fonctions du mandat

Méthode : liste relevée le 1er octobre 2026 en interrogeant toutes les routes GET documentées avec des paramètres réels (plus de 500 appels), puis revue dans le code le 21 octobre 2026, après l'ajout des métadonnées de séance des amendements. Les colonnes renvoyées sous un autre nom (par exemple cible_types.label en cibleNom, groupes_thematiques.position_moyenne en position) comptent comme exposées.


Migrations

  • Les migrations sont dans apps/api/prisma/migrations/.

  • Production : les migrations s'appliquent avec prisma migrate deploy (depuis apps/api, avec le DATABASE_URL de production). Elles ne sont pas jouées automatiquement au déploiement : l'image de l'API démarre directement le serveur.

  • Base neuve (développement local, CI) : l'historique n'est pas rejouable depuis une base vide, la migration 20260313_add_sujets référençant une table créée par une migration ultérieure. Le schéma s'applique avec pnpm db:push (en CI : prisma db push --skip-generate, puis db:triggers).

  • Créer une migration : prisma migrate dev échoue pour la même raison (sa base fantôme rejoue l'historique). Le SQL se génère sans base de données, en comparant l'ancienne version du schéma à la nouvelle, puis se relit et se range dans apps/api/prisma/migrations/<AAAAMMJJHHMMSS>_<nom>/migration.sql :

    TerminalCode
    cd apps/api git show origin/master:apps/api/prisma/schema.prisma > /tmp/schema-avant.prisma npx prisma migrate diff --from-schema-datamodel /tmp/schema-avant.prisma --to-schema-datamodel prisma/schema.prisma --script
  • Toute modification du schéma destinée à la production doit être accompagnée de son fichier de migration.

Triggers

Les triggers qui tiennent updated_at sont dans apps/api/prisma/triggers.sql. Prisma ne les connaît pas : prisma db push ne les crée pas, et prisma migrate diff ne les voit pas, ni pour les créer ni pour les supprimer. pnpm db:push joue donc triggers.sql juste après le schéma ; pnpm db:triggers le rejoue seul.

  • Nouvelle table : createdAt et updatedAt en @default(now()), jamais @updatedAt, et son trigger écrit à la main dans triggers.sql et dans le migration.sql qui crée la table. Le test apps/api/src/test/triggers-horodatage.test.ts et l'invariant tables_sans_horodatage de check-quality signalent un oubli.
  • Nouvelle colonne : elle compte comme du contenu. Si elle décrit le traitement plutôt que la donnée (date de calcul, empreinte), l'ajouter aux colonnes ignorées du trigger de sa table, dans triggers.sql et dans la migration.
  • Liaison many-to-many : ne pas écrire { set: [...] }. Prisma le joue en effaçant puis réinsérant tous les liens, ce qui date le parent même quand la liste n'a pas changé. alignerLiens() (services/ingestion/src/utils/synchro-differentielle.ts) calcule les seuls connect et disconnect nécessaires.
  • Échec de prisma migrate deploy : une migration qui échoue (sur un lock_timeout, quand une requête longue retient un verrou) est annulée en entier, mais Prisma la garde marquée en échec et refuse d'aller plus loin. La débloquer avec npx prisma migrate resolve --rolled-back <nom>, puis relancer migrate deploy.
Enrichissement IAOrchestrateur d'Ingestion
On this page
  • 📐 Conventions
  • 💡 Points de modélisation à connaître
    • 1. Une personne, plusieurs mandats
    • 2. Changement de chambre
    • 3. Le groupe politique appartient au mandat, pas à la personne
    • 4. Deux tables de mandats distinctes
    • 5. Groupes politiques
    • 6. Gouvernements et présidence
    • 7. Statistiques pré-calculées
    • 8. Scrutins, votes et amendements
    • 9. Sous-amendements et discussions
    • 10. Interventions
    • 11. Sujets
    • 12. Élections sénatoriales
  • 📊 Catalogue des tables
    • Parlementaires et groupes
    • Gouvernements et présidence
    • Travail législatif
    • Commissions et agenda
    • Lobbying (HATVP)
    • Élections sénatoriales 2026
    • Technique
  • 🗺️ Couverture
  • 🔒 Ce que l'API n'expose pas
  • Migrations
    • Triggers