Phases 6 et 7 : application SvelteKit, PWA, systemd, documentation

Application — rendu serveur sur adapter-node, conforme au §3bis :
- tableau de bord, recherche à facettes, fiche détaillée, calendrier
- /a-propos et /methode prérendues ; tout le reste en rendu serveur
- API JSON ouverte : /api/textes, /api/textes/[slug], /api/echeances
- recherche FTS5 avec surlignage et classement bm25 pondéré (le titre pèse
  dix fois plus que les points clés)
- facettes recalculées sur le résultat filtré par les AUTRES facettes : cocher
  un thème doit recompter les statuts encore disponibles, sinon les compteurs
  mentent

Identité OKI sobrifiée : tokens en CSS vanilla, thème sombre par défaut et
clair opt-in, or comme seule couleur d'action, flag-bar une fois par écran,
polices Archivo/Inter auto-hébergées. Politique de sécurité de contenu
interdisant toute requête tierce. Les statuts ne reposent jamais sur la seule
couleur : libellé en toutes lettres et pastille de forme distincte.

Budget respecté très largement : 6,6 Ko de JavaScript sur l'accueil (3 Ko
compressés) pour un plafond de 100 Ko. Les faits clés — AFD à 500 €, saisine
2026-915 DC, statut — sont lisibles sans JavaScript.

Trois défauts trouvés en testant l'application, pas en relisant le code :
- v_textes est une vue, et une vue SQLite n'a pas de rowid : la jointure avec
  l'index plein texte échouait (migration 005)
- ppl-montagne remontait en « pertinence forte » alors que le corpus dit
  « sans portée pour la Guadeloupe » — la cotation comptait le mot sans lire
  la négation. Idem pour pjl-logement et accord-globe. Corrigé par une lecture
  du voisinage, avec quatre tests de non-régression.
- les libellés d'affichage étaient importés depuis /server dans un
  composant, ce que SvelteKit interdit à raison

PWA : service worker réseau-d'abord pour les pages, cache-d'abord pour le
coffre du build. Une veille législative ne doit pas servir une page périmée
quand le réseau répond — une date de promulgation change tout. Les réponses
issues du cache portent un en-tête qui le dit.

systemd : timer du pipeline (6 h, 18 h, dimanche 9 h, avec dispersion et
rattrapage), application activée par socket avec mise en sommeil à 300 s.
Unités durcies, validées par systemd-analyze.

Documentation : README d'installation sur Debian/Ubuntu vierge,
README-pipeline avec le piège des deux couples d'identifiants PISTE, et
scripts/verifier-conformite.sh qui contrôle mécaniquement le §3bis et le §8.

172 tests, npm run check à 0 erreur, make verifier au vert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Cyber Mawonaj
2026-07-25 22:34:22 -04:00
co-authored by Claude Opus 5
parent feb5544599
commit 34d81c66a8
52 changed files with 7459 additions and 40 deletions
+55
View File
@@ -309,3 +309,58 @@ def test_rapport_lisible(base) -> None:
texte = rapport.en_texte()
assert "Rapport de veille" in texte
assert "conforme aux sources officielles" in texte
# ─────────────────────────────────────────────────────────────────────────────
# 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