Phase 1 : socle Python, schéma SQLite + FTS5, modèles pydantic

- pyproject/uv sur Python 3.12, dépendances épinglées
- migrations versionnées (PRAGMA user_version) ; 001_initial pose textes,
  sources, evenements, decisions_cc, insights, veille_log, veille_changements
- index FTS5 à contenu externe, tokenizer unicode61 remove_diacritics 2 :
  « chlordecone » retrouve « chlordécone », highlight() disponible
- vue v_textes : devant_cc combine le statut et les affaires CC en instance,
  ce qui réconcilie le §7 (Riposte reste adoptee_non_promulguee) et le §8.7
  (la facette saisie_cc doit remonter ≥ 7 textes)
- modèles pydantic refusant les incohérences de statut : promulguée sans
  numéro, navette avec numéro officiel, slug malformé
- Makefile, .env.example, journalisation structlog

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Cyber Mawonaj
2026-07-25 17:49:11 -04:00
co-authored by Claude Opus 5
parent 3ce7d17db9
commit 31d3935bce
9 changed files with 1316 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
"""Localisation des fichiers du projet.
Un unique point de vérité pour les chemins, afin que les parseurs, les tests et
les collecteurs ne fassent jamais d'hypothèse sur le répertoire courant.
"""
from __future__ import annotations
import os
from pathlib import Path
from dotenv import load_dotenv
# pipeline/chemins.py → pipeline/ → racine du dépôt
RACINE = Path(__file__).resolve().parent.parent
load_dotenv(RACINE / ".env")
# ── Corpus d'entrée — LECTURE SEULE ──────────────────────────────────────────
ENTREE = RACINE / "data" / "input"
RECHERCHE = ENTREE / "research"
RAPPORT_FINAL = ENTREE / "lois-france-2026-guadeloupe.agent.final.md"
RAPPORT_OUTLINE = ENTREE / "lois-france-2026-guadeloupe.agent.outline.md"
BIBLIOGRAPHIE = ENTREE / "lois-france-2026-guadeloupe_ref.md"
CROSS_VERIFICATION = RECHERCHE / "lois-2026_cross_verification.md"
INSIGHTS = RECHERCHE / "lois-2026_insight.md"
PHASE5_VALIDATION = RECHERCHE / "lois-2026_phase5_validation.md"
def chapitre(numero: int) -> Path:
"""Chemin d'un chapitre du rapport : `chapitre(10)` → `…_sec10.md`."""
return ENTREE / f"lois-france-2026-guadeloupe_sec{numero:02d}.md"
def dimension(numero: int) -> Path:
"""Chemin d'un rapport de dimension : `dimension(7)` → `lois-2026_dim07.md`."""
return RECHERCHE / f"lois-2026_dim{numero:02d}.md"
CHAPITRES = [chapitre(n) for n in range(11)]
DIMENSIONS = [dimension(n) for n in range(1, 13)]
# ── Sorties ──────────────────────────────────────────────────────────────────
DONNEES = RACINE / "data"
BASE_SQLITE = RACINE / os.environ.get("VEILLE_DB", "data/veille.db")
DUMP_JSON = DONNEES / "textes.json"
CACHE_HTTP = RACINE / os.environ.get("VEILLE_CACHE", "data/cache_http")
MIGRATIONS = Path(__file__).resolve().parent / "migrations"
CONFIG = Path(__file__).resolve().parent / "config.yaml"
def verifier_corpus() -> list[str]:
"""Retourne la liste des fichiers d'entrée attendus mais absents."""
attendus = [
RAPPORT_FINAL,
BIBLIOGRAPHIE,
CROSS_VERIFICATION,
INSIGHTS,
PHASE5_VALIDATION,
*CHAPITRES,
*DIMENSIONS,
]
return [str(p.relative_to(RACINE)) for p in attendus if not p.exists()]