URL: https://linuxfr.org/users/oumph/journaux/avis-et-decisions-cada-et-generation-d-un-gros-pdf Title: Avis et décisions CADA et génération d'un gros PDF Authors: Benoît Sibaud Date: 2023年05月17日T19:44:40+02:00 License: CC By-SA Tags: cada, pdf et python Score: 42 En France, la [CADA](https://fr.wikipedia.org/wiki/Commission_d%27acc%C3%A8s_aux_documents_administratifs) ou Commission d’accès aux documents administratifs est _une autorité administrative indépendante qui a pour objectif de faciliter et contrôler l’accès des particuliers aux documents administratifs_. Et Wikipédia ajoute : elle « émet des _conseils_ quand elle est saisie par une administration, mais son activité principale est de fournir des _avis_ aux particuliers qui se heurtent au refus d’une administration de communiquer un ou plusieurs documents qu’elle détient. » - Le site de la Commission d’accès aux dossiers administratifs : https://www.cada.fr/ - Ma Dada (demande d’accès aux dossiers administratifs) https://madada.fr/ - Avis et conseils de la CADA publiés en opendata : https://www.data.gouv.fr/fr/datasets/avis-et-conseils-de-la-cada/ Note 1: en Belgique aussi il existe une CADA, mais je serais bien en peine d’évoquer les similitudes ou différences. Ce travail est laissé aux commentaires :). Note 2: ne pas confondre avec un autre [CADA](https://fr.wikipedia.org/wiki/Centre_d%27accueil_de_demandeurs_d%27asile_en_France) français qui n’a pas de rapport ## Contexte (mi CADA su CADA) On m’a récemment demandé un coup de main pour pouvoir faire des recherches dans les avis et conseils CADA publiés. Sur data.gouv.fr, on peut trouver tout cela en fichier CSV, et sinon il est possible d’utiliser l'[API de la CADA](https://cada.data.gouv.fr/api/) pour faire des recherches. Mais bon ouvrir un fichier CSV de plusieurs centaines de MiB et des dizaines de milliers de lignes dont des cellules de plusieurs écrans, ça peut être compliqué ou peu lisible pour le commun des mortels. Et puis tout le monde ne sait pas utiliser une API (ou gérer l’Unicode dans les résultats ou les requêtes ou... n’a pas envie d’avoir besoin d’un accès Internet permanent pour cela). Bref coup de téléphone, question « _est-ce que c’est compliqué de produire un PDF à partir d’un CSV ?_ », évaluation mentale hyperprécise de développeur débutant en Python « _tranquille, 5 min_ » et réponse « _oui, j’essaie de te faire ça ce week-end_ ». ## Yapuka (ou abraCADAbra) Je vais sur le site [data.gouv.fr](https://www.data.gouv.fr/fr/datasets/avis-et-conseils-de-la-cada/), je vois 1 fichier principal, 49 mises à jour. Je télécharge donc 50 fichiers. Bon, première erreur, il faut comprendre « mises à jour » du même jeu de données, bref la dernière version contient tout. Au final je vais pondre ce code Python qui lit un CSV et produit du Markdown, en validant un peu ce qui passe à tout hasard. La fonction _display_ indique ce qu’on trouve dans le CSV (selon l’entête de chaque CSV). La fonction _markdown_ sort de façon basique du Markdown à partir de ça (ma stratégie de saut de page n’a pas marché, mais je dirais tant mieux vu le nombre de pages en sortie...). La fonction _validate_ me donne une idée de ce que je traite et si jamais il y a des surprises sur une prochaine mise à jour des données. J’ai notamment été surpris par le nombre de champs pouvant être vides. Et côté _main_ de quoi gérer un offset/limit _à la SQL_, pour ne pas tout traiter en une fois (cf la suite de ce journal). Anecdote : j’ouvre un CSV au pif pour avoir un exemple, et je tombe direct sur une décision concernant ma ville de naissance. Bref, voici le code Python mi-anglais mi-français mi-sérable : ```python #!/usr/bin/env python3 # -*- coding: utf-8 -*- import argparse import csv from datetime import datetime numero_de_dossier = '' administration = '' type_donnee = '' annee = '' seance = '' objet = '' theme = '' mots_cles = '' sens_motivation = '' partie = '' avis = '' def display(row): for i in ["Numéro de dossier", "Administration", "Type", "Année", "Séance", "Objet", "Thème et sous thème", "Mots clés", "Sens et motivation", "Partie", "Avis"]: print(f"{i} : {row[i]}") def markdown(row): print(f"\n# Dossier {row['Numéro de dossier']}\n") for i in ["Administration", "Type", "Année", "Séance", "Objet", "Thème et sous thème", "Mots clés", "Sens et motivation", "Partie", "Avis"]: print(f"**{i} :** {row[i]}") print("\n\\newpage\n") def validate(row): assert len(row['Numéro de dossier']) == 8 annee_dossier = int(row['Numéro de dossier'][0:4]) assert annee_dossier <= datetime.now().year and annee_dossier>= 1984 assert int(row['Numéro de dossier'][4:8]) # row['Administration'] can be empty assert row['Type'] in ['Avis', 'Conseil', 'Sanction'] if row['Année'] != '': annee = int(row['Année']) assert annee <= datetime.now().year and annee>= 1984 datetime.strptime(row['Séance'], '%d/%m/%Y').date() # row['Objet'] can be empty # row['Thème et sous thème'] can be empty # row['Mots clés'] can be empty # row['Sens et motivation'] can be empty # row['Partie'] can be empty # row['Avis'] can be empty def main(): parser = argparse.ArgumentParser(description="Avis et conseils CADA") parser.add_argument("--csv", type=str, required=True, help="export.csv") parser.add_argument("--limit", type=int, required=False, help="nb de dossiers à traiter en partant de l'offset", default=1000000) parser.add_argument("--offset", type=int, required=False, help="position du premier dossier à traiter", default=0) args = parser.parse_args() export = args.csv entries_input = 0 entries_output = 0 with open(export, newline='') as csvfile: reader = csv.DictReader(csvfile, delimiter=',') for row in reader: # display(row) validate(row) if entries_input>= args.offset: markdown(row) entries_output += 1 if entries_output>= args.limit: return entries_input += 1 if __name__ == '__main__': main() ``` et un bout de code _bash_ pour lancer tout ça (la boucle _for_ inutile date de mon erreur de traiter les 50 fichiers CSV au lieu d’un...) : on vérifie le code Python, et ensuite on produit des Markdown de 10000 enregistrements et les PDF associés. ```bash #!/bin/bash set -e flake8 cada.py mkdir -p markdown pdf for csv in data.gouv.fr/cada-2023年04月12日.csv do base=$(basename "$csv" ".csv") limit=10000 #TODO no check 50000+limit < real total for nb in $(seq 0 $limit 50000) do md="markdown/${base}_${nb}.md" ./cada.py --csv "$csv" --limit $limit --offset $nb> "$md" pdf="pdf/${base}_${nb}.pdf" pandoc "$md" -o "$pdf" --pdf-engine=weasyprint --metadata title="cada-2023年04月12日 ($nb)" done done ``` ## Entrées/sorties (petite CADAstrophe) Le fichier `cada-2023年04月12日.csv` contient 50639 enregistrements et fait déjà 156 MiB. Chaque fichier Markdown de 10 000 enregistrements fait environ 30 MiB. Chaque PDF fait de 40 à 50 MiB. La découpe par 10 000 est due à une raison assez simple : la machine utilisée a saturé ses GiB de RAM (après prélèvement du noyau et autres), _swappé_, ramé sévèrement, et la génération du PDF a été tuée par le OutOfMemory-killer (oom-killer) du noyau. `kernel: [ 4699.210362] Out of memory: Killed process 6760 (pandoc) total-vm:1074132060kB, anon-rss:13290136kB, file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:51572kB oom_score_adj:0` Bref ça donne : ```bash $ ls -lnh markdown/ total 164M -rw-r--r-- 1 1000 1000 29M 14 mai 18:31 cada-2023年04月12日_0.md -rw-r--r-- 1 1000 1000 29M 14 mai 18:41 cada-2023年04月12日_10000.md -rw-r--r-- 1 1000 1000 32M 14 mai 18:53 cada-2023年04月12日_20000.md -rw-r--r-- 1 1000 1000 34M 14 mai 19:06 cada-2023年04月12日_30000.md -rw-r--r-- 1 1000 1000 38M 14 mai 19:20 cada-2023年04月12日_40000.md -rw-r--r-- 1 1000 1000 3,4M 14 mai 19:35 cada-2023年04月12日_50000.md $ ls -lnh pdf/ total 442M -rw-r--r-- 1 1000 1000 39M 14 mai 18:41 cada-2023年04月12日_0.pdf -rw-r--r-- 1 1000 1000 39M 14 mai 18:53 cada-2023年04月12日_10000.pdf -rw-r--r-- 1 1000 1000 43M 14 mai 19:06 cada-2023年04月12日_20000.pdf -rw-r--r-- 1 1000 1000 45M 14 mai 19:20 cada-2023年04月12日_30000.pdf -rw-r--r-- 1 1000 1000 50M 14 mai 19:35 cada-2023年04月12日_40000.pdf -rw-r--r-- 1 1000 1000 4,4M 14 mai 19:35 cada-2023年04月12日_50000.pdf -rw-r--r-- 1 1000 1000 224M 14 mai 19:41 cada-2023年04月12日.pdf ``` Les personnes attentives auront noté le pdf obèse de 224 MiB. Il a été obtenu via `pdfunite cada-2023年04月12日_0.pdf cada-2023年04月12日_10000.pdf cada-2023年04月12日_20000.pdf cada-2023年04月12日_30000.pdf cada-2023年04月12日_40000.pdf cada-2023年04月12日_50000.pdf cada-2023年04月12日.pdf` ## Tada! non CADA! Le PDF final fait 52 019 pages (!) et _evince_ (et probablement la plupart des lecteurs de PDF) arrive à l’ouvrir et à rechercher dedans. Mission accomplie. Mais pas en 5min... il suffit de regarder les dates des fichiers plus haut d’ailleurs (et sans compter les essais multiples)... Bref j’ai produit un gros PDF, et le résultat convenait, donc je vous partage ça. Rien d’extraordinaire mais bon ça représente quand même _5min_ de boulot quoi...