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>
92 lines
3.1 KiB
Python
92 lines
3.1 KiB
Python
"""Lecture des analyses transversales (`research/lois-2026_insight.md`).
|
|
|
|
Sept analyses, chacune au format ::
|
|
|
|
## Insight 3 — Le 1er septembre 2026 : « big bang » silencieux …
|
|
**Dérivé de :** dim08, dim09, dim02, dim12, dim03.
|
|
<corps>
|
|
**Implications :** …
|
|
**Confiance : high.**
|
|
|
|
Elles alimentent l'encart « point de veille » du tableau de bord. Elles ne
|
|
portent aucune source primaire propre — la table de correspondance de la
|
|
bibliographie le dit — et ne sont donc jamais présentées comme des faits
|
|
sourcés, mais comme des lectures d'ensemble.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
from pipeline.journal import logger
|
|
from pipeline.modeles import Insight
|
|
from pipeline.parseurs.markdown import decouper_sections, nettoyer
|
|
|
|
log = logger("parseur.analyses")
|
|
|
|
_TITRE = re.compile(r"^Insight\s+(\d+)\s*[—–-]\s*(.+)$", re.IGNORECASE)
|
|
_DERIVE = re.compile(r"\*\*Dérivé de\s*:\*\*\s*(.+)", re.IGNORECASE)
|
|
_IMPLICATIONS = re.compile(r"\*\*Implications\s*:\*\*\s*(.+)", re.IGNORECASE)
|
|
_CONFIANCE = re.compile(r"\*\*Confiance\s*:\s*([a-zé-]+)", re.IGNORECASE)
|
|
_DIMENSION = re.compile(r"\b(dim\d{2}|phase\s?5)\b", re.IGNORECASE)
|
|
|
|
|
|
def parser(markdown: str) -> list[Insight]:
|
|
"""Extrait les analyses transversales du fichier d'insights."""
|
|
analyses: list[Insight] = []
|
|
|
|
for section in decouper_sections(markdown, niveau_max=2):
|
|
entete = _TITRE.match(section.titre.strip())
|
|
if not entete:
|
|
continue
|
|
|
|
corps_brut = section.corps
|
|
implications = None
|
|
if trouve := _IMPLICATIONS.search(corps_brut):
|
|
implications = nettoyer(trouve.group(1))
|
|
|
|
confiance = "medium"
|
|
if trouve := _CONFIANCE.search(corps_brut):
|
|
brut = trouve.group(1).lower()
|
|
confiance = {"high": "high", "élevée": "high", "medium": "medium", "low": "low"}.get(
|
|
brut, "medium"
|
|
)
|
|
|
|
derive_de: list[str] = []
|
|
if trouve := _DERIVE.search(corps_brut):
|
|
derive_de = [
|
|
d.lower().replace(" ", "") for d in _DIMENSION.findall(trouve.group(1))
|
|
]
|
|
|
|
corps = _corps_utile(corps_brut)
|
|
|
|
analyses.append(
|
|
Insight(
|
|
numero=int(entete.group(1)),
|
|
titre=nettoyer(entete.group(2)),
|
|
corps=corps,
|
|
implications=implications,
|
|
confiance=confiance,
|
|
derive_de=derive_de,
|
|
fichier_origine="research/lois-2026_insight.md",
|
|
)
|
|
)
|
|
|
|
log.info("analyses transversales lues", analyses=len(analyses))
|
|
return analyses
|
|
|
|
|
|
def _corps_utile(corps: str) -> str:
|
|
"""Corps de l'analyse, sans les lignes de métadonnées."""
|
|
lignes: list[str] = []
|
|
for ligne in corps.splitlines():
|
|
depouillee = ligne.strip()
|
|
if not depouillee:
|
|
continue
|
|
if _DERIVE.match(depouillee) or _IMPLICATIONS.match(depouillee):
|
|
continue
|
|
if _CONFIANCE.match(depouillee):
|
|
continue
|
|
lignes.append(nettoyer(depouillee))
|
|
return " ".join(lignes).strip()
|