legi.py icon indicating copy to clipboard operation
legi.py copied to clipboard

Adaptation à la base JORF

Open Seb35 opened this issue 5 years ago • 18 comments

Cette requête d’intégration s’inscrit dans le cadre de #33 et permet d’utiliser l’infrastructure existante de legi.py pour la base JORF.

La base JORF a quasiment la même structure que la base LEGI, à savoir des textes découpés en articles et en sections. La principale (et quasiment seule différence) est que tous les articles (resp. les sections, resp. les textes) sont dans le dossier 'article' (resp. 'section_ta', resp. 'texte'), au contraire de la base LEGI où toutes les données d’un texte sont dans un dossier LEGITEXT. Une différence minime de JORF est qu’il n’y a pas de grands dossiers comme "code_et_TNC_en_vigueur".

J’ai fait le choix dans cette implémentation de créer un fichier SQLite séparé pour la base JORF. C’est discutable, mais c’est une première proposition qui est fortement conservatrice. Une solution alternative serait de mettre toutes les bases dans un même fichier SQLite, peut-être avec des préfixes de tables (par ex. "legi_textes_versions") ; cela aurait l’avantage de permettre les requêtes SQL sur plusieurs bases, mais peut-être l’inconvénient que les fichiers SQLite deviennent énormes (legi.sqlite fait 4 Gio, jorf.sqlite 5 Gio, et il y a encore JADE qui est de l’ordre de LEGI, et KALI, CAPP, CASS de taille moyenne, le total d’une unique base ferait 20-25 Gio).

Pour différencier les bases SQLite, j’ai ajouté une métadonnée "base" dans db_meta qui peut valoir pour l’instant "LEGI" ou "JORF". Au lancement, il a une vérification de cette valeur pour interdire d’ajouter des données de LEGI dans une base JORF. S’il n’y a pas de telle métadonnée, la valeur utilisée est "LEGI" pour rétro-compatibilité.

À l’initialisation d’une nouvelle base SQLite, il peut être précisé un nouveau paramètre "--base JORF" pour télécharger ou créer une base JORF. Par défaut, une base LEGI est téléchargée ou créée. Lors de la mise à jour d’une base SQLite, ce paramètre peut être omis et il est alors lu la nature de la base SQLite ; ce paramètre peut toutefois être déclaré pour vérifier la nature de la base SQLite, et en cas de désaccord le programme ne va pas plus loin.

Seb35 avatar Jan 27 '19 17:01 Seb35

Je signale ici un problème qui se produit seulement sur le fichier JORF_20190201-213848.tar.gz qui contient semble-t-il des instructions de suppression et qui cause la levée d'une exception. Sinon les 100 premiers fichiers des tarballs passent sans problème.

Processing JORF_20190201-213848.tar.gz... 8388it [00:00, 10892.97it/s] made 7190 changes in the database: { "delete from liens": 317, "delete from sommaires": 2126, "insert into liens": 323, "insert into sommaires": 2126, "insert into textes_structs": 1, "insert into textes_versions": 1, "update in articles": 1430, "update in sections": 696, "update in textes_structs": 85, "update in textes_versions": 85 } skipped 40 files in unknown folder conteneur skipped 2703 files in unknown folder eli Traceback (most recent call last): File "/home/develie/opt/lib/python3.7/runpy.py", line 193, in _run_module_as_main "main", mod_spec) File "/home/develie/opt/lib/python3.7/runpy.py", line 85, in _run_code exec(code, run_globals) File "/home/develie/JORF/legi.py/legi/tar2sqlite.py", line 591, in main() File "/home/develie/JORF/legi.py/legi/tar2sqlite.py", line 565, in main process_archive(db, args.directory + '/' + archive_name, not args.skip_links) File "/home/develie/JORF/legi.py/legi/tar2sqlite.py", line 488, in process_archive suppress(base, get_table, db, liste_suppression) File "/home/develie/JORF/legi.py/legi/tar2sqlite.py", line 49, in suppress assert parts[0] == base.lower() AssertionError

semiosys avatar Feb 04 '19 15:02 semiosys

