@tricoteuses/senat
Version:
Handle French Sénat's open data
327 lines (326 loc) • 14.8 kB
JavaScript
import { sql } from "kysely";
import { jsonArrayFrom } from "kysely/helpers/postgres";
import { dbSenat } from "../databases";
import { concat, rtrim, toDateString } from "./util";
import { textes, rapports } from "./documents";
function datesSeances(lectureAssembleeId) {
return jsonArrayFrom(dbSenat
.withSchema("dosleg")
.selectFrom("dosleg.date_seance")
.where("dosleg.date_seance.lecidt", "=", lectureAssembleeId)
.select(({ ref }) => [toDateString(ref("dosleg.date_seance.date_s")).as("date")]));
}
function lecturesAssemblee(lectureId) {
return jsonArrayFrom(dbSenat
.withSchema("dosleg")
.selectFrom("lecass")
.where("lecass.lecidt", "=", lectureId)
.leftJoin("ass", "ass.codass", "lecass.codass")
.leftJoin("org", "org.orgcod", "lecass.orgcod")
.leftJoin("orippr", "orippr.oripprcod", "lecass.orippr")
.select(({ ref }) => [
rtrim(ref("ass.libass")).as("assemblee"),
"org.orgnom as libelle_organisme",
"org.senorgcod as code_organisme",
"lecass.ordreass as ordre_lecture_assemblee",
"lecass.sesann as session",
"orippr.oripprlib as origine_proposition",
"lecass.ptlnum as numero_petite_loi",
"lecass.ptlurl as url_petite_loi",
"lecass.loiintmod as loi_intitule_modifie",
"lecass.debatsurl as url_cr_debats",
toDateString(ref("lecass.lecassamecomdat")).as("date_publication_amendements_commission"),
toDateString(ref("lecass.lecassamedat")).as("date_publication_amendements_seance"),
textes(ref("lecass.lecassidt")).as("textes"),
rapports(ref("lecass.lecassidt")).as("rapports"),
datesSeances(ref("lecass.lecassidt")).as("dates_seances"),
])
.orderBy("lecass.ordreass", "asc"));
}
function lectures(loiId) {
return jsonArrayFrom(dbSenat
.withSchema("dosleg")
.selectFrom("lecture")
.leftJoin("typlec", "typlec.typleccod", "lecture.typleccod")
.where("lecture.loicod", "=", loiId)
.select(({ ref }) => [
rtrim(ref("typlec.typleclib")).as("type_lecture"),
rtrim(ref("lecture.leccom")).as("libelle"),
"typlec.typlecord as ordre_lecture",
lecturesAssemblee(ref("lecture.lecidt")).as("lectures_assemblee"),
])
.orderBy("typlec.typlecord", "asc"));
}
function themes(loiId) {
return jsonArrayFrom(dbSenat
.withSchema("dosleg")
.selectFrom("the")
.leftJoin("loithe", "loithe.thecle", "the.thecle")
.where("loithe.loicod", "=", loiId)
.select(["the.thelib as libelle"]));
}
const findAllDossiersQuery = dbSenat
.withSchema("dosleg")
.selectFrom("loi")
.leftJoin("typloi", "typloi.typloicod", "loi.typloicod")
.leftJoin("etaloi", "etaloi.etaloicod", "loi.etaloicod")
.leftJoin("deccoc", "deccoc.deccoccod", "loi.deccoccod")
.select(({ eb, ref, val }) => [
rtrim(ref("loi.loicod")).as("code"),
"loi.numero as numero",
"loi.signet as signet",
sql `NULLIF(regexp_replace(loi.url_an, '^.*\\/(DL[^\\/]+)\\.asp$', '\\1'), '')`.as("code_dossier_an"),
"loi.signetalt as signet_alternatif",
rtrim(ref("loi.motclef")).as("mot_cle"),
rtrim(ref("loi.loient")).as("titre_court"),
concat(rtrim(ref("typloi.typloiden")), val(" "), rtrim(ref("loi.loitit"))).as("titre"),
concat(rtrim(ref("typloi.typloiden")), val(" "), rtrim(ref("loi.loiint"))).as("titre_long"),
concat(rtrim(ref("typloi.typloiden")), val(" "), rtrim(ref("loi.loiintori"))).as("titre_long_original"),
concat(val("https://www.senat.fr/dossier-legislatif/"), ref("loi.signet"), val(".html")).as("url"),
"loi.urgence as urgence",
rtrim(ref("typloi.groupe")).as("code_nature_dossier"),
rtrim(ref("typloi.typloilib")).as("libelle_type_dossier"),
rtrim(ref("etaloi.etaloilib")).as("etat_dossier"),
"loi.url_an as url_dossier_assemblee_nationale",
"loi.url_presart as url_presentation_articles",
"loi.url_ordonnance as url_ordonnance",
"loi.orgcod as code_organisme_resolution",
"deccoc.deccoclib as libelle_decision_CoC",
toDateString(ref("loi.date_decision")).as("date_decision_CoC"),
"loi.num_decision as num_decision_CoC",
"loi.deccocurl as url_decision_CoC",
"loi.doscocurl as url_dossier_CoC",
toDateString(ref("loi.saisine_date")).as("date_saisine_CoC"),
"loi.saisine_par as condition_saisine_CoC",
toDateString(ref("loi.proaccdat")).as("date_procedure_acceleree"),
toDateString(ref("loi.proaccoppdat")).as("date_opposition_procedure_acceleree"),
toDateString(ref("loi.retproaccdat")).as("date_retrait_procedure_acceleree"),
toDateString(ref("loi.date_loi")).as("date_promulgation"),
eb
.case()
.when("loi.loititjo", "is not", null)
.then(concat(rtrim(ref("typloi.typloiden")), val(" "), rtrim(ref("loi.loititjo"))))
.else("")
.end()
.as("titre_JO"),
toDateString(ref("loi.loidatjo")).as("date_publication_JO"),
"loi.loinumjo as numero_JO",
"loi.url_jo as url_JO",
toDateString(ref("loi.loidatjo2")).as("date_publication_JO_correctif_1"),
"loi.loinumjo2 as numero_JO_correctif_1",
"loi.url_jo2 as url_JO_correctif_1",
toDateString(ref("loi.loidatjo3")).as("date_publication_JO_correctif_2"),
"loi.loinumjo3 as numero_JO_correctif_2",
"loi.url_jo3 as url_JO_correctif_2",
lectures(ref("loi.loicod")).as("lectures"),
themes(ref("loi.loicod")).as("themes"),
])
.$narrowType();
export function findAllDossiers() {
return findAllDossiersQuery.stream();
}
export function getCodeActeLecture(codeNatureDossier, typeLecture, assemblee) {
const codeAssemblee = assemblee === "Sénat" ? "SN" : assemblee === "Assemblée nationale" ? "AN" : null;
if (typeLecture === "Commission mixte paritaire") {
return "CMP";
}
if (!codeAssemblee) {
return null;
}
if (["ppl", "pjl", "cvn"].includes(codeNatureDossier) && typeLecture === "Première lecture") {
return `${codeAssemblee}1`;
}
if (["ppl", "pjl", "cvn"].includes(codeNatureDossier) && typeLecture === "Deuxième lecture") {
return `${codeAssemblee}2`;
}
if (["ppl", "pjl", "cvn"].includes(codeNatureDossier) && typeLecture === "Troisième lecture") {
return `${codeAssemblee}3`;
}
if (["ppl", "pjl", "cvn"].includes(codeNatureDossier) && typeLecture === "Quatrième lecture") {
return `${codeAssemblee}4`;
}
if (["ppl", "pjl", "cvn"].includes(codeNatureDossier) && typeLecture === "Nouvelle lecture") {
return `${codeAssemblee}NLEC`;
}
if (["ppl", "pjl", "cvn"].includes(codeNatureDossier) && typeLecture === "Lecture définitive") {
return `${codeAssemblee}LDEF`;
}
if (["ppr"].includes(codeNatureDossier) && typeLecture === "Première lecture") {
return `${codeAssemblee}LUNI`;
}
return null;
}
// Helper pour déterminer le code de phase (SN1, SN2, CMP...)
function getPhasePrefix(lecture, assemblee) {
if (assemblee !== "Sénat")
return null;
const typeLibelle = (lecture.type_lecture || "").toLowerCase();
if (typeLibelle.includes("cmp") || typeLibelle.includes("mixte"))
return "CMP";
if (typeLibelle.includes("nouvelle lecture"))
return "SNNLEC";
if (typeLibelle.includes("d\u00e9finitive"))
return "SNLDEF";
if (typeLibelle.includes("unique"))
return "SNLUNI";
if (lecture.ordre_lecture) {
return `SN${lecture.ordre_lecture}`;
}
if (typeLibelle.includes("premi\u00e8re"))
return "SN1";
if (typeLibelle.includes("deuxi\u00e8me") || typeLibelle.includes("seconde"))
return "SN2";
return "SN1";
}
export function buildActesLegislatifs(dossier) {
const actes = [];
const loiSignet = dossier.signet;
const lectures = dossier.lectures || [];
for (const lecture of lectures) {
const lecturesAssemblee = lecture.lectures_assemblee || [];
for (const lecAss of lecturesAssemblee) {
// On ne traite que la partie SÉNAT
if (lecAss.assemblee !== "Sénat")
continue;
const phasePrefix = getPhasePrefix(lecture, lecAss.assemblee);
if (!phasePrefix)
continue;
// Préparation des textes (tri chronologique)
const textes = lecAss.textes || [];
const textesTries = [...textes].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
// =================================================================
// A. DÉPÔT
// =================================================================
const depotTexte = textesTries.find((t) => (t.origine || "").toLowerCase().includes("déposé") ||
(t.origine || "").toLowerCase().includes("transmis") ||
t.ordre_origine === "0");
if (depotTexte && depotTexte.date) {
actes.push({
code_acte: `${phasePrefix}-DEPOT`,
date: depotTexte.date,
libelle: `Dépôt du texte n°${depotTexte.numero}`,
id: depotTexte.id,
numero: depotTexte.numero,
uid: `${loiSignet}-${phasePrefix}-DEPOT`,
session: lecAss.session,
chambre: "SN",
signet_dossier: loiSignet,
texte_url: depotTexte.url,
code_organisme: null,
});
}
// =================================================================
// B. COMMISSION (Rapports)
// =================================================================
const rapports = lecAss.rapports || [];
for (const rap of rapports) {
if (rap.date) {
actes.push({
code_acte: `${phasePrefix}-COM-FOND`,
date: rap.date,
libelle: `Rapport n°${rap.numero} de la commission`,
id: rap.id,
numero: rap.numero,
code_organisme: rap.code_organisme,
adoption: rap.adoption,
uid: `${loiSignet}-${phasePrefix}-COM`,
session: lecAss.session,
chambre: "SN",
signet_dossier: loiSignet,
texte_url: rap.url,
});
}
}
// =================================================================
// C. SÉANCE PUBLIQUE
// =================================================================
const datesSeances = lecAss.dates_seances || [];
if (datesSeances.length > 0) {
// Tri des objets dates
datesSeances.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
const premiereSeance = datesSeances[0];
if (premiereSeance && premiereSeance.date) {
actes.push({
// Champs pour buildParlementActeLegislatif
code_acte: `${phasePrefix}-DEBATS-SEANCE`,
date: premiereSeance.date,
libelle: `Discussion en séance publique`,
uid: `${loiSignet}-${phasePrefix}-DEBATS-SEANCE`,
session: lecAss.session,
chambre: "SN",
signet_dossier: loiSignet,
code_organisme: null,
});
}
}
// =================================================================
// D. DÉCISION / VOTE
// =================================================================
const texteFinal = [...textesTries].reverse().find((t) => {
const origine = (t.origine || "").toLowerCase();
return (origine.includes("adopté") ||
origine.includes("rejeté") ||
origine.includes("devenu résolution") ||
t.code_adoption === "O");
});
if (texteFinal && texteFinal.date) {
const origine = (texteFinal.origine || "").toLowerCase();
let libelleStatut = "Adopté";
if (origine.includes("rejeté")) {
libelleStatut = "Rejeté";
}
else if (origine.includes("devenue résolution")) {
libelleStatut = "Adopté";
}
actes.push({
code_acte: `${phasePrefix}-DEBATS-DEC`,
date: texteFinal.date,
libelle: `${libelleStatut === "Adopté" ? "Adoption" : "Rejet"} (Texte n°${texteFinal.numero})`,
id: texteFinal.id,
numero: texteFinal.numero,
adoption: libelleStatut,
uid: `${loiSignet}-DEC-${texteFinal.numero}`,
session: lecAss.session,
chambre: "SN",
signet_dossier: loiSignet,
texte_url: texteFinal.url,
code_organisme: null,
});
}
}
}
// =================================================================
// E. HORS LECTURE (CC & PROMULGATION)
// =================================================================
if (dossier.date_decision_CoC) {
actes.push({
code_acte: "CC",
date: dossier.date_decision_CoC,
libelle: `Décision du Conseil constitutionnel`,
id: dossier.url_decision_CoC,
uid: `${loiSignet}-CC`,
chambre: "AN",
signet_dossier: loiSignet,
texte_url: dossier.url_decision_CoC || dossier.url_dossier_CoC,
});
}
if (dossier.date_promulgation) {
actes.push({
code_acte: "PROM",
date: dossier.date_promulgation,
libelle: `Promulgation de la loi`,
date_publication_JO: dossier.date_publication_JO,
numero_JO: dossier.numero_JO,
url_legifrance: dossier.url_JO,
id: dossier.url_JO,
uid: `${loiSignet}-PROM`,
chambre: "AN",
signet_dossier: loiSignet,
});
}
return actes.sort((a, b) => {
const dateA = new Date(a.date).getTime();
const dateB = new Date(b.date).getTime();
return dateA - dateB;
});
}