@tricoteuses/senat 3.1.6 → 3.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/src/config.d.ts +43 -0
- package/lib/src/config.js +37 -0
- package/lib/src/conversion_textes.d.ts +11 -0
- package/lib/src/conversion_textes.js +320 -0
- package/lib/src/databases_postgres.d.ts +4 -0
- package/lib/src/databases_postgres.js +23 -0
- package/lib/src/datasets.d.ts +38 -0
- package/lib/src/datasets.js +247 -0
- package/lib/src/git.d.ts +27 -0
- package/lib/src/git.js +251 -0
- package/lib/src/loaders.d.ts +52 -0
- package/lib/src/loaders.js +260 -0
- package/lib/src/model/agenda.d.ts +6 -0
- package/lib/src/model/agenda.js +148 -0
- package/lib/src/model/ameli.d.ts +67 -0
- package/lib/src/model/ameli.js +150 -0
- package/lib/src/model/commission.d.ts +19 -0
- package/lib/src/model/commission.js +269 -0
- package/lib/src/model/debats.d.ts +39 -0
- package/lib/src/model/debats.js +112 -0
- package/lib/src/model/documents.d.ts +32 -0
- package/lib/src/model/documents.js +182 -0
- package/lib/src/model/dosleg.d.ts +144 -0
- package/lib/src/model/dosleg.js +468 -0
- package/lib/src/model/index.d.ts +7 -0
- package/lib/src/model/index.js +7 -0
- package/lib/src/model/questions.d.ts +54 -0
- package/lib/src/model/questions.js +91 -0
- package/lib/src/model/scrutins.d.ts +48 -0
- package/lib/src/model/scrutins.js +121 -0
- package/lib/src/model/seance.d.ts +3 -0
- package/lib/src/model/seance.js +267 -0
- package/lib/src/model/sens.d.ts +112 -0
- package/lib/src/model/sens.js +385 -0
- package/lib/src/model/util.d.ts +1 -0
- package/lib/src/model/util.js +15 -0
- package/lib/src/raw_types/ameli.d.ts +1762 -0
- package/lib/src/raw_types/ameli.js +1074 -0
- package/lib/src/raw_types/debats.d.ts +380 -0
- package/lib/src/raw_types/debats.js +266 -0
- package/lib/src/raw_types/dosleg.d.ts +2954 -0
- package/lib/src/raw_types/dosleg.js +2005 -0
- package/lib/src/raw_types/questions.d.ts +699 -0
- package/lib/src/raw_types/questions.js +493 -0
- package/lib/src/raw_types/sens.d.ts +7843 -0
- package/lib/src/raw_types/sens.js +4691 -0
- package/lib/src/raw_types_schemats/ameli.d.ts +541 -0
- package/lib/src/raw_types_schemats/ameli.js +2 -0
- package/lib/src/raw_types_schemats/debats.d.ts +127 -0
- package/lib/src/raw_types_schemats/debats.js +2 -0
- package/lib/src/raw_types_schemats/dosleg.d.ts +977 -0
- package/lib/src/raw_types_schemats/dosleg.js +2 -0
- package/lib/src/raw_types_schemats/questions.d.ts +237 -0
- package/lib/src/raw_types_schemats/questions.js +2 -0
- package/lib/src/raw_types_schemats/sens.d.ts +2709 -0
- package/lib/src/raw_types_schemats/sens.js +2 -0
- package/lib/src/scripts/convert_data.js +7 -5
- package/lib/src/scripts/debug_dosleg_query.d.ts +6 -0
- package/lib/src/scripts/debug_dosleg_query.js +50 -0
- package/lib/src/types/agenda.d.ts +45 -0
- package/lib/src/types/agenda.js +1 -0
- package/lib/src/types/ameli.d.ts +5 -0
- package/lib/src/types/ameli.js +1 -0
- package/lib/src/types/compte_rendu.d.ts +83 -0
- package/lib/src/types/compte_rendu.js +1 -0
- package/lib/src/types/debats.d.ts +2 -0
- package/lib/src/types/debats.js +1 -0
- package/lib/src/types/dosleg.d.ts +70 -0
- package/lib/src/types/dosleg.js +1 -0
- package/lib/src/types/questions.d.ts +2 -0
- package/lib/src/types/questions.js +1 -0
- package/lib/src/types/sens.d.ts +8 -0
- package/lib/src/types/sens.js +1 -0
- package/lib/src/types/sessions.d.ts +6 -0
- package/lib/src/types/sessions.js +19 -0
- package/lib/src/types/texte.d.ts +72 -0
- package/lib/src/types/texte.js +15 -0
- package/lib/src/validators/config.d.ts +9 -0
- package/lib/src/validators/config.js +10 -0
- package/package.json +1 -1
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import * as cheerio from "cheerio";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { makeReunionUid } from "../utils/reunion_parsing.js";
|
|
4
|
+
import { norm } from "../utils/string_cleaning.js";
|
|
5
|
+
import { frDateToISO, hourShortToStartTime } from "../utils/date.js";
|
|
6
|
+
import { toCRDate } from "./util.js";
|
|
7
|
+
const PARA_h3_SEL = "p.sh_justify, p.sh_center, p.sh_marge, p[align], li, h3";
|
|
8
|
+
function findDayRoot($, targetISO) {
|
|
9
|
+
let $root = $();
|
|
10
|
+
$("h2").each((_, el) => {
|
|
11
|
+
const txt = norm($(el).text());
|
|
12
|
+
const m = txt.match(/(?:Lundi|Mardi|Mercredi|Jeudi|Vendredi|Samedi|Dimanche)\s+(.+)$/i);
|
|
13
|
+
const iso = m ? frDateToISO(m[1]) : undefined;
|
|
14
|
+
if (iso === targetISO && $root.length === 0)
|
|
15
|
+
$root = $(el);
|
|
16
|
+
});
|
|
17
|
+
return $root;
|
|
18
|
+
}
|
|
19
|
+
function normalizeSpaces(s) {
|
|
20
|
+
return s.replace(/[\u00A0\u202F\u2009]/g, " ");
|
|
21
|
+
}
|
|
22
|
+
function stripIntroPunct(s) {
|
|
23
|
+
return s.replace(/^[\s]*[.:;]?\s*(?:[–—-]\s*)+/u, "");
|
|
24
|
+
}
|
|
25
|
+
function collectLeadingHeaderStrongEls($, $clone) {
|
|
26
|
+
const els = [];
|
|
27
|
+
const nodes = $clone.contents().toArray();
|
|
28
|
+
for (const node of nodes) {
|
|
29
|
+
if (node.type === "text") {
|
|
30
|
+
if (norm(node.data || ""))
|
|
31
|
+
break;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (node.type === "tag") {
|
|
35
|
+
const $n = $(node);
|
|
36
|
+
if ($n.is("strong, b")) {
|
|
37
|
+
els.push(node);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if ($n.is("a") && $n.children("strong, b").length) {
|
|
41
|
+
$n.children("strong, b").each((_, el) => {
|
|
42
|
+
els.push(el);
|
|
43
|
+
});
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return els;
|
|
50
|
+
}
|
|
51
|
+
// Remove orateur's name from text and clean intro punct
|
|
52
|
+
export function getRemainingTextAfterSpeakerHeader($, $p) {
|
|
53
|
+
const $clone = $p.clone();
|
|
54
|
+
// 1) Remove <strong> at start
|
|
55
|
+
const headerStrongEls = collectLeadingHeaderStrongEls($, $clone);
|
|
56
|
+
for (const el of headerStrongEls)
|
|
57
|
+
$(el).remove();
|
|
58
|
+
// 2) normalize + clean intro punct
|
|
59
|
+
let remainingHtml = $clone.html() || "";
|
|
60
|
+
remainingHtml = normalizeSpaces(cheerio.load(remainingHtml).text());
|
|
61
|
+
remainingHtml = stripIntroPunct(remainingHtml);
|
|
62
|
+
const remainingText = norm(remainingHtml || "");
|
|
63
|
+
return remainingText;
|
|
64
|
+
}
|
|
65
|
+
function buildPointsFromParagraphs($, paras) {
|
|
66
|
+
const points = [];
|
|
67
|
+
let ordreAbsoluSeance = 0;
|
|
68
|
+
const normSpeaker = (s) => s
|
|
69
|
+
.normalize("NFKC")
|
|
70
|
+
.replace(/\s+/g, " ")
|
|
71
|
+
.replace(/[.:]\s*$/, "")
|
|
72
|
+
.trim();
|
|
73
|
+
const normQual = (s) => s
|
|
74
|
+
.normalize("NFKC")
|
|
75
|
+
.replace(/\s+/g, " ")
|
|
76
|
+
.replace(/^\s*,\s*|\s+$/g, "")
|
|
77
|
+
.replace(/[\s\u00A0]*[.,;:–—-]+$/u, "")
|
|
78
|
+
.trim();
|
|
79
|
+
let currentOrateur = null;
|
|
80
|
+
let currentQualite = "";
|
|
81
|
+
let currentTexte = "";
|
|
82
|
+
function isPresidentQual(qual) {
|
|
83
|
+
return /\bprésident(e)?\b/i.test(qual);
|
|
84
|
+
}
|
|
85
|
+
// Flush the buffered speaker’s text into points[] if any.
|
|
86
|
+
function flush() {
|
|
87
|
+
if (!currentOrateur || !currentTexte.trim())
|
|
88
|
+
return;
|
|
89
|
+
ordreAbsoluSeance++;
|
|
90
|
+
points.push({
|
|
91
|
+
code_grammaire: "PAROLE_GENERIQUE",
|
|
92
|
+
roledebat: isPresidentQual(currentQualite) ? "président" : "",
|
|
93
|
+
ordre_absolu_seance: String(ordreAbsoluSeance),
|
|
94
|
+
orateurs: { orateur: { nom: currentOrateur, id: "", qualite: currentQualite || "" } },
|
|
95
|
+
texte: { _: currentTexte.trim() },
|
|
96
|
+
});
|
|
97
|
+
currentOrateur = null;
|
|
98
|
+
currentQualite = "";
|
|
99
|
+
currentTexte = "";
|
|
100
|
+
}
|
|
101
|
+
function addPoint(payload) {
|
|
102
|
+
ordreAbsoluSeance++;
|
|
103
|
+
points.push({ ...payload, ordre_absolu_seance: String(ordreAbsoluSeance) });
|
|
104
|
+
}
|
|
105
|
+
for (const $p of paras) {
|
|
106
|
+
if ($p.closest("table").length)
|
|
107
|
+
continue;
|
|
108
|
+
const tagName = ($p.prop("tagName") || "").toString().toLowerCase();
|
|
109
|
+
const rawText = ($p.text() || "").replace(/\u00a0/g, " ").trim();
|
|
110
|
+
const text = norm(rawText);
|
|
111
|
+
if (!text || text.length <= 3)
|
|
112
|
+
continue;
|
|
113
|
+
const html = ($p.html() || "").trim();
|
|
114
|
+
const italicSpans = $p.find("i, em, span[style*='italic']");
|
|
115
|
+
const firstItalicOuter = italicSpans.length ? $(italicSpans[0]).prop("outerHTML") || "" : "";
|
|
116
|
+
const htmlBeforeFirstItalic = firstItalicOuter ? html.split(firstItalicOuter)[0].trim() : "";
|
|
117
|
+
const isPureItalic = italicSpans.length > 0 && italicSpans.length === $p.find("span,i,em").length && htmlBeforeFirstItalic === "";
|
|
118
|
+
if (tagName === "h3") {
|
|
119
|
+
flush();
|
|
120
|
+
addPoint({
|
|
121
|
+
code_style: "Titre",
|
|
122
|
+
code_grammaire: "TITRE_TEXTE_DISCUSSION",
|
|
123
|
+
texte: { _: text },
|
|
124
|
+
});
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const boldSpans = $p.find("strong, b");
|
|
128
|
+
const joinedBold = norm(boldSpans
|
|
129
|
+
.map((_, el) => $(el).text() || "")
|
|
130
|
+
.get()
|
|
131
|
+
.join(""));
|
|
132
|
+
const [namePartRaw, qualPartRaw] = joinedBold.split(/\s*,\s+/, 2);
|
|
133
|
+
const namePart = namePartRaw ? normSpeaker(namePartRaw) : "";
|
|
134
|
+
const qualPart = qualPartRaw ? normQual(qualPartRaw) : "";
|
|
135
|
+
const looksLikeName = namePart.length > 3 && /^(M\.|Mme)[\s\u00A0\u202F]+/i.test(namePart);
|
|
136
|
+
const startsWithName = namePart && text.startsWith(namePart);
|
|
137
|
+
const isNewSpeaker = looksLikeName && startsWithName && namePart !== currentOrateur;
|
|
138
|
+
if (isNewSpeaker) {
|
|
139
|
+
flush();
|
|
140
|
+
currentOrateur = namePart;
|
|
141
|
+
currentQualite = qualPart;
|
|
142
|
+
const remainingText = getRemainingTextAfterSpeakerHeader($, $p);
|
|
143
|
+
currentTexte = remainingText;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (isPureItalic || (!joinedBold && !currentOrateur && text)) {
|
|
147
|
+
flush();
|
|
148
|
+
addPoint({
|
|
149
|
+
code_style: "Info Italiques",
|
|
150
|
+
code_grammaire: "PAROLE_GENERIQUE",
|
|
151
|
+
texte: { _: "<i>" + text + "</i>" },
|
|
152
|
+
});
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
// concat text because same orateur
|
|
156
|
+
if (currentOrateur) {
|
|
157
|
+
const removeOrateurFromText = getRemainingTextAfterSpeakerHeader($, $p);
|
|
158
|
+
currentTexte += (currentTexte ? "<br/><br/>" : "") + removeOrateurFromText;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
flush();
|
|
163
|
+
return points;
|
|
164
|
+
}
|
|
165
|
+
const TIME_RE = /(?:\b[àa]\s*)?(\d{1,2})\s*(?:h|heures?)\s*(?:([0-5]\d))?/i;
|
|
166
|
+
export function cleanTitle(t) {
|
|
167
|
+
return (t || "").replace(/\s+/g, " ").trim();
|
|
168
|
+
}
|
|
169
|
+
function parseTimeToHHmm(text) {
|
|
170
|
+
const m = normalizeSpaces(text).match(TIME_RE);
|
|
171
|
+
if (!m)
|
|
172
|
+
return undefined;
|
|
173
|
+
const hh = m[1]?.padStart(2, "0");
|
|
174
|
+
const mm = (m[2] ?? "00").padStart(2, "0");
|
|
175
|
+
const h = Number(hh);
|
|
176
|
+
if (h >= 0 && h <= 23)
|
|
177
|
+
return `${hh}:${mm}`;
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
function findNearbyTime($, $h3) {
|
|
181
|
+
let cur = $h3.prev();
|
|
182
|
+
for (let i = 0; i < 3 && cur.length; i++, cur = cur.prev()) {
|
|
183
|
+
const direct = parseTimeToHHmm(cur.text());
|
|
184
|
+
if (direct)
|
|
185
|
+
return direct;
|
|
186
|
+
const italic = parseTimeToHHmm(cur.find("i, em").first().text());
|
|
187
|
+
if (italic)
|
|
188
|
+
return italic;
|
|
189
|
+
}
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
export function extractDayH3Sections($, dateISO) {
|
|
193
|
+
const sections = [];
|
|
194
|
+
const $dayRoot = findDayRoot($, dateISO);
|
|
195
|
+
if ($dayRoot.length === 0)
|
|
196
|
+
return sections;
|
|
197
|
+
const $range = $dayRoot.nextUntil("h2");
|
|
198
|
+
const $h3s = $range.filter("h3").add($range.find("h3"));
|
|
199
|
+
$h3s.each((_, el) => {
|
|
200
|
+
const $h3 = $(el);
|
|
201
|
+
const title = cleanTitle($h3.text());
|
|
202
|
+
if (!title)
|
|
203
|
+
return;
|
|
204
|
+
const time = findNearbyTime($, $h3);
|
|
205
|
+
sections.push({ title, $start: $h3, time });
|
|
206
|
+
});
|
|
207
|
+
return sections;
|
|
208
|
+
}
|
|
209
|
+
export function parseCommissionCRSectionFromDom($, htmlFilePath, opts) {
|
|
210
|
+
try {
|
|
211
|
+
const { dateISO, hourShort, organe, section, matched } = opts;
|
|
212
|
+
const seanceRef = matched?.uid ?? makeReunionUid(dateISO, "COM", matched?.events[0].id ?? hourShort ?? "", organe ?? undefined);
|
|
213
|
+
const uid = seanceRef.replace(/^RU/, "CRC");
|
|
214
|
+
const dateSeance = toCRDate(dateISO, matched?.startTime ?? hourShortToStartTime(hourShort));
|
|
215
|
+
const $dayRoot = findDayRoot($, dateISO);
|
|
216
|
+
if ($dayRoot.length === 0) {
|
|
217
|
+
console.warn(`[COM-CR][parse] day root not found for ${dateISO} in ${path.basename(htmlFilePath)}`);
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
const paras = [];
|
|
221
|
+
let $cursor = section.$start;
|
|
222
|
+
// Jump title if we do not want to add it to paragraphes
|
|
223
|
+
$cursor = $cursor.next();
|
|
224
|
+
while ($cursor.length && !$cursor.is("h2") && !$cursor.is("h3")) {
|
|
225
|
+
if ($cursor.is(PARA_h3_SEL)) {
|
|
226
|
+
paras.push($cursor);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
const $ps = $cursor.find(PARA_h3_SEL);
|
|
230
|
+
if ($ps.length)
|
|
231
|
+
$ps.each((_, p) => {
|
|
232
|
+
paras.push($(p));
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
$cursor = $cursor.next();
|
|
236
|
+
}
|
|
237
|
+
const points = buildPointsFromParagraphs($, paras);
|
|
238
|
+
if (points.length < 4 || !points.some((pt) => pt.code_grammaire === "PAROLE_GENERIQUE" && pt.orateurs)) {
|
|
239
|
+
console.warn(`[COM-CR][parse] Insufficient points or no interventions found for a section in ${path.basename(htmlFilePath)}`);
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
const session = dateISO.slice(5, 7) >= "10" ? `${dateISO.slice(0, 4)}` : `${Number(dateISO.slice(0, 4)) - 1}`;
|
|
243
|
+
const contenu = {
|
|
244
|
+
quantiemes: { journee: dateISO, session },
|
|
245
|
+
point: points,
|
|
246
|
+
};
|
|
247
|
+
const metadonnees = {
|
|
248
|
+
dateSeance,
|
|
249
|
+
dateSeanceJour: dateISO,
|
|
250
|
+
numSeanceJour: "",
|
|
251
|
+
numSeance: "",
|
|
252
|
+
typeAssemblee: "SN",
|
|
253
|
+
legislature: "",
|
|
254
|
+
session,
|
|
255
|
+
nomFichierJo: path.basename(htmlFilePath),
|
|
256
|
+
validite: "non-certifie",
|
|
257
|
+
etat: "definitif",
|
|
258
|
+
diffusion: "publique",
|
|
259
|
+
version: "1",
|
|
260
|
+
environnement: "prod",
|
|
261
|
+
heureGeneration: new Date(),
|
|
262
|
+
};
|
|
263
|
+
return { uid, seanceRef, sessionRef: session, metadonnees, contenu };
|
|
264
|
+
}
|
|
265
|
+
catch (e) {
|
|
266
|
+
console.error(`[COM-CR][parse] error section file=${path.basename(htmlFilePath)}:`, e);
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface DebatAuteurRow {
|
|
2
|
+
code: string | null;
|
|
3
|
+
matricule: string | null;
|
|
4
|
+
nom: string | null;
|
|
5
|
+
prenom: string | null;
|
|
6
|
+
}
|
|
7
|
+
export interface DebatInterventionRow {
|
|
8
|
+
analyse: string | null;
|
|
9
|
+
auteur: DebatAuteurRow | null;
|
|
10
|
+
auteur_code: string;
|
|
11
|
+
fonction_intervenant: string | null;
|
|
12
|
+
id: string | null;
|
|
13
|
+
url: string | null;
|
|
14
|
+
}
|
|
15
|
+
export interface DebatSectionRow {
|
|
16
|
+
categorie: string | null;
|
|
17
|
+
id?: string | null;
|
|
18
|
+
interventions: DebatInterventionRow[];
|
|
19
|
+
lecture_id?: string | null;
|
|
20
|
+
libelle?: string | null;
|
|
21
|
+
numero?: string | null;
|
|
22
|
+
objet: string | null;
|
|
23
|
+
type: string | null;
|
|
24
|
+
url?: string | null;
|
|
25
|
+
}
|
|
26
|
+
export interface DebatLectureRow {
|
|
27
|
+
id: string;
|
|
28
|
+
}
|
|
29
|
+
export interface DebatResult {
|
|
30
|
+
date_seance: string | null;
|
|
31
|
+
etat_synchronisation: string | null;
|
|
32
|
+
id: string | null;
|
|
33
|
+
lectures: DebatLectureRow[];
|
|
34
|
+
numero: string | null;
|
|
35
|
+
sections: DebatSectionRow[];
|
|
36
|
+
sections_divers: DebatSectionRow[];
|
|
37
|
+
url: string | null;
|
|
38
|
+
}
|
|
39
|
+
export declare function findAll(): AsyncGenerator<DebatResult, void, unknown>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { streamUnsafeQuery } from "../databases_postgres.js";
|
|
2
|
+
function buildFindAllDebatsQuery() {
|
|
3
|
+
return {
|
|
4
|
+
params: [],
|
|
5
|
+
query: `
|
|
6
|
+
select
|
|
7
|
+
to_char(debats.datsea, 'YYYYMMDD') as id,
|
|
8
|
+
to_char(debats.datsea, 'YYYY-MM-DD') as date_seance,
|
|
9
|
+
debats.numero::text as numero,
|
|
10
|
+
debats.deburl as url,
|
|
11
|
+
debats.debsyn as etat_synchronisation,
|
|
12
|
+
(
|
|
13
|
+
select coalesce(json_agg(section_rows order by section_rows.section_order nulls last), '[]'::json)
|
|
14
|
+
from (
|
|
15
|
+
select
|
|
16
|
+
secdis.secdisordid::text as id,
|
|
17
|
+
secdis.secdisnum as numero,
|
|
18
|
+
secdis.secdisobj as objet,
|
|
19
|
+
secdis.secdisurl as url,
|
|
20
|
+
typsec.typseclib as type,
|
|
21
|
+
typsec.typseccat as categorie,
|
|
22
|
+
secdis.lecassidt as lecture_id,
|
|
23
|
+
secdis.secdisordid as section_order,
|
|
24
|
+
(
|
|
25
|
+
select coalesce(
|
|
26
|
+
json_agg(intervention_rows order by intervention_rows.intervention_order nulls last),
|
|
27
|
+
'[]'::json
|
|
28
|
+
)
|
|
29
|
+
from (
|
|
30
|
+
select
|
|
31
|
+
intpjl.intordid::text as id,
|
|
32
|
+
intpjl.autcod as auteur_code,
|
|
33
|
+
intpjl.intfon as fonction_intervenant,
|
|
34
|
+
intpjl.inturl as url,
|
|
35
|
+
intpjl.intana as analyse,
|
|
36
|
+
json_build_object(
|
|
37
|
+
'code', auteur.autcod,
|
|
38
|
+
'nom', auteur.nomuse,
|
|
39
|
+
'prenom', auteur.prenom,
|
|
40
|
+
'matricule', auteur.autmat
|
|
41
|
+
) as auteur,
|
|
42
|
+
intpjl.intordid as intervention_order
|
|
43
|
+
from senat.debats_intpjl as intpjl
|
|
44
|
+
left join senat.dosleg_auteur as auteur on intpjl.autcod = auteur.autcod
|
|
45
|
+
where intpjl.secdiscle = secdis.secdiscle
|
|
46
|
+
) as intervention_rows
|
|
47
|
+
) as interventions
|
|
48
|
+
from senat.debats_secdis as secdis
|
|
49
|
+
left join senat.debats_typsec as typsec on secdis.typseccod = typsec.typseccod
|
|
50
|
+
where secdis.datsea = debats.datsea
|
|
51
|
+
) as section_rows
|
|
52
|
+
) as sections,
|
|
53
|
+
(
|
|
54
|
+
select coalesce(json_agg(section_rows), '[]'::json)
|
|
55
|
+
from (
|
|
56
|
+
select
|
|
57
|
+
secdivers.secdiverslibelle as libelle,
|
|
58
|
+
secdivers.secdiversobj as objet,
|
|
59
|
+
typsec.typseclib as type,
|
|
60
|
+
typsec.typseccat as categorie,
|
|
61
|
+
(
|
|
62
|
+
select coalesce(
|
|
63
|
+
json_agg(intervention_rows order by intervention_rows.intervention_order nulls last),
|
|
64
|
+
'[]'::json
|
|
65
|
+
)
|
|
66
|
+
from (
|
|
67
|
+
select
|
|
68
|
+
intdivers.intdiversordid::text as id,
|
|
69
|
+
intdivers.autcod as auteur_code,
|
|
70
|
+
intdivers.intfon as fonction_intervenant,
|
|
71
|
+
intdivers.inturl as url,
|
|
72
|
+
intdivers.intana as analyse,
|
|
73
|
+
json_build_object(
|
|
74
|
+
'code', auteur.autcod,
|
|
75
|
+
'nom', auteur.nomuse,
|
|
76
|
+
'prenom', auteur.prenom,
|
|
77
|
+
'matricule', auteur.autmat
|
|
78
|
+
) as auteur,
|
|
79
|
+
intdivers.intdiversordid as intervention_order
|
|
80
|
+
from senat.debats_intdivers as intdivers
|
|
81
|
+
left join senat.dosleg_auteur as auteur on intdivers.autcod = auteur.autcod
|
|
82
|
+
where intdivers.secdiverscle = secdivers.secdiverscle
|
|
83
|
+
) as intervention_rows
|
|
84
|
+
) as interventions
|
|
85
|
+
from senat.debats_secdivers as secdivers
|
|
86
|
+
left join senat.debats_typsec as typsec on secdivers.typseccod = typsec.typseccod
|
|
87
|
+
where secdivers.datsea = debats.datsea
|
|
88
|
+
) as section_rows
|
|
89
|
+
) as sections_divers,
|
|
90
|
+
(
|
|
91
|
+
select coalesce(json_agg(lecture_rows), '[]'::json)
|
|
92
|
+
from (
|
|
93
|
+
select lecassdeb.lecassidt as id
|
|
94
|
+
from senat.debats_lecassdeb as lecassdeb
|
|
95
|
+
where lecassdeb.datsea = debats.datsea
|
|
96
|
+
) as lecture_rows
|
|
97
|
+
) as lectures
|
|
98
|
+
from senat.debats_debats as debats
|
|
99
|
+
`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export async function* findAll() {
|
|
103
|
+
const { query, params } = buildFindAllDebatsQuery();
|
|
104
|
+
for await (const row of streamUnsafeQuery(query, params)) {
|
|
105
|
+
yield {
|
|
106
|
+
...row,
|
|
107
|
+
lectures: row.lectures ?? [],
|
|
108
|
+
sections: row.sections ?? [],
|
|
109
|
+
sections_divers: row.sections_divers ?? [],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface AuteurDocumentRow {
|
|
2
|
+
matricule: string | null;
|
|
3
|
+
nom_usuel: string;
|
|
4
|
+
ordre: string | null;
|
|
5
|
+
prenom: string | null;
|
|
6
|
+
qualite: string | null;
|
|
7
|
+
role: string | null;
|
|
8
|
+
}
|
|
9
|
+
export interface DocumentAnnexeRow {
|
|
10
|
+
type_document: string;
|
|
11
|
+
url: string | null;
|
|
12
|
+
}
|
|
13
|
+
export interface DocumentResult {
|
|
14
|
+
auteurs: AuteurDocumentRow[];
|
|
15
|
+
code_adoption?: string | null;
|
|
16
|
+
code_organisme: string | null;
|
|
17
|
+
date: string;
|
|
18
|
+
documents_annexes?: DocumentAnnexeRow[];
|
|
19
|
+
id: string | null;
|
|
20
|
+
modification?: string | null;
|
|
21
|
+
numero: number | string | null;
|
|
22
|
+
ordre_origine?: string | null;
|
|
23
|
+
origine?: string | null;
|
|
24
|
+
session: number | null;
|
|
25
|
+
signet_dossier?: string | null;
|
|
26
|
+
sous_titre?: string | null;
|
|
27
|
+
titre: string | null;
|
|
28
|
+
type: string | null;
|
|
29
|
+
url: string | null;
|
|
30
|
+
}
|
|
31
|
+
export declare function findAllTextes(): AsyncGenerator<DocumentResult, void, unknown>;
|
|
32
|
+
export declare function findAllRapports(): AsyncGenerator<DocumentResult, void, unknown>;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { streamUnsafeQuery } from "../databases_postgres.js";
|
|
2
|
+
function buildAuteursRapportSql(rapportIdSql) {
|
|
3
|
+
return `
|
|
4
|
+
(
|
|
5
|
+
select coalesce(json_agg(author_rows order by author_rows.ordre_num nulls last), '[]'::json)
|
|
6
|
+
from (
|
|
7
|
+
select
|
|
8
|
+
auteur.prenom as prenom,
|
|
9
|
+
auteur.nomuse as nom_usuel,
|
|
10
|
+
auteur.autmat as matricule,
|
|
11
|
+
ecr.ecrnumtri::text as ordre,
|
|
12
|
+
ecr.ecrnumtri as ordre_num,
|
|
13
|
+
rolsig.rolsiglib as role,
|
|
14
|
+
ecr.ecrqua as qualite
|
|
15
|
+
from senat.dosleg_auteur as auteur
|
|
16
|
+
left join senat.dosleg_ecr as ecr on ecr.autcod = auteur.autcod
|
|
17
|
+
left join senat.dosleg_rolsig as rolsig on rolsig.signataire = ecr.signataire
|
|
18
|
+
where ecr.rapcod = ${rapportIdSql}
|
|
19
|
+
) as author_rows
|
|
20
|
+
)
|
|
21
|
+
`;
|
|
22
|
+
}
|
|
23
|
+
function buildDocumentsAttachesSql(rapportIdSql) {
|
|
24
|
+
return `
|
|
25
|
+
(
|
|
26
|
+
select coalesce(json_agg(attached_rows), '[]'::json)
|
|
27
|
+
from (
|
|
28
|
+
select
|
|
29
|
+
docatt.docatturl as url,
|
|
30
|
+
typatt.typattlib as type_document
|
|
31
|
+
from senat.dosleg_docatt as docatt
|
|
32
|
+
left join senat.dosleg_typatt as typatt on docatt.typattcod = typatt.typattcod
|
|
33
|
+
where docatt.rapcod = ${rapportIdSql}
|
|
34
|
+
) as attached_rows
|
|
35
|
+
)
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
function buildAuteursTexteSql(texteIdSql) {
|
|
39
|
+
return `
|
|
40
|
+
(
|
|
41
|
+
select coalesce(json_agg(author_rows order by author_rows.ordre_num nulls last), '[]'::json)
|
|
42
|
+
from (
|
|
43
|
+
select
|
|
44
|
+
auteur.prenom as prenom,
|
|
45
|
+
auteur.nomuse as nom_usuel,
|
|
46
|
+
auteur.autmat as matricule,
|
|
47
|
+
ecr.ecrnumtri::text as ordre,
|
|
48
|
+
ecr.ecrnumtri as ordre_num,
|
|
49
|
+
rolsig.rolsiglib as role,
|
|
50
|
+
ecr.ecrqua as qualite
|
|
51
|
+
from senat.dosleg_auteur as auteur
|
|
52
|
+
left join senat.dosleg_ecr as ecr on ecr.autcod = auteur.autcod
|
|
53
|
+
left join senat.dosleg_rolsig as rolsig on rolsig.signataire = ecr.signataire
|
|
54
|
+
where ecr.texcod = ${texteIdSql}
|
|
55
|
+
) as author_rows
|
|
56
|
+
)
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
59
|
+
function stripTrailingHashesSql(expr) {
|
|
60
|
+
return `regexp_replace(${expr}, '#+$', '')`;
|
|
61
|
+
}
|
|
62
|
+
function rapportIdSql() {
|
|
63
|
+
return `
|
|
64
|
+
case
|
|
65
|
+
when rap.rapurl is not null then
|
|
66
|
+
${stripTrailingHashesSql("regexp_replace(trim(rap.rapurl), '^(.*/)?(.*?)(\\.html)?$', '\\2')")}
|
|
67
|
+
else null
|
|
68
|
+
end
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
71
|
+
function rapportUrlSql() {
|
|
72
|
+
return `
|
|
73
|
+
case
|
|
74
|
+
when rap.typurl = 'I' then ${stripTrailingHashesSql("'https://www.senat.fr/rap/' || rtrim(rap.rapurl)")}
|
|
75
|
+
else ${stripTrailingHashesSql("rtrim(rap.rapurl)")}
|
|
76
|
+
end
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
79
|
+
function texteIdSql() {
|
|
80
|
+
return `
|
|
81
|
+
case
|
|
82
|
+
when texte.texurl is not null then
|
|
83
|
+
${stripTrailingHashesSql("regexp_replace(trim(texte.texurl), '^(.*/)?(.*?)(\\.html)?$', '\\2')")}
|
|
84
|
+
else null
|
|
85
|
+
end
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
88
|
+
function texteUrlSql() {
|
|
89
|
+
return `
|
|
90
|
+
case
|
|
91
|
+
when texte.typurl = 'I' then ${stripTrailingHashesSql("'https://www.senat.fr/leg/' || rtrim(texte.texurl)")}
|
|
92
|
+
else ${stripTrailingHashesSql("rtrim(texte.texurl)")}
|
|
93
|
+
end
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
function baseRapportsSelect(includeSignetDossier) {
|
|
97
|
+
const signet = includeSignetDossier ? "loi.signet as signet_dossier," : "";
|
|
98
|
+
return `
|
|
99
|
+
select
|
|
100
|
+
${signet}
|
|
101
|
+
rap.rapnum as numero,
|
|
102
|
+
raporg.orgcod as code_organisme,
|
|
103
|
+
${rapportIdSql()} as id,
|
|
104
|
+
${rapportUrlSql()} as url,
|
|
105
|
+
rtrim(denrap.libdenrap) as type,
|
|
106
|
+
rtrim(rtrim(rap.raptil)) as titre,
|
|
107
|
+
rtrim(rtrim(rap.rapsoustit)) as sous_titre,
|
|
108
|
+
to_char(rap.date_depot, 'YYYY-MM-DD') as date,
|
|
109
|
+
rap.sesann::int as session,
|
|
110
|
+
${buildAuteursRapportSql("rap.rapcod")} as auteurs,
|
|
111
|
+
${buildDocumentsAttachesSql("rap.rapcod")} as documents_annexes
|
|
112
|
+
`;
|
|
113
|
+
}
|
|
114
|
+
function baseTextesSelect(includeSignetDossier) {
|
|
115
|
+
const signet = includeSignetDossier ? "loi.signet as signet_dossier," : "";
|
|
116
|
+
return `
|
|
117
|
+
select
|
|
118
|
+
${signet}
|
|
119
|
+
texte.texnum as numero,
|
|
120
|
+
texte.orgcod as code_organisme,
|
|
121
|
+
${texteIdSql()} as id,
|
|
122
|
+
${texteUrlSql()} as url,
|
|
123
|
+
rtrim(oritxt.oritxtlib) as origine,
|
|
124
|
+
oritxt.oriordre as ordre_origine,
|
|
125
|
+
oritxt.oritxtado as code_adoption,
|
|
126
|
+
oritxt.oritxtmod as modification,
|
|
127
|
+
rtrim(typtxt.typtxtlib) as type,
|
|
128
|
+
to_char(texte.txtoritxtdat, 'YYYY-MM-DD') as date,
|
|
129
|
+
texte.sesann::int as session,
|
|
130
|
+
${buildAuteursTexteSql("texte.texcod")} as auteurs,
|
|
131
|
+
null::text as titre
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
function buildFindAllRapportsQuery() {
|
|
135
|
+
return {
|
|
136
|
+
params: [],
|
|
137
|
+
query: `
|
|
138
|
+
${baseRapportsSelect(true)}
|
|
139
|
+
from senat.dosleg_rap as rap
|
|
140
|
+
left join senat.dosleg_raporg as raporg on raporg.rapcod = rap.rapcod
|
|
141
|
+
left join senat.dosleg_denrap as denrap on denrap.coddenrap = rap.coddenrap
|
|
142
|
+
left join senat.dosleg_lecassrap as lecassrap on lecassrap.rapcod = rap.rapcod
|
|
143
|
+
left join senat.dosleg_lecass as lecass on lecass.lecassidt = lecassrap.lecassidt
|
|
144
|
+
left join senat.dosleg_lecture as lecture on lecture.lecidt = lecass.lecidt
|
|
145
|
+
left join senat.dosleg_loi as loi on loi.loicod = lecture.loicod
|
|
146
|
+
`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function buildFindAllTextesQuery() {
|
|
150
|
+
return {
|
|
151
|
+
params: [],
|
|
152
|
+
query: `
|
|
153
|
+
${baseTextesSelect(true)}
|
|
154
|
+
from senat.dosleg_texte as texte
|
|
155
|
+
left join senat.dosleg_oritxt as oritxt on oritxt.oritxtcod = texte.oritxtcod
|
|
156
|
+
left join senat.dosleg_typtxt as typtxt on typtxt.typtxtcod = texte.typtxtcod
|
|
157
|
+
left join senat.dosleg_lecass as lecass on lecass.lecassidt = texte.lecassidt
|
|
158
|
+
left join senat.dosleg_lecture as lecture on lecture.lecidt = lecass.lecidt
|
|
159
|
+
left join senat.dosleg_loi as loi on loi.loicod = lecture.loicod
|
|
160
|
+
order by array_position(array['0','2','1'], oritxt.oriordre)
|
|
161
|
+
`,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export async function* findAllTextes() {
|
|
165
|
+
const { query, params } = buildFindAllTextesQuery();
|
|
166
|
+
for await (const row of streamUnsafeQuery(query, params)) {
|
|
167
|
+
yield {
|
|
168
|
+
...row,
|
|
169
|
+
auteurs: row.auteurs ?? [],
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
export async function* findAllRapports() {
|
|
174
|
+
const { query, params } = buildFindAllRapportsQuery();
|
|
175
|
+
for await (const row of streamUnsafeQuery(query, params)) {
|
|
176
|
+
yield {
|
|
177
|
+
...row,
|
|
178
|
+
auteurs: row.auteurs ?? [],
|
|
179
|
+
documents_annexes: row.documents_annexes ?? [],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|