2026-07-25 21:48:56 -04:00
|
|
|
"""Run de veille : détection des écarts, classification, export.
|
|
|
|
|
|
|
|
|
|
Aucun test ne va sur le réseau : les collectes sont fabriquées à la main, ce
|
|
|
|
|
qui permet de vérifier exactement quels écarts le pipeline doit voir — et
|
|
|
|
|
surtout lesquels il ne doit pas inventer.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import sqlite3
|
|
|
|
|
from datetime import date
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from pipeline import db
|
|
|
|
|
from pipeline.collecteurs.base import ResultatCollecte, TexteCollecte
|
|
|
|
|
from pipeline.modeles import (
|
|
|
|
|
Confiance,
|
|
|
|
|
DecisionCC,
|
|
|
|
|
Evenement,
|
|
|
|
|
Pertinence,
|
|
|
|
|
ResultatCC,
|
|
|
|
|
Source,
|
|
|
|
|
Statut,
|
|
|
|
|
Texte,
|
|
|
|
|
TypeEtape,
|
|
|
|
|
TypeTexte,
|
|
|
|
|
)
|
|
|
|
|
from pipeline.update import appliquer, classer_nouveau, comparer, exporter_json
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def base(tmp_path) -> sqlite3.Connection:
|
|
|
|
|
"""Base minimale : une loi promulguée et une loi en attente de décision."""
|
|
|
|
|
cx = db.initialiser(tmp_path / "veille.db")
|
|
|
|
|
db.enregistrer_textes(
|
|
|
|
|
cx,
|
|
|
|
|
[
|
|
|
|
|
Texte(
|
|
|
|
|
id="loi-2026-491",
|
|
|
|
|
numero_officiel="2026-491",
|
|
|
|
|
type=TypeTexte.LOI,
|
|
|
|
|
titre_court="Chlordécone : responsabilité de l'État",
|
|
|
|
|
statut=Statut.PROMULGUEE,
|
|
|
|
|
date_promulgation=date(2026, 6, 12),
|
|
|
|
|
guadeloupe_pertinence=Pertinence.FORTE,
|
|
|
|
|
sources=[
|
|
|
|
|
Source(
|
|
|
|
|
url="https://www.legifrance.gouv.fr/jorf/id/JORFTEXT000054245243",
|
|
|
|
|
editeur="Légifrance",
|
|
|
|
|
)
|
|
|
|
|
],
|
|
|
|
|
evenements=[
|
|
|
|
|
Evenement(
|
|
|
|
|
date_evenement=date(2026, 6, 12),
|
|
|
|
|
type_etape=TypeEtape.PROMULGATION,
|
|
|
|
|
description="Promulgation",
|
|
|
|
|
)
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
Texte(
|
|
|
|
|
id="loi-riposte",
|
|
|
|
|
type=TypeTexte.LOI,
|
|
|
|
|
titre_court="Loi « Riposte » (ordre public, sécurité, tranquillité)",
|
|
|
|
|
statut=Statut.ADOPTEE_NON_PROMULGUEE,
|
|
|
|
|
date_adoption=date(2026, 7, 21),
|
|
|
|
|
sources=[Source(url="https://www.assemblee-nationale.fr/dyn/17/textes/l17t0340")],
|
|
|
|
|
decisions_cc=[
|
|
|
|
|
DecisionCC(
|
|
|
|
|
numero_affaire="2026-915 DC",
|
|
|
|
|
date_saisine=date(2026, 7, 24),
|
|
|
|
|
resultat=ResultatCC.EN_INSTANCE,
|
|
|
|
|
)
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
return cx
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _collecte(**kwargs) -> ResultatCollecte:
|
|
|
|
|
return ResultatCollecte(collecteur="essai", **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# Détection des écarts
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_aucun_ecart_quand_la_source_confirme_la_base(base) -> None:
|
|
|
|
|
"""Le cas normal : la veille ne doit rien signaler quand rien ne bouge."""
|
|
|
|
|
collecte = _collecte(
|
|
|
|
|
textes=[
|
|
|
|
|
TexteCollecte(
|
|
|
|
|
titre="LOI n° 2026-491 du 12 juin 2026 chlordécone",
|
|
|
|
|
source_url="https://www.legifrance.gouv.fr/jorf/id/JORFTEXT000054245243",
|
|
|
|
|
collecteur="essai",
|
|
|
|
|
numero_officiel="2026-491",
|
|
|
|
|
statut=Statut.PROMULGUEE,
|
|
|
|
|
date_promulgation=date(2026, 6, 12),
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
rapport = comparer(base, [collecte])
|
|
|
|
|
assert rapport.ecarts == []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_un_texte_inconnu_est_un_ajout(base) -> None:
|
|
|
|
|
collecte = _collecte(
|
|
|
|
|
textes=[
|
|
|
|
|
TexteCollecte(
|
|
|
|
|
titre="LOI n° 2026-700 du 1er septembre 2026 relative aux transports scolaires",
|
|
|
|
|
source_url="https://www.legifrance.gouv.fr/jorf/id/X",
|
|
|
|
|
collecteur="essai",
|
|
|
|
|
numero_officiel="2026-700",
|
|
|
|
|
statut=Statut.PROMULGUEE,
|
|
|
|
|
date_promulgation=date(2026, 9, 1),
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
rapport = comparer(base, [collecte])
|
|
|
|
|
assert rapport.ajouts == 1
|
|
|
|
|
assert rapport.ecarts[0].nature == "ajout"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_un_changement_de_statut_est_detecte(base) -> None:
|
|
|
|
|
"""La promulgation de la loi « Riposte » : l'événement que la veille attend."""
|
|
|
|
|
collecte = _collecte(
|
|
|
|
|
textes=[
|
|
|
|
|
TexteCollecte(
|
|
|
|
|
titre="Loi « Riposte » (ordre public, sécurité, tranquillité)",
|
|
|
|
|
source_url="https://www.legifrance.gouv.fr/jorf/id/Y",
|
|
|
|
|
collecteur="essai",
|
|
|
|
|
statut=Statut.PROMULGUEE,
|
|
|
|
|
date_promulgation=date(2026, 8, 26),
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
rapport = comparer(base, [collecte])
|
|
|
|
|
statuts = [e for e in rapport.ecarts if e.nature == "statut"]
|
|
|
|
|
assert len(statuts) == 1
|
|
|
|
|
assert statuts[0].texte_id == "loi-riposte"
|
|
|
|
|
assert statuts[0].ancienne_valeur == "adoptee_non_promulguee"
|
|
|
|
|
assert statuts[0].nouvelle_valeur == "promulguee"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_une_decision_rendue_est_detectee(base) -> None:
|
|
|
|
|
collecte = _collecte(
|
|
|
|
|
decisions_cc=[
|
|
|
|
|
DecisionCC(
|
|
|
|
|
numero_affaire="2026-915 DC",
|
|
|
|
|
date_decision=date(2026, 8, 24),
|
|
|
|
|
resultat=ResultatCC.NON_CONFORMITE_PARTIELLE,
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
rapport = comparer(base, [collecte])
|
|
|
|
|
natures = {e.champ for e in rapport.ecarts if e.nature == "decision_cc"}
|
|
|
|
|
assert any("date_decision" in (c or "") for c in natures)
|
|
|
|
|
assert any("resultat" in (c or "") for c in natures)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_une_affaire_inconnue_est_signalee(base) -> None:
|
|
|
|
|
"""Cas réel : le corpus ignorait le numéro 2026-907 DC de la LPM."""
|
|
|
|
|
collecte = _collecte(
|
|
|
|
|
decisions_cc=[
|
|
|
|
|
DecisionCC(
|
|
|
|
|
numero_affaire="2026-907 DC",
|
|
|
|
|
date_saisine=date(2026, 7, 6),
|
|
|
|
|
resume="Loi actualisant la programmation militaire",
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
rapport = comparer(base, [collecte])
|
|
|
|
|
assert any("affaire inconnue" in e.description for e in rapport.ecarts)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_une_affaire_en_instance_deja_connue_ne_produit_rien(base) -> None:
|
|
|
|
|
collecte = _collecte(
|
|
|
|
|
decisions_cc=[
|
|
|
|
|
DecisionCC(
|
|
|
|
|
numero_affaire="2026-915 DC",
|
|
|
|
|
date_saisine=date(2026, 7, 24),
|
|
|
|
|
resultat=ResultatCC.EN_INSTANCE,
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
assert comparer(base, [collecte]).ecarts == []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# Écriture
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_le_dry_run_n_ecrit_rien(base) -> None:
|
|
|
|
|
"""`comparer` ne doit jamais toucher à la base."""
|
|
|
|
|
avant = db.statistiques(base)
|
|
|
|
|
collecte = _collecte(
|
|
|
|
|
textes=[
|
|
|
|
|
TexteCollecte(
|
|
|
|
|
titre="Loi « Riposte » (ordre public, sécurité, tranquillité)",
|
|
|
|
|
source_url="https://x.fr/y",
|
|
|
|
|
collecteur="essai",
|
|
|
|
|
statut=Statut.PROMULGUEE,
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
comparer(base, [collecte])
|
|
|
|
|
assert db.statistiques(base) == avant
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_appliquer_ecrit_le_changement_et_le_journalise(base) -> None:
|
|
|
|
|
collecte = _collecte(
|
|
|
|
|
textes=[
|
|
|
|
|
TexteCollecte(
|
|
|
|
|
titre="Loi « Riposte » (ordre public, sécurité, tranquillité)",
|
|
|
|
|
source_url="https://x.fr/y",
|
|
|
|
|
collecteur="essai",
|
|
|
|
|
statut=Statut.PROMULGUEE,
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
rapport = comparer(base, [collecte])
|
|
|
|
|
run_id = db.ouvrir_run(base, "update")
|
|
|
|
|
appliquer(base, rapport, run_id)
|
|
|
|
|
|
|
|
|
|
statut = base.execute("SELECT statut FROM textes WHERE id = 'loi-riposte'").fetchone()[0]
|
|
|
|
|
assert statut == "promulguee"
|
|
|
|
|
|
|
|
|
|
journal = base.execute(
|
|
|
|
|
"SELECT nature, champ, nouvelle_valeur FROM veille_changements WHERE run_id = ?",
|
|
|
|
|
(run_id,),
|
|
|
|
|
).fetchall()
|
|
|
|
|
assert any(
|
|
|
|
|
ligne["champ"] == "statut" and ligne["nouvelle_valeur"] == "promulguee"
|
|
|
|
|
for ligne in journal
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# Classification d'un texte nouveau
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_un_texte_collecte_est_marque_comme_automatique() -> None:
|
|
|
|
|
"""§5.3 : la machine propose avec une confiance basse, l'humain valide."""
|
|
|
|
|
texte = classer_nouveau(
|
|
|
|
|
TexteCollecte(
|
|
|
|
|
titre="LOI n° 2026-700 du 1er septembre 2026 relative à l'eau en Guadeloupe",
|
|
|
|
|
source_url="https://www.legifrance.gouv.fr/jorf/id/Z",
|
|
|
|
|
collecteur="legifrance",
|
|
|
|
|
numero_officiel="2026-700",
|
|
|
|
|
statut=Statut.PROMULGUEE,
|
|
|
|
|
date_promulgation=date(2026, 9, 1),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
assert texte.id == "loi-2026-700"
|
|
|
|
|
assert texte.confiance is Confiance.LOW
|
|
|
|
|
assert texte.a_verifier is True
|
|
|
|
|
assert "déduits" in (texte.motif_verification or "")
|
|
|
|
|
assert texte.guadeloupe_pertinence is Pertinence.FORTE # « Guadeloupe » dans l'intitulé
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_un_texte_sans_numero_recoit_un_identifiant_lisible() -> None:
|
|
|
|
|
texte = classer_nouveau(
|
|
|
|
|
TexteCollecte(
|
|
|
|
|
titre="Proposition de loi relative aux sargasses en Martinique",
|
|
|
|
|
source_url="https://www.senat.fr/x",
|
|
|
|
|
collecteur="senat",
|
|
|
|
|
statut=Statut.NAVETTE,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
assert texte.id and " " not in texte.id
|
|
|
|
|
assert "sargasses" in texte.id
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# Export statique
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_export_json(base, tmp_path, monkeypatch) -> None:
|
|
|
|
|
from pipeline import chemins
|
|
|
|
|
|
|
|
|
|
destination = tmp_path / "textes.json"
|
|
|
|
|
monkeypatch.setattr(chemins, "DUMP_JSON", destination)
|
|
|
|
|
|
|
|
|
|
total = exporter_json(base)
|
|
|
|
|
assert total == 2
|
|
|
|
|
|
|
|
|
|
charge = json.loads(destination.read_text(encoding="utf-8"))
|
|
|
|
|
assert charge["date_arrete_corpus"] == "2026-07-25"
|
|
|
|
|
identifiants = {t["id"] for t in charge["textes"]}
|
|
|
|
|
assert identifiants == {"loi-2026-491", "loi-riposte"}
|
|
|
|
|
|
|
|
|
|
chlordecone = next(t for t in charge["textes"] if t["id"] == "loi-2026-491")
|
|
|
|
|
assert chlordecone["sources"][0]["url"].startswith("https://www.legifrance.gouv.fr")
|
|
|
|
|
assert chlordecone["evenements"][0]["type_etape"] == "promulgation"
|
|
|
|
|
|
|
|
|
|
riposte = next(t for t in charge["textes"] if t["id"] == "loi-riposte")
|
|
|
|
|
assert riposte["decisions_cc"][0]["numero_affaire"] == "2026-915 DC"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_rapport_lisible(base) -> None:
|
|
|
|
|
rapport = comparer(base, [_collecte()])
|
|
|
|
|
rapport.mode = "dry-run"
|
|
|
|
|
texte = rapport.en_texte()
|
|
|
|
|
assert "Rapport de veille" in texte
|
|
|
|
|
assert "conforme aux sources officielles" in texte
|
2026-07-25 22:34:22 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# Cotation Guadeloupe : les négations
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_une_mention_niee_ne_vaut_pas_pertinence() -> None:
|
|
|
|
|
"""« Sans portée pour la Guadeloupe » nomme le territoire pour l'écarter.
|
|
|
|
|
|
|
|
|
|
Sans lecture de la négation, la PPL montagne recevait la cotation la plus
|
|
|
|
|
forte alors que le rapport dit l'inverse — et remontait dans la facette
|
|
|
|
|
« pertinence forte pour la Guadeloupe ».
|
|
|
|
|
"""
|
|
|
|
|
from pipeline.guadeloupe import coter
|
|
|
|
|
|
|
|
|
|
cotation = coter(
|
|
|
|
|
"PPL « pour une montagne vivante et souveraine ». Continuité de "
|
|
|
|
|
"l'urbanisation ; sans portée pour la Guadeloupe."
|
|
|
|
|
)
|
|
|
|
|
assert cotation.pertinence is Pertinence.FAIBLE
|
|
|
|
|
assert cotation.score == 0
|
|
|
|
|
assert cotation.exclusions
|
|
|
|
|
assert "écarte explicitement" in (cotation.note or "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_pas_de_volet_outre_mer_est_une_negation() -> None:
|
|
|
|
|
from pipeline.guadeloupe import coter
|
|
|
|
|
|
|
|
|
|
cotation = coter("PJL logement. Véto des maires ; pas de volet outre-mer à ce stade.")
|
|
|
|
|
assert cotation.pertinence is Pertinence.FAIBLE
|
|
|
|
|
assert cotation.score == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_une_mention_affirmee_compte_toujours() -> None:
|
|
|
|
|
from pipeline.guadeloupe import coter
|
|
|
|
|
|
|
|
|
|
cotation = coter(
|
|
|
|
|
"Chlordécone : responsabilité de l'État. Guadeloupe et Martinique au cœur du texte."
|
|
|
|
|
)
|
|
|
|
|
assert cotation.pertinence is Pertinence.FORTE
|
|
|
|
|
assert "guadeloupe" in cotation.expressions
|
|
|
|
|
assert not cotation.exclusions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_la_negation_ne_porte_que_sur_son_voisinage() -> None:
|
|
|
|
|
"""Une exclusion lointaine ne doit pas annuler une mention affirmée."""
|
|
|
|
|
from pipeline.guadeloupe import coter
|
|
|
|
|
|
|
|
|
|
cotation = coter(
|
|
|
|
|
"Fouilles jusqu'à 40 km du littoral, soit la quasi-totalité de la Guadeloupe. "
|
|
|
|
|
+ "Texte de portée générale. " * 12
|
|
|
|
|
+ "Le dispositif est sans objet pour les collectivités du Pacifique."
|
|
|
|
|
)
|
|
|
|
|
assert cotation.pertinence is Pertinence.FORTE
|
2026-07-25 22:41:18 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# Application effective : créer, mettre à jour, converger
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_un_ajout_cree_reellement_le_texte(base) -> None:
|
|
|
|
|
"""Un écart d'ajout doit écrire le texte, pas seulement le journaliser."""
|
|
|
|
|
collecte = _collecte(
|
|
|
|
|
textes=[
|
|
|
|
|
TexteCollecte(
|
|
|
|
|
titre="LOI n° 2026-103 du 19 février 2026 de finances pour 2026",
|
|
|
|
|
source_url="https://www.legifrance.gouv.fr/jorf/id/JORFTEXT000999",
|
|
|
|
|
collecteur="senat",
|
|
|
|
|
numero_officiel="2026-103",
|
|
|
|
|
statut=Statut.PROMULGUEE,
|
|
|
|
|
date_promulgation=date(2026, 2, 19),
|
|
|
|
|
sources=[
|
|
|
|
|
Source(
|
|
|
|
|
url="https://www.legifrance.gouv.fr/jorf/id/JORFTEXT000999",
|
|
|
|
|
editeur="Légifrance",
|
|
|
|
|
)
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
rapport = comparer(base, [collecte])
|
|
|
|
|
crees = appliquer(base, rapport, db.ouvrir_run(base, "update"))
|
|
|
|
|
|
|
|
|
|
assert crees == 1
|
|
|
|
|
ligne = base.execute("SELECT * FROM textes WHERE id = 'loi-2026-103'").fetchone()
|
|
|
|
|
assert ligne is not None
|
|
|
|
|
assert ligne["confiance"] == "low"
|
|
|
|
|
assert ligne["a_verifier"] == 1
|
|
|
|
|
assert ligne["source_seed"] == "collecteur:senat"
|
|
|
|
|
# Un texte sans source serait un texte invérifiable : la collecte doit
|
|
|
|
|
# transmettre la sienne.
|
|
|
|
|
assert base.execute(
|
|
|
|
|
"SELECT COUNT(*) FROM sources WHERE texte_id = 'loi-2026-103'"
|
|
|
|
|
).fetchone()[0] >= 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_une_decision_collectee_est_ecrite_pas_seulement_journalisee(base) -> None:
|
|
|
|
|
"""Sans écriture, le même écart serait redétecté à chaque passage."""
|
|
|
|
|
collecte = _collecte(
|
|
|
|
|
decisions_cc=[
|
|
|
|
|
DecisionCC(
|
|
|
|
|
numero_affaire="2026-915 DC",
|
|
|
|
|
date_decision=date(2026, 8, 24),
|
|
|
|
|
resultat=ResultatCC.NON_CONFORMITE_PARTIELLE,
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
rapport = comparer(base, [collecte])
|
|
|
|
|
appliquer(base, rapport, db.ouvrir_run(base, "update"))
|
|
|
|
|
|
|
|
|
|
ligne = base.execute(
|
|
|
|
|
"SELECT * FROM decisions_cc WHERE numero_affaire = '2026-915 DC'"
|
|
|
|
|
).fetchone()
|
|
|
|
|
assert ligne["date_decision"] == "2026-08-24"
|
|
|
|
|
assert ligne["resultat"] == "non_conformite_partielle"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_le_pipeline_converge(base) -> None:
|
|
|
|
|
"""Rejouer la même collecte après application ne doit plus rien signaler.
|
|
|
|
|
|
|
|
|
|
C'est la propriété qui rend une veille utilisable : si le rapport répétait
|
|
|
|
|
les mêmes écarts à chaque passage, il deviendrait illisible et on cesserait
|
|
|
|
|
de le lire.
|
|
|
|
|
"""
|
|
|
|
|
collecte = _collecte(
|
|
|
|
|
textes=[
|
|
|
|
|
TexteCollecte(
|
|
|
|
|
titre="LOI n° 2026-103 du 19 février 2026 de finances pour 2026",
|
|
|
|
|
source_url="https://www.legifrance.gouv.fr/jorf/id/JORFTEXT000999",
|
|
|
|
|
collecteur="senat",
|
|
|
|
|
numero_officiel="2026-103",
|
|
|
|
|
statut=Statut.PROMULGUEE,
|
|
|
|
|
date_promulgation=date(2026, 2, 19),
|
|
|
|
|
)
|
|
|
|
|
],
|
|
|
|
|
decisions_cc=[
|
|
|
|
|
DecisionCC(
|
|
|
|
|
numero_affaire="2026-915 DC",
|
|
|
|
|
date_decision=date(2026, 8, 24),
|
|
|
|
|
resultat=ResultatCC.NON_CONFORMITE_PARTIELLE,
|
|
|
|
|
)
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
premier = comparer(base, [collecte])
|
|
|
|
|
assert premier.ecarts, "le premier passage doit voir des écarts"
|
|
|
|
|
appliquer(base, premier, db.ouvrir_run(base, "update"))
|
|
|
|
|
|
|
|
|
|
second = comparer(base, [collecte])
|
|
|
|
|
assert second.ecarts == [], "le second passage ne doit plus rien signaler"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_une_affaire_orpheline_est_rattachee_par_son_intitule(base) -> None:
|
|
|
|
|
"""Cas réel : le Conseil donne l'intitulé complet, la base le nom d'usage."""
|
|
|
|
|
collecte = _collecte(
|
|
|
|
|
decisions_cc=[
|
|
|
|
|
DecisionCC(
|
|
|
|
|
numero_affaire="2026-916 DC",
|
|
|
|
|
date_saisine=date(2026, 8, 1),
|
|
|
|
|
resume="Loi « Riposte » ordre public sécurité tranquillité",
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
rapport = comparer(base, [collecte])
|
|
|
|
|
(ecart,) = [e for e in rapport.ecarts if e.nature == "decision_cc"]
|
|
|
|
|
assert ecart.texte_id == "loi-riposte"
|
|
|
|
|
assert "rattachée à loi-riposte" in ecart.description
|