Je signale ici un problème qui se produit seulement sur le fichier JORF_20190201-213848.tar.gz qui contient semble-t-il des instructions de suppression et qui cause la levée d'une exception. Sinon les 100 premiers fichiers des tarballs passent sans problème.

C’est plus subtil, car JORF_20190108-001815.tar.gz contient aussi une telle liste de suppression qui fonctionne. C’était le seul exemple sur la période 2018-11-29–2019-01-27, donc c’est un mécanisme qui n’a pas été beaucoup éprouvé.

En l’espèce, la liste de suppression de JORF_20190201-213848.tar.gz est :

null/JORFDOLE000038084246

qui déclenche à juste titre l’exception. On ignore complètement cette vérification dans JORF spécifiquement (pour conserver la vérification dans LEGI) ? On autorise et ignore cette valeur 'null/…' dans l’hypothèse (vraisemblable) que c’est un bug dans l’export de JORF ?

Seb35 avatar Feb 04 '19 18:02 Seb35

On ignore complètement cette vérification dans JORF spécifiquement (pour conserver la vérification dans LEGI) ? On autorise et ignore cette valeur 'null/…' dans l’hypothèse (vraisemblable) que c’est un bug dans l’export de JORF ? Merci. Difficile de savoir en effet. Provisoirement pour le cas de JOFR j'ai mis un continue dans la boucle afin d'éviter l'exception levée par assert.

semiosys avatar Feb 07 '19 07:02 semiosys

Je suis en train de faire tourner pour tester cette PR, avec quelques détails supplémentaires que je pousserai (le problème du null dans liste_suppression.dat et des contraintes NOT NULL oubliées dans le schéma).

Seb35 avatar Feb 09 '19 12:02 Seb35

Cette version fonctionne sur JORF sur la période 20181129-070000 à 20190209-005447.

Les nouveaux commits changent ceci :

  • les bases autres que LEGI doivent utiliser l’option --raw et ne doivent pas utiliser --anomalies,
  • les entrées dans liste_suppression commençant par null sont ignorées,
  • la colonne dossier vaut NULL au lieu de 'jorf'.

Seb35 avatar Feb 09 '19 13:02 Seb35

Super merci.

semiosys avatar Feb 09 '19 14:02 semiosys

J'ai constaté une chose étrange que je n'arrive pas à comprendre : il s'agit peut-être d'une différence entre la base JOFR construite par legy.py et la base JOFR publiée sur LegiFrance.

J'ai observé cette différence par hasard sur un texte: JORFTEXT000037864346 J'ai vérifié quelques autres textes sans pouvoir retrouver l'anomalie, si c'est une anomalie toutefois, quelque chose m'a peut-être échappé au sujet de la colonne mtime.

Journal officiel du 28 décembre 2018 https://www.legifrance.gouv.fr/affichJO.do?idJO=JORFCONT000037864056&fastPos=1&fastReqId=117687219

Item 17 : Arrêté du 27 décembre 2018 relatif à la prévention, à la réduction et à la limitation des nuisances lumineuses

Si je cherche ce texte dans la base jofr.sqlite SELECT * FROM "textes_versions_brutes_view" WHERE "cid" LIKE 'JORFTEXT000037864346'

titrefull: Arrêté du 27 décembre 2018 relatif à la prévention, à la réduction et à la limitation des nuisances lumineuses date_texte: 2018-12-27

Et si je cherche les articles associés: SELECT * FROM "articles" WHERE "cid" LIKE 'JORFTEXT000037864346' Il y a 9 articles correspondant à ce texte, tous insérés avec un mtime = 1547499273 Or, il semblerait que ce mtime correspondant à la date du 14 janvier : SELECT date(1547499273, 'unixepoch', 'localtime'); 2019-01-14

Or dans la version initiale du texte du mois de décembre, les 9 articles sont déjà présents : https://www.legifrance.gouv.fr/affichTexte.do;jsessionid=4A77CEBDCECB7D1A9D3DBF0B2573BF32.tplgfr28s_2?cidTexte=JORFTEXT000037864346&dateTexte=&oldAction=rechJO&categorieLien=id&idJO=JORFCONT000037864056

