Lors de la nuit du code citoyen (voir ce compte-rendu), j’ai tenté avec d’autres de reconnaître les liens dans le texte. Je pensais que ça serait direct car la base LEGI a les liens, mais elle ne donne pas les correspondances directes dans le texte, le problème reste donc entier.
Après avoir essayé d’écrire des expressions rationnelles, il est apparu que ça serait laborieux. Peu avant la fin du hackathon, Pierre-Yves a proposé d’utiliser les PEG (Parsing Expression Grammar) qui en résumé sont des expressions rationnelles bien plus puissantes et plus faciles à lire où chaque partie de l’expression est nommée et peut être composée avec d’autres expressions nommées. En Python, il y a la librairie Parsimonious.
Je viens de tester si ça peut effectivement fonctionner : le prototype fonctionne et le code est court et maintenable. Ci-après la grammaire que j’ai utilisée et les quelques lignes de Python pour tester, et enfin ce qu’il reste à faire pour aboutir au meilleur résultat.
Grammaire de reconnaissance de liens
lien = lienarticle? denature? numtexte? date?
lienarticle = "article " numarticle (" " cies)?
lientexte = nature numtexte? date?
numarticle = ~r"([LDR]\.?|LO)? *[0-9-]+"i
cies = ~r"((quinqu|sex|sept|oct|non)ies|(un|duo|ter|quater|quin|sex|sept|octo|novo)?(dec|vic|tric|quadrag|quinquag|sexag|septuag|octog|nonag)ies|semel|bis|ter|quater)"
nature = "arrêté" / "code" / "décret" / "loi" / "ordonnance"
denature = " "+ ( "de l'arrêté" / "de l’arrêté" / "du code" / "du décret" / "de la loi" / "de l'ordonnance" / "de l’ordonnance" )
numtexte = ~r" *n° *(1[789]|20)?[0-9]{2}-[0-9]+"
jour = ~r"(1er|[12][0-9]|3[01]|[1-9])"
mois = "janvier" / "février" / "mars" / "avril" / "mai" / "juin" / "juillet" / "août" / "septembre" / "octobre" / "novembre" / "décembre"
annee = ~r"(1[56789]|20)[0-9]{2}"
date = " "* "du" " "* jour " " mois " " annee
Code Python de test
- Utiliser Python 3 car il y a des problèmes d’Unicode avec Python 2.7
- Installer Parsimonious avec pip, par exemple
pip3 install parsimonious
- Écrire la grammaire ci-dessus dans un fichier nommé
grammaire.txt
- En Python 3 :
from parsimonious.grammar import Grammar f = open( 'grammaire.txt', 'r' ); grammaire = f.read(); f.close(); grammar = Grammar( grammaire ) print( grammar.parse( "article 5-5-4 quatertricies de l'ordonnance n° 96-28 du 27 août 2018" ) )
- Cela donne l’AST (Abstract Syntax Tree) suivant :
<Node called "lien" matching "article 5-5-4 quatertricies de l'ordonnance n° 96-28 du 27 août 2018"> <Node matching "article 5-5-4 quatertricies"> <Node called "lienarticle" matching "article 5-5-4 quatertricies"> <Node matching "article "> <RegexNode called "numarticle" matching "5-5-4"> <Node matching " quatertricies"> <Node matching " quatertricies"> <Node matching " "> <RegexNode called "cies" matching "quatertricies"> <Node matching " de l'ordonnance"> <Node called "denature" matching " de l'ordonnance"> <Node matching " "> <Node matching " "> <Node matching "de l'ordonnance"> <Node matching "de l'ordonnance"> <Node matching " n° 96-28"> <RegexNode called "numtexte" matching " n° 96-28"> <Node matching " du 27 août 2018"> <Node called "date" matching " du 27 août 2018"> <Node matching " "> <Node matching " "> <Node matching "du"> <Node matching " "> <Node matching " "> <RegexNode called "jour" matching "27"> <Node matching " "> <Node called "mois" matching "août"> <Node matching "août"> <Node matching " "> <RegexNode called "annee" matching "2018">
- Si vous essayez avec d’autres expressions, il faut remarquer que la variable “lien” de la grammaire capturera toujours l’expression étant donné que toutes ses sous-variables sont optionnelles. Parsimonious envoit toutefois une exception
parsimonious.exceptions.IncompleteParseError: Rule 'lien' matched in its entirety, but it didn't consume all the text. The non-matching portion of the text begins with 'tricies de l'ordonna' (line 1, column 21).
Reste à faire
EDIT: la méthode ci-dessus est implémentée dans la librairie metslesliens avec une grammaire PEG qui s’est un peu améliorée. Les coches ci-dessous sont désormais implémentées et les ✓… sont en cours.
C’est donc un prototype ci-dessus, il reste :
-
lire l’AST pour en extraire les informations importantes : le numéro de l’article (ici 5-5-4 quatertricies) et le nom du texte (ici ordonnance 96-28 du 27 août 2018 – je sais, le numéro 96-28 ne respecte pas la légistique étant donné que l’année est 2018, mais il n’y a pas forcément besoin de rajouter des contraintes, d’autant que ça pourrait reconnaître des erreurs éventuelles dans les numérotations)
- aligner/mettre en correspondance les candidats de liens avec une base de données réelles des articles existants dans les textes (par exemple la base legi.py), à la bonne date de vigueur
- Lorsqu’il n’y a pas d’alignement, c’est soit que le programme a un problème, soit que la base a un problème ; dans le 2e cas, ça peut valoir le coup de faire une liste des problèmes qui serait vérifiée par des humains (voire proposée à la DILA dans un second temps une fois que la plupart des faux-positifs seront éliminés)
- ✓… Améliorer et optimiser la grammaire ; par exemple j’ai remarqué qu’il y avait vraiment de tout dans les noms d’articles (pas seulement des “28-3-1 quavicies”, mais aussi des “1609 nonies A ter” (coucou le CGI) et des vraies expressions comme “Annexe : Agent de constatation” ou “Annexe II habitats humides” ou “Tableau des abréviations” ou “Cotation des épreuves hommes” ou cet article “Execution” sans mauvais jeu de mots), il faut arriver donc à capter de telles expressions, peut-être en utilisant des expressions non-gloutonnes
- Lorsqu’une table de correspondance est fournie par la DILA, comme dans la base LEGI, mettre en correspondance les expressions candidates avec les liens fournis (on a le choix ici d’aligner soit avec les liens fournis soit avec une BDD externe, soit les deux et vérifier qu’on a le même résultat)
- Contribuer à Parsimonious, par exemple en écrivant une doc, et/ou chercher d’autres librairies
-
Écrire une librairie (Python ?) pour packager cette reconnaissance de liens
-
Ajouter la reconnaissance de contexte, par exemple les liens “article 7 du présent code”
- ✓… Ajouter la reconnaissance des alinéas, des paragraphes, des points dans les numérotations, par exemple les liens “le point c. de l’antépénultimème alinéa du troisième paragraphe de l’article 7-3-1 A sexvicies-0 du code général des impôts” (noter qu’il faut ici créer le lien vers l’alinéa, il n’est pas forcément besoin de reconnaître l’alinéa dans le texte cible, ce qui est un autre problème avec sa propre complexité) (issue #5)