Files

277 lines
15 KiB
SQL
Raw Permalink Normal View History

-- ============================================================================
-- Migration 001 — schéma initial de la veille législative
--
-- Sémantique conforme au §4 du prompt de mission. Les libellés d'énumération
-- sont ceux du corpus de recherche : ils ne sont pas traduits ni normalisés,
-- de façon à ce qu'une valeur en base soit toujours retrouvable dans un
-- fichier de `data/input/`.
-- ============================================================================
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
-- ─────────────────────────────────────────────────────────────────────────────
-- Table principale : un texte législatif ou une décision juridictionnelle
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE textes (
-- Identifiant lisible et stable, servant aussi d'URL : « loi-2026-491 ».
id TEXT PRIMARY KEY,
-- Numéro officiel une fois attribué (« 2026-491 »). NULL tant que le texte
-- n'est pas promulgué : c'est précisément ce qui distingue un texte adopté
-- d'un texte en vigueur.
numero_officiel TEXT,
type TEXT NOT NULL CHECK (type IN (
'loi', 'loi_organique', 'pjl', 'ppl', 'ordonnance',
'decision_cc', 'decret', 'accord_international')),
titre_court TEXT NOT NULL,
titre_officiel TEXT,
-- Règle d'or du rapport : le statut prime sur l'intitulé.
statut TEXT NOT NULL CHECK (statut IN (
'promulguee', 'adoptee_non_promulguee', 'saisie_cc',
'validee_cc', 'censuree_partiellement', 'navette',
'deposee_non_examinee', 'annoncee', 'rejetee')),
statut_date TEXT, -- date du dernier changement de statut (ISO 8601)
date_depot TEXT,
date_adoption TEXT,
date_promulgation TEXT,
date_entree_vigueur TEXT,
prochaine_echeance TEXT,
prochaine_echeance_label TEXT,
-- Tableaux JSON. SQLite les stocke en texte ; les fonctions json_*
-- permettent de filtrer sans table de jointure supplémentaire.
themes TEXT NOT NULL DEFAULT '[]',
-- Objet JSON : { "individus": {"sens": "negatif", "note": "…"}, … }
-- Sens admis : positif | negatif | mixte | neutre.
impacts TEXT NOT NULL DEFAULT '{}',
guadeloupe_pertinence TEXT CHECK (guadeloupe_pertinence IN ('forte', 'moyenne', 'faible')),
guadeloupe_note TEXT,
resume TEXT,
points_cles TEXT NOT NULL DEFAULT '[]',
confiance TEXT NOT NULL DEFAULT 'medium'
CHECK (confiance IN ('high', 'medium', 'low')),
-- Fichier de `data/input/` dont provient l'entrée, ou nom du collecteur.
source_seed TEXT,
-- Drapeau de revue humaine : toute classification automatique le lève.
a_verifier INTEGER NOT NULL DEFAULT 0 CHECK (a_verifier IN (0, 1)),
motif_verification TEXT,
derniere_verif TEXT,
cree_le TEXT NOT NULL DEFAULT (datetime('now')),
maj_le TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE UNIQUE INDEX idx_textes_numero ON textes (numero_officiel)
WHERE numero_officiel IS NOT NULL;
CREATE INDEX idx_textes_statut ON textes (statut);
CREATE INDEX idx_textes_type ON textes (type);
CREATE INDEX idx_textes_gpe ON textes (guadeloupe_pertinence);
CREATE INDEX idx_textes_promulgation ON textes (date_promulgation);
CREATE INDEX idx_textes_echeance ON textes (prochaine_echeance);
-- ─────────────────────────────────────────────────────────────────────────────
-- Sources : une citation vérifiable par fait important
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE sources (
id INTEGER PRIMARY KEY AUTOINCREMENT,
texte_id TEXT NOT NULL REFERENCES textes (id) ON DELETE CASCADE,
url TEXT NOT NULL,
titre TEXT,
editeur TEXT,
date_publication TEXT,
-- T1 = source primaire (Légifrance/JO, AN, Sénat, Conseil constitutionnel,
-- Conseil d'État, vie-publique, préfectures) ; T2 = presse et analyses.
tier TEXT NOT NULL DEFAULT 'T2' CHECK (tier IN ('T1', 'T2')),
extrait_verbatim TEXT,
contexte TEXT,
confiance TEXT CHECK (confiance IN ('high', 'medium', 'low')),
-- Marqueur d'origine dans le corpus : « dim07-24 », « phase5-3.1 », « ref-481 ».
marqueur TEXT,
fichier_origine TEXT,
cree_le TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX idx_sources_texte ON sources (texte_id);
CREATE UNIQUE INDEX idx_sources_unicite ON sources (texte_id, url, COALESCE(marqueur, ''));
-- ─────────────────────────────────────────────────────────────────────────────
-- Événements : la timeline parlementaire d'un texte
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE evenements (
id INTEGER PRIMARY KEY AUTOINCREMENT,
texte_id TEXT NOT NULL REFERENCES textes (id) ON DELETE CASCADE,
date_evenement TEXT NOT NULL,
type_etape TEXT NOT NULL CHECK (type_etape IN (
'depot', 'adoption_1re_lecture', 'adoption_definitive',
'commission_mixte_paritaire', 'transmission', 'saisine_cc',
'decision_cc', 'promulgation', 'publication_jo',
'entree_vigueur', 'rejet', 'annonce', 'autre')),
description TEXT NOT NULL,
source_url TEXT,
-- Vrai lorsque l'événement est postérieur à la date d'arrêté des données :
-- il s'agit alors d'une échéance prévisionnelle, pas d'un fait acquis.
previsionnel INTEGER NOT NULL DEFAULT 0 CHECK (previsionnel IN (0, 1)),
cree_le TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX idx_evenements_texte ON evenements (texte_id, date_evenement);
CREATE INDEX idx_evenements_date ON evenements (date_evenement);
CREATE UNIQUE INDEX idx_evenements_unicite
ON evenements (texte_id, date_evenement, type_etape, description);
-- ─────────────────────────────────────────────────────────────────────────────
-- Décisions du Conseil constitutionnel
--
-- `date_decision IS NULL` signifie « affaire en instance » : c'est ce qui
-- alimente la facette « saisi du Conseil constitutionnel » de l'interface.
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE decisions_cc (
id INTEGER PRIMARY KEY AUTOINCREMENT,
texte_id TEXT REFERENCES textes (id) ON DELETE SET NULL,
numero_affaire TEXT NOT NULL UNIQUE, -- « 2026-915 DC »
date_saisine TEXT,
date_decision TEXT,
resultat TEXT CHECK (resultat IN (
'conforme', 'conforme_avec_reserves', 'non_conformite_partielle',
'non_conformite_totale', 'en_instance')),
saisissants TEXT,
resume TEXT,
url TEXT,
date_decision_attendue TEXT, -- prévisionnel, tant que la décision n'est pas rendue
cree_le TEXT NOT NULL DEFAULT (datetime('now')),
maj_le TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX idx_cc_texte ON decisions_cc (texte_id);
CREATE INDEX idx_cc_instance ON decisions_cc (date_decision);
-- ─────────────────────────────────────────────────────────────────────────────
-- Analyses transversales issues de `lois-2026_insight.md`
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE insights (
id INTEGER PRIMARY KEY AUTOINCREMENT,
numero INTEGER NOT NULL UNIQUE,
titre TEXT NOT NULL,
corps TEXT NOT NULL,
implications TEXT,
confiance TEXT NOT NULL DEFAULT 'medium',
derive_de TEXT NOT NULL DEFAULT '[]', -- JSON : ["dim02", "dim03", …]
fichier_origine TEXT
);
-- ─────────────────────────────────────────────────────────────────────────────
-- Journal des exécutions du pipeline
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE veille_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
horodatage TEXT NOT NULL DEFAULT (datetime('now')),
mode TEXT NOT NULL CHECK (mode IN ('seed', 'update', 'dry-run')),
duree_s REAL,
ajouts INTEGER NOT NULL DEFAULT 0,
modifications INTEGER NOT NULL DEFAULT 0,
echecs INTEGER NOT NULL DEFAULT 0,
alertes TEXT NOT NULL DEFAULT '[]', -- JSON
rapport TEXT -- compte-rendu lisible
);
CREATE INDEX idx_veille_log_date ON veille_log (horodatage DESC);
-- Détail des changements d'un run, pour l'encart « derniers changements ».
CREATE TABLE veille_changements (
id INTEGER PRIMARY KEY AUTOINCREMENT,
run_id INTEGER NOT NULL REFERENCES veille_log (id) ON DELETE CASCADE,
texte_id TEXT,
nature TEXT NOT NULL CHECK (nature IN ('ajout', 'statut', 'date', 'source', 'autre')),
champ TEXT,
ancienne_valeur TEXT,
nouvelle_valeur TEXT,
description TEXT
);
CREATE INDEX idx_changements_run ON veille_changements (run_id);
-- ─────────────────────────────────────────────────────────────────────────────
-- Recherche plein texte
--
-- Table FTS5 à contenu externe : l'index ne duplique pas les données, il est
-- tenu à jour par les déclencheurs ci-dessous. `remove_diacritics 2` permet de
-- trouver « chlordécone » en tapant « chlordecone ».
-- ─────────────────────────────────────────────────────────────────────────────
CREATE VIRTUAL TABLE textes_fts USING fts5 (
titre_court,
titre_officiel,
resume,
points_cles,
guadeloupe_note,
numero_officiel,
content = 'textes',
content_rowid = 'rowid',
tokenize = "unicode61 remove_diacritics 2"
);
CREATE TRIGGER textes_fts_ai AFTER INSERT ON textes BEGIN
INSERT INTO textes_fts (rowid, titre_court, titre_officiel, resume, points_cles,
guadeloupe_note, numero_officiel)
VALUES (new.rowid, new.titre_court, new.titre_officiel, new.resume, new.points_cles,
new.guadeloupe_note, new.numero_officiel);
END;
CREATE TRIGGER textes_fts_ad AFTER DELETE ON textes BEGIN
INSERT INTO textes_fts (textes_fts, rowid, titre_court, titre_officiel, resume,
points_cles, guadeloupe_note, numero_officiel)
VALUES ('delete', old.rowid, old.titre_court, old.titre_officiel, old.resume,
old.points_cles, old.guadeloupe_note, old.numero_officiel);
END;
CREATE TRIGGER textes_fts_au AFTER UPDATE ON textes BEGIN
INSERT INTO textes_fts (textes_fts, rowid, titre_court, titre_officiel, resume,
points_cles, guadeloupe_note, numero_officiel)
VALUES ('delete', old.rowid, old.titre_court, old.titre_officiel, old.resume,
old.points_cles, old.guadeloupe_note, old.numero_officiel);
INSERT INTO textes_fts (rowid, titre_court, titre_officiel, resume, points_cles,
guadeloupe_note, numero_officiel)
VALUES (new.rowid, new.titre_court, new.titre_officiel, new.resume, new.points_cles,
new.guadeloupe_note, new.numero_officiel);
END;
-- ─────────────────────────────────────────────────────────────────────────────
-- Vue de consultation : ajoute les informations dérivées dont l'interface a
-- besoin sans dupliquer d'état en base.
-- ─────────────────────────────────────────────────────────────────────────────
CREATE VIEW v_textes AS
SELECT
t.*,
(SELECT COUNT(*) FROM sources s WHERE s.texte_id = t.id) AS nb_sources,
(SELECT COUNT(*) FROM evenements e WHERE e.texte_id = t.id) AS nb_evenements,
-- Un texte est « devant le Conseil constitutionnel » soit par son statut,
-- soit parce qu'une affaire le concernant est en instance. Les deux voies
-- sont nécessaires : le §7 du prompt impose que la loi « Riposte » garde le
-- statut `adoptee_non_promulguee`, alors qu'elle doit apparaître dans la
-- facette « saisi du Conseil constitutionnel ».
CASE WHEN t.statut = 'saisie_cc'
OR EXISTS (SELECT 1 FROM decisions_cc d
WHERE d.texte_id = t.id AND d.date_decision IS NULL)
THEN 1 ELSE 0 END AS devant_cc
FROM textes t;