Phase 5 : collecteurs officiels et run de veille — dry-run contre sources réelles
Collecteurs, chacun isolé et tolérant à l'échec : - legifrance : OAuth PISTE validé en production. Deux pièges vérifiés sur le compte réel — seul le couple « Client ID / Client secret » ouvre un jeton (la clé d'API donne invalid_client), et les identifiants de production ne passent pas en bac à sable. - senat : liste chronologique des lois promulguées. L'URL du §5.1 (senat.fr/lois/index.html) est morte ; l'équivalent fonctionnel est /dossiers-legislatifs/lois-promulguees.html — 31 lois 2026 relevées. - conseil_constitutionnel : sans le paramètre ?id=32246 la page des affaires en instance ne rend que des QPC et aucune affaire DC. Les colonnes DC et QPC diffèrent : lecture par en-tête, jamais par position. Rapprochement à trois issues — numéro officiel, similarité de titre, et une zone de signalement où le pipeline s'abstient plutôt que de fusionner. make update-dry contre les sources réelles : aucun faux positif sur les 52 textes du corpus, et trois apports que le rapport n'avait pas — le numéro d'affaire 2026-907 DC de la programmation militaire, la date de saisine du 16 juillet pour l'aide à mourir, les décisions du 23 juillet sur 908 et 909. Une asymétrie de sources est documentée par un test plutôt que corrigée en douce : le Sénat ne qualifie pas la loi 2026-650 d'organique, Légifrance si. Le collecteur rapporte ce que sa source dit ; le champ n'est pas comparé. Fixtures HTML figées : aucun test ne touche au réseau. 156 tests, 91 % de couverture (chaque module au-dessus du seuil de 70 %). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c9fc8f3f67
commit
feb5544599
Vendored
+163
@@ -0,0 +1,163 @@
|
||||
<html><body><table class="saisine-list sticky-header responsive-enabled" data-striping="1">
|
||||
<caption class="sr-only">Tableau des affaires en instance</caption>
|
||||
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" specifier="field_numero_affaire" aria-sort="ascending" class="is-active"><a href="/decisions/affaires-en-instances?id=32246&sort=desc&order=Affaire%20n%C2%B0" title="trier par Affaire n°" rel="nofollow">Affaire n°<span class="tablesort tablesort--desc">
|
||||
<span class="visually-hidden">
|
||||
Trier par ordre décroissant
|
||||
</span>
|
||||
</span>
|
||||
</a></th>
|
||||
<th scope="col" specifier="field_disposition_loi"><a href="/decisions/affaires-en-instances?id=32246&sort=asc&order=Disposition" title="trier par Disposition" rel="nofollow">Disposition</a></th>
|
||||
<th scope="col" specifier="field_auteur_saisine"><a href="/decisions/affaires-en-instances?id=32246&sort=asc&order=Auteur%20de%20la%20saisine" title="trier par Auteur de la saisine" rel="nofollow">Auteur de la saisine</a></th>
|
||||
<th scope="col" specifier="field_dates_saisine"><a href="/decisions/affaires-en-instances?id=32246&sort=asc&order=Date%20de%20la%20saisine" title="trier par Date de la saisine" rel="nofollow">Date de la saisine</a></th>
|
||||
<th scope="col" specifier="field_date_limite_reception"><a href="/decisions/affaires-en-instances?id=32246&sort=asc&order=Date%20de%20cl%C3%B4ture%20de%20l%27instruction" title="trier par Date de clôture de l'instruction" rel="nofollow">Date de clôture de l'instruction</a></th>
|
||||
<th scope="col" specifier="field_decision_rendue"><a href="/decisions/affaires-en-instances?id=32246&sort=asc&order=Date%20de%20lecture%20de%20la%20d%C3%A9cision" title="trier par Date de lecture de la décision" rel="nofollow">Date de lecture de la décision</a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td><div class="mobile-hide" data-column="Affaire n°">2026-907 DC<span class="arrow"></span>
|
||||
</div>
|
||||
</td>
|
||||
<td><div class="mobile-hide" data-column="Disposition">Loi actualisant la programmation militaire pour les années 2024 à 2030 et portant diverses dispositions intéressant la défense</div>
|
||||
</td>
|
||||
<td><div class="auteurs" data-column="Auteur de la saisine"><a href="https://www.conseil-constitutionnel.fr/sites/default/files/2026-07/2026907dc_saisinedep.pdf" target="_blank" class="auteur"><p>Plus de soixante députés</p>
|
||||
</a></div>
|
||||
</td>
|
||||
<td><div class="date" data-column="Date de la saisine"><p>06.07.2026</p>
|
||||
<p class="cell-value hidden">2026-07-06T00:00:00+0200</p>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><div class="no-date" data-column="Date de lecture de la décision"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td><div class="mobile-hide" data-column="Affaire n°">2026-910 DC<span class="arrow"></span>
|
||||
</div>
|
||||
</td>
|
||||
<td><div class="mobile-hide" data-column="Disposition">Loi relative au droit à l'aide à mourir</div>
|
||||
</td>
|
||||
<td><div class="auteurs" data-column="Auteur de la saisine"><a href="https://www.conseil-constitutionnel.fr/sites/default/files/2026-07/2026910dc_saisinepdtsen.pdf" target="_blank" class="auteur"><p>Président du Sénat</p>
|
||||
</a><a href="https://www.conseil-constitutionnel.fr/sites/default/files/2026-07/2026910dc_saisinesen_0.pdf" target="_blank" class="auteur"><p>Plus de soixante sénateurs</p>
|
||||
</a><a href="https://www.conseil-constitutionnel.fr/sites/default/files/2026-07/2026910dc_saisinespm.pdf" target="_blank" class="auteur"><p>Premier ministre</p>
|
||||
</a><a href="https://www.conseil-constitutionnel.fr/sites/default/files/2026-07/2026910dc_saisinedep.pdf" target="_blank" class="auteur"><p>Plus de soixante députés</p>
|
||||
</a><a href="https://www.conseil-constitutionnel.fr/sites/default/files/2026-07/2026910dc_saisinedep2.pdf" target="_blank" class="auteur"><p>Plus de soixante députés</p>
|
||||
</a></div>
|
||||
</td>
|
||||
<td><div class="date" data-column="Date de la saisine"><p>16.07.2026</p>
|
||||
<p class="cell-value hidden">2026-07-16T09:00:00+0200</p>
|
||||
<p>17.07.2026</p>
|
||||
<p class="cell-value hidden">2026-07-17T11:00:00+0200</p>
|
||||
<p>17.07.2026</p>
|
||||
<p class="cell-value hidden">2026-07-17T12:00:00+0200</p>
|
||||
<p>20.07.2026</p>
|
||||
<p class="cell-value hidden">2026-07-20T07:00:00+0200</p>
|
||||
<p>20.07.2026</p>
|
||||
<p class="cell-value hidden">2026-07-20T08:00:00+0200</p>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><div class="no-date" data-column="Date de lecture de la décision"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td><div class="mobile-hide" data-column="Affaire n°">2026-911 DC<span class="arrow"></span>
|
||||
</div>
|
||||
</td>
|
||||
<td><div class="mobile-hide" data-column="Disposition">Loi visant à protéger les mineurs des risques auxquels les expose l’utilisation des réseaux sociaux</div>
|
||||
</td>
|
||||
<td><div class="auteurs" data-column="Auteur de la saisine"><a href="https://www.conseil-constitutionnel.fr/sites/default/files/2026-07/2026911dc_saisinedep.pdf" target="_blank" class="auteur"><p>Plus de soixante députés</p>
|
||||
</a><a href="https://www.conseil-constitutionnel.fr/sites/default/files/2026-07/2026911dc_saisinedep2.pdf" target="_blank" class="auteur"><p>Plus de soixante députés</p>
|
||||
</a></div>
|
||||
</td>
|
||||
<td><div class="date" data-column="Date de la saisine"><p>23.07.2026</p>
|
||||
<p class="cell-value hidden">2026-07-23T00:00:00+0200</p>
|
||||
<p>24.07.2026</p>
|
||||
<p class="cell-value hidden">2026-07-24T18:00:00+0200</p>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><div class="no-date" data-column="Date de lecture de la décision"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td><div class="mobile-hide" data-column="Affaire n°">2026-912 DC<span class="arrow"></span>
|
||||
</div>
|
||||
</td>
|
||||
<td><div class="mobile-hide" data-column="Disposition">Loi pour une montagne vivante et souveraine</div>
|
||||
</td>
|
||||
<td><div class="auteurs" data-column="Auteur de la saisine"><a href="https://www.conseil-constitutionnel.fr/sites/default/files/2026-07/2026912dc_saisinedep.pdf" target="_blank" class="auteur"><p>Plus de soixante députés</p>
|
||||
</a></div>
|
||||
</td>
|
||||
<td><div class="date" data-column="Date de la saisine"><p>23.07.2026</p>
|
||||
<p class="cell-value hidden">2026-07-23T00:00:00+0200</p>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><div class="no-date" data-column="Date de lecture de la décision"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td><div class="mobile-hide" data-column="Affaire n°">2026-913 DC<span class="arrow"></span>
|
||||
</div>
|
||||
</td>
|
||||
<td><div class="mobile-hide" data-column="Disposition">Loi visant à moderniser la gestion du patrimoine immobilier de l’État</div>
|
||||
</td>
|
||||
<td><div class="auteurs" data-column="Auteur de la saisine"><a href="https://www.conseil-constitutionnel.fr/sites/default/files/2026-07/2026913dc_saisinedep.pdf" target="_blank" class="auteur"><p>Plus de soixante députés</p>
|
||||
</a></div>
|
||||
</td>
|
||||
<td><div class="date" data-column="Date de la saisine"><p>23.07.2026</p>
|
||||
<p class="cell-value hidden">2026-07-23T00:00:00+0200</p>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><div class="no-date" data-column="Date de lecture de la décision"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td><div class="mobile-hide" data-column="Affaire n°">2026-914 DC<span class="arrow"></span>
|
||||
</div>
|
||||
</td>
|
||||
<td><div class="mobile-hide" data-column="Disposition">Loi d'urgence pour la protection et la souveraineté agricoles</div>
|
||||
</td>
|
||||
<td><div class="auteurs" data-column="Auteur de la saisine"><a href="https://www.conseil-constitutionnel.fr/sites/default/files/2026-07/2026914dc_saisinedep.pdf" target="_blank" class="auteur"><p>Plus de soixante députés</p>
|
||||
</a><a href="https://www.conseil-constitutionnel.fr/sites/default/files/2026-07/2026914dc_saisinedep_2.pdf" target="_blank" class="auteur"><p>Plus de soixante députés</p>
|
||||
</a></div>
|
||||
</td>
|
||||
<td><div class="date" data-column="Date de la saisine"><p>24.07.2026</p>
|
||||
<p class="cell-value hidden">2026-07-24T14:00:00+0200</p>
|
||||
<p>24.07.2026</p>
|
||||
<p class="cell-value hidden">2026-07-24T15:00:00+0200</p>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><div class="no-date" data-column="Date de lecture de la décision"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td><div class="mobile-hide" data-column="Affaire n°">2026-915 DC<span class="arrow"></span>
|
||||
</div>
|
||||
</td>
|
||||
<td><div class="mobile-hide" data-column="Disposition">Loi visant à offrir des réponses immédiates aux phénomènes troublant l’ordre public, la sécurité et la tranquillité de nos concitoyens</div>
|
||||
</td>
|
||||
<td><div class="auteurs" data-column="Auteur de la saisine"><a href="https://www.conseil-constitutionnel.fr/sites/default/files/2026-07/2026915dc_saisinedep1.pdf" target="_blank" class="auteur"><p>Plus de soixante députés</p>
|
||||
</a><a href="https://www.conseil-constitutionnel.fr/sites/default/files/2026-07/2026915dc_saisinedep2.pdf" target="_blank" class="auteur"><p>Plus de soixante députés</p>
|
||||
</a></div>
|
||||
</td>
|
||||
<td><div class="date" data-column="Date de la saisine"><p>24.07.2026</p>
|
||||
<p class="cell-value hidden">2026-07-24T08:00:00+0200</p>
|
||||
<p>24.07.2026</p>
|
||||
<p class="cell-value hidden">2026-07-24T09:00:00+0200</p>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
<td><div class="no-date" data-column="Date de lecture de la décision"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></body></html>
|
||||
Vendored
+345
@@ -0,0 +1,345 @@
|
||||
<html><body><article class="landing landing--type-decisions landing--view-mode-full">
|
||||
|
||||
<header>
|
||||
|
||||
<div class="field field--name-field-image-bandeau field--type-entity-reference field--label-hidden field__item"><article class="media media--type-image media--view-mode-bandeau-landing">
|
||||
|
||||
|
||||
<div class="field field--name-field-media-image field--type-image field--label-hidden field__item"> <picture>
|
||||
<source srcset="/sites/default/files/styles/bandeau_landing_wide/public/2020-07/bg-landing-decisions.jpg.webp?h=06f6671c&itok=Wyn5lULp 1x" media="all and (min-width: 1024px)" type="image/webp" width="1350" height="550">
|
||||
<source srcset="/sites/default/files/styles/bandeau_landing_narrow/public/2020-07/bg-landing-decisions.jpg.webp?h=06f6671c&itok=yKNGqhX1 1x" media="all and (min-width: 481px) and (max-width: 1023px)" type="image/webp" width="1024" height="417">
|
||||
<source srcset="/sites/default/files/styles/bandeau_landing_mobile/public/2020-07/bg-landing-decisions.jpg.webp?h=06f6671c&itok=qFsmUyCr 1x" media="all and (max-width: 480px)" type="image/webp" width="480" height="196">
|
||||
<img loading="lazy" aria-hidden="true" width="480" height="196" src="/sites/default/files/styles/bandeau_landing_mobile/public/2020-07/bg-landing-decisions.jpg.webp?h=06f6671c&itok=qFsmUyCr" alt="Bandeau de la landing Toutes les décisions">
|
||||
|
||||
</picture>
|
||||
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
<h1 class="title">Les décisions du Conseil Constitutionnel</h1>
|
||||
|
||||
<div class="clearfix text-formatted field field--name-field-texte field--type-text-long field--label-hidden field__item"><p>Toutes les décisions, ainsi que des outils statistiques et explicatifs pour mieux connaître l’activité du Conseil constitutionnel depuis sa création</p>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-lien-decision field--type-link field--label-hidden field__item"><a href="/les-decisions/decisions-par-type">Comprendre les types de décisions</a></div>
|
||||
|
||||
<div class="field field--name-field-lien-affaires-instance field--type-link field--label-hidden field__item"><a href="/decisions/affaires-instances">Les affaires en instance</a></div>
|
||||
<div class="formulaire-recherche-decisions"><form data-drupal-selector="cc-landing-recherche-decisions" action="/decisions" method="post" id="cc-landing-recherche-decisions" accept-charset="UTF-8">
|
||||
<h3 class="titre-formulaire" data-drupal-selector="edit-titre">Rechercher une décision</h3>
|
||||
<div class="js-form-item form-item js-form-type-search form-type-search js-form-item-textfield form-item-textfield">
|
||||
<label for="edit-textfield">Rechercher un terme ou un numéro de décision</label>
|
||||
<input list="autocomplete-list" data-drupal-selector="edit-textfield" type="search" id="edit-textfield" name="textfield" value="" size="60" maxlength="128" placeholder="Ex: égalité devant la loi" class="form-search">
|
||||
|
||||
</div>
|
||||
<datalist id="autocomplete-list" data-drupal-selector="edit-autocomplete-list"></datalist>
|
||||
<div class="js-form-item form-item js-form-type-select form-type-select js-form-item-types form-item-types form-no-label">
|
||||
<select data-drupal-selector="edit-types" id="edit-types" name="types" class="form-select"><option value="all">Tous les types</option><option value="32205">Question prioritaire de constitutionnalité</option><option value="32246">Constitutionnalité des lois, traités, des règlements des Assemblées</option><option value="32797">Loi ordinaire</option><option value="32800">Loi organique</option><option value="32803">Traité</option><option value="32806">Règlement des assemblées</option><option value="32211">Élections à l'Assemblée nationale</option><option value="32220">Élections au Sénat</option><option value="32224">Loi du pays de Nouvelle-Calédonie</option><option value="32221">Compétences outre-mer</option><option value="32223">Déclassement</option><option value="32215">Incompatibilité des parlementaires</option><option value="32400">Obligations fiscales</option><option value="32216">Déchéance de parlementaires</option><option value="32222">Fins de non-recevoir</option><option value="32212">Avis de l'article 16</option><option value="32219">Élection présidentielle</option><option value="32214">Fonctionnement du Conseil constitutionnel</option><option value="32218">Référendum</option><option value="32403">Référendum d'initiative partagée</option><option value="32217">Divers élections</option><option value="32213">Autres textes et décisions</option></select>
|
||||
</div>
|
||||
<div class="block-facets block-facet-date-range-picker js-form-wrapper form-wrapper" id="periode" data-drupal-selector="edit-date-range"><div class="form-group js-form-wrapper form-wrapper" data-drupal-selector="edit-form-group" id="edit-form-group"><label data-drupal-selector="edit-label">Par période</label>
|
||||
<div class="js-form-item form-item js-form-type-date form-type-date js-form-item-date-from form-item-date-from form-no-label">
|
||||
<span class="clear-input hidden">X</span>
|
||||
<input id="edit-date-from" name="date_from" data-drupal-selector="edit-date-from" type="date" value="" class="form-date">
|
||||
|
||||
</div>
|
||||
<div class="js-form-item form-item js-form-type-date form-type-date js-form-item-date-to form-item-date-to form-no-label">
|
||||
<span class="clear-input hidden">X</span>
|
||||
<input id="edit-date-to" name="date_to" data-drupal-selector="edit-date-to" type="date" value="" class="form-date">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input data-drupal-selector="edit-submit" type="submit" id="edit-submit" name="op" value="Lancer une recherche" class="button js-form-submit form-submit">
|
||||
<input class="reset reload-page button js-form-submit form-submit" data-drupal-selector="edit-reset" type="submit" id="edit-reset" name="op" value="Réinitialiser">
|
||||
<a href="/recherche-experte" class="lien-page-recherche" data-smarttag="[]" data-drupal-selector="edit-lien" id="edit-lien">Plus de critères de recherche</a><input autocomplete="off" data-drupal-selector="form-if1ifd6wffso28jvmkzvnsozeco7wqt14mauqf8sdi" type="hidden" name="form_build_id" value="form-_IF1iFd6WfFSo28JVMkZvnSozEcO7wQt14Mauqf8sdI">
|
||||
<input data-drupal-selector="edit-cc-landing-recherche-decisions" type="hidden" name="form_id" value="cc_landing_recherche_decisions">
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</header>
|
||||
<div class="sidebar">
|
||||
<h3>Outils et ressources</h3>
|
||||
|
||||
<div class="content">
|
||||
|
||||
<div class="item">
|
||||
|
||||
<div class="field field--name-field-date-analytique field--type-string field--label-above">
|
||||
<div class="field__label">Les tables analytiques</div>
|
||||
<div class="field__item">À jour au 1<sup>er</sup> septembre 2024</div>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-reference-analytique field--type-entity-reference field--label-hidden field__item"><article class="media media--type-file media--view-mode-ressource-decisions">
|
||||
|
||||
<a href="https://www.conseil-constitutionnel.fr/sites/default/files/2024-10/1958-202408_tables.pdf">Tables analytiques 1958-2024</a><div class="type-size"><span class="type">Pdf</span>
|
||||
<span class="size">33.95 Mo</span>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-lien-analytique field--type-link field--label-hidden field__item"><a href="/les-decisions/tables-analytiques">En savoir plus</a></div>
|
||||
|
||||
</div>
|
||||
<div class="item">
|
||||
|
||||
<div class="field field--name-field-lien-dispositions field--type-link field--label-above">
|
||||
<div class="field__label">Recueil des dispositions validées</div>
|
||||
<div class="field__item"><a href="/dispositions">Voir le recueil</a></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="item">
|
||||
|
||||
<div class="field field--name-field-date-bilan field--type-string field--label-above">
|
||||
<div class="field__label">Bilan statistique</div>
|
||||
<div class="field__item">À jour au 30 juin 2025</div>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-lien-bilan field--type-link field--label-hidden field__item"><a href="/bilan-statistique">Voir le bilan</a></div>
|
||||
|
||||
</div>
|
||||
<div class="item">
|
||||
|
||||
<div class="field field--name-field-lien-archive-seance field--type-link field--label-above">
|
||||
<div class="field__label">Archives des comptes rendus de séances</div>
|
||||
<div class="field__item"><a href="/les-decisions/archives-des-comptes-rendus-de-seances">En savoir plus</a></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper-content">
|
||||
<div class="wrapper-dernieres-decisions"><h2>Les dernières décisions</h2>
|
||||
|
||||
<article class="node node--type-decision node--promoted node--view-mode-synthetique">
|
||||
|
||||
<a href="/decision/2026/2026908DC.htm" rel="bookmark" title="Décision n° 2026-908 DC du 23 juillet 2026">
|
||||
<div class="node__content">
|
||||
<h3 class="title">Décision n° 2026-908 DC du 23 juillet 2026</h3>
|
||||
|
||||
<div class="field field--name-field-titre-complet field--type-string-long field--label-hidden field__item">
|
||||
<p>Loi organique relative au renforcement des juridictions criminelles</p>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-solution-normalisee field--type-string field--label-hidden field__item">
|
||||
<p>Conformité</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="node node--type-decision node--promoted node--view-mode-synthetique">
|
||||
|
||||
<a href="/decision/2026/2026909DC.htm" rel="bookmark" title="Décision n° 2026-909 DC du 23 juillet 2026">
|
||||
<div class="node__content">
|
||||
<h3 class="title">Décision n° 2026-909 DC du 23 juillet 2026</h3>
|
||||
|
||||
<div class="field field--name-field-titre-complet field--type-string-long field--label-hidden field__item">
|
||||
<p>Loi sur la justice criminelle et le respect des victimes</p>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-solution-normalisee field--type-string field--label-hidden field__item">
|
||||
<p>Non conformité partielle - réserve</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="node node--type-decision node--promoted node--view-mode-synthetique">
|
||||
|
||||
<a href="/decision/2026/2026906DC.htm" rel="bookmark" title="Décision n° 2026-906 DC du 23 juillet 2026">
|
||||
<div class="node__content">
|
||||
<h3 class="title">Décision n° 2026-906 DC du 23 juillet 2026</h3>
|
||||
|
||||
<div class="field field--name-field-titre-complet field--type-string-long field--label-hidden field__item">
|
||||
<p>Loi visant à renforcer la sécurité, la rétention administrative et la prévention des risques d’attentat</p>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-solution-normalisee field--type-string field--label-hidden field__item">
|
||||
<p>Conformité - réserve</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="node node--type-decision node--promoted node--view-mode-synthetique">
|
||||
|
||||
<a href="/decision/2026/20261213QPC.htm" rel="bookmark" title="Décision n° 2026-1213 QPC du 10 juillet 2026">
|
||||
<div class="node__content">
|
||||
<h3 class="title">Décision n° 2026-1213 QPC du 10 juillet 2026</h3>
|
||||
|
||||
<div class="field field--name-field-titre-complet field--type-string-long field--label-hidden field__item">
|
||||
<p>Société Altaroc Partners et autres [Notification du droit de se taire à la personne à laquelle sont notifiés des griefs dans le cadre d'une procédure de sanction par l’Autorité des marchés financiers]</p>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-solution-normalisee field--type-string field--label-hidden field__item">
|
||||
<p>Conformité</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="node node--type-decision node--promoted node--view-mode-synthetique">
|
||||
|
||||
<a href="/decision/2026/20269LP.htm" rel="bookmark" title="Décision n° 2026-9 LP du 9 juillet 2026">
|
||||
<div class="node__content">
|
||||
<h3 class="title">Décision n° 2026-9 LP du 9 juillet 2026</h3>
|
||||
|
||||
<div class="field field--name-field-titre-complet field--type-string-long field--label-hidden field__item">
|
||||
<p>Loi du pays modifiant le code minier de la Nouvelle-Calédonie (partie législative)</p>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-solution-normalisee field--type-string field--label-hidden field__item">
|
||||
<p>Conformité</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="node node--type-decision node--promoted node--view-mode-synthetique">
|
||||
|
||||
<a href="/decision/2026/2026326L.htm" rel="bookmark" title="Décision n° 2026-326 L du 9 juillet 2026">
|
||||
<div class="node__content">
|
||||
<h3 class="title">Décision n° 2026-326 L du 9 juillet 2026</h3>
|
||||
|
||||
<div class="field field--name-field-titre-complet field--type-string-long field--label-hidden field__item">
|
||||
<p>Nature juridique de certaines dispositions de l’article L. 122-8 du code de l’énergie</p>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-solution-normalisee field--type-string field--label-hidden field__item">
|
||||
<p>Réglementaire</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="node node--type-decision node--promoted node--view-mode-synthetique">
|
||||
|
||||
<a href="/decision/2026/20261211QPC.htm" rel="bookmark" title="Décision n° 2026-1211 QPC du 3 juillet 2026">
|
||||
<div class="node__content">
|
||||
<h3 class="title">Décision n° 2026-1211 QPC du 3 juillet 2026</h3>
|
||||
|
||||
<div class="field field--name-field-titre-complet field--type-string-long field--label-hidden field__item">
|
||||
<p>M. Maurice D. [Interdiction de l’inscription au tableau de l’ordre des médecins, chirurgiens dentistes et sages-femmes inscrits ou enregistrés dans un autre État]</p>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-solution-normalisee field--type-string field--label-hidden field__item">
|
||||
<p>Conformité</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="node node--type-decision node--promoted node--view-mode-synthetique">
|
||||
|
||||
<a href="/decision/2026/20261212QPC.htm" rel="bookmark" title="Décision n° 2026-1212 QPC du 3 juillet 2026">
|
||||
<div class="node__content">
|
||||
<h3 class="title">Décision n° 2026-1212 QPC du 3 juillet 2026</h3>
|
||||
|
||||
<div class="field field--name-field-titre-complet field--type-string-long field--label-hidden field__item">
|
||||
<p>M. Valeriu M. [Circonstances justifiant l’aggravation de la suspension prononcée par l’Agence française de lutte contre le dopage]</p>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-solution-normalisee field--type-string field--label-hidden field__item">
|
||||
<p>Non conformité totale</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="node node--type-decision node--promoted node--view-mode-synthetique">
|
||||
|
||||
<a href="/decision/2026/2026325L.htm" rel="bookmark" title="Décision n° 2026-325 L du 2 juillet 2026">
|
||||
<div class="node__content">
|
||||
<h3 class="title">Décision n° 2026-325 L du 2 juillet 2026</h3>
|
||||
|
||||
<div class="field field--name-field-titre-complet field--type-string-long field--label-hidden field__item">
|
||||
<p>Nature juridique de certaines dispositions de l’article L. 1422-3 du code du travail et de l’article L. 131-3 du code de l’entrée et du séjour des étrangers et du droit d’asile</p>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-solution-normalisee field--type-string field--label-hidden field__item">
|
||||
<p>Réglementaire</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="node node--type-decision node--promoted node--view-mode-synthetique">
|
||||
|
||||
<a href="/decision/2026/20261210QPC.htm" rel="bookmark" title="Décision n° 2026-1210 QPC du 25 juin 2026">
|
||||
<div class="node__content">
|
||||
<h3 class="title">Décision n° 2026-1210 QPC du 25 juin 2026</h3>
|
||||
|
||||
<div class="field field--name-field-titre-complet field--type-string-long field--label-hidden field__item">
|
||||
<p>Société Orange SA [Cumul de poursuites et de sanctions en cas de manquement aux règles encadrant la prospection directe par voie automatisée]</p>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-solution-normalisee field--type-string field--label-hidden field__item">
|
||||
<p>Non conformité totale - effet différé - réserve transitoire</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
</div>
|
||||
<a href="/les-decisions" class="lien-toutes-decisions">Toutes les décisions</a>
|
||||
</div></article><article class="node node--type-decision node--promoted node--view-mode-synthetique">
|
||||
|
||||
<a href="/decision/2026/2026908DC.htm" rel="bookmark" title="Décision n° 2026-908 DC du 23 juillet 2026">
|
||||
<div class="node__content">
|
||||
<h3 class="title">Décision n° 2026-908 DC du 23 juillet 2026</h3>
|
||||
|
||||
<div class="field field--name-field-titre-complet field--type-string-long field--label-hidden field__item">
|
||||
<p>Loi organique relative au renforcement des juridictions criminelles</p>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-solution-normalisee field--type-string field--label-hidden field__item">
|
||||
<p>Conformité</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</article><article class="node node--type-decision node--promoted node--view-mode-synthetique">
|
||||
|
||||
<a href="/decision/2026/2026909DC.htm" rel="bookmark" title="Décision n° 2026-909 DC du 23 juillet 2026">
|
||||
<div class="node__content">
|
||||
<h3 class="title">Décision n° 2026-909 DC du 23 juillet 2026</h3>
|
||||
|
||||
<div class="field field--name-field-titre-complet field--type-string-long field--label-hidden field__item">
|
||||
<p>Loi sur la justice criminelle et le respect des victimes</p>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-solution-normalisee field--type-string field--label-hidden field__item">
|
||||
<p>Non conformité partielle - réserve</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</article><article class="node node--type-decision node--promoted node--view-mode-synthetique">
|
||||
|
||||
<a href="/decision/2026/2026906DC.htm" rel="bookmark" title="Décision n° 2026-906 DC du 23 juillet 2026">
|
||||
<div class="node__content">
|
||||
<h3 class="title">Décision n° 2026-906 DC du 23 juillet 2026</h3>
|
||||
|
||||
<div class="field field--name-field-titre-complet field--type-string-long field--label-hidden field__item">
|
||||
<p>Loi visant à renforcer la sécurité, la rétention administrative et la prévention des risques d’attentat</p>
|
||||
</div>
|
||||
|
||||
<div class="field field--name-field-solution-normalisee field--type-string field--label-hidden field__item">
|
||||
<p>Conformité - réserve</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</article></body></html>
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<html><body><a href="/dossier-legislatif/pjl25-457.html">Loi n° 2026-650 du 23 juillet 2026 relative au renforcement des juridictions criminelles
|
||||
parue au JO n°171 du 24 juillet 2026</a><a href="/dossier-legislatif/pjl25-456.html">Loi n° 2026-651 du 23 juillet 2026 sur la justice criminelle et le respect des victimes
|
||||
parue au JO n°171 du 24 juillet 2026</a><a href="/dossier-legislatif/ppl25-214.html">Loi n° 2026-630 du 13 juillet 2026 visant à assurer le droit de chaque enfant à être assisté d'un avocat dans le cadre d'une mesure d'assistance éducative et de protection de l'enfance
|
||||
parue au JO n°163 du 14 juillet 2026</a><a href="/dossier-legislatif/ppl23-431.html">Loi n° 2026-602 du 8 juillet 2026 visant à réduire l'impact environnemental de l'industrie textile
|
||||
parue au JO n°159 du 9 juillet 2026</a><a href="/dossier-legislatif/pjl25-283.html">Loi n° 2026-574 du 30 juin 2026 portant habilitation de l'assemblée de Martinique à fixer des règles applicables sur son territoire en application de l'article 73 de la Constitution en matière d'énergie, d'eau et d'assainissement
|
||||
parue au JO n°152 du 1 juillet 2026</a><a href="/dossier-legislatif/ppl25-359.html">Loi n° 2026-554 du 29 juin 2026 visant à relancer les investissements dans le secteur de l'hydroélectricité pour contribuer à la transition énergétique
|
||||
parue au JO n°151 du 30 juin 2026</a><a href="/dossier-legislatif/ppl25-320.html">Loi n° 2026-555 du 29 juin 2026 visant à réparer les préjudices causés par la transplantation de mineurs de La Réunion en France hexagonale de 1962 à 1984
|
||||
parue au JO n°151 du 30 juin 2026</a><a href="/dossier-legislatif/ppl25-273.html">Loi n° 2026-553 du 29 juin 2026 visant à améliorer l'accès au logement des travailleurs des services publics
|
||||
parue au JO n°151 du 30 juin 2026</a><a href="/dossier-legislatif/ppl24-008.html">Loi n° 2026-542 du 26 juin 2026 relative à la sortie des collections publiques de restes humains kali'nas et arawaks en vue de funérailles sur le territoire de la Guyane
|
||||
parue au JO n°149 du 27 juin 2026</a><a href="/dossier-legislatif/pjl25-024.html">Loi n° 2026-534 du 25 juin 2026 relative à la lutte contre les fraudes sociales et fiscales
|
||||
parue au JO n°148 du 26 juin 2026</a><a href="/dossier-legislatif/ppl23-483.html">Loi n° 2026-532 du 24 juin 2026 visant à ouvrir le dispositif de réduction d'activité aux moniteurs de ski stagiaires
|
||||
parue au JO n°147 du 25 juin 2026</a><a href="/dossier-legislatif/ppl24-180.html">Loi n° 2026-492 du 12 juin 2026 visant à améliorer la protection et l'accompagnement des parents d'enfants atteints d'un cancer, d'une maladie grave ou d'un handicap
|
||||
parue au JO n°137 du 13 juin 2026</a><a href="/dossier-legislatif/ppl23-373.html">Loi n° 2026-491 du 12 juin 2026 visant à reconnaître la responsabilité de l'État et à indemniser les victimes du chlordécone
|
||||
parue au JO n°137 du 13 juin 2026</a><a href="/dossier-legislatif/pjl25-470.html">Loi n° 2026-470 du 11 juin 2026 portant transposition de l'avenant n° 3 du 25 février 2026 au protocole d'accord du 10 novembre 2023 relatif à l'assurance chômage
|
||||
parue au JO n°136 du 12 juin 2026</a><a href="/dossier-legislatif/ppl25-213.html">Loi n° 2026-442 du 4 juin 2026 visant à étendre à toutes les communes la compensation financière prévue pour les communes de plus de 3 500 habitants pour l'exercice de l'ensemble des compétences du service public de la petite enfance
|
||||
parue au JO n°130 du 5 juin 2026</a><a href="/dossier-legislatif/ppl24-475.html">Loi n° 2026-441 du 4 juin 2026 portant pérennisation du contrat de professionnalisation expérimental
|
||||
parue au JO n°130 du 5 juin 2026</a><a href="/dossier-legislatif/ppl24-636.html">Loi n° 2026-410 du 28 mai 2026 organique portant régularisation des natifs dans le corps électoral pour les élections au congrès et aux assemblées de province de Nouvelle-Calédonie
|
||||
parue au JO n°124 du 29 mai 2026</a><a href="/dossier-legislatif/pjl23-550.html">Loi n° 2026-403 du 26 mai 2026 de simplification de la vie économique
|
||||
parue au JO n°122 du 27 mai 2026</a><a href="/dossier-legislatif/ppl24-662.html">Loi n° 2026-404 du 26 mai 2026 visant à garantir l'égal accès de tous à l'accompagnement et aux soins palliatifs
|
||||
parue au JO n°122 du 27 mai 2026</a><a href="/dossier-legislatif/ppl24-226.html">Loi n° 2026-381 du 19 mai 2026 visant à soutenir les collectivités territoriales dans la prévention et la gestion des inondations
|
||||
parue au JO n°117 du 20 mai 2026</a><a href="/dossier-legislatif/ppl25-401.html">Loi n° 2026-373 du 15 mai 2026 facilitant l'exercice en France des médecins diplômés au Royaume-Uni ayant débuté leurs études avant le Brexit
|
||||
parue au JO n°114 du 16 mai 2026</a><a href="/dossier-legislatif/pjl24-871.html">Loi n° 2026-351 du 9 mai 2026 relatif à la restitution de biens culturels ayant fait l'objet d'une appropriation illicite
|
||||
parue au JO n°109 du 10 mai 2026</a><a href="/dossier-legislatif/ppl24-637.html">Loi n° 2026-350 du 9 mai 2026 visant à garantir le droit de visite des parlementaires et des bâtonniers dans les lieux de privation de liberté
|
||||
parue au JO n°109 du 10 mai 2026</a><a href="/dossier-legislatif/ppl25-187.html">Loi n° 2026-307 du 23 avril 2026 visant à instaurer une procédure simplifiée de recouvrement des créances commerciales incontestées
|
||||
parue au JO n°97 du 24 avril 2026</a><a href="/dossier-legislatif/ppl24-415.html">Loi n° 2026-248 du 7 avril 2026 visant à simplifier la sortie de l'indivision et la gestion des successions vacantes
|
||||
parue au JO n°83 du 8 avril 2026</a><a href="/dossier-legislatif/ppl25-328.html">Loi n° 2026-249 du 7 avril 2026 visant à permettre le remboursement des frais d'expertise comptable aux candidats
|
||||
parue au JO n°83 du 8 avril 2026</a><a href="/dossier-legislatif/ppl24-413.html">Loi n° 2026-247 du 7 avril 2026 relative aux missions des professionnels de santé, vétérinaires, psychothérapeutes et psychologues des services d'incendie et de secours
|
||||
parue au JO n°83 du 8 avril 2026</a><a href="/dossier-legislatif/pjl24-630.html">Loi n° 2026-201 du 20 mars 2026 relative à l'organisation des jeux Olympiques et Paralympiques de 2030
|
||||
parue au JO n°69 du 21 mars 2026</a><a href="/dossier-legislatif/ppl23-569.html">Loi n° 2026-122 du 23 février 2026 relative à la confidentialité des consultations des juristes d'entreprise
|
||||
parue au JO n°48 du 25 février 2026</a><a href="/dossier-legislatif/pjlf2026.html">Loi n° 2026-103 du 19 février 2026 de finances pour 2026
|
||||
parue au JO n°43 du 20 février 2026</a><a href="/dossier-legislatif/ppl24-223.html">Loi n° 2026-6 du 7 janvier 2026 organique tendant à modifier le II de l'article 43 de la loi organique n° 2004-192 du 27 février 2004 portant statut d'autonomie de la Polynésie française
|
||||
parue au JO n°6 du 8 janvier 2026</a></body></html>
|
||||
@@ -212,7 +212,10 @@ def test_une_fiche_a_timeline_et_sources(base_complete) -> None:
|
||||
|
||||
def test_les_deductions_sont_signalees(base_complete) -> None:
|
||||
"""Toute valeur déduite porte un motif de vérification lisible."""
|
||||
for ligne in base_complete.execute("SELECT id, motif_verification FROM textes WHERE a_verifier = 1"):
|
||||
lignes = base_complete.execute(
|
||||
"SELECT id, motif_verification FROM textes WHERE a_verifier = 1"
|
||||
)
|
||||
for ligne in lignes:
|
||||
assert ligne["motif_verification"], f"{ligne['id']} marqué à vérifier sans motif"
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
"""Collecteurs et rapprochement.
|
||||
|
||||
Les parseurs sont testés sur des fixtures HTML figées, capturées le 25 juillet
|
||||
2026 sur les sites officiels : aucun test ne dépend du réseau. Une fixture qui
|
||||
cesse de passer signale un changement de structure du site — c'est le but.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from selectolax.parser import HTMLParser
|
||||
|
||||
from pipeline.collecteurs.conseil_constitutionnel import (
|
||||
_index_des_colonnes,
|
||||
_lire_decision_rendue,
|
||||
_lire_ligne_instance,
|
||||
)
|
||||
from pipeline.collecteurs.http import ClientHttp, _cle_de_cache
|
||||
from pipeline.collecteurs.legifrance import CollecteurLegifrance, _lire_titre
|
||||
from pipeline.collecteurs.senat import CollecteurSenat
|
||||
from pipeline.modeles import ResultatCC, Statut, TypeTexte
|
||||
from pipeline.rapprochement import Issue, normaliser, rapprocher, similarite
|
||||
|
||||
FIXTURES = Path(__file__).parent / "fixtures"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Conseil constitutionnel
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
@pytest.fixture(scope="module")
|
||||
def affaires_dc() -> list:
|
||||
tableau = HTMLParser(
|
||||
(FIXTURES / "cc_affaires_dc.html").read_text(encoding="utf-8")
|
||||
).css_first("table")
|
||||
colonnes = _index_des_colonnes(tableau)
|
||||
return [
|
||||
affaire
|
||||
for ligne in tableau.css("tbody tr")
|
||||
if (affaire := _lire_ligne_instance(ligne, colonnes))
|
||||
]
|
||||
|
||||
|
||||
def test_les_sept_affaires_dc_en_instance(affaires_dc) -> None:
|
||||
"""Le corpus en annonçait sept ; le registre officiel les confirme."""
|
||||
numeros = {a.numero_affaire for a in affaires_dc}
|
||||
assert numeros == {
|
||||
"2026-907 DC",
|
||||
"2026-910 DC",
|
||||
"2026-911 DC",
|
||||
"2026-912 DC",
|
||||
"2026-913 DC",
|
||||
"2026-914 DC",
|
||||
"2026-915 DC",
|
||||
}
|
||||
|
||||
|
||||
def test_saisine_de_la_loi_riposte(affaires_dc) -> None:
|
||||
riposte = next(a for a in affaires_dc if a.numero_affaire == "2026-915 DC")
|
||||
assert riposte.date_saisine == date(2026, 7, 24)
|
||||
assert riposte.resultat is ResultatCC.EN_INSTANCE
|
||||
assert "soixante députés" in (riposte.saisissants or "")
|
||||
|
||||
|
||||
def test_la_programmation_militaire_recoit_son_numero(affaires_dc) -> None:
|
||||
"""Le rapport la citait sans numéro d'affaire ; la collecte le fournit."""
|
||||
lpm = next(a for a in affaires_dc if a.numero_affaire == "2026-907 DC")
|
||||
assert lpm.date_saisine == date(2026, 7, 6)
|
||||
assert "programmation militaire" in (lpm.resume or "").lower()
|
||||
|
||||
|
||||
def test_colonnes_lues_par_entete_et_non_par_position() -> None:
|
||||
"""Les tableaux DC et QPC n'ont pas les mêmes colonnes."""
|
||||
tableau = HTMLParser(
|
||||
(FIXTURES / "cc_affaires_dc.html").read_text(encoding="utf-8")
|
||||
).css_first("table")
|
||||
colonnes = _index_des_colonnes(tableau)
|
||||
assert colonnes["saisissants"] != colonnes["date_saisine"]
|
||||
assert set(colonnes) >= {"affaire", "disposition", "saisissants", "date_saisine"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("intitule", "attendu"),
|
||||
[
|
||||
("2026-908 DC du 23 juillet 2026 Loi organique … Conformité", ResultatCC.CONFORME),
|
||||
(
|
||||
"2026-906 DC du 23 juillet 2026 Loi … Conformité - réserve",
|
||||
ResultatCC.CONFORME_AVEC_RESERVES,
|
||||
),
|
||||
(
|
||||
"2026-909 DC du 23 juillet 2026 Loi … Non conformité partielle",
|
||||
ResultatCC.NON_CONFORMITE_PARTIELLE,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_lecture_du_resultat_d_une_decision(intitule: str, attendu: ResultatCC) -> None:
|
||||
"""« Conformité » ne contient pas « conforme » : l'ordre des motifs compte."""
|
||||
decision = _lire_decision_rendue(f"Décision n° {intitule}", None)
|
||||
assert decision is not None
|
||||
assert decision.resultat is attendu
|
||||
assert decision.date_decision == date(2026, 7, 23)
|
||||
|
||||
|
||||
def test_decisions_rendues_de_la_fixture() -> None:
|
||||
document = HTMLParser((FIXTURES / "cc_decisions.html").read_text(encoding="utf-8"))
|
||||
decisions = [
|
||||
d
|
||||
for article in document.css("article")
|
||||
if (d := _lire_decision_rendue(" ".join(article.text(separator=" ").split()), None))
|
||||
]
|
||||
numeros = {d.numero_affaire for d in decisions}
|
||||
assert {"2026-906 DC", "2026-908 DC", "2026-909 DC"} <= numeros
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Sénat
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
@pytest.fixture(scope="module")
|
||||
def lois_senat() -> list:
|
||||
document = HTMLParser(
|
||||
(FIXTURES / "senat_lois_promulguees.html").read_text(encoding="utf-8")
|
||||
)
|
||||
collecteur = CollecteurSenat(ClientHttp(hors_ligne=True))
|
||||
textes = []
|
||||
for ancre in document.css("a"):
|
||||
libelle = " ".join(ancre.text(separator=" ", strip=True).split())
|
||||
if texte := collecteur._lire_intitule(libelle, ancre.attributes.get("href")):
|
||||
textes.append(texte)
|
||||
return textes
|
||||
|
||||
|
||||
def test_senat_releve_les_lois_de_la_fenetre(lois_senat) -> None:
|
||||
numeros = {t.numero_officiel for t in lois_senat}
|
||||
# Quatre lois de la fenêtre de veille, aux dates connues du corpus.
|
||||
assert {"2026-651", "2026-650", "2026-630", "2026-602"} <= numeros
|
||||
|
||||
|
||||
def test_senat_lit_dates_et_nature(lois_senat) -> None:
|
||||
par_numero = {t.numero_officiel: t for t in lois_senat}
|
||||
|
||||
justice = par_numero["2026-651"]
|
||||
assert justice.date_promulgation == date(2026, 7, 23)
|
||||
assert justice.statut is Statut.PROMULGUEE
|
||||
assert justice.type is TypeTexte.LOI
|
||||
|
||||
|
||||
def test_les_deux_sources_ne_disent_pas_la_meme_chose_du_caractere_organique(
|
||||
lois_senat,
|
||||
) -> None:
|
||||
"""Asymétrie réelle entre les sources, à ne pas corriger en silence.
|
||||
|
||||
Légifrance intitule la loi n° 2026-650 « LOI organique n° 2026-650 … » ;
|
||||
la liste du Sénat écrit « Loi n° 2026-650 … » sans la qualifier. Le
|
||||
collecteur rapporte ce que sa source dit — c'est le rôle du rapprochement
|
||||
de ne pas laisser la source la plus pauvre écraser la plus riche. Le run de
|
||||
mise à jour ne compare d'ailleurs pas le champ `type`.
|
||||
"""
|
||||
organique = par_numero_senat(lois_senat)["2026-650"]
|
||||
assert organique.type is TypeTexte.LOI, (
|
||||
"le Sénat ne qualifie pas cette loi d'organique : le collecteur ne doit "
|
||||
"pas le deviner à sa place"
|
||||
)
|
||||
|
||||
|
||||
def par_numero_senat(lois: list) -> dict:
|
||||
return {t.numero_officiel: t for t in lois}
|
||||
|
||||
|
||||
def test_senat_ne_confond_pas_l_objet_avec_l_etat(lois_senat) -> None:
|
||||
"""Le libellé se termine par « parue » : ce n'est pas l'objet de la loi."""
|
||||
for texte in lois_senat:
|
||||
assert not texte.titre.rstrip().endswith("parue")
|
||||
assert "en cours" not in texte.titre
|
||||
|
||||
|
||||
def test_senat_produit_une_source_primaire(lois_senat) -> None:
|
||||
for texte in lois_senat:
|
||||
assert texte.sources
|
||||
assert texte.sources[0].tier == "T1"
|
||||
assert texte.sources[0].url.startswith("https://www.senat.fr")
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Légifrance
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
def test_legifrance_indisponible_sans_identifiants(monkeypatch) -> None:
|
||||
monkeypatch.delenv("LEGIFRANCE_CLIENT_ID", raising=False)
|
||||
collecteur = CollecteurLegifrance(ClientHttp(hors_ligne=True))
|
||||
disponible, motif = collecteur.est_disponible()
|
||||
assert disponible is False
|
||||
assert "repli" in (motif or "")
|
||||
|
||||
|
||||
def test_legifrance_lit_un_resultat_de_recherche() -> None:
|
||||
titre = {
|
||||
"id": "LEGITEXT000054249605_14-06-2026",
|
||||
"cid": "JORFTEXT000054245243",
|
||||
"title": "LOI n° 2026-491 du 12 juin 2026 visant à reconnaître la responsabilité de l'Etat",
|
||||
}
|
||||
texte = _lire_titre(titre, "2026-491")
|
||||
assert texte is not None
|
||||
assert texte.date_promulgation == date(2026, 6, 12)
|
||||
assert texte.source_url.endswith("JORFTEXT000054245243")
|
||||
assert texte.sources[0].tier == "T1"
|
||||
|
||||
|
||||
def test_legifrance_ecarte_un_resultat_hors_sujet() -> None:
|
||||
titre = {"cid": "X", "title": "LOI n° 2026-999 du 1er janvier 2026 sans rapport"}
|
||||
assert _lire_titre(titre, "2026-491") is None
|
||||
|
||||
|
||||
def test_legifrance_deduit_le_caractere_organique() -> None:
|
||||
titre = {
|
||||
"cid": "JORF1",
|
||||
"title": "LOI organique n° 2026-650 du 23 juillet 2026 relative aux juridictions",
|
||||
}
|
||||
texte = _lire_titre(titre, "2026-650")
|
||||
assert texte.type is TypeTexte.LOI_ORGANIQUE
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Client HTTP
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
def test_le_cache_distingue_les_corps_de_requete() -> None:
|
||||
a = _cle_de_cache("POST", "https://x.fr/search", {"q": "2026-491"}, None)
|
||||
b = _cle_de_cache("POST", "https://x.fr/search", {"q": "2026-403"}, None)
|
||||
assert a != b
|
||||
|
||||
|
||||
def test_mode_hors_ligne_ne_touche_pas_au_reseau(tmp_path) -> None:
|
||||
client = ClientHttp(cache=tmp_path, hors_ligne=True)
|
||||
reponse = client.get("https://exemple.invalide/rien")
|
||||
assert reponse.code == 0
|
||||
assert not reponse.a_reussi
|
||||
|
||||
|
||||
def test_le_cache_est_relu(tmp_path) -> None:
|
||||
client = ClientHttp(cache=tmp_path, hors_ligne=True)
|
||||
cle = _cle_de_cache("GET", "https://x.fr/a", None, None)
|
||||
client._ecrire_cache(cle, "contenu")
|
||||
reponse = client.get("https://x.fr/a")
|
||||
assert reponse.texte == "contenu"
|
||||
assert reponse.depuis_le_cache
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Rapprochement
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
def test_normalisation_retire_le_vocabulaire_passe_partout() -> None:
|
||||
assert "loi" not in normaliser("LOI n° 2026-491 relative au chlordécone")
|
||||
assert "chlordecone" in normaliser("LOI n° 2026-491 relative au chlordécone")
|
||||
|
||||
|
||||
def test_similarite_reconnait_un_titre_court_dans_un_titre_long() -> None:
|
||||
court = "Loi Riposte ordre public sécurité tranquillité"
|
||||
long = (
|
||||
"Loi visant à offrir des réponses immédiates aux phénomènes troublant "
|
||||
"l'ordre public, la sécurité et la tranquillité de nos concitoyens"
|
||||
)
|
||||
assert similarite(court, long) >= 0.72
|
||||
|
||||
|
||||
def test_le_numero_officiel_prime_sur_le_titre() -> None:
|
||||
correspondance = rapprocher(
|
||||
titre="Un intitulé totalement différent",
|
||||
numero_officiel="2026-491",
|
||||
candidats=[("loi-2026-491", "Chlordécone : responsabilité de l'État", "2026-491")],
|
||||
)
|
||||
assert correspondance.issue is Issue.RAPPROCHE
|
||||
assert correspondance.texte_id == "loi-2026-491"
|
||||
assert correspondance.score == 1.0
|
||||
|
||||
|
||||
def test_un_texte_inconnu_est_declare_nouveau() -> None:
|
||||
correspondance = rapprocher(
|
||||
titre="Loi sur les moniteurs de ski stagiaires en zone de montagne",
|
||||
numero_officiel=None,
|
||||
candidats=[("loi-2026-491", "Chlordécone : responsabilité de l'État", "2026-491")],
|
||||
)
|
||||
assert correspondance.issue is Issue.NOUVEAU
|
||||
|
||||
|
||||
def test_une_correspondance_douteuse_est_signalee_pas_fusionnee() -> None:
|
||||
"""Entre les deux seuils, le pipeline s'abstient et demande un humain."""
|
||||
correspondance = rapprocher(
|
||||
titre="Loi relative à la protection des enfants et des mineurs",
|
||||
numero_officiel=None,
|
||||
candidats=[("pjl-protection-enfants", "PJL relatif à la protection des enfants", None)],
|
||||
)
|
||||
assert correspondance.issue in (Issue.RAPPROCHE, Issue.A_SIGNALER)
|
||||
if correspondance.issue is Issue.A_SIGNALER:
|
||||
assert "vérification humaine" in correspondance.motif
|
||||
|
||||
|
||||
def test_aucun_candidat() -> None:
|
||||
correspondance = rapprocher(titre="X", numero_officiel="2026-1", candidats=[])
|
||||
assert correspondance.issue is Issue.NOUVEAU
|
||||
@@ -0,0 +1,311 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user