diff --git a/alembic/versions/42ff9c981be7_ajout_des_modèles_de_résultats_pour_les_.py b/alembic/versions/42ff9c981be7_ajout_des_modèles_de_résultats_pour_les_.py new file mode 100644 index 0000000..a8dd600 --- /dev/null +++ b/alembic/versions/42ff9c981be7_ajout_des_modèles_de_résultats_pour_les_.py @@ -0,0 +1,238 @@ +"""Ajout des modèles de résultats pour les européennes 2024 + géométrie administrative + +Revision ID: 42ff9c981be7 +Revises: +Create Date: 2024-06-08 17:40:27.348858 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '42ff9c981be7' +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('bloc2024', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('nom', sa.String(length=32), nullable=False), + sa.Column('couleur', sa.String(length=7), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('nom') + ) + op.create_table('nuance2024', + sa.Column('code', sa.String(length=8), nullable=False), + sa.Column('nom', sa.String(length=32), nullable=False), + sa.Column('couleur', sa.String(length=7), nullable=False), + sa.PrimaryKeyConstraint('code'), + sa.UniqueConstraint('nom') + ) + op.create_table('region', + sa.Column('code_insee', sa.String(length=3), nullable=False), + sa.Column('libelle', sa.String(length=64), nullable=False), + sa.Column('geometry', sa.JSON(), nullable=False), + sa.PrimaryKeyConstraint('code_insee'), + sa.UniqueConstraint('libelle') + ) + op.create_table('resultats2024_france', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('inscrits', sa.Integer(), nullable=False), + sa.Column('votants', sa.Integer(), nullable=False), + sa.Column('abstentions', sa.Integer(), nullable=False), + sa.Column('exprimes', sa.Integer(), nullable=False), + sa.Column('blancs', sa.Integer(), nullable=False), + sa.Column('nuls', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('departement', + sa.Column('code_insee', sa.String(length=2), nullable=False), + sa.Column('libelle', sa.String(length=64), nullable=False), + sa.Column('region_code', sa.String(length=3), nullable=False), + sa.Column('geometry', sa.JSON(), nullable=False), + sa.ForeignKeyConstraint(['region_code'], ['region.code_insee'], ), + sa.PrimaryKeyConstraint('code_insee'), + sa.UniqueConstraint('libelle') + ) + op.create_table('liste2024', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('nom', sa.String(length=256), nullable=False), + sa.Column('nom_majuscules', sa.String(length=256), nullable=False), + sa.Column('numero', sa.Integer(), nullable=False), + sa.Column('nuance_id', sa.String(length=8), nullable=False), + sa.Column('bloc_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['bloc_id'], ['bloc2024.id'], ), + sa.ForeignKeyConstraint(['nuance_id'], ['nuance2024.code'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('nom'), + sa.UniqueConstraint('nom_majuscules'), + sa.UniqueConstraint('numero') + ) + op.create_table('resultats2024_region', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('region_id', sa.String(length=3), nullable=False), + sa.Column('resultats_france_id', sa.Integer(), nullable=False), + sa.Column('inscrits', sa.Integer(), nullable=False), + sa.Column('votants', sa.Integer(), nullable=False), + sa.Column('abstentions', sa.Integer(), nullable=False), + sa.Column('exprimes', sa.Integer(), nullable=False), + sa.Column('blancs', sa.Integer(), nullable=False), + sa.Column('nuls', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['region_id'], ['region.code_insee'], ), + sa.ForeignKeyConstraint(['resultats_france_id'], ['resultats2024_france.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('candidat2024', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('liste_id', sa.Integer(), nullable=False), + sa.Column('ordre', sa.Integer(), nullable=False), + sa.Column('nom', sa.String(length=256), nullable=False), + sa.Column('prenom', sa.String(length=256), nullable=False), + sa.Column('sexe', sa.Enum('MASCULIN', 'FEMININ', name='genre'), nullable=False), + sa.Column('date_naissance', sa.Date(), nullable=False), + sa.Column('profession', sa.String(length=256), nullable=False), + sa.Column('code_personnalite', sa.Enum('DEFAUT', 'EURODEPUTE', 'DEPUTE', 'SENATEUR', 'MINISTRE', 'PRESIDENT_CONSEIL_REGIONAL', 'PRESIDENT_CONSEIL_DEPARTEMENTAL', 'MAIRE', name='personnalite'), nullable=False), + sa.Column('sortant', sa.Boolean(), nullable=False), + sa.ForeignKeyConstraint(['liste_id'], ['liste2024.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('circonscription', + sa.Column('id', sa.String(length=6), nullable=False), + sa.Column('departement_code', sa.String(length=2), nullable=False), + sa.Column('numero', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['departement_code'], ['departement.code_insee'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('commune', + sa.Column('code_insee', sa.String(length=5), nullable=False), + sa.Column('libelle', sa.String(length=64), nullable=False), + sa.Column('departement_code', sa.String(length=2), nullable=False), + sa.Column('geometry', sa.JSON(), nullable=False), + sa.ForeignKeyConstraint(['departement_code'], ['departement.code_insee'], ), + sa.PrimaryKeyConstraint('code_insee') + ) + op.create_table('resultats2024_departement', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('dpt_id', sa.String(length=2), nullable=False), + sa.Column('resultats_region_id', sa.Integer(), nullable=False), + sa.Column('inscrits', sa.Integer(), nullable=False), + sa.Column('votants', sa.Integer(), nullable=False), + sa.Column('abstentions', sa.Integer(), nullable=False), + sa.Column('exprimes', sa.Integer(), nullable=False), + sa.Column('blancs', sa.Integer(), nullable=False), + sa.Column('nuls', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['dpt_id'], ['departement.code_insee'], ), + sa.ForeignKeyConstraint(['resultats_region_id'], ['resultats2024_region.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('voix2024_france', + sa.Column('liste_id', sa.Integer(), nullable=False), + sa.Column('resultats_france_id', sa.Integer(), nullable=False), + sa.Column('voix', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['liste_id'], ['liste2024.id'], ), + sa.ForeignKeyConstraint(['resultats_france_id'], ['resultats2024_france.id'], ), + sa.PrimaryKeyConstraint('liste_id', 'resultats_france_id') + ) + op.create_table('voix2024_region', + sa.Column('liste_id', sa.Integer(), nullable=False), + sa.Column('resultats_region_id', sa.Integer(), nullable=False), + sa.Column('voix', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['liste_id'], ['liste2024.id'], ), + sa.ForeignKeyConstraint(['resultats_region_id'], ['resultats2024_region.id'], ), + sa.PrimaryKeyConstraint('liste_id', 'resultats_region_id') + ) + op.create_table('bureau_vote', + sa.Column('id', sa.String(length=16), nullable=False), + sa.Column('commune_code', sa.String(length=5), nullable=False), + sa.Column('code_bureau', sa.String(length=8), nullable=False), + sa.Column('circo_code', sa.String(length=6), nullable=False), + sa.Column('libelle', sa.String(length=256), nullable=False), + sa.Column('adresse', sa.String(length=256), nullable=True), + sa.Column('geometry', sa.JSON(), nullable=False), + sa.ForeignKeyConstraint(['circo_code'], ['circonscription.id'], ), + sa.ForeignKeyConstraint(['commune_code'], ['commune.code_insee'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('resultats2024_commune', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('commune_id', sa.String(length=5), nullable=False), + sa.Column('resultats_dpt_id', sa.Integer(), nullable=False), + sa.Column('inscrits', sa.Integer(), nullable=False), + sa.Column('votants', sa.Integer(), nullable=False), + sa.Column('abstentions', sa.Integer(), nullable=False), + sa.Column('exprimes', sa.Integer(), nullable=False), + sa.Column('blancs', sa.Integer(), nullable=False), + sa.Column('nuls', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['commune_id'], ['commune.code_insee'], ), + sa.ForeignKeyConstraint(['resultats_dpt_id'], ['resultats2024_departement.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('voix2024_departement', + sa.Column('liste_id', sa.Integer(), nullable=False), + sa.Column('resultats_departement_id', sa.Integer(), nullable=False), + sa.Column('voix', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['liste_id'], ['liste2024.id'], ), + sa.ForeignKeyConstraint(['resultats_departement_id'], ['resultats2024_departement.id'], ), + sa.PrimaryKeyConstraint('liste_id', 'resultats_departement_id') + ) + op.create_table('resultats2024_bureau_vote', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('bv_id', sa.String(length=16), nullable=False), + sa.Column('resultats_commune_id', sa.Integer(), nullable=False), + sa.Column('inscrits', sa.Integer(), nullable=False), + sa.Column('votants', sa.Integer(), nullable=False), + sa.Column('abstentions', sa.Integer(), nullable=False), + sa.Column('exprimes', sa.Integer(), nullable=False), + sa.Column('blancs', sa.Integer(), nullable=False), + sa.Column('nuls', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['bv_id'], ['bureau_vote.id'], ), + sa.ForeignKeyConstraint(['resultats_commune_id'], ['resultats2024_commune.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('voix2024_commune', + sa.Column('liste_id', sa.Integer(), nullable=False), + sa.Column('resultats_commune_id', sa.Integer(), nullable=False), + sa.Column('voix', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['liste_id'], ['liste2024.id'], ), + sa.ForeignKeyConstraint(['resultats_commune_id'], ['resultats2024_commune.id'], ), + sa.PrimaryKeyConstraint('liste_id', 'resultats_commune_id') + ) + op.create_table('voix2024_bureau_vote', + sa.Column('liste_id', sa.Integer(), nullable=False), + sa.Column('resultats_bureau_vote_id', sa.Integer(), nullable=False), + sa.Column('voix', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['liste_id'], ['liste2024.id'], ), + sa.ForeignKeyConstraint(['resultats_bureau_vote_id'], ['resultats2024_bureau_vote.id'], ), + sa.PrimaryKeyConstraint('liste_id', 'resultats_bureau_vote_id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('voix2024_bureau_vote') + op.drop_table('voix2024_commune') + op.drop_table('resultats2024_bureau_vote') + op.drop_table('voix2024_departement') + op.drop_table('resultats2024_commune') + op.drop_table('bureau_vote') + op.drop_table('voix2024_region') + op.drop_table('voix2024_france') + op.drop_table('resultats2024_departement') + op.drop_table('commune') + op.drop_table('circonscription') + op.drop_table('candidat2024') + op.drop_table('resultats2024_region') + op.drop_table('liste2024') + op.drop_table('departement') + op.drop_table('resultats2024_france') + op.drop_table('region') + op.drop_table('nuance2024') + op.drop_table('bloc2024') + # ### end Alembic commands ### diff --git a/alembic/versions/62495c819f2b_ajout_des_modèles_de_géométries_.py b/alembic/versions/62495c819f2b_ajout_des_modèles_de_géométries_.py deleted file mode 100644 index d950f56..0000000 --- a/alembic/versions/62495c819f2b_ajout_des_modèles_de_géométries_.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Ajout des modèles de géométries administratives - -Revision ID: 62495c819f2b -Revises: 9d99f3ea6b66 -Create Date: 2024-06-08 16:13:15.457114 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '62495c819f2b' -down_revision: Union[str, None] = '9d99f3ea6b66' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('region', - sa.Column('code_insee', sa.String(length=3), nullable=False), - sa.Column('libelle', sa.String(length=64), nullable=False), - sa.Column('geometry', sa.JSON(), nullable=False), - sa.PrimaryKeyConstraint('code_insee'), - sa.UniqueConstraint('libelle') - ) - op.create_table('departement', - sa.Column('code_insee', sa.String(length=2), nullable=False), - sa.Column('libelle', sa.String(length=64), nullable=False), - sa.Column('region_code', sa.String(length=3), nullable=False), - sa.Column('geometry', sa.JSON(), nullable=False), - sa.ForeignKeyConstraint(['region_code'], ['region.code_insee'], ), - sa.PrimaryKeyConstraint('code_insee'), - sa.UniqueConstraint('libelle') - ) - op.create_table('circonscription', - sa.Column('id', sa.String(length=6), nullable=False), - sa.Column('departement_code', sa.String(length=2), nullable=False), - sa.Column('numero', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['departement_code'], ['departement.code_insee'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('commune', - sa.Column('code_insee', sa.String(length=5), nullable=False), - sa.Column('libelle', sa.String(length=64), nullable=False), - sa.Column('departement_code', sa.String(length=2), nullable=False), - sa.Column('geometry', sa.JSON(), nullable=False), - sa.ForeignKeyConstraint(['departement_code'], ['departement.code_insee'], ), - sa.PrimaryKeyConstraint('code_insee') - ) - op.create_table('bureau_vote', - sa.Column('id', sa.String(length=16), nullable=False), - sa.Column('commune_code', sa.String(length=5), nullable=False), - sa.Column('code_bureau', sa.String(length=8), nullable=False), - sa.Column('circo_code', sa.String(length=6), nullable=False), - sa.Column('libelle', sa.String(length=256), nullable=False), - sa.Column('adresse', sa.String(length=256), nullable=True), - sa.Column('geometry', sa.JSON(), nullable=False), - sa.ForeignKeyConstraint(['circo_code'], ['circonscription.id'], ), - sa.ForeignKeyConstraint(['commune_code'], ['commune.code_insee'], ), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('bureau_vote') - op.drop_table('commune') - op.drop_table('circonscription') - op.drop_table('departement') - op.drop_table('region') - # ### end Alembic commands ### diff --git a/alembic/versions/9d99f3ea6b66_ajout_des_modèles_de_candidat_es_2024.py b/alembic/versions/9d99f3ea6b66_ajout_des_modèles_de_candidat_es_2024.py deleted file mode 100644 index cc7b93f..0000000 --- a/alembic/versions/9d99f3ea6b66_ajout_des_modèles_de_candidat_es_2024.py +++ /dev/null @@ -1,72 +0,0 @@ -"""Ajout des modèles de candidat⋅es 2024 - -Revision ID: 9d99f3ea6b66 -Revises: -Create Date: 2024-06-08 13:01:08.629780 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '9d99f3ea6b66' -down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('bloc2024', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('nom', sa.String(length=32), nullable=False), - sa.Column('couleur', sa.String(length=7), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('nom') - ) - op.create_table('nuance2024', - sa.Column('code', sa.String(length=8), nullable=False), - sa.Column('nom', sa.String(length=32), nullable=False), - sa.Column('couleur', sa.String(length=7), nullable=False), - sa.PrimaryKeyConstraint('code'), - sa.UniqueConstraint('nom') - ) - op.create_table('liste2024', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('nom', sa.String(length=256), nullable=False), - sa.Column('numero', sa.Integer(), nullable=False), - sa.Column('nuance_id', sa.String(length=8), nullable=False), - sa.Column('bloc_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['bloc_id'], ['bloc2024.id'], ), - sa.ForeignKeyConstraint(['nuance_id'], ['nuance2024.code'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('nom'), - sa.UniqueConstraint('numero') - ) - op.create_table('candidat2024', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('liste_id', sa.Integer(), nullable=False), - sa.Column('ordre', sa.Integer(), nullable=False), - sa.Column('nom', sa.String(length=256), nullable=False), - sa.Column('prenom', sa.String(length=256), nullable=False), - sa.Column('sexe', sa.Enum('MASCULIN', 'FEMININ', name='genre'), nullable=False), - sa.Column('date_naissance', sa.Date(), nullable=False), - sa.Column('profession', sa.String(length=256), nullable=False), - sa.Column('code_personnalite', sa.Enum('DEFAUT', 'EURODEPUTE', 'DEPUTE', 'SENATEUR', 'MINISTRE', 'PRESIDENT_CONSEIL_REGIONAL', 'PRESIDENT_CONSEIL_DEPARTEMENTAL', 'MAIRE', name='personnalite'), nullable=False), - sa.Column('sortant', sa.Boolean(), nullable=False), - sa.ForeignKeyConstraint(['liste_id'], ['liste2024.id'], ), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('candidat2024') - op.drop_table('liste2024') - op.drop_table('nuance2024') - op.drop_table('bloc2024') - # ### end Alembic commands ### diff --git a/main.py b/main.py index ac7af1f..198857a 100755 --- a/main.py +++ b/main.py @@ -6,7 +6,7 @@ import os from dotenv import load_dotenv from sqlalchemy import create_engine -from nupes.scripts import import_candidats_2024, import_geographie +from nupes.scripts import import_candidats_2024, import_geographie, import_resultats_web_2024 def parse_args(): @@ -16,7 +16,7 @@ def parse_args(): "régionales", "départementales", "municipales"], default="européennes") parser.add_argument('--year', '-y', type=int, help="Année de l'élection", default=2024) parser.add_argument('action', help="Action à réaliser", - choices=["import_candidats", "import_geographie"]) + choices=["import_candidats", "import_geographie", "import_resultats"]) parser.add_argument('--debug', '-d', action='store_true', help="Mode debug") return parser.parse_args() @@ -35,6 +35,9 @@ def main(): case "import_candidats": print(f"Import des candidats pour les élections {args.type} {args.year}") import_candidats_2024.run(engine) + case "import_resultats": + print(f"Import des résultats pour les élections {args.type} {args.year}") + import_resultats_web_2024.run(engine) case _: print(f"Action {args.action} non reconnue") diff --git a/nupes/data.py b/nupes/data.py index ffbea27..cfd4ad7 100644 --- a/nupes/data.py +++ b/nupes/data.py @@ -6,21 +6,41 @@ import requests DATA_DIR = Path(__file__).parent / 'data' -def get_file(url, filename, etag: str = None): +def get_file(url, filename, force_etag: str = ""): if not DATA_DIR.is_dir(): - DATA_DIR.mkdir() + DATA_DIR.mkdir(parents=True) - head_response = requests.head(url, allow_redirects=True) - headers = head_response.headers - if not etag: - etag = headers.get('ETag').split('/')[-1].replace('"', '') + file_dir = DATA_DIR / filename + if not file_dir.is_dir(): + file_dir.mkdir(parents=True) - file = DATA_DIR / filename.format(etag=etag) - if file.exists(): - return file + symlink_file = file_dir / 'latest' - response = requests.get(url, allow_redirects=True) - with file.open('wb') as f: - f.write(response.content) + etag = "" + if symlink_file.exists(): + file = symlink_file.resolve() + etag = file.name - return file + response = requests.get(url, headers={"If-None-Match": f'"{etag}'}, allow_redirects=True) + if response.status_code == 404: + print(url) + return None + response.raise_for_status() + if response.status_code != 304: + if force_etag: + etag = force_etag + else: + etag = response.headers.get('ETag').split('/')[-1].replace('"', '') + + if symlink_file.exists(): + symlink_file.resolve().unlink() + + file = file_dir / etag + + with file.open('wb') as f: + f.write(response.content) + + symlink_file.unlink(missing_ok=True) + symlink_file.symlink_to(file) + + return symlink_file diff --git a/nupes/models/europeennes2024.py b/nupes/models/europeennes2024.py index 7e0c706..fc679ca 100644 --- a/nupes/models/europeennes2024.py +++ b/nupes/models/europeennes2024.py @@ -5,7 +5,7 @@ from typing import List from sqlalchemy import Boolean, Date, Enum, ForeignKey, Integer, String from sqlalchemy.orm import mapped_column, Mapped, relationship -from nupes.models import Base +from nupes.models import Base, Region, Departement, Commune class Bloc(Base): @@ -33,12 +33,22 @@ class Liste(Base): id: Mapped[int] = mapped_column(primary_key=True) nom: Mapped[str] = mapped_column(String(256), unique=True) + nom_majuscules: Mapped[str] = mapped_column(String(256), unique=True, nullable=True) numero: Mapped[int] = mapped_column(Integer(), unique=True) nuance_id: Mapped[str] = mapped_column(ForeignKey("nuance2024.code")) bloc_id: Mapped[int] = mapped_column(ForeignKey("bloc2024.id")) nuance: Mapped[Nuance] = relationship(Nuance, back_populates="listes") bloc: Mapped[Bloc] = relationship(Bloc, back_populates="listes") + candidats: Mapped[List["Candidat"]] = relationship("Candidat", back_populates="liste") + + resultats_nationaux: Mapped[List["VoixListeFrance"]] = relationship("VoixListeFrance", back_populates="liste") + resultats_par_region: Mapped[List["VoixListeRegion"]] = relationship("VoixListeRegion", back_populates="liste") + resultats_par_departement: Mapped[List["VoixListeDepartement"]] = relationship("VoixListeDepartement", + back_populates="liste") + resultats_par_commune: Mapped[List["VoixListeCommune"]] = relationship("VoixListeCommune", back_populates="liste") + resultats_par_bureau_vote: Mapped[List["VoixListeBureauVote"]] = relationship("VoixListeBureauVote", + back_populates="liste") class Candidat(Base): @@ -68,3 +78,157 @@ class Candidat(Base): profession: Mapped[str] = mapped_column(String(256)) code_personnalite: Mapped[str] = mapped_column(Enum(Personnalite)) sortant: Mapped[bool] = mapped_column(Boolean()) + + liste: Mapped[Liste] = relationship(Liste, back_populates="candidats") + + +class ResultatsFrance(Base): + __tablename__ = "resultats2024_france" + + id: Mapped[int] = mapped_column(primary_key=True) + inscrits: Mapped[int] = mapped_column(Integer(), default=0) + votants: Mapped[int] = mapped_column(Integer(), default=0) + abstentions: Mapped[int] = mapped_column(Integer(), default=0) + exprimes: Mapped[int] = mapped_column(Integer(), default=0) + blancs: Mapped[int] = mapped_column(Integer(), default=0) + nuls: Mapped[int] = mapped_column(Integer(), default=0) + + resultats_regions: Mapped[List["ResultatsRegion"]] = relationship("ResultatsRegion", + back_populates="resultats_france") + voix_listes: Mapped[List["VoixListeFrance"]] = relationship("VoixListeFrance", back_populates="resultats_france") + + +class ResultatsRegion(Base): + __tablename__ = "resultats2024_region" + + id: Mapped[int] = mapped_column(primary_key=True) + region_id: Mapped[str] = mapped_column(ForeignKey("region.code_insee")) + resultats_france_id: Mapped[int] = mapped_column(ForeignKey("resultats2024_france.id")) + inscrits: Mapped[int] = mapped_column(Integer(), default=0) + votants: Mapped[int] = mapped_column(Integer(), default=0) + abstentions: Mapped[int] = mapped_column(Integer(), default=0) + exprimes: Mapped[int] = mapped_column(Integer(), default=0) + blancs: Mapped[int] = mapped_column(Integer(), default=0) + nuls: Mapped[int] = mapped_column(Integer(), default=0) + + region = relationship(Region, back_populates="resultats2024") + resultats_france = relationship(ResultatsFrance, back_populates="resultats_regions") + resultats_departements: Mapped[List["ResultatsDepartement"]] = relationship("ResultatsDepartement", + back_populates="resultats_region") + voix_listes: Mapped[List["VoixListeRegion"]] = relationship("VoixListeRegion", back_populates="resultats_region") + + +class ResultatsDepartement(Base): + __tablename__ = "resultats2024_departement" + + id: Mapped[int] = mapped_column(primary_key=True) + dpt_id: Mapped[str] = mapped_column(ForeignKey("departement.code_insee")) + resultats_region_id: Mapped[int] = mapped_column(ForeignKey("resultats2024_region.id")) + inscrits: Mapped[int] = mapped_column(Integer(), default=0) + votants: Mapped[int] = mapped_column(Integer(), default=0) + abstentions: Mapped[int] = mapped_column(Integer(), default=0) + exprimes: Mapped[int] = mapped_column(Integer(), default=0) + blancs: Mapped[int] = mapped_column(Integer(), default=0) + nuls: Mapped[int] = mapped_column(Integer(), default=0) + + departement = relationship(Departement, back_populates="resultats2024") + resultats_region = relationship(ResultatsRegion, back_populates="resultats_departements") + resultats_communes: Mapped[List["ResultatsCommune"]] = relationship("ResultatsCommune", + back_populates="resultats_departement") + voix_listes: Mapped[List["VoixListeDepartement"]] = relationship("VoixListeDepartement", + back_populates="resultats_departement") + + +class ResultatsCommune(Base): + __tablename__ = "resultats2024_commune" + + id: Mapped[int] = mapped_column(primary_key=True) + commune_id: Mapped[str] = mapped_column(ForeignKey("commune.code_insee")) + resultats_dpt_id: Mapped[int] = mapped_column(ForeignKey("resultats2024_departement.id")) + inscrits: Mapped[int] = mapped_column(Integer(), default=0) + votants: Mapped[int] = mapped_column(Integer(), default=0) + abstentions: Mapped[int] = mapped_column(Integer(), default=0) + exprimes: Mapped[int] = mapped_column(Integer(), default=0) + blancs: Mapped[int] = mapped_column(Integer(), default=0) + nuls: Mapped[int] = mapped_column(Integer(), default=0) + + commune = relationship(Commune, back_populates="resultats2024") + resultats_departement = relationship(ResultatsDepartement, back_populates="resultats_communes") + resultats_bureaux_vote: Mapped[List["ResultatsBureauVote"]] = relationship("ResultatsBureauVote", + back_populates="resultats_commune") + voix_listes: Mapped[List["VoixListeCommune"]] = relationship("VoixListeCommune", back_populates="resultats_commune") + + +class ResultatsBureauVote(Base): + __tablename__ = "resultats2024_bureau_vote" + + id: Mapped[int] = mapped_column(primary_key=True) + bv_id: Mapped[str] = mapped_column(ForeignKey("bureau_vote.id")) + resultats_commune_id: Mapped[int] = mapped_column(ForeignKey("resultats2024_commune.id")) + inscrits: Mapped[int] = mapped_column(Integer(), default=0) + votants: Mapped[int] = mapped_column(Integer(), default=0) + abstentions: Mapped[int] = mapped_column(Integer(), default=0) + exprimes: Mapped[int] = mapped_column(Integer(), default=0) + blancs: Mapped[int] = mapped_column(Integer(), default=0) + nuls: Mapped[int] = mapped_column(Integer(), default=0) + + bureau_vote = relationship("BureauVote", back_populates="resultats2024") + resultats_commune = relationship(ResultatsCommune, back_populates="resultats_bureaux_vote") + voix_listes: Mapped[List["VoixListeBureauVote"]] = relationship("VoixListeBureauVote", + back_populates="resultats_bureau_vote") + + +class VoixListeFrance(Base): + __tablename__ = "voix2024_france" + + liste_id: Mapped[int] = mapped_column(ForeignKey("liste2024.id"), primary_key=True) + resultats_france_id: Mapped[int] = mapped_column(ForeignKey("resultats2024_france.id"), primary_key=True) + voix: Mapped[int] = mapped_column(Integer(), default=0) + + liste: Mapped[Liste] = relationship(Liste, back_populates="resultats_nationaux") + resultats_france: Mapped[ResultatsFrance] = relationship(ResultatsFrance, back_populates="voix_listes") + + +class VoixListeRegion(Base): + __tablename__ = "voix2024_region" + + liste_id: Mapped[int] = mapped_column(ForeignKey("liste2024.id"), primary_key=True) + resultats_region_id: Mapped[int] = mapped_column(ForeignKey("resultats2024_region.id"), primary_key=True) + voix: Mapped[int] = mapped_column(Integer(), default=0) + + liste: Mapped[Liste] = relationship(Liste, back_populates="resultats_par_region") + resultats_region: Mapped[ResultatsRegion] = relationship(ResultatsRegion, back_populates="voix_listes") + + +class VoixListeDepartement(Base): + __tablename__ = "voix2024_departement" + + liste_id: Mapped[int] = mapped_column(ForeignKey("liste2024.id"), primary_key=True) + resultats_departement_id: Mapped[int] = mapped_column(ForeignKey("resultats2024_departement.id"), primary_key=True) + voix: Mapped[int] = mapped_column(Integer(), default=0) + + liste: Mapped[Liste] = relationship(Liste, back_populates="resultats_par_departement") + resultats_departement: Mapped[ResultatsDepartement] = relationship(ResultatsDepartement, + back_populates="voix_listes") + + +class VoixListeCommune(Base): + __tablename__ = "voix2024_commune" + + liste_id: Mapped[int] = mapped_column(ForeignKey("liste2024.id"), primary_key=True) + resultats_commune_id: Mapped[int] = mapped_column(ForeignKey("resultats2024_commune.id"), primary_key=True) + voix: Mapped[int] = mapped_column(Integer(), default=0) + + liste: Mapped[Liste] = relationship(Liste, back_populates="resultats_par_commune") + resultats_commune: Mapped[ResultatsCommune] = relationship(ResultatsCommune, back_populates="voix_listes") + + +class VoixListeBureauVote(Base): + __tablename__ = "voix2024_bureau_vote" + + liste_id: Mapped[int] = mapped_column(ForeignKey("liste2024.id"), primary_key=True) + resultats_bureau_vote_id: Mapped[int] = mapped_column(ForeignKey("resultats2024_bureau_vote.id"), primary_key=True) + voix: Mapped[int] = mapped_column(Integer(), default=0) + + liste: Mapped[Liste] = relationship(Liste, back_populates="resultats_par_bureau_vote") + resultats_bureau_vote: Mapped[ResultatsBureauVote] = relationship(ResultatsBureauVote, back_populates="voix_listes") diff --git a/nupes/models/geographie.py b/nupes/models/geographie.py index 9861db4..db94f16 100644 --- a/nupes/models/geographie.py +++ b/nupes/models/geographie.py @@ -15,6 +15,8 @@ class Region(Base): departements: Mapped[List["Departement"]] = relationship("Departement", back_populates="region") + resultats2024 = relationship("ResultatsRegion", back_populates="region") + class Departement(Base): __tablename__ = "departement" @@ -27,6 +29,8 @@ class Departement(Base): region: Mapped[Region] = relationship(Region, back_populates="departements") communes: Mapped[List["Commune"]] = relationship("Commune", back_populates="departement") + resultats2024 = relationship("ResultatsDepartement", back_populates="departement") + class Commune(Base): __tablename__ = "commune" @@ -39,6 +43,8 @@ class Commune(Base): departement: Mapped[Departement] = relationship(Departement, back_populates="communes") bureaux_vote: Mapped[List["BureauVote"]] = relationship("BureauVote", back_populates="commune") + resultats2024 = relationship("ResultatsCommune", back_populates="commune") + class Circonscription(Base): __tablename__ = "circonscription" @@ -64,3 +70,5 @@ class BureauVote(Base): commune: Mapped[Commune] = relationship(Commune, back_populates="bureaux_vote") circonscription: Mapped[Circonscription] = relationship(Circonscription, back_populates="bureaux_vote") + + resultats2024 = relationship("ResultatsBureauVote", back_populates="bureau_vote") diff --git a/nupes/scripts/import_candidats_2024.py b/nupes/scripts/import_candidats_2024.py index f32b9da..f7806fd 100644 --- a/nupes/scripts/import_candidats_2024.py +++ b/nupes/scripts/import_candidats_2024.py @@ -118,14 +118,17 @@ def importer_listes(engine: Engine) -> None: liste.nuance_id = liste_dict["nuance_id"] liste.bloc_id = liste_dict["bloc_id"] else: - session.add(Liste(**liste_dict)) + liste = Liste(numero=liste_dict['numero'], nom=liste_dict['nom'], + nom_majuscules=liste_dict['nom'].upper(), nuance_id=liste_dict['nuance_id'], + bloc_id=liste_dict['bloc_id']) + session.add(liste) session.commit() def importer_candidats(engine: Engine) -> None: DATASET_URL = "https://www.data.gouv.fr/fr/datasets/r/483cd4bd-1b0e-4b52-a923-aadadf9c8f62" - file = get_file(DATASET_URL, "candidats_2024-{etag}.csv") + file = get_file(DATASET_URL, "candidats_2024.csv") with file.open('r') as f: with Session(engine) as session: @@ -134,6 +137,7 @@ def importer_candidats(engine: Engine) -> None: line: dict[str, str] numero_panneau = line.get("Numéro de panneau") liste = session.execute(select(Liste).filter_by(numero=numero_panneau)).scalar_one() + liste.nom_majuscules = line['Libellé de la liste'] if candidat := session.execute(select(Candidat).filter_by(liste_id=liste.id, ordre=line['Ordre'])) \ .scalar_one_or_none(): diff --git a/nupes/scripts/import_geographie.py b/nupes/scripts/import_geographie.py index e502a01..ea09bb8 100644 --- a/nupes/scripts/import_geographie.py +++ b/nupes/scripts/import_geographie.py @@ -15,7 +15,7 @@ def importer_regions(engine: Engine) -> None: "/georef-france-region?select=data_processed").json()['data_processed'] file = get_file("https://public.opendatasoft.com/api/explore/v2.1/catalog/datasets" "/georef-france-region/exports/geojson?lang=fr&timezone=Europe%2FBerlin", - "georef-france-region-{etag}.geojson", etag) + "georef-france-region.geojson", etag) with file.open('r') as f: features = json.load(f)['features'] @@ -42,7 +42,7 @@ def importer_departements(engine: Engine) -> None: "/georef-france-departement?select=data_processed").json()['data_processed'] file = get_file("https://public.opendatasoft.com/api/explore/v2.1/catalog/datasets" "/georef-france-departement/exports/geojson?lang=fr&timezone=Europe%2FBerlin", - "georef-france-departement-{etag}.geojson", etag) + "georef-france-departement.geojson", etag) with file.open('r') as f: @@ -72,7 +72,7 @@ def importer_communes(engine: Engine) -> None: "/georef-france-commune?select=data_processed").json()['data_processed'] file = get_file("https://public.opendatasoft.com/api/explore/v2.1/catalog/datasets" "/georef-france-commune/exports/geojson?lang=fr&timezone=Europe%2FBerlin", - "georef-france-commune-{etag}.geojson", etag) + "georef-france-commune.geojson", etag) with file.open('r') as f: features = json.load(f)['features'] @@ -101,7 +101,7 @@ def importer_bureaux_vote(engine: Engine) -> None: "/elections-france-bureau-vote-2022?select=data_processed").json()['data_processed'] file = get_file("https://public.opendatasoft.com/api/explore/v2.1/catalog/datasets" "/elections-france-bureau-vote-2022/exports/geojson?lang=fr&timezone=Europe%2FBerlin", - "elections-france-bureau-vote-2022-{etag}.geojson", etag) + "elections-france-bureau-vote-2022.geojson", etag) with file.open('r') as f: features = json.load(f)['features'] @@ -170,8 +170,8 @@ def importer_contours_bureaux_vote(engine: Engine) -> None: def run(engine: Engine) -> None: - # importer_regions(engine) - # importer_departements(engine) - # importer_communes(engine) + importer_regions(engine) + importer_departements(engine) + importer_communes(engine) importer_bureaux_vote(engine) importer_contours_bureaux_vote(engine) diff --git a/nupes/scripts/import_resultats_web_2024.py b/nupes/scripts/import_resultats_web_2024.py new file mode 100644 index 0000000..bab2d7d --- /dev/null +++ b/nupes/scripts/import_resultats_web_2024.py @@ -0,0 +1,297 @@ +import re + +from bs4 import BeautifulSoup +from sqlalchemy import Engine, select +from sqlalchemy.orm import Session +from tqdm import tqdm + +from nupes.data import get_file +from nupes.models.europeennes2024 import ResultatsFrance, ResultatsRegion, ResultatsDepartement, ResultatsCommune, \ + VoixListeFrance, VoixListeRegion, VoixListeDepartement, VoixListeCommune, Liste +from nupes.models.geographie import Region, Departement, Commune + +BASE_URL = "https://www.resultats-elections.interieur.gouv.fr/europeennes2024/ensemble_geographique" + + +def importer_resultats_france(engine: Engine) -> None: + file = get_file(f"{BASE_URL}/index.html", "resultats2024/resultats.html") + + with file.open() as f: + resultats = analyser_resultats(f) + + if not resultats: + return + + with Session(engine) as session: + if resultats_france := session.execute(select(ResultatsFrance)).scalar_one_or_none(): + resultats_france.inscrits = resultats["inscrits"] + resultats_france.abstentions = resultats["abstentions"] + resultats_france.votants = resultats["votants"] + resultats_france.blancs = resultats["blancs"] + resultats_france.nuls = resultats["nuls"] + resultats_france.exprimes = resultats["exprimes"] + else: + resultats_france = ResultatsFrance( + inscrits=resultats["inscrits"], + abstentions=resultats["abstentions"], + votants=resultats["votants"], + blancs=resultats["blancs"], + nuls=resultats["nuls"], + exprimes=resultats["exprimes"], + ) + session.add(resultats_france) + + for voix_liste in resultats["resultats_par_liste"]: + if voix_liste_france := session.execute( + select(VoixListeFrance).join(Liste).filter_by(nom_majuscules=voix_liste["nom"].upper()) + ).scalar_one_or_none(): + voix_liste_france.voix = voix_liste["voix"] + else: + liste = session.execute(select(Liste).filter_by(nom_majuscules=voix_liste["nom"].upper())).scalar_one() + voix_liste_france = VoixListeFrance(liste_id=liste.id, voix=voix_liste["voix"]) + session.add(voix_liste_france) + + session.commit() + +def importer_resultats_regions(engine: Engine) -> None: + with Session(engine) as session: + for region in tqdm(session.execute(select(Region)).scalars().all(), desc="Régions"): + reg_code = region.code_insee + if reg_code == "984" or reg_code == "989": + # TAAF + Île de Clipperton, pas d'élection + continue + elif reg_code == "977" or reg_code == "978": + # Saint-Barthélemy et Saint-Martin, pas mis dans une région mais dans un unique département + continue + + file = get_file(f"{BASE_URL}/{reg_code}/index.html", + f"resultats2024/region_{reg_code}/resultats.html") + + with file.open() as f: + resultats = analyser_resultats(f) + + if not resultats: + continue + + if resultats_region := session.execute(select(ResultatsRegion).filter_by(region_id=reg_code)) \ + .scalar_one_or_none(): + resultats_region.inscrits = resultats["inscrits"] + resultats_region.abstentions = resultats["abstentions"] + resultats_region.votants = resultats["votants"] + resultats_region.blancs = resultats["blancs"] + resultats_region.nuls = resultats["nuls"] + resultats_region.exprimes = resultats["exprimes"] + else: + resultats_region = ResultatsRegion( + region_id=reg_code, + inscrits=resultats["inscrits"], + abstentions=resultats["abstentions"], + votants=resultats["votants"], + blancs=resultats["blancs"], + nuls=resultats["nuls"], + exprimes=resultats["exprimes"], + ) + session.add(resultats_region) + + for voix_liste in resultats["resultats_par_liste"]: + if voix_liste_region := session.execute( + select(VoixListeRegion).join(Liste).filter_by(nom_majuscules=voix_liste["nom"].upper()) + ).scalar_one_or_none(): + voix_liste_region.voix = voix_liste["voix"] + else: + liste = session.execute(select(Liste).filter_by(nom_majuscules=voix_liste["nom"].upper())) \ + .scalar_one() + voix_liste_region = VoixListeRegion(liste_id=liste.id, voix=voix_liste["voix"]) + session.add(voix_liste_region) + + session.commit() + + +def importer_resultats_departements(engine: Engine) -> None: + with Session(engine) as session: + for dpt in tqdm(session.execute(select(Departement)).scalars().all(), desc="Départements"): + reg_code = dpt.region_code + reg_path = f"{reg_code}/" + dpt_code = dpt.code_insee + if dpt_code == "984" or dpt_code == "989": + # TAAF + Île de Clipperton, pas d'élection + continue + elif dpt_code == "977" or dpt_code == "978": + # Pour une raison obscure, Saint-Barthélemy et Saint-Martin sont dans le département ZX + reg_path = "" + dpt_code = "ZX" + if dpt_code in ["975", "977", "978", "986", "987", "988"]: + # Pour ces collectivités d'outre-mer, qui ne sont pas dans des régions, + # on ne les regroupe pas dans des régions + reg_path = "" + + file = get_file(f"{BASE_URL}/{reg_path}{dpt_code}/index.html", + f"resultats2024/region_{reg_code}/dpt_{dpt_code}/resultats.html") + + with file.open() as f: + resultats = analyser_resultats(f) + + if not resultats: + continue + + if resultats_departement := session.execute( + select(ResultatsDepartement).filter_by(dpt_id=dpt_code)).scalar_one_or_none(): + resultats_departement.inscrits = resultats["inscrits"] + resultats_departement.abstentions = resultats["abstentions"] + resultats_departement.votants = resultats["votants"] + resultats_departement.blancs = resultats["blancs"] + resultats_departement.nuls = resultats["nuls"] + resultats_departement.exprimes = resultats["exprimes"] + else: + resultats_departement = ResultatsDepartement( + dpt_id=dpt_code, + inscrits=resultats["inscrits"], + abstentions=resultats["abstentions"], + votants=resultats["votants"], + blancs=resultats["blancs"], + nuls=resultats["nuls"], + exprimes=resultats["exprimes"], + ) + session.add(resultats_departement) + + for voix_liste in resultats["resultats_par_liste"]: + if voix_liste_departement := session.execute( + select(VoixListeDepartement).join(Liste).filter_by(nom_majuscules=voix_liste["nom"].upper()) + ).scalar_one_or_none(): + voix_liste_departement.voix = voix_liste["voix"] + else: + liste = session.execute(select(Liste).filter_by(nom_majuscules=voix_liste["nom"].upper())) \ + .scalar_one() + voix_liste_departement = VoixListeDepartement(liste_id=liste.id, voix=voix_liste["voix"]) + session.add(voix_liste_departement) + + session.commit() + + +def importer_resultats_communes(engine: Engine) -> None: + with Session(engine) as session: + for commune in tqdm(session.execute(select(Commune)).scalars().all(), desc="Communes"): + reg_code = commune.departement.region_code + reg_path = f"{reg_code}/" + dpt_code = commune.departement_code + com_code = commune.code_insee + if dpt_code == "984" or dpt_code == "989": + # TAAF + Île de Clipperton, pas d'élection + continue + elif dpt_code == "977" or dpt_code == "978": + # Pour une raison obscure, Saint-Barthélemy et Saint-Martin sont dans le département ZX + reg_path = "" + dpt_code = "ZX" + com_code = f"ZX{com_code[2:]}" + if dpt_code in ["975", "977", "978", "986", "987", "988"]: + # Pour ces collectivités d'outre-mer, qui ne sont pas dans des régions, + # on ne les regroupe pas dans des régions + reg_path = "" + if com_code in ["16355", "25282", "55050", "55189", "69152"]: + # Aucun⋅e habitant⋅e donc pas de bureau de vote + # Ou alors la commune a récemment fusionné + continue + + file = get_file(f"{BASE_URL}/{reg_path}{dpt_code}/{com_code}/index.html", + f"resultats2024/region_{reg_code}/dpt_{dpt_code}" + f"/commune_{com_code}/resultats.html") + if not file: + continue + + with file.open() as f: + resultats = analyser_resultats(f) + + if not resultats: + continue + + if resultats_commune := session.execute( + select(ResultatsCommune).filter_by(commune_id=com_code)).scalar_one_or_none(): + resultats_commune.inscrits = resultats["inscrits"] + resultats_commune.abstentions = resultats["abstentions"] + resultats_commune.votants = resultats["votants"] + resultats_commune.blancs = resultats["blancs"] + resultats_commune.nuls = resultats["nuls"] + resultats_commune.exprimes = resultats["exprimes"] + else: + resultats_commune = ResultatsCommune( + commune_id=com_code, + inscrits=resultats["inscrits"], + abstentions=resultats["abstentions"], + votants=resultats["votants"], + blancs=resultats["blancs"], + nuls=resultats["nuls"], + exprimes=resultats["exprimes"], + ) + session.add(resultats_commune) + + for voix_liste in resultats["resultats_par_liste"]: + if voix_liste_commune := session.execute( + select(VoixListeCommune).join(Liste).filter_by(nom_majuscules=voix_liste["nom"].upper()) + ).scalar_one_or_none(): + voix_liste_commune.voix = voix_liste["voix"] + else: + liste = session.execute(select(Liste).filter_by(nom_majuscules=voix_liste["nom"].upper())) \ + .scalar_one() + voix_liste_commune = VoixListeCommune(liste_id=liste.id, voix=voix_liste["voix"]) + session.add(voix_liste_commune) + + session.commit() + + +def analyser_resultats(file) -> dict: + parsed_data = {} + + soup = BeautifulSoup(file, 'html.parser') + tables = soup.find_all('table') + + for table in tables: + if table.find('th', text=re.compile("Voix")): + resultats_par_liste = [] + parsed_data['resultats_par_liste'] = resultats_par_liste + + tbody = table.tbody + for line in tbody.find_all('tr'): + cells = line.find_all('td') + if len(cells) == 0: + continue + + liste = {} + resultats_par_liste.append(liste) + + liste['nom'] = cells[0].string + liste['voix'] = int(cells[1].string.replace(" ", "")) + elif table.find('td', text=re.compile("Inscrits")): + tbody = table.tbody + for line in tbody.find_all('tr'): + cells = line.find_all('td') + if len(cells) == 0: + continue + + nom = cells[0].string + number = int(cells[1].string.replace(" ", "")) + match nom: + case "Inscrits": + parsed_data["inscrits"] = number + case "Abstentions": + parsed_data["abstentions"] = number + case "Votants": + parsed_data["votants"] = number + case "Blancs": + parsed_data["blancs"] = number + case "Nuls": + parsed_data["nuls"] = number + case "Exprimés": + parsed_data["exprimes"] = number + case _: + print(f"Unknown cell: {nom}") + + return parsed_data + + +def run(engine: Engine) -> None: + importer_resultats_france(engine) + importer_resultats_regions(engine) + importer_resultats_departements(engine) + importer_resultats_communes(engine) + + analyser_resultats("") diff --git a/requirements.txt b/requirements.txt index c872d26..7ac5659 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ alembic~=1.13.1 +beautifulsoup4~=4.12.3 python-dotenv~=1.0.1 requests~=2.32.3 SQLAlchemy~=2.0.30