Je trouve étrange que les articles associés au texte aient un mtime de plus de 2 semaines de retard par rapport à la date supposée d'insertion de ces articles dans l'archive tgz.

Par conséquent si je veux afficher le JO d'une date donnée, je ne peux me fier à mtime de la table article. Et le champ date_texte de la vue textes_versions_brutes_view est parfois renseigné mais souvent a la valeur 2999-01-01. Du coup je ne vois pas trop comment faire.

Si quelqu'un à une explication ou une solution je suis preneur. Merci !

semiosys avatar Feb 18 '19 10:02 semiosys

Il y a plusieurs choses dans ce message. Je les prends une par une ci-après, mais globalement c’est une mauvaise compréhension de ce champ mtime.

J'ai constaté une chose étrange que je n'arrive pas à comprendre : il s'agit peut-être d'une différence entre la base JOFR construite par legy.py et la base JOFR publiée sur LegiFrance.

J'ai observé cette différence par hasard sur un texte: JORFTEXT000037864346 J'ai vérifié quelques autres textes sans pouvoir retrouver l'anomalie, si c'est une anomalie toutefois, quelque chose m'a peut-être échappé au sujet de la colonne mtime.

[…]

Je trouve étrange que les articles associés au texte aient un mtime de plus de 2 semaines de retard par rapport à la date supposée d'insertion de ces articles dans l'archive tgz.

Par conséquent si je veux afficher le JO d'une date donnée, je ne peux me fier à mtime de la table article. Et le champ date_texte de la vue textes_versions_brutes_view est parfois renseigné mais souvent a la valeur 2999-01-01. Du coup je ne vois pas trop comment faire.

Si quelqu'un à une explication ou une solution je suis preneur. Merci !

Le champ mtime est la date de modification de l’enregistrement du texte, et il peut tout à fait y avoir des modifications "éditoriales" (ajouts de métadonnées, corrections de métadonnées, …) qui modifient cette date, qui peut alors être (largement) postérieure à la date de publication. J’ai également remarqué que cette date mtime varie de quelques secondes dans une livraison donnée, probablement car le processus d’export prend du temps et donc que ça enregistre des dates de modification légèrement différentes.

En l’espèce, ce texte JORFTEXT000037864346 a été modifié au moins deux fois :

  • une première fois dans la livraison JORF_20181228-022602.tar.gz contenant la parution initiale du texte
  • une deuxième fois (ou une énième fois) dans la livraison JORF_20190114-215430.tar.gz contenant une version modifié du texte ; c’est actuellement cette version éditoriale qui est la dernière en date.

Je n’ai pas regardé les modifications apportées, mais tu peux le faire en décompressant ces deux fichiers et en faisant des diffs sur le texte et ses articles (fichiers dans texte/versions, texte/structs, section_ta et article). Je ne crois pas qu’il y ait d’outil pour ce faire puisque legi.py écrase les enregistrements. Ou disons, ça peut se faire avec Archéo Lex (je le fais sur LEGI) en enregistrant des branches Git tous les jours, mais il ne faut rater aucun jour et ça ne fait apparaitre que ce qui est réellement dans les données exportées par Archéo Lex (autrement dit le texte mais pas les métadonnées).

Sur la remarque de l’affichage sur Légifrance, il est (très) probable que Légifrance ne s’appuie pas sur le champ mtime pour l’affichage du JORF mais plus probablement en affichant simplement tous les articles d’un texte paru au JORF (et s’appuie il s’appuie probablement sur le champ etat pour la base LEGI).

Autrement dit, si tu cherches ce qui paraît au JORF, il faut plutôt que tu t’appuies sur le champ date_publi dans textes_versions.

Sur les dates 2999-01-01, c’est la "norme" dans les bases DILA pour signifier "pas de date", soit parce que la date n’a pas été renseignée (dans le cas des vieux textes pas encore complètement saisis), soit parce que, dans les dates de fin, il n’y a pas de date de fin (la plupart des lois sont censées durer indéfiniment, du moins tant qu’elles ne sont pas abrogées). Je mentionne aussi pour information la date spéciale 2222-02-22 qui signifie "date d’entrée en vigueur future indéterminée" qui se trouve dans la base LEGI (il ne semble pas y en avoir dans JORF) lorsqu’une version consolidée en vigueur future est préparée, mais que la date d’entrée en vigueur est inconnue pour l’instant, généralement car cette date dépend d’un événement qui n’a pas encore été fixé (par exemple une date d’élection).

