1238 lines
53 KiB
Python
Executable File
1238 lines
53 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
from collections import OrderedDict
|
|
import csv
|
|
from datetime import datetime
|
|
from math import log, pi, tan
|
|
import enum
|
|
import json
|
|
|
|
from flask import Flask, Response, abort, redirect
|
|
from flask_migrate import Migrate
|
|
from flask_sqlalchemy import SQLAlchemy
|
|
import requests
|
|
from sqlalchemy import Boolean, Column, Date, Enum, Float, ForeignKey, Integer, JSON, String, desc, func
|
|
from sqlalchemy.orm import relationship
|
|
from tqdm import tqdm
|
|
|
|
app = Flask(__name__)
|
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://nupes:nupes@psql.adm.ynerant.fr:5432/nupes'
|
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
|
|
app.config['SECRET_KEY'] = "random string"
|
|
# app.config['SQLALCHEMY_ECHO'] = True
|
|
|
|
db = SQLAlchemy(app)
|
|
Migrate(app, db)
|
|
|
|
|
|
class Nuance(db.Model):
|
|
__tablename__ = 'nuance'
|
|
code = Column(String(3), primary_key=True, nullable=False)
|
|
name = Column(String(255), unique=True, nullable=False)
|
|
color = Column(String(6), nullable=False)
|
|
|
|
|
|
class CandidatPresidentielle(db.Model):
|
|
__tablename__ = 'candidat_presidentielle'
|
|
id = Column(Integer, primary_key=True)
|
|
last_name = Column(String(255))
|
|
first_name = Column(String(255))
|
|
slug = Column(String(255), unique=True)
|
|
nuance_id = Column(String(3), ForeignKey('nuance.code'))
|
|
nuance = relationship('Nuance')
|
|
|
|
|
|
class CandidatLegislatives(db.Model):
|
|
class Gender(enum.Enum):
|
|
MALE = 'M'
|
|
FEMALE = 'F'
|
|
|
|
__tablename__ = 'candidat_legislatives'
|
|
id = Column(Integer, primary_key=True)
|
|
last_name = Column(String(255))
|
|
first_name = Column(String(255))
|
|
birth_date = Column(Date)
|
|
gender = Column(Enum(Gender))
|
|
nuance_id = Column(String(3), ForeignKey('nuance.code'))
|
|
nuance = relationship('Nuance')
|
|
circonscription_id = Column(String(8), ForeignKey('circonscription.id'))
|
|
circonscription = relationship('Circonscription')
|
|
pane_number = Column(Integer)
|
|
job = Column(String(255))
|
|
exitting = Column(Boolean)
|
|
last_name_suppl = Column(String(255))
|
|
first_name_suppl = Column(String(255))
|
|
birth_date_suppl = Column(Date)
|
|
gender_suppl = Column(Enum(Gender))
|
|
exitting_suppl = Column(Boolean)
|
|
|
|
|
|
class Region(db.Model):
|
|
__tablename__ = 'region'
|
|
id = Column(Integer, primary_key=True)
|
|
name = Column(String(32), unique=True)
|
|
code = Column(Integer, unique=True)
|
|
|
|
|
|
class Departement(db.Model):
|
|
__tablename__ = 'departement'
|
|
id = Column(Integer, primary_key=True)
|
|
name = Column(String(32), unique=True)
|
|
region_id = Column(Integer, ForeignKey('region.id'))
|
|
region = relationship('Region')
|
|
|
|
|
|
class Circonscription(db.Model):
|
|
__tablename__ = 'circonscription'
|
|
id = Column(String(8), primary_key=True)
|
|
number = Column(Integer)
|
|
label = Column(String(255))
|
|
departement_id = Column(Integer, ForeignKey("departement.id"))
|
|
departement = relationship('Departement')
|
|
geometry = Column(JSON)
|
|
|
|
|
|
class Commune(db.Model):
|
|
__tablename__ = 'commune'
|
|
id = Column(Integer, primary_key=True)
|
|
code = Column(Integer)
|
|
name = Column(String(255), index=True)
|
|
departement_id = Column(Integer, ForeignKey('departement.id'))
|
|
departement = relationship('Departement')
|
|
|
|
|
|
class BureauVote(db.Model):
|
|
__tablename__ = 'bureauvote'
|
|
id = Column(String(16), primary_key=True)
|
|
label = Column(String(255))
|
|
code = Column(String(8))
|
|
epci_code = Column(Integer)
|
|
epci_name = Column(String(255))
|
|
address = Column(String(255))
|
|
circonscription_id = Column(String(8), ForeignKey('circonscription.id'))
|
|
circonscription = relationship('Circonscription')
|
|
commune_id = Column(Integer, ForeignKey('commune.id'))
|
|
commune = relationship('Commune')
|
|
latitude = Column(Float, nullable=True)
|
|
longitude = Column(Float, nullable=True)
|
|
|
|
|
|
class VotePresidentielle(db.Model):
|
|
__tablename__ = 'vote_presidentielle'
|
|
id = Column(Integer, primary_key=True)
|
|
bv_id = Column(String(16), ForeignKey('bureauvote.id'))
|
|
bv = relationship('BureauVote')
|
|
inscrits = Column(Integer)
|
|
votants = Column(Integer)
|
|
abstentions = Column(Integer)
|
|
exprimes = Column(Integer)
|
|
blancs = Column(Integer)
|
|
nuls = Column(Integer)
|
|
|
|
|
|
class VoteCandidatPresidentielle(db.Model):
|
|
__tablename__ = 'vote_candidat_presidentielle'
|
|
id = Column(Integer, primary_key=True)
|
|
vote_id = Column(Integer, ForeignKey('vote_presidentielle.id'))
|
|
vote = relationship('VotePresidentielle')
|
|
candidat_id = Column(Integer, ForeignKey('candidat_presidentielle.id'))
|
|
candidat = relationship('CandidatPresidentielle')
|
|
voix = Column(Integer)
|
|
|
|
|
|
class VoteLegislatives(db.Model):
|
|
__tablename__ = 'vote_legislatives'
|
|
id = Column(Integer, primary_key=True)
|
|
circonscription_id = Column(String(8), ForeignKey('circonscription.id'))
|
|
circonscription = relationship('Circonscription')
|
|
round = Column(Integer, default=1)
|
|
inscrits = Column(Integer)
|
|
votants = Column(Integer)
|
|
abstentions = Column(Integer)
|
|
exprimes = Column(Integer)
|
|
blancs = Column(Integer)
|
|
nuls = Column(Integer)
|
|
|
|
|
|
class VoteCandidatLegislatives(db.Model):
|
|
__tablename__ = 'vote_candidat_legislatives'
|
|
id = Column(Integer, primary_key=True)
|
|
vote_id = Column(Integer, ForeignKey('vote_legislatives.id'))
|
|
vote = relationship('VoteLegislatives')
|
|
candidat_id = Column(Integer, ForeignKey('candidat_legislatives.id'))
|
|
candidat = relationship('CandidatLegislatives')
|
|
voix = Column(Integer)
|
|
|
|
|
|
@app.get('/upload')
|
|
def upload():
|
|
content = ""
|
|
|
|
with open('elections-france-presidentielles-2022-1er-tour-par-bureau-de-vote.json') as f:
|
|
data = json.load(f)
|
|
#data = []
|
|
|
|
candidats = {candidat.last_name: candidat for candidat in CandidatPresidentielle.query.all()}
|
|
regs = {reg.code: reg for reg in Region.query.all()}
|
|
dpts = {dpt.id: dpt for dpt in Departement.query.all()}
|
|
circos = {circo.id: circo for circo in Circonscription.query.all()}
|
|
comms = {(comm.code, comm.name): comm for comm in Commune.query.all()}
|
|
bvs = {bv.id: bv for bv in BureauVote.query.all()}
|
|
vps = {vp.bv_id: vp for vp in VotePresidentielle.query.all()}
|
|
vcps = {(vcp.vote_id, vcp.candidat_id): vcp for vcp in VoteCandidatPresidentielle.query.all()}
|
|
|
|
for i, record in enumerate(tqdm(data)):
|
|
try:
|
|
fields = record['fields']
|
|
|
|
dpt_id = fields['dep_code']
|
|
dpt_name = fields['dep_name']
|
|
if dpt_name == "Corse-du-Sud":
|
|
dpt_id = 201
|
|
elif dpt_name == "Haute-Corse":
|
|
dpt_id = 202
|
|
elif dpt_name == "Guadeloupe":
|
|
dpt_id = 971
|
|
elif dpt_name == "Martinique":
|
|
dpt_id = 972
|
|
elif dpt_name == "Guyane":
|
|
dpt_id = 973
|
|
elif dpt_name == "La Réunion":
|
|
dpt_id = 974
|
|
elif dpt_name == "Saint-Pierre-et-Miquelon":
|
|
dpt_id = 975
|
|
elif dpt_name == "Mayotte":
|
|
dpt_id = 976
|
|
elif dpt_name == "Saint-Martin/Saint-Barthélemy":
|
|
dpt_id = 977
|
|
elif dpt_name == "Wallis et Futuna":
|
|
dpt_id = 986
|
|
elif dpt_name == "Polynésie française":
|
|
dpt_id = 987
|
|
elif dpt_name == "Nouvelle-Calédonie":
|
|
dpt_id = 988
|
|
else:
|
|
dpt_id = int(dpt_id)
|
|
|
|
reg_name = fields.get('reg_name', dpt_name)
|
|
reg_code = int(fields.get('reg_code', dpt_id))
|
|
if reg_code in regs:
|
|
reg = regs[reg_code]
|
|
else:
|
|
q = Region.query.filter(Region.code == reg_code)
|
|
if True or not q.count():
|
|
reg = Region(code=reg_code, name=reg_name)
|
|
db.session.add(reg)
|
|
else:
|
|
reg = q.one()
|
|
regs[reg_code] = reg
|
|
|
|
if dpt_id in dpts:
|
|
dpt = dpts[dpt_id]
|
|
else:
|
|
q = Departement.query.filter(Departement.id == dpt_id)
|
|
if True or not q.count():
|
|
dpt = Departement(id=dpt_id, name=dpt_name, region_id=reg.id)
|
|
db.session.add(dpt)
|
|
else:
|
|
dpt = q.one()
|
|
dpts[dpt_id] = dpt
|
|
|
|
circo_label = fields['libelle_de_la_circonscription']
|
|
circo_nb = int(fields['code_de_la_circonscription'])
|
|
circo_id = f"{dpt_id:003d}-{circo_nb:02d}"
|
|
if circo_id in circos:
|
|
circo = circos[circo_id]
|
|
else:
|
|
q = Circonscription.query.filter(Circonscription.id == circo_id)
|
|
if True or not q.count():
|
|
circo = Circonscription(id=circo_id, number=circo_nb,
|
|
label=circo_label, departement_id=dpt.id)
|
|
db.session.add(circo)
|
|
else:
|
|
circo = q.one()
|
|
circos[circo_id] = circo
|
|
|
|
com_code = int(fields['com_code'].replace('2A', '201').replace('2B', '202'))
|
|
com_name = fields['com_name']
|
|
if (com_code, com_name) in comms:
|
|
com = comms[(com_code, com_name)]
|
|
else:
|
|
q = Commune.query.filter(Commune.code == com_code, Commune.name == com_name)
|
|
if True or not q.count():
|
|
com = Commune(code=com_code, name=com_name, departement_id=dpt_id)
|
|
db.session.add(com)
|
|
else:
|
|
com = q.one()
|
|
comms[(com_code, com_name)] = com
|
|
|
|
bv_code = fields['code_du_b_vote']
|
|
if bv_code.endswith('.0'):
|
|
bv_code = bv_code[:-2]
|
|
bv_id = f"{com_code}-{bv_code}"
|
|
if bv_id in bvs:
|
|
bv = bvs[bv_id]
|
|
else:
|
|
q = BureauVote.query.filter(BureauVote.id == bv_id)
|
|
if True or not q.count():
|
|
bv_label = fields.get('lib_du_b_vote', "")
|
|
bv_location = fields.get('location', None)
|
|
bv_address = fields.get('address', "")
|
|
bv_epci_name = fields.get('epci_name', "")
|
|
bv_epci_code = int(fields.get('epci_code', "0"))
|
|
bv = BureauVote(id=bv_id, code=bv_code, label=bv_label, address=bv_address,
|
|
epci_name=bv_epci_name, epci_code=bv_epci_code,
|
|
circonscription_id=circo.id, commune_id=com.id)
|
|
if bv_location:
|
|
bv.latitude, bv.longitude = bv_location
|
|
db.session.add(bv)
|
|
else:
|
|
bv = q.one()
|
|
bvs[bv_id] = bv
|
|
|
|
# q = VotePresidentielle.query.filter(VotePresidentielle.bv_id == bv_id)
|
|
# if not q.count():
|
|
if bv_id not in vps:
|
|
vp = VotePresidentielle(bv_id=bv_id,
|
|
inscrits=fields['inscrits'],
|
|
abstentions=fields['abstentions'],
|
|
votants=fields['votants'],
|
|
exprimes=fields['exprimes'],
|
|
nuls=fields['nuls'],
|
|
blancs=fields['blancs'])
|
|
vps[bv_id] = vp
|
|
db.session.add(vp)
|
|
else:
|
|
vp = vps[bv_id]
|
|
# vp = q.one()
|
|
|
|
candidat = candidats[fields['nom']]
|
|
# candidat = CandidatPresidentielle.query.filter(
|
|
# CandidatPresidentielle.last_name == fields['nom']).one()
|
|
|
|
if (vp.id, candidat.id) not in vcps:
|
|
# q = VoteCandidatPresidentielle.query.filter(
|
|
# VoteCandidatPresidentielle.vote_id == vp.id,
|
|
# VoteCandidatPresidentielle.candidat_id == candidat.id,
|
|
# )
|
|
# if not q.count():
|
|
vcp = VoteCandidatPresidentielle(
|
|
vote_id=vp.id,
|
|
candidat_id=candidat.id,
|
|
voix=fields['voix'],
|
|
)
|
|
vcps[(vp.id, candidat.id)] = vcp
|
|
db.session.add(vcp)
|
|
except Exception:
|
|
print(record['fields'])
|
|
raise
|
|
|
|
if i % 50000 == 0:
|
|
db.session.commit()
|
|
db.session.commit()
|
|
|
|
return "done"
|
|
|
|
|
|
@app.get('/upload-circos')
|
|
def upload_circos():
|
|
with open('france-circonscriptions-legislatives-2012.json') as f:
|
|
data = json.load(f)
|
|
|
|
for feature in tqdm(data['features']):
|
|
prop = feature['properties']
|
|
dpt_id = prop['code_dpt']
|
|
num_circ = int(prop['num_circ'])
|
|
if dpt_id == '2A':
|
|
# Corse du Sud
|
|
dpt_id = 201
|
|
elif dpt_id == '2B':
|
|
# Haute-Corse
|
|
dpt_id = 202
|
|
elif dpt_id == 'ZA':
|
|
# Guadeloupe
|
|
dpt_id = 971
|
|
elif dpt_id == 'ZB':
|
|
# Martinique
|
|
dpt_id = 972
|
|
elif dpt_id == 'ZC':
|
|
# Guyane
|
|
dpt_id = 973
|
|
elif dpt_id == 'ZD':
|
|
# La Réunion
|
|
dpt_id = 974
|
|
elif dpt_id == 'ZS':
|
|
# Saint-Pierre-et-Miquelon
|
|
dpt_id = 975
|
|
elif dpt_id == 'ZM':
|
|
# Mayotte
|
|
dpt_id = 976
|
|
elif dpt_id == 'ZX':
|
|
# Saint-Martin / Saint-Barthélémy
|
|
dpt_id = 977
|
|
elif dpt_id == 'ZW':
|
|
# Wallis-et-Futuna
|
|
dpt_id = 986
|
|
elif dpt_id == 'ZP':
|
|
# Polynésie française
|
|
dpt_id = 987
|
|
elif dpt_id == 'ZN':
|
|
# Nouvelle-Calédonie
|
|
dpt_id = 988
|
|
else:
|
|
dpt_id = int(dpt_id)
|
|
circ = Circonscription.query.filter(Circonscription.departement_id == dpt_id,
|
|
Circonscription.number == num_circ).one()
|
|
circ.geometry = feature['geometry']['coordinates']
|
|
db.session.add(circ)
|
|
db.session.commit()
|
|
|
|
return "done"
|
|
|
|
|
|
@app.get('/upload-legislatives')
|
|
def upload_legislatives():
|
|
with open('candidats-legislatives-2022.csv') as f:
|
|
csvfile = csv.DictReader(f)
|
|
|
|
for row in tqdm(csvfile):
|
|
dpt_id = row['Code du département']
|
|
if dpt_id == '2A':
|
|
# Corse du Sud
|
|
dpt_id = 201
|
|
elif dpt_id == '2B':
|
|
# Haute-Corse
|
|
dpt_id = 202
|
|
elif dpt_id == 'ZA':
|
|
# Guadeloupe
|
|
dpt_id = 971
|
|
elif dpt_id == 'ZB':
|
|
# Martinique
|
|
dpt_id = 972
|
|
elif dpt_id == 'ZC':
|
|
# Guyane
|
|
dpt_id = 973
|
|
elif dpt_id == 'ZD':
|
|
# La Réunion
|
|
dpt_id = 974
|
|
elif dpt_id == 'ZS':
|
|
# Saint-Pierre-et-Miquelon
|
|
dpt_id = 975
|
|
elif dpt_id == 'ZM':
|
|
# Mayotte
|
|
dpt_id = 976
|
|
elif dpt_id == 'ZX':
|
|
# Saint-Martin / Saint-Barthélémy
|
|
dpt_id = 977
|
|
elif dpt_id == 'ZW':
|
|
# Wallis-et-Futuna
|
|
dpt_id = 986
|
|
elif dpt_id == 'ZP':
|
|
# Polynésie française
|
|
dpt_id = 987
|
|
elif dpt_id == 'ZN':
|
|
# Nouvelle-Calédonie
|
|
dpt_id = 988
|
|
elif dpt_id == 'ZZ':
|
|
# Français⋅es établi⋅es à l'étranger
|
|
dpt_id = 97
|
|
else:
|
|
dpt_id = int(dpt_id)
|
|
|
|
circo_nb = int(row['Code circonscription'])
|
|
circo_id = f'{dpt_id:03d}-{circo_nb:02d}'
|
|
pane_id = row['N° panneau']
|
|
last_name = row['Nom candidat']
|
|
first_name = row['Prénom candidat']
|
|
gender = CandidatLegislatives.Gender(row['Sexe candidat'])
|
|
birth_date = datetime.strptime(row['Date naissance candidat'], '%d/%m/%Y').date()
|
|
nuance_id = row['Nuance candidat']
|
|
pane_number = row['N° panneau']
|
|
job = row['Profession candidat']
|
|
exitting = row['Le candidat est sortant'] == 'oui'
|
|
last_name_suppl = row['Nom remplaçant']
|
|
first_name_suppl = row['Prénom remplaçant']
|
|
birth_date_suppl = datetime.strptime(row['Date naiss. remplaçant'], '%d/%m/%Y').date()
|
|
gender_suppl = CandidatLegislatives.Gender(row['Sexe remplaçant'])
|
|
exitting_suppl = row['Le remplaçant est sortant'] == 'oui'
|
|
|
|
q = CandidatLegislatives.query.filter(
|
|
CandidatLegislatives.last_name == last_name,
|
|
CandidatLegislatives.first_name == first_name,
|
|
CandidatLegislatives.circonscription_id == circo_id,
|
|
CandidatLegislatives.nuance_id == nuance_id,
|
|
)
|
|
|
|
if not q.count():
|
|
candidat = CandidatLegislatives(
|
|
last_name=last_name,
|
|
first_name=first_name,
|
|
birth_date=birth_date,
|
|
gender=gender,
|
|
nuance_id=nuance_id,
|
|
circonscription_id=circo_id,
|
|
pane_number=pane_number,
|
|
job=job,
|
|
exitting=exitting,
|
|
last_name_suppl=last_name_suppl,
|
|
first_name_suppl=first_name_suppl,
|
|
gender_suppl=gender_suppl,
|
|
exitting_suppl=exitting_suppl,
|
|
)
|
|
db.session.add(candidat)
|
|
|
|
db.session.commit()
|
|
|
|
return ""
|
|
|
|
|
|
@app.get('/draw/<map_type>')
|
|
def draw(map_type: str):
|
|
groups = [
|
|
(Circonscription.query.filter((Circonscription.departement_id <= 95) | (Circonscription.departement_id == 201) | (Circonscription.departement_id == 202)), 30000),
|
|
(Circonscription.query.filter(Circonscription.departement_id.in_([77, 78, 91, 95])), 60000),
|
|
(Circonscription.query.filter(Circonscription.departement_id.in_([75, 92, 93, 94])), 200000),
|
|
(Circonscription.query.filter(Circonscription.departement_id == 69), 60000),
|
|
(Circonscription.query.filter(Circonscription.departement_id == 13), 60000),
|
|
(Circonscription.query.filter(Circonscription.departement_id == 971), 40000),
|
|
(Circonscription.query.filter(Circonscription.departement_id == 972), 40000),
|
|
(Circonscription.query.filter(Circonscription.departement_id == 973), 10000),
|
|
(Circonscription.query.filter(Circonscription.departement_id == 974), 40000),
|
|
(Circonscription.query.filter(Circonscription.departement_id == 975), 40000),
|
|
(Circonscription.query.filter(Circonscription.departement_id == 976), 40000),
|
|
(Circonscription.query.filter(Circonscription.departement_id == 977), 60000),
|
|
(Circonscription.query.filter(Circonscription.departement_id == 986), 60000),
|
|
(Circonscription.query.filter(Circonscription.departement_id == 987), 5000),
|
|
(Circonscription.query.filter(Circonscription.departement_id == 988), 20000),
|
|
(Circonscription.query.filter(Circonscription.id == '097-01'), 30000),
|
|
(Circonscription.query.filter(Circonscription.id == '097-02'), 30000),
|
|
(Circonscription.query.filter(Circonscription.id == '097-03'), 30000),
|
|
(Circonscription.query.filter(Circonscription.id == '097-04'), 30000),
|
|
(Circonscription.query.filter(Circonscription.id == '097-05'), 30000),
|
|
(Circonscription.query.filter(Circonscription.id == '097-06'), 30000),
|
|
(Circonscription.query.filter(Circonscription.id == '097-07'), 30000),
|
|
(Circonscription.query.filter(Circonscription.id == '097-08'), 30000),
|
|
(Circonscription.query.filter(Circonscription.id == '097-09'), 30000),
|
|
(Circonscription.query.filter(Circonscription.id == '097-10'), 30000),
|
|
(Circonscription.query.filter(Circonscription.id == '097-11'), 30000),
|
|
]
|
|
|
|
nuances = {n.code: n for n in Nuance.query.all()}
|
|
|
|
content = "<!doctype html>\n<html lang=\"fr\">\n"
|
|
content += "<head>\n<meta charset=\"utf-8\">\n<title>Élections législatives - 2ème tour</title>\n<style>\n"
|
|
content += "\nul.nuanced {\n\tlist-style: none;\n}\n"
|
|
for nuance in nuances.values():
|
|
content += f"ul li.{nuance.code.lower()}::before {{\n"
|
|
content += "\tcontent: \"\\25A0\";\n"
|
|
content += f"\tcolor: #{nuance.color};\n"
|
|
content += "\tfont-weight: bold;\n"
|
|
content += "\tdisplay: inline-block;\n"
|
|
content += "\twidth: 1em;\n"
|
|
content += "\tmargin-left: -1em;\n"
|
|
content += "}\n"
|
|
|
|
content += "</style>\n</head>\n<body>\n"
|
|
|
|
w, h = 20000, 20000
|
|
|
|
voix_pres = VoteCandidatPresidentielle.query\
|
|
.filter(VoteCandidatPresidentielle.vote_id == VotePresidentielle.id)\
|
|
.filter(VoteCandidatPresidentielle.candidat_id == CandidatPresidentielle.id)\
|
|
.filter(BureauVote.id == VotePresidentielle.bv_id)\
|
|
.group_by(CandidatPresidentielle.nuance_id, BureauVote.circonscription_id)\
|
|
.with_entities(CandidatPresidentielle.nuance_id, BureauVote.circonscription_id,
|
|
func.sum(VoteCandidatPresidentielle.voix).label('tot_voix'))\
|
|
.order_by(BureauVote.circonscription_id, desc('tot_voix')).all()
|
|
per_circo_pres = {}
|
|
for row in voix_pres:
|
|
per_circo_pres.setdefault(row[1], OrderedDict())
|
|
per_circo_pres[row[1]][row[0]] = row[2]
|
|
|
|
voix_leg_1 = VoteCandidatLegislatives.query\
|
|
.filter(VoteCandidatLegislatives.vote_id == VoteLegislatives.id)\
|
|
.filter(VoteLegislatives.round == 1)\
|
|
.filter(VoteCandidatLegislatives.candidat_id == CandidatLegislatives.id)\
|
|
.with_entities(CandidatLegislatives.circonscription_id, CandidatLegislatives.last_name,
|
|
CandidatLegislatives.first_name, CandidatLegislatives.nuance_id,
|
|
VoteCandidatLegislatives.voix)\
|
|
.order_by(CandidatLegislatives.circonscription_id, VoteCandidatLegislatives.voix.desc(),
|
|
CandidatLegislatives.pane_number).all()
|
|
|
|
voix_leg_2 = VoteCandidatLegislatives.query\
|
|
.filter(VoteCandidatLegislatives.vote_id == VoteLegislatives.id)\
|
|
.filter(VoteLegislatives.round == 2)\
|
|
.filter(VoteCandidatLegislatives.candidat_id == CandidatLegislatives.id)\
|
|
.with_entities(CandidatLegislatives.circonscription_id, CandidatLegislatives.last_name,
|
|
CandidatLegislatives.first_name, CandidatLegislatives.nuance_id,
|
|
VoteCandidatLegislatives.voix)\
|
|
.order_by(CandidatLegislatives.circonscription_id, VoteCandidatLegislatives.voix.desc(),
|
|
CandidatLegislatives.pane_number).all()
|
|
|
|
votes_leg_1 = {vl.circonscription_id: vl for vl in VoteLegislatives.query\
|
|
.filter(VoteLegislatives.round == 1).all()}
|
|
votes_leg_2 = {vl.circonscription_id: vl for vl in VoteLegislatives.query\
|
|
.filter(VoteLegislatives.round == 2).all()}
|
|
|
|
scores_1_n = {n: 0 for n in nuances}
|
|
scores_2_n = {n: 0 for n in nuances}
|
|
stats_1 = {
|
|
'inscrits': sum(vote.inscrits for vote in votes_leg_1.values() if vote.inscrits),
|
|
'abstentions': sum(vote.abstentions for vote in votes_leg_1.values() if vote.abstentions),
|
|
'votants': sum(vote.votants for vote in votes_leg_1.values() if vote.votants),
|
|
'exprimes': sum(vote.exprimes for vote in votes_leg_1.values() if vote.exprimes),
|
|
'blancs': sum(vote.blancs for vote in votes_leg_1.values() if vote.blancs),
|
|
'nuls': sum(vote.nuls for vote in votes_leg_1.values() if vote.nuls),
|
|
}
|
|
stats_2 = {
|
|
'inscrits': sum(vote.inscrits for vote in votes_leg_2.values() if vote.inscrits),
|
|
'abstentions': sum(vote.abstentions for vote in votes_leg_2.values() if vote.abstentions),
|
|
'votants': sum(vote.votants for vote in votes_leg_2.values() if vote.votants),
|
|
'exprimes': sum(vote.exprimes for vote in votes_leg_2.values() if vote.exprimes),
|
|
'blancs': sum(vote.blancs for vote in votes_leg_2.values() if vote.blancs),
|
|
'nuls': sum(vote.nuls for vote in votes_leg_2.values() if vote.nuls),
|
|
}
|
|
|
|
premier_tour, premier_tour_n = set(), {n: 0 for n in nuances}
|
|
second_tour_1, second_tour_1_n = set(), {n: 0 for n in nuances}
|
|
second_tour_2, second_tour_2_n = set(), {n: 0 for n in nuances}
|
|
configurations = {}
|
|
|
|
winners = {n: 0 for n in nuances}
|
|
|
|
per_circo_leg_1, per_circo_leg_2 = {}, {}
|
|
for row in voix_leg_1:
|
|
per_circo_leg_1.setdefault(row[0], [])
|
|
per_circo_leg_1[row[0]].append(row[1:])
|
|
for row in voix_leg_2:
|
|
per_circo_leg_2.setdefault(row[0], [])
|
|
per_circo_leg_2[row[0]].append(row[1:])
|
|
|
|
for circo_id, rows in per_circo_leg_1.items():
|
|
vote = votes_leg_1[circo_id]
|
|
if vote.exprimes:
|
|
winner_r = rows[0]
|
|
if winner_r[3] / vote.exprimes >= 0.5 and winner_r[3] / vote.inscrits >= 0.25:
|
|
premier_tour.add(f"{winner_r[1]} {winner_r[0]} ({winner_r[2]}) - {circo_id}")
|
|
premier_tour_n[winner_r[2]] += 1
|
|
winners[winner_r[2]] += 1
|
|
else:
|
|
second_tour_1.add(f"{winner_r[1]} {winner_r[0]} ({winner_r[2]}) - {circo_id}")
|
|
second_tour_1_n[winner_r[2]] += 1
|
|
sec = rows[1]
|
|
second_tour_2.add(f"{sec[1]} {sec[0]} ({sec[2]}) - {circo_id}")
|
|
second_tour_2_n[sec[2]] += 1
|
|
tr = rows[2]
|
|
config = [winner_r[2], sec[2]]
|
|
if tr[3] / vote.inscrits >= 0.125:
|
|
print("3ème position :", tr)
|
|
second_tour_2.add(f"{tr[1]} {tr[0]} ({tr[2]}) - {circo_id}")
|
|
second_tour_2_n[tr[2]] += 1
|
|
config.append(tr[2])
|
|
|
|
config = tuple(sorted(config))
|
|
configurations.setdefault(config, 0)
|
|
configurations[config] += 1
|
|
|
|
for r in rows:
|
|
scores_1_n[r[2]] += r[3]
|
|
|
|
for circo_id, rows in per_circo_leg_2.items():
|
|
vote = votes_leg_2[circo_id]
|
|
if vote.exprimes:
|
|
winners[rows[0][2]] += 1
|
|
for r in rows:
|
|
scores_2_n[r[2]] += r[3]
|
|
|
|
for group, ratio in groups:
|
|
minx, miny, maxx, maxy = w, h, 0, 0
|
|
svg = ""
|
|
for circo in group:
|
|
if circo.departement_id != 97:
|
|
polygons = circo.geometry
|
|
if isinstance(polygons[0][0][0], float):
|
|
polygons = [polygons]
|
|
else:
|
|
polygons = [[[[0, 0], [0, 1], [1, 1], [1, 0]]]]
|
|
|
|
color = 0xffffff
|
|
|
|
scores = {}
|
|
if map_type.startswith('p'):
|
|
if circo.id in per_circo_pres:
|
|
scores = per_circo_pres[circo.id]
|
|
winner_id = next(iter(scores))
|
|
winner = nuances[winner_id]
|
|
color = int(winner.color, 16)
|
|
elif map_type.startswith('l'):
|
|
if circo.id in per_circo_leg_2:
|
|
if '1' in map_type:
|
|
winner_r = per_circo_leg_1[circo.id][0]
|
|
winner = nuances[winner_r[2]]
|
|
if per_circo_leg_1[circo.id][0][3]:
|
|
color = int(winner.color, 16)
|
|
else:
|
|
winner_r = per_circo_leg_2[circo.id][0]
|
|
winner = nuances[winner_r[2]]
|
|
if per_circo_leg_2[circo.id][0][3] or len(per_circo_leg_2[circo.id]) == 1:
|
|
color = int(winner.color, 16)
|
|
scores_1 = {f"{s[1]} {s[0]} ({s[2]})": s[3] for s in per_circo_leg_1[circo.id]}
|
|
scores_2 = {f"{s[1]} {s[0]} ({s[2]})": s[3] for s in per_circo_leg_2[circo.id]}
|
|
|
|
for polygon in polygons:
|
|
if len(polygon) > 1:
|
|
print(circo)
|
|
polygon = polygon[0]
|
|
cartesian = [(w * (p[0] + 180) / 360, h / 2 - w / (2 * pi) * log(tan(pi / 4 + p[1] * pi / 360))) for p in polygon]
|
|
points = cartesian
|
|
|
|
minx = min(minx, min(p[0] for p in points))
|
|
miny = min(miny, min(p[1] for p in points))
|
|
maxx = max(maxx, max(p[0] for p in points))
|
|
maxy = max(maxy, max(p[1] for p in points))
|
|
|
|
svg += f'<a href="/circo/{circo.id}">\n'
|
|
svg += f'<polygon id="circo-{circo.id}" points="{" ".join(f"{p[0]},{p[1]}" for p in points)}" style="fill: #{color:06x}; stroke: black; stroke-width: 0.1%">\n'
|
|
svg += "<title>"
|
|
svg += f"{circo.label} de {circo.departement.name}\n"
|
|
tot_voix_1 = sum(scores_1.values())
|
|
tot_voix_2 = sum(scores_2.values())
|
|
svg += "\n2<sup>ème tour</sup>\n"
|
|
for name, voix in scores_2.items():
|
|
if tot_voix_2:
|
|
svg += f"{name} : {100 * voix / tot_voix_2:.02f} % ({voix})\n"
|
|
else:
|
|
svg += f"{name}\n"
|
|
svg += "\n1<sup>er</sup> tour\n"
|
|
for name, voix in scores_1.items():
|
|
if tot_voix_1:
|
|
svg += f"{name} : {100 * voix / tot_voix_1:.02f} % ({voix})\n"
|
|
else:
|
|
svg += f"{name}\n"
|
|
svg += "</title>\n"
|
|
svg += "</polygon>\n</a>\n"
|
|
|
|
svg += "</svg>\n"
|
|
svg = f'<svg xmlns="http://www.w3.org/2000/svg" width="{(maxx - minx) * ratio / w}" viewbox="{minx} {miny} {maxx - minx} {maxy - miny}">\n' + svg
|
|
content += svg
|
|
|
|
if not map_type.startswith('l'):
|
|
content += "</body>\n</html>\n"
|
|
return content
|
|
|
|
content += "<hr>\n"
|
|
|
|
content += "<h2>Second tour</h2>\n"
|
|
|
|
content += "<h3>Nombre de sièges</h3>\n"
|
|
|
|
content += f"Sièges attribués : {sum(winners.values())}/577\n"
|
|
|
|
content += "<ul class=\"nuanced\">\n"
|
|
for i, (n, seats) in enumerate(sorted(winners.items(), key=lambda x: -x[1])):
|
|
if seats:
|
|
content += f"<li class=\"{n.lower()}\">"
|
|
if i == 0:
|
|
content += "<span style=\"font-weight: bold;\">"
|
|
content += f"{nuances[n].name} : {seats} sièges"
|
|
if i == 0 and seats >= 289:
|
|
content += " MAJORITÉ ABSOLUE</span>"
|
|
elif i == 0:
|
|
content += " MAJORITÉ RELATIVE</span>"
|
|
content += "</li>\n"
|
|
content += "</ul>\n"
|
|
|
|
content += "<h3>Statistiques nationales</h3>\n"
|
|
|
|
if not stats_2['inscrits']:
|
|
# Résultats pas encore arrivés
|
|
# On truque pour éviter les divisions par 0 en attendant
|
|
stats_2['inscrits'] = 1
|
|
stats_2['votants'] = 1
|
|
stats_2['exprimes'] = 1
|
|
|
|
content += "<h4>Résultats nationaux</h4>\n"
|
|
content += "<ul class=\"nuanced\">\n"
|
|
for n, voix in sorted(scores_2_n.items(), key=lambda x: -x[1]):
|
|
content += f"<li class=\"{n.lower()}\">{nuances[n].name} : {100 * voix / stats_2['exprimes']:.02f} % ({voix:,d})</li>\n"
|
|
content += "</ul>\n"
|
|
|
|
content += "<ul>\n"
|
|
content += f"<li>Inscrits : {stats_2['inscrits']:,d}</li>\n"
|
|
content += f"<li>Votant⋅es : {stats_2['votants']:,d} ({100 * stats_2['votants'] / stats_2['inscrits']:.02f} %)</li>\n"
|
|
content += f"<li>Abstentions : {stats_2['abstentions']:,d} ({100 * stats_2['abstentions'] / stats_2['inscrits']:.02f} %)</li>\n"
|
|
content += f"<li>Exprimés : {stats_2['exprimes']:,d} ({100 * stats_2['exprimes'] / stats_2['votants']:.02f} %)</li>\n"
|
|
content += f"<li>Blancs : {stats_2['blancs']:,d} ({100 * stats_2['blancs'] / stats_2['exprimes']:.02f} %)</li>\n"
|
|
content += f"<li>Nuls : {stats_2['nuls']:,d} ({100 * stats_2['nuls'] / stats_2['exprimes']:.02f} %)</li>\n"
|
|
content += "</ul>\n"
|
|
|
|
content += "<hr>\n"
|
|
|
|
content += "<h2>Premier tour</h2>\n"
|
|
|
|
content += "<h3>Statistiques nationales</h3>\n"
|
|
|
|
content += "<h4>Résultats nationaux</h4>\n"
|
|
content += "<ul class=\"nuanced\">\n"
|
|
for n, voix in sorted(scores_1_n.items(), key=lambda x: -x[1]):
|
|
content += f"<li class=\"{n.lower()}\">{nuances[n].name} : {100 * voix / stats_1['exprimes']:.02f} % ({voix:,d})</li>\n"
|
|
content += "</ul>\n"
|
|
|
|
content += "<ul>\n"
|
|
content += f"<li>Inscrits : {stats_1['inscrits']:,d}</li>\n"
|
|
content += f"<li>Votant⋅es : {stats_1['votants']:,d} ({100 * stats_1['votants'] / stats_1['inscrits']:.02f} %)</li>\n"
|
|
content += f"<li>Abstentions : {stats_1['abstentions']:,d} ({100 * stats_1['abstentions'] / stats_1['inscrits']:.02f} %)</li>\n"
|
|
content += f"<li>Exprimés : {stats_1['exprimes']:,d} ({100 * stats_1['exprimes'] / stats_1['votants']:.02f} %)</li>\n"
|
|
content += f"<li>Blancs : {stats_1['blancs']:,d} ({100 * stats_1['blancs'] / stats_1['exprimes']:.02f} %)</li>\n"
|
|
content += f"<li>Nuls : {stats_1['nuls']:,d} ({100 * stats_1['nuls'] / stats_1['exprimes']:.02f} %)</li>\n"
|
|
content += "</ul>\n"
|
|
|
|
content += "<h4>Victoires au premier tour</h4>\n"
|
|
content += "<ul class=\"nuanced\">\n"
|
|
for c in premier_tour:
|
|
nuance_id = c[-13:-10]
|
|
circo_id = c[-6:]
|
|
content += f"<li class=\"{nuance_id.lower()}\"><a href=\"/circo/{circo_id}\">{c}</a></li>\n"
|
|
content += "</ul>\n"
|
|
|
|
content += "<h3>Seconds tours</h3>\n"
|
|
|
|
second_tour_tot_n = {n: second_tour_1_n[n] + second_tour_2_n[n] for n in nuances}
|
|
second_tour_tot_n = {k: v for k, v in second_tour_tot_n.items() if v}
|
|
|
|
content += "<ul class=\"nuanced\">\n"
|
|
for n, tot in sorted(second_tour_tot_n.items(), key=lambda x: -x[1]):
|
|
content += f"<li class=\"{n.lower()}\">{nuances[n].name} : {tot} ({second_tour_1_n[n]} + {second_tour_2_n[n]})</li>\n"
|
|
content += "</ul>\n"
|
|
|
|
content += "<h4>Configurations</h4>\n"
|
|
content += "<ul>\n"
|
|
for config, tot in sorted(configurations.items(), key=lambda x: -x[1]):
|
|
content += f"<li>{' - '.join(config)} : {tot}</li>\n"
|
|
content += "</ul>\n"
|
|
|
|
content += "</body>\n</html>\n"
|
|
|
|
return content
|
|
|
|
|
|
@app.get('/circo/<circo_id>')
|
|
def info_circo(circo_id: str):
|
|
circo = Circonscription.query.filter(Circonscription.id == circo_id)
|
|
if not circo.count():
|
|
abort(404)
|
|
|
|
circo = circo.one()
|
|
|
|
nuances = {n.code: n for n in Nuance.query.all()}
|
|
voix_pres = VoteCandidatPresidentielle.query\
|
|
.filter(VoteCandidatPresidentielle.vote_id == VotePresidentielle.id)\
|
|
.filter(VoteCandidatPresidentielle.candidat_id == CandidatPresidentielle.id)\
|
|
.filter(BureauVote.id == VotePresidentielle.bv_id)\
|
|
.filter(BureauVote.circonscription_id == circo_id)\
|
|
.group_by(CandidatPresidentielle.last_name, CandidatPresidentielle.first_name,
|
|
CandidatPresidentielle.nuance_id)\
|
|
.with_entities(CandidatPresidentielle.last_name, CandidatPresidentielle.first_name,
|
|
CandidatPresidentielle.nuance_id,
|
|
func.sum(VoteCandidatPresidentielle.voix).label('tot_voix'),
|
|
func.sum(VotePresidentielle.inscrits),
|
|
func.sum(VotePresidentielle.votants),
|
|
func.sum(VotePresidentielle.abstentions),
|
|
func.sum(VotePresidentielle.exprimes),
|
|
func.sum(VotePresidentielle.blancs),
|
|
func.sum(VotePresidentielle.nuls))\
|
|
.order_by(desc('tot_voix')).all()
|
|
voix_pres_nuance = VoteCandidatPresidentielle.query\
|
|
.filter(VoteCandidatPresidentielle.vote_id == VotePresidentielle.id)\
|
|
.filter(VoteCandidatPresidentielle.candidat_id == CandidatPresidentielle.id)\
|
|
.filter(BureauVote.id == VotePresidentielle.bv_id)\
|
|
.filter(BureauVote.circonscription_id == circo_id)\
|
|
.group_by(CandidatPresidentielle.nuance_id)\
|
|
.with_entities(CandidatPresidentielle.nuance_id,
|
|
func.sum(VoteCandidatPresidentielle.voix).label('tot_voix'))\
|
|
.order_by(desc('tot_voix')).all()
|
|
|
|
|
|
|
|
vote_leg_1 = VoteLegislatives.query.filter(VoteLegislatives.round == 1,
|
|
VoteLegislatives.circonscription_id == circo_id).one()
|
|
vote_leg_2 = VoteLegislatives.query.filter(VoteLegislatives.round == 2,
|
|
VoteLegislatives.circonscription_id == circo_id).one()
|
|
|
|
voix_leg_1 = VoteCandidatLegislatives.query\
|
|
.filter(VoteCandidatLegislatives.vote_id == VoteLegislatives.id)\
|
|
.filter(VoteLegislatives.round == 1)\
|
|
.filter(VoteCandidatLegislatives.candidat_id == CandidatLegislatives.id)\
|
|
.filter(CandidatLegislatives.circonscription_id == circo_id)\
|
|
.with_entities(CandidatLegislatives.last_name, CandidatLegislatives.first_name,
|
|
CandidatLegislatives.nuance_id, VoteCandidatLegislatives.voix)\
|
|
.order_by(VoteCandidatLegislatives.voix.desc(), CandidatLegislatives.pane_number).all()
|
|
|
|
voix_leg_2 = VoteCandidatLegislatives.query\
|
|
.filter(VoteCandidatLegislatives.vote_id == VoteLegislatives.id)\
|
|
.filter(VoteLegislatives.round == 2)\
|
|
.filter(VoteCandidatLegislatives.candidat_id == CandidatLegislatives.id)\
|
|
.filter(CandidatLegislatives.circonscription_id == circo_id)\
|
|
.with_entities(CandidatLegislatives.last_name, CandidatLegislatives.first_name,
|
|
CandidatLegislatives.nuance_id, VoteCandidatLegislatives.voix)\
|
|
.order_by(VoteCandidatLegislatives.voix.desc(), CandidatLegislatives.pane_number).all()
|
|
|
|
has_results_1 = voix_leg_1[0][3] > 0
|
|
has_results_2 = voix_leg_2[0][3] > 0
|
|
|
|
communes = Commune.query.filter(Commune.id == BureauVote.commune_id)\
|
|
.filter(BureauVote.circonscription_id == circo_id).order_by(Commune.name).all()
|
|
|
|
html = "<!doctype html>\n<html lang=\"fr\">\n"
|
|
html += f"<head>\n<meta charset=\"utf-8\">\n<title>Circonscription {circo_id}</title>\n"
|
|
html += "<style>\n"
|
|
html += "\nul.nuanced {\n\tlist-style: none;\n}\n"
|
|
for nuance in nuances.values():
|
|
html += f"ul li.{nuance.code.lower()}::before {{\n"
|
|
html += "\tcontent: \"\\25A0\";\n"
|
|
html += f"\tcolor: #{nuance.color};\n"
|
|
html += "\tfont-weight: bold;\n"
|
|
html += "\tdisplay: inline-block;\n"
|
|
html += "\twidth: 1em;\n"
|
|
html += "\tmargin-left: -1em;\n"
|
|
html += "}\n"
|
|
html += "</style>\n</head>\n<body>\n"
|
|
|
|
html += "<a href=\"/draw/legislatives\">Retour carte de France - Législatives 2<sup>ème</sup> tour</a><br>"
|
|
html += "<a href=\"/draw/presidentielles\">Retour carte de France - Présidentielles 1<sup>er</sup> tour</a>"
|
|
|
|
html += f"<h1>{circo.departement.name} - {circo.label}</h1>\n"
|
|
|
|
svg = ""
|
|
color = int(nuances[voix_leg_2[0][2] if has_results_2 else voix_leg_1[0][2]].color if has_results_1 else nuances[voix_pres[0][2]].color, 16)
|
|
|
|
w, h = 500, 500
|
|
minx, miny, maxx, maxy = w, h, 0, 0
|
|
if circo.departement_id != 97:
|
|
polygons = circo.geometry
|
|
if isinstance(polygons[0][0][0], float):
|
|
polygons = [polygons]
|
|
else:
|
|
polygons = [[[[0, 0], [0, 1], [1, 1], [1, 0]]]]
|
|
|
|
for polygon in polygons:
|
|
polygon = polygon[0]
|
|
cartesian = [(w * (p[0] + 180) / 360, h / 2 - w / (2 * pi) * log(tan(pi / 4 + p[1] * pi / 360))) for p in polygon]
|
|
points = cartesian
|
|
|
|
minx = min(minx, min(p[0] for p in points))
|
|
miny = min(miny, min(p[1] for p in points))
|
|
maxx = max(maxx, max(p[0] for p in points))
|
|
maxy = max(maxy, max(p[1] for p in points))
|
|
|
|
svg += f'<polygon id="circo-{circo.id}" points="{" ".join(f"{p[0]},{p[1]}" for p in points)}" style="fill: #{color:06x}; stroke: black; stroke-width: 0.1%" />\n'
|
|
|
|
svg += "</svg>"
|
|
svg = f'<svg xmlns="http://www.w3.org/2000/svg" width="500" viewbox="{minx} {miny} {maxx - minx} {maxy - miny}">\n' + svg
|
|
|
|
html += svg + "\n"
|
|
|
|
html += "<h2>Communes</h2>\n"
|
|
html += f"Département : {circo.departement.name} - {circo.departement.id} ({circo.departement.region.name})\n"
|
|
html += "<ul>\n"
|
|
for commune in communes:
|
|
html += f"<li>{commune.name}</li>\n"
|
|
html += "</ul>\n"
|
|
|
|
html += "<h2>Résultats législatives 2ème tour</h2>\n"
|
|
|
|
if has_results_2:
|
|
tot_voix = sum(res[3] for res in voix_leg_2)
|
|
html += "<ul class=\"nuanced\">\n"
|
|
for i, res in enumerate(voix_leg_2):
|
|
html += f"<li class=\"{res[2].lower()}\">"
|
|
if i == 0:
|
|
html += "<span style=\"font-weight: bold;\">"
|
|
html += f"{res[1]} {res[0]} ({res[2]}) : {100 * res[3] / tot_voix:.02f} % ({res[3]})"
|
|
if i == 0:
|
|
html += "</span>"
|
|
html += "</li>\n"
|
|
html += "</ul>\n"
|
|
|
|
html += "Statistiques :\n"
|
|
|
|
if vote_leg_2.inscrits == 0:
|
|
vote_leg_2.inscrits = 1
|
|
vote_leg_2.votants = 1
|
|
vote_leg_2.exprimes = 1
|
|
|
|
html += "<ul>\n"
|
|
html += f"<li>Inscrit⋅es : {vote_leg_2.inscrits}</li>\n"
|
|
html += f"<li>Votant⋅es : {vote_leg_2.votants} ({100 * vote_leg_2.votants / vote_leg_2.inscrits:.02f} %)</li>\n"
|
|
html += f"<li>Abstentionistes : {vote_leg_2.abstentions} ({100 * vote_leg_2.abstentions / vote_leg_2.inscrits:.02f} %)</li>\n"
|
|
html += f"<li>Voix exprimées : {vote_leg_2.exprimes}</li>\n"
|
|
html += f"<li>Bulletins blancs : {vote_leg_2.blancs} ({100 * vote_leg_2.blancs / vote_leg_2.exprimes:.02f} %)</li>\n"
|
|
html += f"<li>Bulletins nuls : {vote_leg_2.nuls} ({100 * vote_leg_2.nuls / vote_leg_2.exprimes:.02f} %)</li>\n"
|
|
html += "</ul>\n"
|
|
else:
|
|
html += "Résultats indisponibles. Liste des candidats :\n"
|
|
html += "<ul class=\"nuanced\">\n"
|
|
for res in voix_leg_2:
|
|
html += f"<li class=\"{res[2].lower()}\">{res[1]} {res[0]} ({res[2]})</li>\n"
|
|
html += "</ul>\n"
|
|
|
|
|
|
html += "<h2>Résultats législatives 1er tour</h2>\n"
|
|
|
|
if has_results_1:
|
|
tot_voix = sum(res[3] for res in voix_leg_1)
|
|
html += "<ul class=\"nuanced\">\n"
|
|
for i, res in enumerate(voix_leg_1):
|
|
qualified = False
|
|
if i == 0:
|
|
qualified = True
|
|
elif voix_leg_1[0][3] / vote_leg_1.exprimes < 0.5 or voix_leg_1[0][3] / vote_leg_1.inscrits < 0.25:
|
|
if i == 1:
|
|
qualified = True
|
|
elif i == 2 and res[3] / vote_leg_1.inscrits >= 0.125:
|
|
qualified = True
|
|
|
|
html += f"<li class=\"{res[2].lower()}\">"
|
|
if qualified:
|
|
html += "<span style=\"font-weight: bold;\">"
|
|
html += f"{res[1]} {res[0]} ({res[2]}) : {100 * res[3] / tot_voix:.02f} % ({res[3]})"
|
|
if qualified:
|
|
html += "</span>"
|
|
html += "</li>\n"
|
|
html += "</ul>\n"
|
|
html += "Statistiques :\n"
|
|
html += "<ul>\n"
|
|
html += f"<li>Inscrit⋅es : {vote_leg_1.inscrits}</li>\n"
|
|
html += f"<li>Votant⋅es : {vote_leg_1.votants} ({100 * vote_leg_1.votants / vote_leg_1.inscrits:.02f} %)</li>\n"
|
|
html += f"<li>Abstentionistes : {vote_leg_1.abstentions} ({100 * vote_leg_1.abstentions / vote_leg_1.inscrits:.02f} %)</li>\n"
|
|
html += f"<li>Voix exprimées : {vote_leg_1.exprimes}</li>\n"
|
|
html += f"<li>Bulletins blancs : {vote_leg_1.blancs} ({100 * vote_leg_1.blancs / vote_leg_1.exprimes:.02f} %)</li>\n"
|
|
html += f"<li>Bulletins nuls : {vote_leg_1.nuls} ({100 * vote_leg_1.nuls / vote_leg_1.exprimes:.02f} %)</li>\n"
|
|
html += "</ul>\n"
|
|
else:
|
|
html += "Résultats indisponibles. Liste des candidats :\n"
|
|
html += "<ul class=\"nuanced\">\n"
|
|
for res in voix_leg_1:
|
|
html += f"<li class=\"{res[2].lower()}\">{res[1]} {res[0]} ({res[2]})</li>\n"
|
|
html += "</ul>\n"
|
|
|
|
html += "<h2>Rappels présidentielles 1er tour</h2>\n"
|
|
tot_voix = sum(res[3] for res in voix_pres)
|
|
|
|
html += "Par candidat :\n"
|
|
html += "<ul class=\"nuanced\">\n"
|
|
for res in voix_pres:
|
|
html += f"<li class=\"{res[2].lower()}\">{res[1]} {res[0]} ({res[2]}) : {100 * res[3] / tot_voix:.02f} % ({res[3]})</li>\n"
|
|
html += "</ul>\n"
|
|
|
|
html += "Par nuance :\n"
|
|
html += "<ul class=\"nuanced\">\n"
|
|
for res in voix_pres_nuance:
|
|
html += f"<li class=\"{res[0].lower()}\">{res[0]} : {100 * res[1] / tot_voix:.02f} % ({res[1]})</li>\n"
|
|
html += "</ul>\n"
|
|
|
|
html += "Statistiques :\n"
|
|
inscrits, votants, abstentions, exprimes, blancs, nuls = voix_pres[0][4:]
|
|
html += "<ul>\n"
|
|
html += f"<li>Inscrit⋅es : {inscrits}</li>\n"
|
|
html += f"<li>Votant⋅es : {votants} ({100 * votants / inscrits:.02f} %)</li>\n"
|
|
html += f"<li>Abstentionistes : {abstentions} ({100 * abstentions / inscrits:.02f} %)</li>\n"
|
|
html += f"<li>Voix exprimées : {exprimes}</li>\n"
|
|
html += f"<li>Bulletins blancs : {blancs} ({100 * blancs / exprimes:.02f} %)</li>\n"
|
|
html += f"<li>Bulletins nuls : {nuls} ({100 * nuls / exprimes:.02f} %)</li>\n"
|
|
html += "</ul>\n"
|
|
html += "<ul>\n"
|
|
|
|
html += "</body>\n</html>\n"
|
|
|
|
return html
|
|
|
|
|
|
@app.get('/refresh/<circo_id>')
|
|
def refresh(circo_id: str):
|
|
circo = Circonscription.query.filter(Circonscription.id == circo_id)
|
|
|
|
if not circo.count():
|
|
abort(404)
|
|
|
|
circo = circo.one()
|
|
vote = VoteLegislatives.query.filter(VoteLegislatives.circonscription_id == circo_id,
|
|
VoteLegislatives.round == 2).one()
|
|
|
|
dpt_id = circo.departement_id
|
|
if dpt_id == 201:
|
|
dpt_id = '02A'
|
|
elif dpt_id == 202:
|
|
dpt_id = '02B'
|
|
elif dpt_id == 97:
|
|
dpt_id = '099'
|
|
else:
|
|
dpt_id = f'{dpt_id:03d}'
|
|
|
|
url = f"https://www.resultats-elections.interieur.gouv.fr/legislatives-2022/{dpt_id}/{dpt_id}{circo.number:02d}.html"
|
|
resp = requests.get(url)
|
|
|
|
if resp.status_code != 200:
|
|
abort(resp.status_code)
|
|
|
|
data = {'status': 'OK', 'resultats': {}, 'statistiques': {'inscrits': 0, 'votants': 0, 'abstentions': 0,
|
|
'exprimes': 0, 'blancs': 0, 'nuls': 0}}
|
|
|
|
content = resp.content.decode('iso-8859-15').lower()
|
|
if "voix" not in content or "rappel" not in content:
|
|
return Response(json.dumps({'status': 'error', 'message': 'results are not available'}), mimetype='application/json', status=404)
|
|
|
|
content = content[content.index('voix'):content.index('rappel')]
|
|
|
|
lines = content.split('\n')
|
|
for i, line in enumerate(lines):
|
|
if ">m." in line or ">mme" in line:
|
|
name = line.split('>')[-2].split('<')[0]
|
|
full_name = name.split(' ', 1)[1]
|
|
field = func.concat(func.lower(CandidatLegislatives.first_name), ' ',
|
|
func.lower(CandidatLegislatives.last_name))
|
|
nuance_code = lines[i + 1].split('>')[-2].split('<')[0].upper()
|
|
candidat = CandidatLegislatives.query.filter(CandidatLegislatives.circonscription_id == circo_id,
|
|
CandidatLegislatives.nuance_id == nuance_code,
|
|
field == full_name).one()
|
|
vc = VoteCandidatLegislatives.query.filter(VoteCandidatLegislatives.vote_id == vote.id,
|
|
VoteCandidatLegislatives.candidat_id == candidat.id).one()
|
|
for j in range(2, 10):
|
|
l = lines[i + j]
|
|
candidate = l.split('>')[-2].split('<', 2)[0].replace(' ', '')
|
|
if candidate.isnumeric():
|
|
vc.voix = int(candidate)
|
|
data['resultats'][f"{candidat.first_name} {candidat.last_name} ({nuance_code})"] = vc.voix
|
|
db.session.add(vc)
|
|
break
|
|
elif '>inscrits' in line or '>abstentions' in line or '>votants' in line\
|
|
or '>blancs' in line or '>nuls' in line or '>exprimés' in line:
|
|
name = line.split('>')[-2].split('<')[0].replace('é', 'e')
|
|
tot = int(lines[i + 1].split('>')[-2].split('<', 2)[0].replace(' ', ''))
|
|
setattr(vote, name, tot)
|
|
data['statistiques'][name] = tot
|
|
|
|
db.session.add(vote)
|
|
db.session.commit()
|
|
|
|
return Response(json.dumps(data), mimetype='application/json')
|
|
|
|
|
|
@app.get('/')
|
|
def index():
|
|
return redirect('/draw/legislatives')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if Nuance.query.count() == 0:
|
|
db.session.add_all([
|
|
Nuance(name="Divers extrême gauche",
|
|
code="DXG",
|
|
color="bb0000"),
|
|
Nuance(name="Parti radical de gauche",
|
|
code="RDG",
|
|
color="ffc0c0"),
|
|
Nuance(name="Nouvelle union populaire écologique et sociale",
|
|
code="NUP",
|
|
color="bb1840"),
|
|
Nuance(name="Divers gauche",
|
|
code="DVG",
|
|
color="ffc0c0"),
|
|
Nuance(name="Écologistes",
|
|
code="ECO",
|
|
color="77ff77"),
|
|
Nuance(name="Divers",
|
|
code="DIV",
|
|
color="eeeeee"),
|
|
Nuance(name="Régionalistes",
|
|
code="REG",
|
|
color="dcbfa3"),
|
|
Nuance(name="Ensemble",
|
|
code="ENS",
|
|
color="ffeb00"),
|
|
Nuance(name="Divers centre",
|
|
code="DVC",
|
|
color="fac577"),
|
|
Nuance(name="Union des Démocrates et des Indépendants",
|
|
code="UDI",
|
|
color="00ffff"),
|
|
Nuance(name="Les Républicains",
|
|
code="LR",
|
|
color="0066cc"),
|
|
Nuance(name="Divers droite",
|
|
code="DVD",
|
|
color="adc1fd"),
|
|
Nuance(name="Droite souverainiste",
|
|
code="DSV",
|
|
color="0082c4"),
|
|
Nuance(name="Reconquête !",
|
|
code="REC",
|
|
color="404040"),
|
|
Nuance(name="Rassemblement National",
|
|
code="RN",
|
|
color="0d378a"),
|
|
Nuance(name="Divers extrême droite",
|
|
code="DXD",
|
|
color="404040"),
|
|
|
|
CandidatPresidentielle(last_name="ARTHAUD",
|
|
first_name="Nathalie",
|
|
slug="nathalie_arthaud",
|
|
nuance_id="DXG"),
|
|
CandidatPresidentielle(last_name="POUTOU",
|
|
first_name="Philippe",
|
|
slug="philippe_poutou",
|
|
nuance_id="DXG"),
|
|
CandidatPresidentielle(last_name="ROUSSEL",
|
|
first_name="Fabien",
|
|
slug="fabien_roussel",
|
|
nuance_id="NUP"),
|
|
CandidatPresidentielle(last_name="MÉLENCHON",
|
|
first_name="Jean-Luc",
|
|
slug="jean_luc_melenchon",
|
|
nuance_id="NUP"),
|
|
CandidatPresidentielle(last_name="HIDALGO",
|
|
first_name="Anne",
|
|
slug="anne_hidalgo",
|
|
nuance_id="NUP"),
|
|
CandidatPresidentielle(last_name="JADOT",
|
|
first_name="Yannick",
|
|
slug="yannick_jadot",
|
|
nuance_id="NUP"),
|
|
CandidatPresidentielle(last_name="MACRON",
|
|
first_name="Emmanuel",
|
|
slug="emmanuel_macron",
|
|
nuance_id="ENS"),
|
|
CandidatPresidentielle(last_name="LASSALLE",
|
|
first_name="Jean",
|
|
slug="jean_lassalle",
|
|
nuance_id="DIV"),
|
|
CandidatPresidentielle(last_name="PÉCRESSE",
|
|
first_name="Valérie",
|
|
slug="valerie_pecresse",
|
|
nuance_id="LR"),
|
|
CandidatPresidentielle(last_name="DUPONT-AIGNAN",
|
|
first_name="Nicolas",
|
|
slug="nicolas_dupont_aignan",
|
|
nuance_id="DSV"),
|
|
CandidatPresidentielle(last_name="LE PEN",
|
|
first_name="Marine",
|
|
slug="marine_le_pen",
|
|
nuance_id="RN"),
|
|
CandidatPresidentielle(last_name="ZEMMOUR",
|
|
first_name="Éric",
|
|
slug="eric_zemmour",
|
|
nuance_id="REC"),
|
|
])
|
|
db.session.commit()
|
|
|
|
if VoteCandidatLegislatives.query.count() == 0:
|
|
for c in Circonscription.query.order_by(Circonscription.id).all():
|
|
vote = VoteLegislatives(circonscription_id=c.id)
|
|
db.session.add(vote)
|
|
for candidat in CandidatLegislatives.query.filter(CandidatLegislatives.circonscription_id == c.id).all():
|
|
db.session.add(VoteCandidatLegislatives(candidat_id=candidat.id, vote_id=vote.id))
|
|
db.session.commit()
|
|
|
|
app.run(debug=True)
|