Skip to content

Crée l'ETL complet : dossiers, amendements, acteurs...#8

Open
DavidNO wants to merge 12 commits into
mainfrom
create-parliamentary-db
Open

Crée l'ETL complet : dossiers, amendements, acteurs...#8
DavidNO wants to merge 12 commits into
mainfrom
create-parliamentary-db

Conversation

@DavidNO

@DavidNO DavidNO commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Contexte

Socle DB complet pour le POC d'analyse des amendements. Objectif : ingérer les données brutes de l'API des tricoteuses et pouvoir y greffer plus tard des tables d'analyse (mentions « travaillé avec… », similarité, sémantique).

Tables chargées (législature 17)

Table Contenu Volume
dossiers Dossiers législatifs ~2 500
amendements Amendements ~123 000
acteurs Députés / sénateurs (référentiel) ~3 100
organes Groupes politiques, commissions, assemblées ~5 400
mandats Jointure acteur ↔ organe (appartenance + dates) ~25 600
scrutins Scrutins publics + résultat agrégé ~8 200
groupesVotants Résultat d'un scrutin ventilé par groupe politique ~121 500 *
documents Textes (propositions/projets de loi, rapports…) ~4 500
auteursDocument Auteur(s) d'un document ~20 000
coSignatairesDocument Co-signataires d'un document ~116 000

* groupesVotants n'expose pas de filtre legislature : la table couvre toutes les législatures. ~98 300 lignes se rattachent à un scrutin L17 (12 groupes pour chacun des 8 192 scrutins concernés) ; se scoper par jointure sur scrutins.

Changements

  • Modèle Amendement (models/amendement.py) : table brute amendements, champs issus directement de l'endpoint /amendements, regroupés en catégories (identité, texte, auteur/signataires, rattachement, statut/sort, dates). Colonnes en camelCase pour matcher 1:1 les clés JSON. Seul uid est non nullable ; le reste est nullable (amendements hétérogènes : budgétaires, sous-amendements).
  • Objets parlementaires (models/acteur.py, organe.py, mandat.py, scrutin.py, document.py, auteur_document.py, cosignataire_document.py) : mêmes conventions. Modèles typés d'après un profilage des types JSON réels (int / bool / str) ; artefacts de recherche (vecteurRecherche, embedding) ignorés ; colonnes …RefUid indexées pour les jointures.
  • Rebuild scopé (etl/database.py) : create_db() ne drop/recrée que les tables listées dans ETL_TABLES (désormais les 10 tables sources). Les futures tables d'analyse ne sont ni détruites au rebuild, ni parcourues par la boucle ETL.
  • etl/download.py retries, timeout 90...) + filtre legislature configurable par endpoint :
    • filtré L17 : dossiers, documents, amendements, scrutins, mandats ;
    • non filtrés : acteurs (renvoie une 500 avec le filtre), organes ;
    • auteursDocument / coSignatairesDocument / groupesVotants : l'API n'expose pas de filtre legislature → scopés L17 par jointure (sur documents ou scrutins).
  • Chargement idempotent (etl/loading.py) : insertion par lots (1 000 lignes, pour rester sous la limite de 65 535 paramètres de Postgres) avec déduplication par uid (la pagination de l'API sert parfois deux fois la même ligne) puis upsert on_conflict_do_update. Un rechargement met donc à jour les lignes modifiées et n'en réécrit aucune si rien n'a changé (WHERE ... IS DISTINCT FROM, hors dateMaj qui est l'horodatage d'export du lot, identique pour toutes les lignes et changeant à chaque téléchargement).
  • models/groupe_votant.py : nouvelle table groupesVotants, le résultat de chaque scrutin ventilé par groupe politique (une ligne par couple scrutin × groupe : pour, contre, abstentions, nonVotants, positionMajoritaire). 1:1 avec l'endpoint, sans transformation.

Schéma de la base

Diagramme entité-relation : les 10 tables, leurs colonnes clés (PK + FK + les champs les plus parlants) et les relations principales. La liste complète des 269 colonnes est dans les modèles models/.

Les relations sont des références molles (nullable, sans ForeignKey) ; leurs cibles ont été établies en mesurant le taux de résolution des valeurs réelles dans les données, et non déduites des noms de colonnes. Quelques colonnes …RefUid ne sont pas représentées ici parce qu'elles pointent vers des objets dont l'endpoint n'est pas chargé (étapes législatives, articles, séances, points d'ordre du jour).

