Files
veye-lalwa/pipeline/guadeloupe.py
T
Cyber MawonajandClaude Opus 5 c28243cf87 Phase 3 : sources, citations et seed complet — 368 sources, 0 texte orphelin
Parseurs :
- blocs_claim : les quatre dialectes des rapports de dimension. Le cahier des
  charges en postulait un seul ; sans les quatre, dim05 à dim12 étaient perdus,
  soit les dimensions associations, entreprises, migration et calendrier.
- notes_bas_page : 258 notes, dont 104 sans URL. Les identifiants JORFTEXT
  donnent lieu à une URL canonique reconstruite (schéma fixe, rien d'inventé) ;
  les autres restent sans lien plutôt que d'être devinées.
- bibliographie : 481 références, toutes liées. Indexées par numéro de loi,
  par affaire DC et par cote de document parlementaire — cette dernière étant
  la seule identité d'un texte déposé et non examiné.
- chapitres : rattachement phrase par phrase, résumés repris tels quels.

Résolution des citations : 100 % des 332 marqueurs du rapport, 93 % avec un
lien vérifiable. Il a fallu six conventions de numérotation, une par famille de
dimension (marqueur de fin, en-tête de claim, section, rang, note, section.rang).

Trois pièges corrigés, tous couverts par des tests :
- « décision n° 2026-903 DC » lu comme une loi, ce qui volait à la loi 2026-403
  les phrases — et donc les sources — qui parlaient d'elle
- retrait des marqueurs laissant « … dans les DROM . » ; corrigé à la source
  sans toucher aux espaces avant « ; » et « » » qu'exige le français
- « programmation militaire 2024-2030 » lu comme la loi n° 2024-2030

make seed : 52 textes, 368 sources, 83 événements, 12 affaires CC, 7 analyses.
Zéro texte sans source URL (critère §8.2). Les 2 textes sans résumé et les 17
pertinences déduites sont marqués « à vérifier » avec leur motif.

99 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 19:43:03 -04:00

93 lines
2.9 KiB
Python

"""Détection de la pertinence guadeloupéenne et ultramarine d'un texte.
Le tableau A de l'annexe cote lui-même les 27 lois promulguées ; le tableau B
ne cote pas les textes en cours. Pour ces derniers, la cotation est déduite du
vocabulaire employé par le rapport, avec des poids déclarés dans
`pipeline/config.yaml`.
Toute cotation obtenue ainsi est signalée : le texte porte `a_verifier = 1` et
un motif explicite. L'interface l'affiche comme telle. Une cotation déduite
n'est pas une cotation établie.
"""
from __future__ import annotations
import functools
import re
from dataclasses import dataclass
import yaml
from pipeline import chemins
from pipeline.modeles import Pertinence
from pipeline.parseurs.dates_fr import sans_accents
@dataclass(slots=True)
class Cotation:
"""Résultat d'une détection, avec ce qui l'a motivée."""
pertinence: Pertinence | None
score: int
expressions: list[str]
@property
def note(self) -> str | None:
if not self.expressions:
return None
return "Détecté d'après : " + ", ".join(self.expressions[:6])
@functools.lru_cache(maxsize=1)
def _configuration() -> dict:
return yaml.safe_load(chemins.CONFIG.read_text(encoding="utf-8"))
@functools.lru_cache(maxsize=1)
def _motifs() -> list[tuple[re.Pattern[str], str, int]]:
"""Compile un motif par expression, ancré sur des frontières de mot."""
expressions = _configuration()["guadeloupe"]["expressions"]
compiles: list[tuple[re.Pattern[str], str, int]] = []
for expression, poids in expressions.items():
plat = sans_accents(str(expression)).lower()
compiles.append((re.compile(rf"(?<!\w){re.escape(plat)}\w{{0,3}}(?!\w)"), plat, int(poids)))
# Les expressions les plus lourdes d'abord : le compte-rendu cite en
# priorité ce qui a réellement décidé de la cotation.
compiles.sort(key=lambda triplet: -triplet[2])
return compiles
def coter(*fragments: str | None) -> Cotation:
"""Cote la pertinence guadeloupéenne d'un ensemble de fragments de texte."""
plat = sans_accents(" ".join(f for f in fragments if f)).lower()
if not plat.strip():
return Cotation(pertinence=None, score=0, expressions=[])
score = 0
trouvees: list[str] = []
for motif, expression, poids in _motifs():
if motif.search(plat):
score += poids
trouvees.append(expression)
seuils = _configuration()["guadeloupe"]["seuils"]
if score >= seuils["forte"]:
pertinence = Pertinence.FORTE
elif score >= seuils["moyenne"]:
pertinence = Pertinence.MOYENNE
elif score >= seuils["faible"]:
pertinence = Pertinence.FAIBLE
else:
pertinence = None
return Cotation(pertinence=pertinence, score=score, expressions=trouvees)
MOTIF_VERIFICATION = (
"Pertinence Guadeloupe déduite automatiquement : l'annexe du rapport ne "
"cote que les lois promulguées, pas les textes encore en discussion."
)