UNPKG

@tricoteuses/senat

Version:

Handle French Sénat's open data

155 lines (154 loc) 5.8 kB
import { getSessionsFromStart } from "../types/sessions"; import { iterLoadSenatDossiersLegislatifs } from "../loaders"; export function buildOdj(events, dossierBySenatUrl) { const byObjet = new Map(); // objet -> set de dossier uids let codeEtape = null; let dossier = null; for (const ev of events) { const objetKey = (ev.objet ?? "").trim(); const url = normalizeSenatUrl(ev.urlDossierSenat) ?? undefined; dossier = url ? dossierBySenatUrl[url] : null; const dossierUid = dossier ? pickDossierUid(dossier) : undefined; codeEtape = dossier ? computeCodeEtape(ev, dossier) : null; // si on n’a ni objet ni dossier, ça ne sert à rien de créer un point if (!objetKey && !dossierUid) continue; if (!byObjet.has(objetKey) && dossierUid) { byObjet.set(objetKey, dossierUid); } } if (byObjet.size === 0) return undefined; const pointsOdj = []; for (const [objetKey, dossierUid] of byObjet) { pointsOdj.push({ objet: objetKey || null, dossierLegislatifRef: dossierUid || null, codeEtape, }); } return { pointsOdj }; } function pickDossierUid(d) { if (d["signet"] && d["signet"].trim()) return d["signet"].trim(); if (d["code"] && String(d["code"]).trim()) return String(d["code"]).trim(); return undefined; } function normalizeSenatUrl(url) { if (!url) return null; let u = url.trim(); if (!u) return null; if (!/^https?:\/\//i.test(u)) return u; // force https:// u = u.replace(/^http:\/\//i, "https://"); u = u.replace(/\/+$/, ""); return u; } export function buildSenatDossierIndex(options) { const index = {}; const sessions = getSessionsFromStart(2015); for (const session of sessions) { for (const item of iterLoadSenatDossiersLegislatifs(options["dataDir"], session)) { const dossier = item.item; const url = dossier["url"] ? normalizeSenatUrl(dossier["url"]) : undefined; if (url) index[url] = dossier; } } return index; } function detectLecture(objet) { objet = objet.toLowerCase(); if (objet.includes("première lecture")) return 1; if (objet.includes("deuxième lecture") || objet.includes("2ème")) return 2; if (objet.includes("troisième lecture") || objet.includes("3ème")) return 3; return undefined; } function computeCodeEtape(ev, dossier) { // In order to match with stage, we need to remove the '-SEANCE' suffix from the codeActe const cleanCode = (code) => code.replace(/-SEANCE$/, ""); const lecture = detectLecture(ev.objet ?? ""); const organe = ev.organe ?? ""; const nature = organe.toLowerCase().includes("commission") ? "COM" : organe.toLowerCase().includes("séance publique") ? "DEBATS" : ""; const evDate = ev.date.split("T")[0]; const flat = buildFlatActes(dossier); // 1) Strict matching: same date + same nature let candidates = flat.filter((a) => { if (a.date !== evDate) return false; if (nature && !a.codeActe.includes(nature)) return false; return true; }); // If a specific lecture is detected in the agenda event, refine the candidates if (lecture !== undefined && candidates.length > 0) { const withLecture = candidates.filter((c) => c.ordreLecture === lecture); if (withLecture.length > 0) { candidates = withLecture; } } if (candidates.length > 0) { // Multiple candidates: pick the most specific one (longest code string) candidates.sort((a, b) => b.codeActe.length - a.codeActe.length); return cleanCode(candidates[0].codeActe); } // 2) Fallback COM: If no exact date match for a commission event, // take the latest commission act for this lecture on or before the event date. if (nature === "COM") { let comActs = flat.filter((a) => a.codeActe.includes("COM") && a.date <= evDate); if (lecture !== undefined) { const byLecture = comActs.filter((a) => a.ordreLecture === lecture); if (byLecture.length > 0) comActs = byLecture; } if (comActs.length > 0) { comActs.sort((a, b) => b.date.localeCompare(a.date) || b.codeActe.length - a.codeActe.length); return cleanCode(comActs[0].codeActe); } } // 3) Fallback general lecture: if nothing else worked but a lecture is identified, // find any act belonging to that lecture (e.g., SN1-DEPOT). if (lecture !== undefined) { const genericActe = flat.find((a) => a.ordreLecture === lecture); if (genericActe) { return cleanCode(genericActe.codeActe); } } console.log(`✖ No stage code found for ev=${ev.id} (Date: ${evDate}, Nature: ${nature}, Lecture: ${lecture})`, { totalActsInDossier: dossier["actes_legislatifs"]?.length || 0, firstActDate: flat[0]?.date, }); return null; } function buildFlatActes(dossier) { const actes = dossier["actes_legislatifs"] ?? []; const res = []; for (const acte of actes) { if (acte["chambre"] !== "SN") continue; const codeActe = acte.code_acte; const dateActe = acte.date?.split("T")[0]; if (!codeActe || !dateActe) continue; const match = codeActe.match(/^(?:SN|AN)(\d+)/); const ordreLecture = match ? parseInt(match[1], 10) : undefined; res.push({ codeActe, date: dateActe, ordreLecture, }); } return res; }