Compare commits

...

17 Commits
v5.0 ... master

Author SHA1 Message Date
151ef275c8 MAJ CHALGELOG 2020-09-30 13:46:45 +02:00
2d803d8d59 Merge branch 'optimise_bdd_insee' into 'master'
Optimisation de l abase INSEE pour accélérer les recherches.

Closes #14

See merge request sdjgeek/purge-registres-deces-insee!6
2020-09-30 13:36:45 +02:00
19126e57aa Optimisation de l abase INSEE pour accélérer les recherches. 2020-09-30 13:33:59 +02:00
35d82173c6 Merge branch '10-code-geo' into 'master'
Convertir les code insee en nom de ville

Closes #10

See merge request sdjgeek/purge-registres-deces-insee!5
2020-08-04 08:47:46 +02:00
bb709eb2c1 release 7.0 2020-08-04 08:46:04 +02:00
cfdab63598 Convertir les code insee en nom de ville 2020-08-03 22:40:21 +02:00
6e4f587cce Minor release 6.1 2020-07-30 20:25:39 +02:00
88a94c6983 BUG: Ne crash plus si maiden_name est vide dans les registres 2020-07-30 12:01:48 +02:00
35fbec4fff Merge branch 'excel_in' into 'master'
Release 6.0

Closes #11, #12, and #13

See merge request sdjgeek/purge-registres-deces-insee!4
2020-07-29 13:21:39 +02:00
e0d2949b00 MAJ Changelog, ajout des entêtes 2020-07-29 13:19:35 +02:00
a26450d98d Tentative de creation d'un launcher pour windows 2020-07-29 12:56:27 +02:00
308ed11077 Lecture et écriture des fichiers Excel + quelques bugs
- Mise en place de la lecture de la liste des membres à
partir d'un fichier Excel
- Mise en place de l'écriture de la liste des membres décédés
dans un fichier Excel
- Correction du bug: le traitement ne rend pas la main à l'IHM
losque terminé
- Vider le texte de l'IHM lorsqu'on clique sur OK
2020-07-29 09:56:37 +02:00
9febc4cc55 Mettre la barre de progression à zéro lorsqu'on donne la valeur max. 2020-07-28 16:51:42 +02:00
87093bc92c Envoyer l'info que le traitement est terminé à l'IHM 2020-07-28 16:50:35 +02:00
8856641003 WIP préparation du lecteur de fichier excel 2020-07-18 16:49:02 +02:00
3fec540b67 Abstraction de site_eglise pour donner possibilité de recevoir une liste de membre par un autre moyen 2020-07-18 16:18:17 +02:00
b83958fd51 Mise à jour des entêtes 2020-07-17 21:49:45 +02:00
13 changed files with 670 additions and 174 deletions

View File

@ -1,5 +1,25 @@
# Changelog
### [v8.0] 2020-09-30
- Optimisation de la base INSEE
- Création d'une table pour les prénoms, et d'une pour les noms
- Ajout d'index
- Ajout d'un mécanisme de mise à jour de la base insee
### [v7.0] 2020-08-04
- Convertit les Codes Officiels Géographiques en nom de commune
### [v6.1] 2020-07-30
- BUG: Lorsque maiden_name est None, ne pas essayer de le strip
### [v6.0] 2020-07-29
- Lecture de la liste des membres à partir d'un fichier Excel
- Écriture de la liste des membres décédés dans un fichier Excel
- Vider le texte de l'IHM lorsqu'on clique sur OK
- Fichier BAT pour installer et lancer l'IHM sous Windows
- BUG: le traitement rend la main à l'IHM losque terminé
- BUG: la barre de progression est remise à zero entre chaque traitement
### [v5.0] 2020-07-17
- Mise en place d'une interface graphique
- Ajout d'un fichier install.py

View File