Enfin, même si tu dois trouver l’information que tu cherches en utilisant la colonne date_publi, je pense que ce que prépare Adrien pour la base KALI et la notion de conteneur pourra t’être très utile pour JORF, pour obtenir directement tous les textes parus au JO d’un jour.

Seb35 avatar Feb 18 '19 13:02 Seb35

Merci @Seb35 pour ces précisions j'apprends aussi des choses ! J'ai bien avancé sur le support de KALI dans ce projet legi.py et on a prévu d'essayer avec JORF aussi.

@semiosys Si tu souhaites essayer en partant de ma branche KALI n'hésite pas, avec un peu de chance tout marchera sans aucune modification :)

adipasquale avatar Feb 18 '19 14:02 adipasquale

Merci beaucoup @Seb35 c'est beaucoup plus clair !

semiosys avatar Feb 18 '19 14:02 semiosys

Hello @Changaco, est-ce qu'on peut aider pour merger cette PR ? On a forké le repo sur le compte GitHub SocialGouv et on commence à pas mal avancer sur les branches pour ajouter le support de KALI et d'autres bases que SQLite.

Notre objectif c'était autant que possible de ne pas diverger mais de faire évoluer ce projet, tiens nous au courant si ça t'intéresse on est disponibles pour aider !

adipasquale avatar Feb 25 '19 07:02 adipasquale

Bonsoir,

Je rencontre un erreur dans le traitement des tarballs, ça bloque au 21 août dernier. J'ai vu que la branche principale legi avait été sensiblement retouchée récemment; peut-être que le branche JOFR n'a pas suivi cette évolution ?

Si vous avez une idée je suis preneur Merci

./cron/cron.sh 476 remote files, 476 common files (0 invalid), 0 missing files read format "warc" is not supported read filter "lz4" is not supported write format "warc" is not supported write filter "lz4" is not supported 0it [00:00, ?it/s] Traceback (most recent call last): File "/home/dev/opt/lib/python3.7/runpy.py", line 193, in _run_module_as_main "main", mod_spec) File "/home/dev/opt/lib/python3.7/runpy.py", line 85, in _run_code exec(code, run_globals) File "/home/dev/app/legilibre/JORF/legi.py/legi/tar2sqlite.py", line 601, in main() File "/home/dev/app/legilibre/JORF/legi.py/legi/tar2sqlite.py", line 575, in main process_archive(db, args.directory + '/' + archive_name, not args.skip_links) File "/home/dev/app/legilibre/JORF/legi.py/legi/tar2sqlite.py", line 204, in process_archive parts = path.split('/') TypeError: a bytes-like object is required, not 'str'

last_update is 20190821-211941 Skipped 402 old archives Processing JORF_20190822-003245.tar.gz...

semiosys avatar Oct 08 '19 18:10 semiosys

Hello @semiosys j'ai arrêté de bosser il y a un moment là dessus, je ne peux pas trop te dire. ça ressemble vaguement à un problème de version python 2/3 ou bien un argument par défaut incorrect mais je ne suis pas sûr.

La version pour le code du travail numérique a bien divergé depuis, je t'encourage à aller voir : https://github.com/SocialGouv/dila2sql

Bonne journée, Adrien

adipasquale avatar Oct 09 '19 07:10 adipasquale

@adipasquale Merci beaucoup Adrien. Je vais jeter un oeil du côté de dila2sql. En espérant que les modèles SQL n'aient pas trop changé par rapport à legilibre.

semiosys avatar Oct 09 '19 08:10 semiosys

Je suis désolé d’avoir laissé le temps filer pour cette PR. Je vais la reprendre prochainement. Par rapport à ta relecture, @Changaco, outre les 3 premiers points techniques que je corrigerai, je suis d’accord sur le 4e qui va changer assez fortement l’orientation de cette PR : prévoir un mécanisme pour stocker plusieurs bases DILA dans une même base SQLite. Pour le couple JORF+LEGI, ça sera d’autant plus utile puisque ça permettra des jointures entre les deux.

