Journal Avis et décisions CADA et génération d'un gros PDF

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
42
17
mai
2023

Sommaire

En France, la CADA 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. »

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 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 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, 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 :

#!/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.

#!/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 :

$ 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…

  • # Mais au fait…

    Posté par  . Évalué à 5.

    …pourquoi un PDF ? Certes on peut chercher hors ligne, mais une fois qu’on a trouvé l’info est vraiment simple à exploiter ?

    Pourquoi un fichier tableur bien formaté ne faisait pas l’affaire ?

    En fait j’imagine peut-être juste pas bien à quoi ressemble le PDF produit.

    • [^] # Re: Mais au fait…

      Posté par  (site web personnel, Mastodon) . Évalué à 3.

      Mais au fait…
      …quelle est la ville de naissance ?

      Sinon ai eu aussi la question du pourquoi PDF…
      ainsi que pourquoi Markdown…

      “It is seldom that liberty of any kind is lost all at once.” ― David Hume

    • [^] # Re: Mais au fait…

      Posté par  (site web personnel, Mastodon) . Évalué à 4.

      52019 pages ça fait beaucoup, beaucoup de lignes. Je ne serais pas étonnée que ça fasse exploser le tableur. Même bien formaté (ça veut dire quoi d'ailleurs bien formaté ?). C'est plus un truc à mettre en base de données ça.

      « Tak ne veut pas quʼon pense à lui, il veut quʼon pense », Terry Pratchett, Déraillé.

      • [^] # Re: Mais au fait…

        Posté par  (site web personnel) . Évalué à 4.

        50639 entrées qui font 52019 pages. Il y a des entrées qui font 7 ou 8 pages, donc dans un tableur, ça fait une cellule (avec multilignes) qui prend plusieurs écrans.

        • [^] # Re: Mais au fait…

          Posté par  (site web personnel, Mastodon) . Évalué à 5.

          Complètement inutilisable sur tableur !

          « Tak ne veut pas quʼon pense à lui, il veut quʼon pense », Terry Pratchett, Déraillé.

          • [^] # Re: Mais au fait…

            Posté par  . Évalué à 3.

            Il m'arrive au boulot de recevoir (et d'ouvrir donc) des fichiers de 200,000 lignes sous Excel sans soucis particulier (si ce n'est une certaine lenteur, bien sûr). Donc, ça, c'est pas un problème.

            Les cellules qui font plus d'une page, on est d'accord que c'est inutilisable sur Tableur. Mais en fait, c'est inutilisable tout court, non ? OK, on peut faire une recherche sur un truc, mais ça doit être un rendu de toute façon dégueulasse ; ça ou un fichier texte, y'a vraiment du mieux dans le PDF ?

            En plus, Acrobat (ou les PDF.js et consorts embarqués dans les navigateurs) sont pas des foudres de guerre. C'est pas pire ?

            Et tant qu'on est dans la curiosité, c'est quoi le format de page retenu ? Du A3, pour que chaque cellule tienne effectivement dans une page (et du coup un zoom nécessaire pour naviguer) ? Autre chose ?

            • [^] # Re: Mais au fait…

              Posté par  (site web personnel) . Évalué à 7.

              J'ai produit du A4 (vu que c'est le choix par défaut), ça fait juste des entrées sur plusieurs pages, mais au moins c'est formaté comme un texte multi-pages dans un bouquin.

              Perso, si c'était pour moi, un CSV ou un Markdown ou l'API ou un import en base de données, tout m'aurait convenu :). Mais il s'agit d'une demande d'un proche (et, divulgâchons, quelques jours plus tard, la personne m'a remercié et elle m'a dit que finalement les PDF c'était trop lourd aussi, et qu'elle allait utiliser l'API… soit exactement ce que j'avais suggéré en premier, mais bon les utilisateurs tout ça :)).

              (au final c'est surtout une expérience supplémentaire dans le chemin vers la génération du gigantesque mythique et inutilisable PDF de l'intégralité de LinuxFr.org)

              • [^] # Re: Mais au fait…

                Posté par  . Évalué à 1. Dernière modification le 11 juin 2023 à 09:45.

                (au final c'est surtout une expérience supplémentaire dans le chemin vers la génération du gigantesque mythique et inutilisable PDF de l'intégralité de LinuxFr.org)

                Ben, c'est pas comme ça que vous faites les backups ?

      • [^] # Re: Mais au fait…

        Posté par  . Évalué à 3. Dernière modification le 23 mai 2023 à 02:48.

        Avec Calc de LibreOffice je ne sais pas, mais avec Gnumeric tu peux ouvrir, et exploiter, des fichiers beaucoup plus gros sans problème. Pas testé de fichier avec des cellules obèses mais je parie que ça passe.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.