@ -26,6 +26,27 @@ except ImportError:
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(p.communicate())
url = "https://git.roflcopter.fr/sdjgeek/purge-registres-deces-insee/-/archive/v5.0/purge-registres-deces-insee-v5.0.zip"
myfile = requests.get(url)
open('purge-registres-deces-insee-v5.0.zip', 'wb').write(myfile.content)
try:
import pandas
except ImportError:
print("Installing pandas")
p = subprocess.Popen([sys.executable, "-m", "pip", "install", "-U", "pandas"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(p.communicate())
try:
import xlrd
except ImportError:
print("Installing xlrd")
p = subprocess.Popen([sys.executable, "-m", "pip", "install", "-U", "xlrd"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(p.communicate())
try:
import numpy
except ImportError:
print("Installing numpy")
p = subprocess.Popen([sys.executable, "-m", "pip", "install", "-U", "numpy"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(p.communicate())

View File

@ -1,53 +1,83 @@
"""Classe d'accès aux données INSEE dans la base SQLite
"""
Copyright (c) 2020 Sdj Geek
Voir le fichier LICENSE
Classe d'accès aux données INSEE dans la base SQLite
"""
import os
import peewee
import requests
import datetime
# Objet de connexion à la base
database = peewee.SqliteDatabase(None)
# Définition du modèle de donnée
class BaseModel(peewee.Model):
class Meta:
database = database
class LastName(BaseModel):
"""Classe contenant tous les noms de famille
"""
last_name = peewee.CharField(unique=True)
class FirstName(BaseModel):
"""Classe contenant tous les prénoms
"""
first_name = peewee.CharField(unique=True)
class Person(BaseModel):
"""Classe représentant une personne dans la base
"""
first_name = peewee.ForeignKeyField(FirstName)
last_name = peewee.ForeignKeyField(LastName)
is_woman = peewee.BooleanField()
date_naissance = peewee.DateField(index=True)
code_lieu_naissance = peewee.CharField()
commune_naissance = peewee.CharField()
pays_naissance = peewee.CharField()
date_deces = peewee.DateField()
code_lieu_deces = peewee.CharField()
numero_act_deces = peewee.CharField()
class ImportedDataset(BaseModel):
"""
"""
dataset = peewee.CharField(unique=True)
# Gestion de l'accès aux données
class BddInsee:
"""Classe encapsulant les accès aux données.
"""
def __init__(self, chemin_base_donnees):
def __init__(self, chemin_base_donnees=None):
"""Initialisation
:param chemin_base_donnees: chemin vers le fichier SQLite
"""
if not chemin_base_donnees:
chemin_base_donnees = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bdd_insee.sqlite")
new = not os.path.isfile(chemin_base_donnees)
database.init(chemin_base_donnees)
if new:
database.create_tables([self.Person])
class Person(peewee.Model):
"""Classe représentant une personne dans la base
"""
last_name = peewee.CharField()
first_name = peewee.CharField()
is_woman = peewee.BooleanField()
annee_naissance = peewee.IntegerField()
mois_naissance = peewee.IntegerField()
jour_naissance = peewee.IntegerField()
code_lieu_naissance = peewee.CharField()
commune_naissance = peewee.CharField()
pays_naissance = peewee.CharField()
annee_deces = peewee.IntegerField()
mois_deces = peewee.IntegerField()
jour_deces = peewee.IntegerField()
code_lieu_deces = peewee.CharField()
numero_act_deces = peewee.CharField()
class Meta:
database = database
database.create_tables([LastName, FirstName, Person, ImportedDataset])
# Fonctions d'accès aux données
def find_person(self, first_name, last_name, maiden_name, annee_naissance, mois_naissance, jour_naissance):
"""Rechercher une personne dans la base
@ -61,26 +91,165 @@ class BddInsee:
"""
if maiden_name:
return self.Person.select().where((self.Person.annee_naissance == int(annee_naissance))
& (self.Person.mois_naissance == int(mois_naissance))
& (self.Person.jour_naissance == int(jour_naissance))
& (self.Person.first_name.contains(first_name.upper()))
& ((self.Person.last_name.contains(last_name.upper()))
| self.Person.last_name.contains(maiden_name.upper())))
# return Person.select().where((Person.annee_naissance == int(annee_naissance))
# & (Person.mois_naissance == int(mois_naissance))
# & (Person.jour_naissance == int(jour_naissance))
# & (Person.first_name.contains(first_name.upper()))
# & ((Person.last_name.contains(last_name.upper()))
# | Person.last_name.contains(maiden_name.upper())))
query = Person.select().join(FirstName).switch(Person).join(LastName)\
.where((Person.date_naissance == datetime.date(int(annee_naissance), int(mois_naissance), int(jour_naissance)))
& (FirstName.first_name.contains(first_name.upper()))
& ((LastName.last_name.contains(last_name.upper()))
| LastName.last_name.contains(maiden_name.upper())))
else:
return self.Person.select().where((self.Person.annee_naissance == int(annee_naissance))
& (self.Person.mois_naissance == int(mois_naissance))
& (self.Person.jour_naissance == int(jour_naissance))
& (self.Person.first_name.contains(first_name.upper()))
& (self.Person.last_name.contains(last_name.upper())))
# return Person.select().where((Person.annee_naissance == int(annee_naissance))
# & (Person.mois_naissance == int(mois_naissance))
# & (Person.jour_naissance == int(jour_naissance))
# & (Person.first_name.contains(first_name.upper()))
# & (Person.last_name.contains(last_name.upper())))
query = Person.select().join(FirstName).switch(Person).join(LastName)\
.where((Person.date_naissance == datetime.date(int(annee_naissance), int(mois_naissance), int(jour_naissance)))
& (FirstName.first_name.contains(first_name.upper()))
& (LastName.last_name.contains(last_name.upper())))
result = list()
if query:
for row in query:
result.append({
'first_name': row.first_name.first_name,
'last_name': row.last_name.last_name,
'date_naissance': row.date_naissance,
'code_lieu_naissance': row.code_lieu_naissance,
'date_deces': row.date_deces,
'code_lieu_deces': row.code_lieu_deces
})
return result
def import_data_list(self, data_list):
# Fonctions d'import des données
def parse_insee_data(self, data_text):
"""Parse le texte d'un fichier de l'INSEE
:param data_text: texte contenu dans un fichier de l'INSEE
:returns: liste de dictionnaires contenant les informations à insérer
"""
data_list = []
for line_number, line in enumerate(data_text.split('\n')):
if not line:
continue
try:
try:
last_name, first_name = line[0:80].strip()[:-1].split("*")
except ValueError:
if '*' not in line[0:80]:
continue
raise
date_naissance = self.parse_date(int(line[81:85]), int(line[85:87]), int(line[87:89]))
date_deces = self.parse_date(int(line[154:158]), int(line[158:160]), int(line[160:162]))
data_list.append({'last_name': last_name.upper(),
'first_name': first_name.strip('/').upper(),
'is_woman': (line[80] == "2"),
'date_naissance': date_naissance,
'code_lieu_naissance': line[89:94].upper(),
'commune_naissance': line[94: 124].strip().upper(),
'pays_naissance': line[124: 154].strip().upper(),
'date_deces': date_deces,
'code_lieu_deces': line[162:167].upper(),
'numero_act_deces': line[167:].strip().upper()})
except ValueError as e:
print(f"Erreur pour parser ligne numéro {line_number}.")
print(line)
raise
return data_list
def parse_date(self, year, month, day):
if year == 0:
return datetime.date.min
if month == 0:
month = 1
elif month > 12:
month = 12
if day == 0:
day = 1
elif day > 30 and month in [4, 6, 9, 11]:
day = 30
elif day > 31 and month in [1, 3, 5, 7, 8, 10, 12]:
day = 31
elif day > 29 and month == 2:
day = 29
try:
date = datetime.date(year, month, day)
except ValueError:
if month == 2 and day == 29 and year not in [1844, 1848, 1852, 1856, 1860, 1864, 1868, 1872, 1876, 1880,
1884, 1888, 1892, 1896, 1904, 1908, 1912, 1916, 1920,
1924, 1928, 1932, 1936, 1940, 1944, 1948, 1952, 1956, 1960,
1964, 1968, 1972, 1976, 1980, 1984, 1988, 1992, 1996, 2000,
2004, 2008, 2012, 2016, 2020]:
day = 28
date = datetime.date(year, month, day)
return date
def import_url_list(self, file_path):
"""Importer les données à partir d'un fichier contenant les URL
Un fichier d'URL contient un nom de dataset et une URL par ligne, sapéras par une espace. Chaque URL conduit
vers un fichier de l'insée qui doit être importé.
:param file_path: chemin vers le fichier d'URL
"""
if not os.path.isfile(file_path):
print(f"Le fichier {file_path} est introuvable.")
return
with open(file_path, 'r') as file_content:
for line in file_content.readlines():
try:
dataset, url = line.split(' ')
url = url.strip('\n')
except:
print(f"Le fichier {file_path} contient une ligne non conforme : {line}.")
continue
dataset_id = ImportedDataset.select().where(ImportedDataset.dataset == dataset)
if not len(dataset_id) == 0:
print(f"Le dataset {dataset} ne sera pas traité, car il a déjà été importé.")
continue
print(f"Importation du dataset {dataset}")
print(f"Téléchargement de : '{url}'.")
result = requests.get(url)
try:
result.raise_for_status()
except:
print(f"Erreur lors du téléchargement du fichier {url}.")
continue
print("Parse des données téléchargées")
data_list = self.parse_insee_data(result.text)
self.import_data_list(data_list, dataset)
def import_data_list(self, data_list, dataset_name):
"""Insérer des données dans la base
:param data_list: liste de dictionnaires contenant les
informations à insérer
:param data_list: liste de dictionnaires contenant les informations à insérer
:param dataset_name: nom du jeu de données
"""
dataset_id = ImportedDataset.select().where(ImportedDataset.dataset == dataset_name)
if not len(dataset_id) == 0:
print(f"Le dataset {dataset_name} a déjà été importé.")
return
print(f"Import en base des {len(data_list)} personnes.")
with database.atomic():
for batch in peewee.chunked(data_list, 70):
self.Person.insert_many(batch).execute()
for data in data_list:
# Gestion de la clé étrangère prénom
first_name = data.pop('first_name')
try:
first_name_id = FirstName.get(FirstName.first_name == first_name)
except peewee.DoesNotExist:
first_name_id = FirstName.create(first_name=first_name)
data['first_name'] = first_name_id
# Gestion de la clé étrangère nom de famille
last_name = data.pop('last_name')
try:
last_name_id = LastName.get(LastName.last_name == last_name)
except peewee.DoesNotExist:
last_name_id = LastName.create(last_name=last_name)
data['last_name'] = last_name_id
Person.insert(**data).execute()
ImportedDataset.insert(dataset=dataset_name).execute()
print("Import terminé")

View File

@ -0,0 +1,54 @@
"""
Copyright (c) 2020 Sdj Geek
Voir le fichier LICENSE
Classe d'accès aux données du site de l'Église
"""
import os
import pandas as pd
import numpy as np
from membre_base import MembreBase, MembreProvider
class ExcelIn(MembreProvider):
def __init__(self, excel_path):
self.excel_path = excel_path
self.dataframe = None
def __len__(self):
return len(self.dataframe)
class Member(MembreBase):
def __init__(self, provider, row):
super().__init__(provider)
self.r_id = str(row["id"])
self.r_first_name = str(row["first_name"]).strip().split(' ')[0].upper()
self.r_last_name = str(row["last_name"]).strip().split(' ')[0].upper()
self.r_maiden_name = str(row["maiden_name"]).strip().split(' ')[0].upper() if type(row["maiden_name"]) == np.str else None
self.r_annee = str(row["annee"])
self.r_mois = str(row["mois"])
self.r_jour = str(row["jour"])
self.r_ville = "<VIDE>"
self.r_sexe = "F" if str(row["status"]) == "Female" else "M"
def get_name(self):
return os.path.basename(self.excel_path)
def load(self):
self.dataframe = pd.read_excel(self.excel_path, skiprows=[0, 1], usecols="B:E,G:I,K", header=None,
names=["last_name", "first_name", "maiden_name", "id", "jour", "mois", "annee",
"status"])
return len(self.dataframe)
def get_member_list(self):
for index, row in self.dataframe.iterrows():
try:
yield self.Member(self, row)
except ValueError:
print(f"Error with member [{row}]")
continue

View File

@ -0,0 +1,30 @@
"""
Copyright (c) 2020 Sdj Geek
Voir le fichier LICENSE
Classe d'accès aux données INSEE dans la base SQLite
"""
import pandas as pd
class ExcelOut:
def __init__(self, out_path):
self.out_path = out_path
self.data = list()
def add_member(self, member):
self.data.append({
'nom_registres': member.get_nom_registres(),
'nom_insee': member.get_nom_insee(),
'mrn': member.r_id,
'death_year': member.i_annee_deces,
'death_month': member.i_mois_deces,
'death_day': member.i_jour_deces
})
def generate_output(self):
df = pd.DataFrame(self.data)
df.to_excel(self.out_path)

View File

@ -1,3 +1,14 @@
"""
Copyright (c) 2020 Sdj Geek
Voir le fichier LICENSE
Ce programme permet de comparer le fichier des personnes décédées établi par l'INSEE avec les registre de membres de
l'Église, afin de déterminer si figurent dans nos registre des personnes décédées.
L'INSEE reçoit des communes les décès enregistrés. Le fichier des personnes décédées établi par l'INSEE est en accès
libre sur le site https://www.data.gouv.fr.
"""
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.filedialog as tkfiledialog
@ -5,6 +16,7 @@ from multiprocessing import Process, Pipe
from trouver_decedes import trouver_decedes
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
# Worker
@ -25,38 +37,50 @@ class MainApplication(tk.Frame):
self.entry_bdd_insee.grid(row=0, column=1, sticky='ew')
self.button_bdd_insee = tk.Button(self.frame, text="...", command=self.command_button_bdd_insee)
self.button_bdd_insee.grid(row=0, column=2, sticky='w')
# Sélection fichier Excel
self.label_fichier_excel = tk.Label(self.frame, text="Fichier Excel")
self.label_fichier_excel.grid(row=1, column=0, sticky='e')
self.value_fichier_excel = tk.StringVar()
self.entry_fichier_excel = tk.Entry(self.frame, state='disabled', textvariable=self.value_fichier_excel)
self.entry_fichier_excel.grid(row=1, column=1, sticky='ew')
self.button_fichier_excel = tk.Button(self.frame, text="...", command=self.command_button_fichier_excel)
self.button_fichier_excel.grid(row=1, column=2, sticky='w')
# Sélection répertoire sortie
self.label_dir_out = tk.Label(self.frame, text="Répertoire de sortie")
self.label_dir_out.grid(row=1, column=0, sticky='e')
self.label_dir_out.grid(row=2, column=0, sticky='e')
self.value_dir_out = tk.StringVar()
self.entry_dir_out = tk.Entry(self.frame, state='disabled', textvariable=self.value_dir_out)
self.entry_dir_out.grid(row=1, column=1, sticky='ew')
self.entry_dir_out.grid(row=2, column=1, sticky='ew')
self.button_dir_out = tk.Button(self.frame, text="...", command=self.command_button_dir_out)
self.button_dir_out.grid(row=1, column=2, sticky='w')
self.button_dir_out.grid(row=2, column=2, sticky='w')
# Sélection des unités à traiter
self.label_units = tk.Label(self.frame, text="Unités à purger")
self.label_units.grid(row=2, column=0, sticky='e')
self.label_units.grid(row=3, column=0, sticky='e')
self.value_units = tk.StringVar()
self.entry_units = tk.Entry(self.frame, textvariable=self.value_units)
self.entry_units.grid(row=2, column=1, sticky='ew', columnspan="2")
self.entry_units.grid(row=3, column=1, sticky='ew', columnspan="2")
# Bouton validation
self.button_valid = tk.Button(self.frame, text="Ok", command=self.command_button_valid)
self.button_valid.grid(row=3, column=0, columnspan=3)
self.button_valid.grid(row=4, column=0, columnspan=3)
# Barre de progression
self.progressbar = ttk.Progressbar(self.frame, orient=tk.HORIZONTAL, mode='determinate')
self.progressbar.grid(row=4, column=0, columnspan=3, sticky='ew')
self.progressbar.grid(row=5, column=0, columnspan=3, sticky='ew')
# Affichage des logs
self.text_log = tk.Text(self.frame, state='disabled')
self.text_log.grid(row=0, column=3, rowspan=5, sticky='nesw')
self.text_log.grid(row=0, column=3, rowspan=6, sticky='nesw')
def command_button_bdd_insee(self):
self.value_bdd_insee.set(tkfiledialog.askopenfilename(title="Fichier de l'INSEE"))
def command_button_fichier_excel(self):
self.value_fichier_excel.set(tkfiledialog.askopenfilename(title="Fichier Excel"))
def command_button_dir_out(self):
self.value_dir_out.set(tkfiledialog.askdirectory(title="Répertoire de sortie"))
@ -65,6 +89,11 @@ class MainApplication(tk.Frame):
self.text_log.insert(tk.END, text)
self.text_log.configure(state='disabled')
def clear_log(self):
self.text_log.configure(state='normal')
self.text_log.delete('1.0', tk.END)
self.text_log.configure(state='disabled')
def watch(self):
if self.run:
if self.pipe.poll():
@ -72,47 +101,52 @@ class MainApplication(tk.Frame):
if message.get('step', False):
self.progressbar.step(message['step'])
elif message.get('text', False):
print(message['text'])
self.add_log(message['text'])
elif message.get('running', False):
elif 'running' in message:
self.run = message['running']
elif message.get('set_max', False):
self.progressbar["value"] = 0
self.progressbar['maximum'] = message['set_max']
self.parent.after(100, self.watch)
else:
print("Recherche terminée")
self.add_log("\nRecherche terminée\n")
self.button_valid.configure(state='normal')
def command_button_valid(self):
self.button_valid.configure(state='disabled')
self.clear_log()
self.run = True
(conn1, conn2) = Pipe()
self.pipe = conn1
Worker(conn2, self.value_bdd_insee.get(), self.value_dir_out.get(), self.value_units.get().split(',')).start()
unite = self.value_units.get().split(',')
if unite == ['']:
unite = None
Worker(conn2, self.value_bdd_insee.get(), self.value_fichier_excel.get(), self.value_dir_out.get(), unite).start()
self.watch()
class Worker(Process):
def __init__(self, pipe, bdd_insee, dir_out, units):
def __init__(self, pipe, bdd_insee, fichier_excel, dir_out, units):
Process.__init__(self)
self.pipe = pipe
self.bdd_insee = bdd_insee
self.fichier_excel = fichier_excel
self.dir_out = dir_out
self.units = units
def tracker(self, step=None, text=None, set_max=None, running=None):
if step:
if step is not None:
self.pipe.send({'step': step})
elif text:
elif text is not None:
self.pipe.send({'text': text})
elif set_max:
elif set_max is not None:
self.pipe.send({'set_max': set_max})
elif done:
elif running is not None:
self.pipe.send({'running': running})
def run(self):
trouver_decedes(chemin_base_donnees=self.bdd_insee,
excel_path=self.fichier_excel,
chemin_repertoire_sortie=self.dir_out,
numeros_unites=self.units,
tracker=self.tracker)

View File

@ -0,0 +1,116 @@
"""
Copyright (c) 2020 Sdj Geek
Voir le fichier LICENSE
"""
from abc import ABC, abstractmethod
from site_opendatasoft import get_ville_from_cog
class MembreBase(ABC):
def __init__(self, provider):
self.provider = provider
# Données issues des registres
self.r_id = None
self.r_first_name = None
self.r_last_name = None
self.r_maiden_name = None
self.r_annee = None
self.r_mois = None
self.r_jour = None
self.r_ville = None
self.r_sexe = None
# Données issues de l'INSEE
self.i_first_name = None
self.i_last_name = None
self.i_annee_naissance = None
self.i_mois_naissance = None
self.i_jour_naissance = None
self.i_ville_naissance = None
self.i_annee_deces = None
self.i_mois_deces = None
self.i_jour_deces = None
self.i_ville_deces = None
def get_nom_registres(self):
nom_registres = f"{self.r_last_name}, {self.r_first_name}"
if self.r_maiden_name:
nom_registres = f"{nom_registres} née {self.r_maiden_name.upper()}"
return nom_registres
def get_nom_insee(self):
return f"{self.i_last_name}, {self.i_first_name}"
def set_insee(self, insee):
self.i_first_name = insee['first_name']
self.i_last_name = insee['last_name']
self.i_annee_naissance = insee['date_naissance'].year
self.i_mois_naissance = insee['date_naissance'].month
self.i_jour_naissance = insee['date_naissance'].day
self.i_ville_naissance = insee['code_lieu_naissance']
self.i_annee_deces = insee['date_deces'].year
self.i_mois_deces = insee['date_deces'].month
self.i_jour_deces = insee['date_deces'].day
self.i_ville_deces = insee['code_lieu_deces']
def convertir_villes_insee(self):
old_value = self.i_ville_naissance
new_value = None
try:
int(self.i_ville_naissance)
except ValueError:
pass
else:
new_value = get_ville_from_cog(self.i_ville_naissance)
if new_value is not None:
self.i_ville_naissance = new_value
if old_value == self.i_ville_deces:
self.i_ville_deces = new_value
else:
try:
int(self.i_ville_deces)
except ValueError:
pass
else:
new_value = get_ville_from_cog(self.i_ville_deces)
if new_value is not None:
self.i_ville_deces = new_value
def get_texte_decede(self):
if self.r_sexe == "F":
feminin = "e"
elif self.r_sexe == "M":
feminin = ""
else:
feminin = "(e)"
self.convertir_villes_insee()
return f"""
Le membre {self.get_nom_registres()} ({self.r_id}),
{feminin} le {self.r_jour:0>2}/{self.r_mois:0>2}/{self.r_annee:0>4} à {self.r_ville.upper()}
semble être décédé{feminin}.
Dans le fichier de l'INSEE on peut trouver {self.get_nom_insee()}
{feminin} le {self.i_jour_naissance:0>2}/{self.i_mois_naissance:0>2}/{self.i_annee_naissance:0>4} à {self.i_ville_naissance}
décédé{feminin} le {self.i_jour_deces:0>2}/{self.i_mois_deces:0>2}/{self.i_annee_deces:0>4} à {self.i_ville_deces}
"""
class MembreProvider(ABC):
@abstractmethod
def load(self):
pass
@abstractmethod
def get_name(self):
pass
@abstractmethod
def __len__(self):
pass
@abstractmethod
def get_member_list(self):
pass

View File

@ -1,31 +0,0 @@
"""
Paramètres de l'application
- chemin_base_donnees : Le chemin vers le fichier téléchargé sur le site data.gouv.fr
- numeros_unites : La liste des unités que le programme doit tester. La liste commence par le caractère '['. Les
numéros d'unités sont séparés par des virgules. La liste se termine par le caractère ']'. Par exemple
[47823, 67745, 31456]
- cookie : Les cookies de la session au compte SDJ. Pour obtenir ces cookies il faut
1- Ouvrir Firefox
2- Ouvrir les outils de développement : CTRL + MAJ + E
3- Aller sur le site des outils pour greffiers : https://lds.org/lcr
4- Entrer identifiant et mot de passe SDJ
5- Aller dans la liste des membres
6- Dans l'outil de développement, cliquer sur la dernière ligne
7- Dans le paneau de droite, dans la section "En-tête de la requête", rechercher le champ "cookie"
8- Copier la valeur contenue dans ce champ
- chemin_repertoire_sortie : Le chemin vers le répertoire qui contiendra la fichiers de sortie
"""
# Chemin complet vers le fichier contenant les registres de l'INSEE.
# Pour le télécharger, rendez-vous ici :
# https://git.roflcopter.fr/sdjgeek/purge-registres-deces-insee/-/wikis/home#t%C3%A9l%C3%A9charger-la-base-de-donn%C3%A9e
chemin_base_donnees = "/chemin/vers/fichier-des-personnes-decedees.sqlite"
# Numéro des l'unités à contrôler
numeros_unites = [47823, 67745, 31456]
# Cookie de session compte SDJ
cookie = "/home/julien/.mozilla/firefox/615tua7r.lds/cookies.sqlite"
# Chemin vers le répertoire de sortie
chemin_repertoire_sortie = "/chemin/vers/repertoire-de-sortie"

View File

@ -1,33 +1,87 @@
"""Classe d'accès aux données du site de l'Église
"""
Copyright (c) 2020 Sdj Geek
Voir le fichier LICENSE
Classe d'accès aux données du site de l'Église
"""
import requests
import browser_cookie3
from membre_base import MembreBase, MembreProvider
class SiteEglise:
def __init__(self, cookie_path=None):
class SiteEglise(MembreProvider):
def __init__(self, unite, cookie_path=None):
"""
:param unite: numéro de l'unité (paroisse, branche)
:param cookie_path: chemin vers le répertoire inscrire les fichiers de sortie
"""
self.unite = unite
self.cookie_jar = browser_cookie3.firefox(cookie_file=cookie_path)
self.as_json = None
def get_member_list(self, unit_number):
"""Recevoir la liste des membres
def __len__(self):
return len(self.as_json)
:param unit_number: numéro de l'unité (paroisse, branche)
:returns: la liste des membres sous forme d'objet JSON
class Membre(MembreBase):
"""
def __init__(self, provider, data):
super().__init__(provider)
self.completed = False
# Données fournies
self.r_id = data['legacyCmisId']
self.r_last_name, self.r_first_name = data['nameListPreferredLocal'].upper().split(',')
self.r_last_name = self.r_last_name.strip().split(' ')[0]
self.r_first_name = self.r_first_name.strip().split(' ')[0]
self.r_annee, self.r_mois, self.r_jour = data['birth']['date']['date'].split('-')
self.r_sexe = data['sex']
# Rechercher le nom de jeune fille si besoin
if data['isSpouse']:
self.complete()
def complete(self):
if not self.completed:
member_profile = self.provider.get_member_profile(self.r_id)
self.r_maiden_name = member_profile['individual']['maidenNameGroup']['name1']['family']
if self.r_maiden_name is not None:
self.r_maiden_name = self.r_maiden_name.strip().split(' ')[0].upper()
self.r_ville = member_profile['individual']['birthPlace']
if not self.r_ville:
self.r_ville = "<VIDE>"
self.completed = True
def set_insee(self, insee):
self.complete()
super().set_insee(insee)
def get_name(self):
return f"Unité_{self.unite}"
def load(self):
r = requests.get('https://lcr.churchofjesuschrist.org/services/umlu/report/member-list',
params={'lang': "fra", 'unitNumber': unit_number},
params={'lang': "fra", 'unitNumber': self.unite},
headers={'Accept': "application/json"},
cookies=self.cookie_jar)
r.raise_for_status()
return r.json()
self.as_json = r.json()
return len(self.as_json)
def get_member_list(self):
"""Recevoir la liste des membres
:returns: la liste des membres sous forme d'objet JSON
"""
for member in self.as_json:
try:
yield self.Membre(self, member)
except ValueError:
print(f"Error with member [{member['nameListPreferredLocal']}, {member['birth']['date']['date']}]")
continue
def get_member_profile(self, member_id):
"""Recevoir les informations sur un membre

View File

@ -0,0 +1,38 @@
"""
Copyright (c) 2020 Sdj Geek
Voir le fichier LICENSE
Classe d'accès aux données du site https://public.opendatasoft.com
"""
import requests
def get_ville_from_cog(cog):
"""
Donne le nom de la commune (et son code postal) correspondant au code officiel géographique (cog) donné en entrée.
:param cog: le code officiel géographique à convertie
:return: le nom de la commune (et son code postal). None si la recherche a échoué.
"""
# Contacter l'API
r = requests.get("https://public.opendatasoft.com/api/records/1.0/search/",
params={'dataset': "correspondance-code-insee-code-postal",
'q': f"insee_com={cog}",
'lang': "fr"},
headers={'Accept': "application/json"})
# Tester la validité de la réponse
try:
r.raise_for_status()
except requests.HTTPError:
print(f"Warning get_ville_from_cog: requests return status {r.headers}")
return None
# Récupérer le résultat
result = r.json()
# Si résultat non vide
if result['nhits'] > 0:
# COMMUNE (CODE POSTAL)
return f"{result['records'][0]['fields']['nom_comm']} ({result['records'][0]['fields']['postal_code']})"
print(f"Warning get_ville_from_cog: no result returned for cog {cog}")
return None

View File

@ -1,8 +1,6 @@
"""
Copyright (c) 2020 Sdj Geek
Voir le fichier LICENSE
------------------------------------------------------------------------------------------------------------------------
Ce programme permet de comparer le fichier des personnes décédées établi par l'INSEE avec les registre de membres de
l'Église, afin de déterminer si figurent dans nos registre des personnes décédées.
@ -10,12 +8,6 @@ l'Église, afin de déterminer si figurent dans nos registre des personnes déc
L'INSEE reçoit des communes les décès enregistrés. Le fichier des personnes décédées établi par l'INSEE est en accès
libre sur le site https://www.data.gouv.fr.
------------------------------------------------------------------------------------------------------------------------
Auteur : SDJ GeeK <sdjgeek@protonmail.com>
Date : 16 juin 2020
Version : 4.0
"""
import argparse
@ -23,89 +15,83 @@ import os
from bdd_insee import BddInsee
from site_eglise import SiteEglise
from excel_in import ExcelIn
from excel_out import ExcelOut
def default_tracker(step=None, text=None, set_max=None):
def default_tracker(step=None, text=None, set_max=None, running=None):
if text:
print(text)
def trouver_decedes(chemin_base_donnees, numeros_unites, chemin_repertoire_sortie, cookie_path=None, tracker=None):
def trouver_decedes(chemin_base_donnees, numeros_unites, chemin_repertoire_sortie, cookie_path=None, excel_path=None,
tracker=None):
"""Recherche les personnes décédées dans les registres
:param chemin_base_donnees: chemin vers le fichier SQLite
:param numeros_unites: liste des numéros d'unités à analyser
:param chemin_repertoire_sortie: chemin vers le répertoire inscrire les fichiers de sortie
:param cookie_path: chemin vers la base de donnée des cookies
:param excel_path: chemin vers le fichier Excel contenant la liste des membres à rechercher
:param tracker: Objet permettant de suivre l'avancée du traitement
"""
# Tracker par défaut
if tracker is None:
tracker = default_tracker
# Initialiser les accès aux données INSEE
base_insee = BddInsee(chemin_base_donnees)
site_eglise = SiteEglise(cookie_path)
# Boucler sur la liste des unités
for unite in numeros_unites:
tracker(text=f"Unité {unite}")
# Initialiser les fournisseurs de liste de membres
fournisseurs_membres = list()
if excel_path:
fournisseurs_membres.append(ExcelIn(excel_path))
if numeros_unites:
for unite in numeros_unites:
fournisseurs_membres.append(SiteEglise(unite, cookie_path))
# Boucler sur la liste fournisseurs
for member_provider in fournisseurs_membres:
tracker(set_max=member_provider.load())
tracker(text=f"Recherche dans {member_provider.get_name()}\n")
# Récupérer la liste des membres
members = site_eglise.get_member_list(unite)
# Préparer le fichier de sortie
output_file = os.path.join(chemin_repertoire_sortie, f"liste_membres_decedes_unite_{unite}.txt")
with open(output_file, 'w') as out_file:
out_file.write("Les lieux dans le fichier de l'INSEE sont donnés en Code Officiel Géographique en vigueur au moment de la prise en compte du décès\n")
# Boucler sur la liste des membres
tracker(set_max=len(members))
for member in members:
# Lire les noms et date de naissance
name_registre = member['nameListPreferredLocal']
full_birthdate = member['birth']['date']['date']
maiden_name = None
ville_registre = None
# S'il s'agit d'une femme mariée, trouver son nom de jeune fille
if member['isSpouse']:
member_profile = site_eglise.get_member_profile(member['legacyCmisId'])
maiden_name = member_profile['individual']['maidenNameGroup']['name1']['family']
ville_registre = member_profile['individual']['birthPlace']
try:
last_name, first_name = name_registre.split(',')
annee_registre, mois_registre, jour_registre = full_birthdate.split('-')
except ValueError:
print(f"Error with member [{name_registre}, {full_birthdate}]")
continue
first_name = first_name.strip().split(' ')[0]
last_name = last_name.strip().split(' ')[0]
query = base_insee.find_person(first_name, last_name, maiden_name, annee_registre, mois_registre, jour_registre)
name_registre = name_registre.upper()
if maiden_name:
name_registre = f"{name_registre} née {maiden_name.upper()}"
if member['sex'] == "F":
feminin = "e"
else:
feminin = ""
for person in query:
if not ville_registre:
member_profile = site_eglise.get_member_profile(member['legacyCmisId'])
ville_registre = member_profile['individual']['birthPlace']
if not ville_registre:
ville_registre = "<VIDE>"
text = f"""
Le membre {name_registre},
{feminin} le {jour_registre:0>2}/{mois_registre:0>2}/{annee_registre:0>4} à {ville_registre.upper()}
semble être décédé{feminin}.
Dans le fichier de l'INSEE on peut trouver {person.last_name}, {person.first_name}
{feminin} le {person.jour_naissance:0>2}/{person.mois_naissance:0>2}/{person.annee_naissance:0>4} à {person.code_lieu_naissance}
décédé{feminin} le {person.jour_deces:0>2}/{person.mois_deces:0>2}/{person.annee_deces:0>4} à {person.code_lieu_deces}
"""
tracker(text=text)
out_file.write(text)
tracker(step=1)
members = member_provider.get_member_list()
# Préparer les fichiers de sortie
output_base_name = os.path.join(chemin_repertoire_sortie, f"liste_membres_decedes_{member_provider.get_name()}")
output_txt = open(output_base_name + ".txt", 'w')
output_xls = ExcelOut(output_base_name + ".xlsx")
output_txt.write("Les lieux dans le fichier de l'INSEE sont donnés en Code Officiel Géographique en vigueur au moment de la prise en compte du décès\n")
# Boucler sur la liste des membres
for member in members:
query = base_insee.find_person(member.r_first_name, member.r_last_name, member.r_maiden_name,
member.r_annee, member.r_mois, member.r_jour)
for person in query:
member.set_insee(person)
text = member.get_texte_decede()
tracker(text=text)
output_txt.write(text)
output_xls.add_member(member)
tracker(step=1)
# Clore les fichiers de sortie
output_txt.close()
output_xls.generate_output()
tracker(running=False)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Recherche des personnes décédées dans les registres.')
parser.add_argument('chemin_base_donnees', type=str, help="chemin vers la base de données")
parser.add_argument('chemin_repertoire_sortie', type=str, help="chemin vers le répertoire de sortie")
parser.add_argument('numeros_unites', type=str, help="numéros des l'unités à traiter, séparés par des virgules (ex: 123,753,469)")
parser.add_argument('--cookie', '-c', type=str, help='chemin vers la base de donnée des cookies de Firefox')
parser.add_argument('--numeros_unites', '-u', type=str, help="numéros des l'unités à traiter, séparés par des virgules (ex: 123,753,469)")
parser.add_argument('--cookie', '-c', type=str, help="chemin vers la base de donnée des cookies de Firefox")
parser.add_argument('--excel', '-e', type=str, help="chemin vers le fichier Excel contenant la liste des membres à rechercher")
args = parser.parse_args()
trouver_decedes(args.chemin_base_donnees, args.numeros_unites.split(','), args.chemin_repertoire_sortie, args.cookie)
if args.numeros_unites:
numeros_unites = args.numeros_unites.split(',')
else:
numeros_unites = list()
trouver_decedes(chemin_base_donnees=args.chemin_base_donnees,
numeros_unites=numeros_unites,
chemin_repertoire_sortie=args.chemin_repertoire_sortie,
cookie_path=args.cookie,
excel_path=args.excel)

View File

@ -1,3 +1,6 @@
peewee
requests
browser_cookie3
browser_cookie3
pandas
xlrd
numpy

2
start_windows.bat Normal file
View File

@ -0,0 +1,2 @@
python install.py
python purge-registres-deces-insee\gui_trouver_decedes.py