Pour tout mettre dans une base SQLite, en théorie il n’y aurait pas besoin de modifier le schéma vu que les id de chaque base sont distincts par leurs préfixes (JORF, LEGI, CONS, etc.) et on pourrait remplir directement avec les autres bases. Mais dans l’hypothèse de cette solution, je pense qu’il faudrait ajouter une colonne 'base' dans toutes les tables avec un index dessus pour pouvoir interroger « juste une base DILA ». L’autre solution, radicalement différente, serait de dupliquer le schéma actuel avec un préfixe, on aurait par exemple jorf_textes_versions, jorf_articles. Je n’ai pas encore réfléchi si une solution est meilleure qu’une autre.

@semiosys Je sais pas si tu as pu résoudre le blocage au 21 août 2019 : Changaco l’a corrigé dans #68.

Seb35 avatar Jan 14 '20 23:01 Seb35

Bon, j’ai réfléchi et regardé quelques autres bases en détails (JORF, KALI), je penche plutôt pour faire des tables séparées avec un préfixe (jorf_textes_versions, jorf_articles) car même si 90 % des colonnes de JORF/KALI sont les mêmes que LEGI, il y a des subtilités qui seraient perdues au passage en SQL.

Par exemple :

  1. dans JORF il y a un nœud XML "ENTREPRISE" qui correspond probablement aux textes concernant les entreprises et ayant une date d’entrée en vigueur différée pour leur laisser le temps de se préparer (voir cette page sur Légifrance) mentionnant la date d’entrée en vigueur et le domaine d’activité concerné.
  2. Ou alors dans KALI il y a plusieurs champs meta concernant : l’application géographique (national, régional, etc), des "codes nomenclatures" (qui ressemblent à des numéros APE, je n’ai pas creusé), les états sont différents de LEGI/JORF (vigueur étendue, vigueur non-étendue) ; puis dans les signataires il y a les syndicats (SIGNSYNDIC), organisations patronales (SIGNPATRON) et d’autres champs spécifiques.

Aussi il serait réducteur d’utiliser la structure existante de LEGI pour les autres bases. Utiliser d’autres tables permettrait aussi d’avoir plus de granularité sur les index (par exemple on veut un index sur un champ de LEGI, mais pas forcément de JORF) et les éventuelles contraintes. Je propose donc d’utiliser des tables distinctes pour les différentes bases DILA. Cela demandera de l’expertise à chaque ajout de base, mais c’est sûrement mieux pour la qualité finale. Techniquement, cela évite tout problème de compatibilité sur ce point.

Cela ferait diverger assez fortement de la première version de cette PR et donc du fork SocialGouv@dila2sql, mais je pense que c’est mieux dans le fond, et de toutes façons ce fork a très fortement divergé depuis un an.

Seb35 avatar Mar 09 '20 09:03 Seb35

J’ai aussi resynchronisé en local cette PR sur la branche maîtresse, ça semble fonctionner mais je continue les changements. Juste, j’ai l’impression que c’est plus lent qu’auparavant (~5-10 items/s sur mon ordinateur alors que j’avais souvenir de quasiment un ordre de grandeur supérieur) ; c’est juste une impression venant de souvenirs peut-être brumeux d’il y a un an.

Seb35 avatar Mar 09 '20 09:03 Seb35

À mon avis JORF et LEGI devraient probablement partager les mêmes tables, car LEGI contient de toute façon des éléments dont les identifiants commencent par JORF.

Utiliser les mêmes tables ne signifie pas réduire les données au plus petit dénominateur commun. On peut soit ajouter une colonne pour chaque donnée supplémentaire, soit une seule colonne pour stocker toutes les données additionnelles en JSON.

Utiliser d’autres tables permettrait aussi d’avoir plus de granularité sur les index (par exemple on veut un index sur un champ de LEGI, mais pas forcément de JORF)

SQLite gère les index partiels: https://sqlite.org/partialindex.html.

Changaco avatar Mar 09 '20 09:03 Changaco