erDiagram
  dossiers {
    string uid PK
    string titre
    string libelleProcedure
    string statut
  }
  documents {
    string uid PK
    string dossierRefUid FK
    string auteurPrincipalUid FK
    text   titrePrincipal
    string classeLibelle
    bool   texteLoi
    string dateDepot
  }
  amendements {
    string uid PK
    string acteurRefUid FK
    string groupePolitiqueRefUid FK
    string dossierRefUid FK
    string documentRefUid FK
    string scrutinRefUid FK
    string numeroLong
    string divisionArticleDesignation
    text   exposeSommaire
    string sortAmendement
    string dateDepot
  }
  acteurs {
    string uid PK
    string groupeParlementaireUid FK
    string nom
    string prenom
    string chambre
    bool   actif
  }
  organes {
    string uid PK
    string codeType
    string libelleAbrev
    string positionPolitique
  }
  mandats {
    string uid PK
    string acteurRefUid FK
    string organeRefUid FK
    string typeOrgane
    string libQualite
    string dateDebut
    string dateFin
  }
  scrutins {
    string uid PK
    string dossierRefUid FK
    string documentRefUid FK
    string amendementRefUid FK
    string dateScrutin
    text   objet
    string code
    int    pour
    int    contre
    int    abstentions
  }
  groupesVotants {
    string uid PK
    string scrutinRefUid FK
    string organeRefUid FK
    string positionMajoritaire
    int    pour
    int    contre
    int    abstentions
  }
  auteursDocument {
    string uid PK
    string documentRefUid FK
    string acteurRefUid FK
    string qualite
  }
  coSignatairesDocument {
    string uid PK
    string documentRefUid FK
    string acteurRefUid FK
    string dateCosignature
  }

  dossiers    ||--o{ documents             : "dossierRefUid"
  dossiers    ||--o{ amendements           : "dossierRefUid"
  dossiers    ||--o{ scrutins              : "dossierRefUid"
  documents   ||--o{ amendements           : "documentRefUid"
  documents   ||--o{ scrutins              : "documentRefUid"
  documents   ||--o{ auteursDocument       : "documentRefUid"
  documents   ||--o{ coSignatairesDocument : "documentRefUid"
  acteurs     ||--o{ documents             : "auteurPrincipalUid"
  acteurs     ||--o{ amendements           : "acteurRefUid"
  acteurs     ||--o{ mandats               : "acteurRefUid"
  acteurs     ||--o{ auteursDocument       : "acteurRefUid"
  acteurs     ||--o{ coSignatairesDocument : "acteurRefUid"
  organes     ||--o{ acteurs               : "groupeParlementaireUid"
  organes     ||--o{ amendements           : "groupePolitiqueRefUid"
  organes     ||--o{ mandats               : "organeRefUid"
  organes     ||--o{ groupesVotants        : "organeRefUid"
  amendements ||--o{ scrutins              : "amendementRefUid"
  scrutins    ||--o{ amendements           : "scrutinRefUid"
  scrutins    ||--o{ groupesVotants        : "scrutinRefUid"
Loading

Notes

  • Les dates sont stockées en texte (l'ETL insère les valeurs JSON brutes).
  • Les votes nominatifs individuels (/votes) ne sont pas chargés. A faire si besoin.

DavidNO added 4 commits July 11, 2026 18:25
Introduce the raw `amendements` table (fields sourced directly from the
Tricoteuses /amendements endpoint, grouped into identity, text, author,
attachment, status and dates). Column names mirror the JSON keys so the
existing schema-driven ETL loads them without changes.

Scope the rebuild to ETL-managed tables (ETL_TABLES) so future analysis
tables are neither dropped by `create_db` nor treated as source files by
the ETL loop. This lets analysis results survive a re-download without
introducing Alembic.

Add `.env.example` wiring the ETL to the local docker-compose Postgres.
getenv returns a string, so bool(getenv("PG_ECHO", False)) was truthy for
any non-empty value including "False", leaving SQL echo permanently on.
Parse the flag explicitly instead.
…égés, textes)

Charge depuis l'API des tricoteuses les objets permettant les recoupements
« qui propose / porte quel texte, via quel groupe, et comment c'est voté » :

- acteurs, organes, mandats (référentiels + jointure acteur↔groupe)
- scrutins (résultat agrégé des votes, lien natif amendementRefUid)
- documents (textes de loi, lien dossierRefUid)
- auteursDocument, coSignatairesDocument (qui dépose/co-signe un texte)

Modèles typés d'après le profilage des types JSON réels, colonnes RefUid
indexées pour les jointures. download.py devient robuste (retries, timeout
long, plus de cap de pages) et gère un filtre legislature configurable par
endpoint. loading.py insère par lots pour rester sous la limite de 65 535
paramètres de Postgres (indispensable pour mandats/coSignatairesDocument).

Périmètre législature 17 ; votes nominatifs individuels laissés de côté
(pas de filtre legislature côté API, résultat agrégé par scrutin suffisant).
@DavidNO
DavidNO changed the base branch from add-amendements-model to main July 16, 2026 19:07
@DavidNO DavidNO changed the title Ajoute les objets parlementaires à l'ETL (acteurs, organes, scrutins, textes) Crée l'ETL complet : dossiers, amendements et objets parlementaires Jul 16, 2026
@DavidNO DavidNO changed the title Crée l'ETL complet : dossiers, amendements et objets parlementaires Crée l'ETL complet : dossiers, amendements, acteurs... Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant