L'Orchestrateur d'Ingestion
L'ingestion est une CLI Node.js (services/ingestion/src/cli.ts, bâtie sur commander) qui télécharge les sources publiques, les transforme et les écrit dans PostgreSQL, puis relie les entités entre elles, calcule les statistiques et génère les résumés IA. Il n'y a ni file de messages ni worker permanent : chaque commande s'exécute puis rend la main, avec une concurrence bornée par p-limit.
⏱️ Deux passages planifiés en production
| Service Railway | Planification (UTC) | Heure de Paris | Commande |
|---|---|---|---|
| Ingestion nightly | 0 3 * * * | 5 h en heure d'été, 4 h en heure d'hiver | node --max-old-space-size=6144 services/ingestion/dist/cli.js smart-sync --all |
| Ingestion intraday | 0 6-21/2 * * * | toutes les deux heures, de 8 h à 22 h en été (de 7 h à 21 h en hiver) | node services/ingestion/dist/cli.js sync-intraday |
Railway exprime ses crons en UTC : l'heure locale glisse donc d'une heure au changement d'heure. Les deux services sont construits depuis services/ingestion/Dockerfile, seul à installer pdftotext.
Le passage intraday ne rafraîchit que ce qui change dans la journée : l'agenda du Sénat (de J-2 à J+2), les vidéos du Sénat (séances et commissions) et celles de l'Assemblée. Il ne touche ni aux scrutins, ni aux amendements, ni aux dossiers, ni aux statistiques.
🔄 Le batch nocturne : smart-sync --all
1. Événements institutionnels
La liste des repères de l'agenda (élections, bornes de session, suspensions, échéances budgétaires) est écrite en dur dans workers/evenements.ts et poussée par un upsert idempotent sur son slug. Aucun appel réseau.
2. Les sources, dans cet ordre
L'ordre suit les dépendances : les commissions avant les députés (dont les mandats pointent vers les commissions), les parlementaires avant les votes, les anciens sénateurs avant les scrutins (les votes historiques ont besoin de l'annuaire), les gouvernements après les parlementaires (un ministre est rattaché à sa fiche), les candidatures en dernier (le rattachement d'un candidat à sa fiche lit le corpus).
assemblee_nationale:commissionsassemblee_nationale:deputessenat:senateurssenat:senateurs_histosenat:fonctionsassemblee_nationale:gouvernementsassemblee_nationale:scrutinssenat:scrutinsassemblee_nationale:amendementssenat:amendementsassemblee_nationale:dossierssenat:dossiersassemblee_nationale:interventionssenat:interventionshatvp:lobbyistesassemblee_nationale:reunionsassemblee_nationale:seances_odjsenat:agendasenat:reunionssenat:dossier_commissionssenat:videosassemblee_nationale:videosinterieur:candidatures
Le détail de chaque source est sur la page Sources de données. Les résultats des élections et les mandats locaux des candidats ne sont dans aucun des deux batchs : ils se lancent à la main (voir Commandes CLI).
Fraîcheur. Avant de télécharger, le batch compare l'ETag et le Last-Modified de la ressource à ceux enregistrés dans source_states. S'ils sont identiques, la source est ignorée (sauf avec --force). Les sources sans en-tête exploitable sont marquées « toujours » et relues à chaque passage.
Échecs. Une source en échec est journalisée (sync_logs.statut = 'failed') et n'arrête pas le batch : elle est retirée des sources « changées », ce qui empêche les étapes qui en dépendent de tourner sur une lecture ratée. Le processus ne sort en erreur que si au moins une source a échoué et qu'aucune n'a été synchronisée avec succès. Un échec partiel se lit dans le récapitulatif et dans les logs d'erreur ; rejouer un batch entier pour une seule source resolliciterait toutes les autres.
3. Rattachements
Chaque étape ne tourne que si une source concernée a changé, et son échec n'est pas bloquant.
| Condition | Étape |
|---|---|
| interventions ou scrutins | Seconde lecture des comptes rendus de l'Assemblée (sans effet si rien n'a bougé), puis rattachement des débats de l'Assemblée aux scrutins qu'ils précèdent (table intervention_scrutin), puis de ceux du Sénat |
| interventions ou amendements | Rattachement des prises de parole à leur dossier, par le numéro de texte en discussion |
| réunions | Débats de commission de l'Assemblée et avis de commission sur les amendements, pour les réunions des 60 derniers jours |
| amendements ou scrutins | Retrait des liens vers un amendement déposé après le vote, puis lecture des pages des scrutins encore sans amendement (Assemblée : 30 derniers jours ; Sénat : nouveaux scrutins) |
| dossiers ou scrutins | Scrutins → dossiers : retrait des liens vers un dossier d'une autre législature (AN) ou d'une procédure sans vote (rapport, mission d'information, 49.3 hors motion de censure), rattachement au dossier que la source désigne (voteRefs), puis, pour les scrutins restés sans dossier : rattachement Sénat, TF-IDF sur les titres, titre (Assemblée), numéro de texte, titre de la loi promulguée |
| amendements, scrutins ou dossiers | Scrutins ↔ amendements (table de jointure), puis dossier des amendements : retrait des dossiers d'une autre législature, dossier du texte (texte_ref), propagation par leurs scrutins et entre amendements d'un même texte ; correction des liens dont l'article contredit le libellé du scrutin ; alignement du dossier des scrutins sur celui de leurs amendements |
| amendements | Texte des articles des nouvelles versions de textes de l'Assemblée (textes_articles). Une version déjà ingérée n'est pas retéléchargée ; seuls les textes que la source ne sert pas, ou dont le gabarit n'est pas couvert (budgets), sont retentés |
| toujours | Index de la base LEGI (legi_textes, legi_articles) : les incréments publiés par la DILA depuis la dernière archive appliquée, quelques secondes. Ne charge jamais le dump global : un index vide ou décroché des archives publiées fait échouer l'étape, à relancer à la main avec sync-index-legi. Son échec apparaît dans le récapitulatif des sources en échec ; sa réussite ne compte pas comme un changement et ne déclenche donc pas les étapes suivantes |
| toujours | Renvois au droit en vigueur dans le dispositif des amendements (amendements_renvois) : amendements nouveaux, modifiés, ou déposés depuis moins de 45 jours (la DILA consolide une loi nouvelle avec quelques jours ou semaines de retard). Vient après les amendements, le texte des articles (qui donne le code modifié quand le dispositif ne le nomme pas) et l'index LEGI. Un échec est journalisé sans bloquer la suite ; check-quality le signale |
| scrutins | Classement de la nature des scrutins encore non classés (nature_vote) |
L'ordre compte : le rattachement scrutin → dossier précède le rattachement scrutin → amendement, qui s'appuie sur le dossier pour éviter les faux positifs. Le texte des articles vient après le rattachement aux dossiers, dont il recopie le dossier_id, et avant l'IA, qui met l'article dans le prompt des scrutins.
4. Étapes exécutées dès qu'au moins une source a changé
Avec --all, les événements institutionnels et les sources « toujours » comptent comme changés : ces étapes tournent donc chaque nuit.
- Statistiques (sauf
--skip-stats) : réalignement deparlementaires.actifsur « a un mandat en cours », statistiques des parlementaires, des groupes, alliances entre groupes, positions par thématique. - Sujets : génération incrémentale (regroupement des dossiers AN et Sénat), liens « construction » (documents officiels), liens « contexte » (vie-publique.fr et Wikipédia, en incrémental).
- Compteurs dénormalisés des sujets (
dossier_count,scrutin_count,date_dernier_vote). VACUUM ANALYZEsurscrutins,amendements,dossiers_legislatifs,parlementaires,votes,interventions.- Déclarations HATVP des parlementaires.
- Statut des sujets, recalculé à partir de l'état de leurs dossiers.
- Enrichissement IA, en cascade : scrutins, dossiers, sujets, descriptions d'amendements par groupe, puis les fiches parlementaires en trois temps : les fiches encore absentes des parlementaires en exercice, jusqu'à 200 fiches devenues périmées (un fait qu'elles affirment a changé, ou le prompt a changé de version), et 25 fiches tirées au hasard parmi celles non rafraîchies depuis 25 jours, anciens parlementaires compris (ce que nos données ignorent, un ancien député devenu maire par exemple, ne périme aucune empreinte). Voir Enrichissement IA.
5. Cache de la page d'accueil
En fin de batch, la CLI attend 120 s, puis appelle POST /api/v1/homepage/warm (invalidation) et GET /api/v1/homepage (reconstruction), authentifiée par CLAIR_INTERNAL_SECRET ; chaque appel est borné à 120 s. Sans ce secret, l'étape est sautée. Les autres entrées du cache Redis de l'API ne sont pas invalidées : elles expirent selon leur durée de vie (voir Limites & Cache).
🧾 Traçabilité
source_states: une ligne par source, avec l'URL de la ressource, le dernierETag, le dernierLast-Modified, la date de dernière synchronisation réussie et celle de dernière vérification.sync_logs: une ligne par source et par passage, avec le statut (started,completed,failed,skipped), les compteurs (traités, créés, mis à jour, erreurs) et l'erreur éventuelle.pnpm ingestion:statusaffiche la fraîcheur de toutes les sources.
🧩 Modules principaux (services/ingestion/src/workers/)
| Fichier | Rôle |
|---|---|
sync.ts | Synchronisation de chaque source, rattachements, orchestration smartSync et syncIntraday |
stats-calculator.ts | Présence, loyauté, participation, alliances, thématiques |
mandats.ts, backfill-mandats.ts, senat-histo.ts, mandats-an-sortants.ts | Mandats parlementaires multi-législatures, anciens sénateurs, clôture des mandats des députés sortants et de leurs mandats d'organe |
changement-chambre.ts | Un député élu sénateur garde sa fiche, qui passe au Sénat ; son identifiant de l'Assemblée est conservé dans parlementaires_identifiants |
interventions-syceron.ts, interventions-commission.ts, avis-commission.ts | Débats de séance et de commission de l'Assemblée, avis de commission |
link-debats-scrutins.ts, link-debats-scrutins-senat.ts, link-interventions-dossiers.ts, link-interventions-ordre-du-jour.ts, segmenter-debats-senat.ts | Rattachement des débats aux scrutins et aux dossiers |
liens-amendements-discordants.ts | Correction des liens scrutin ↔ amendement dont l'article contredit le libellé du scrutin |
parents-amendements.ts, metadonnees-amendements-senat.ts, article-vise-senat.ts | Sous-amendements, discussions et article visé des amendements |
sujet-generator.ts, sujet-links-generator.ts | Sujets et leurs liens sortants |
ia-enrichment.ts, parlementaire-enrichment.ts | Enrichissement IA |
declarations-sync.ts | Déclarations HATVP |
senat-fonctions.ts, senat-dossier-commissions.ts | Fonctions datées des sénateurs (bureau, commissions, groupes) et saisines des commissions du Sénat |
gouvernements.ts | Gouvernements, postes ministériels, présidences de la République et de l'Assemblée (archive AMO30), rattachement à la fiche |
textes-articles.ts | Texte des articles des textes de l'Assemblée |
index-legi.ts | Index de la base LEGI de la DILA (textes et articles de loi, pour les liens vers Légifrance) |
renvois-legi.ts | Renvois au droit en vigueur dans le dispositif des amendements, rattachés à l'index LEGI (grammaire de @tricoteuses/tisseuse) |
evenements.ts | Liste curée des événements institutionnels |
La commande schedule (fichier src/scheduler.ts) lance un planificateur node-cron dans le processus. Elle n'est pas utilisée en production, où la planification est confiée à Railway.