@tricoteuses/senat 3.1.2 → 3.1.3
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/rich_types/dosleg.js +13 -3
- package/lib/src/rich_types/sens.d.ts +2 -0
- package/lib/src/scripts/retrieve_open_data.js +4 -2
- package/lib/src/scripts/shared/make_generate_zod_schemas.js +6 -2
- package/lib/src/server/databases_postgres.js +2 -1
- package/lib/src/server/documents.js +2 -2
- package/lib/src/server/dosleg.js +2 -2
- package/lib/src/server/sens.js +70 -0
- package/lib/src/utils/reunion_parsing.js +1 -1
- package/package.json +3 -1
- package/lib/src/config.d.ts +0 -43
- package/lib/src/config.js +0 -37
- package/lib/src/conversion_textes.d.ts +0 -11
- package/lib/src/conversion_textes.js +0 -320
- package/lib/src/databases_postgres.d.ts +0 -4
- package/lib/src/databases_postgres.js +0 -23
- package/lib/src/datasets.d.ts +0 -38
- package/lib/src/datasets.js +0 -247
- package/lib/src/git.d.ts +0 -27
- package/lib/src/git.js +0 -251
- package/lib/src/loaders.d.ts +0 -52
- package/lib/src/loaders.js +0 -260
- package/lib/src/model/agenda.d.ts +0 -6
- package/lib/src/model/agenda.js +0 -148
- package/lib/src/model/ameli.d.ts +0 -67
- package/lib/src/model/ameli.js +0 -150
- package/lib/src/model/commission.d.ts +0 -19
- package/lib/src/model/commission.js +0 -269
- package/lib/src/model/debats.d.ts +0 -39
- package/lib/src/model/debats.js +0 -112
- package/lib/src/model/documents.d.ts +0 -32
- package/lib/src/model/documents.js +0 -182
- package/lib/src/model/dosleg.d.ts +0 -144
- package/lib/src/model/dosleg.js +0 -468
- package/lib/src/model/index.d.ts +0 -7
- package/lib/src/model/index.js +0 -7
- package/lib/src/model/questions.d.ts +0 -54
- package/lib/src/model/questions.js +0 -91
- package/lib/src/model/scrutins.d.ts +0 -48
- package/lib/src/model/scrutins.js +0 -121
- package/lib/src/model/seance.d.ts +0 -3
- package/lib/src/model/seance.js +0 -267
- package/lib/src/model/sens.d.ts +0 -112
- package/lib/src/model/sens.js +0 -385
- package/lib/src/model/util.d.ts +0 -1
- package/lib/src/model/util.js +0 -15
- package/lib/src/raw_types/ameli.d.ts +0 -1762
- package/lib/src/raw_types/ameli.js +0 -1074
- package/lib/src/raw_types/debats.d.ts +0 -380
- package/lib/src/raw_types/debats.js +0 -266
- package/lib/src/raw_types/dosleg.d.ts +0 -2954
- package/lib/src/raw_types/dosleg.js +0 -2005
- package/lib/src/raw_types/questions.d.ts +0 -699
- package/lib/src/raw_types/questions.js +0 -493
- package/lib/src/raw_types/sens.d.ts +0 -7843
- package/lib/src/raw_types/sens.js +0 -4691
- package/lib/src/raw_types_schemats/ameli.d.ts +0 -541
- package/lib/src/raw_types_schemats/ameli.js +0 -2
- package/lib/src/raw_types_schemats/debats.d.ts +0 -127
- package/lib/src/raw_types_schemats/debats.js +0 -2
- package/lib/src/raw_types_schemats/dosleg.d.ts +0 -977
- package/lib/src/raw_types_schemats/dosleg.js +0 -2
- package/lib/src/raw_types_schemats/questions.d.ts +0 -237
- package/lib/src/raw_types_schemats/questions.js +0 -2
- package/lib/src/raw_types_schemats/sens.d.ts +0 -2709
- package/lib/src/raw_types_schemats/sens.js +0 -2
- package/lib/src/types/agenda.d.ts +0 -45
- package/lib/src/types/agenda.js +0 -1
- package/lib/src/types/ameli.d.ts +0 -5
- package/lib/src/types/ameli.js +0 -1
- package/lib/src/types/compte_rendu.d.ts +0 -83
- package/lib/src/types/compte_rendu.js +0 -1
- package/lib/src/types/debats.d.ts +0 -2
- package/lib/src/types/debats.js +0 -1
- package/lib/src/types/dosleg.d.ts +0 -70
- package/lib/src/types/dosleg.js +0 -1
- package/lib/src/types/questions.d.ts +0 -2
- package/lib/src/types/questions.js +0 -1
- package/lib/src/types/sens.d.ts +0 -8
- package/lib/src/types/sens.js +0 -1
- package/lib/src/types/sessions.d.ts +0 -6
- package/lib/src/types/sessions.js +0 -19
- package/lib/src/types/texte.d.ts +0 -72
- package/lib/src/types/texte.js +0 -15
- package/lib/src/validators/config.d.ts +0 -9
- package/lib/src/validators/config.js +0 -10
|
@@ -39,7 +39,7 @@ function getDateSortValue(value) {
|
|
|
39
39
|
function compareByDate(left, right) {
|
|
40
40
|
return getDateSortValue(left.date) - getDateSortValue(right.date);
|
|
41
41
|
}
|
|
42
|
-
function getPhasePrefix(lecture, assemblee) {
|
|
42
|
+
function getPhasePrefix(lecture, assemblee, codeNatureDossier) {
|
|
43
43
|
if (assemblee !== "Sénat")
|
|
44
44
|
return null;
|
|
45
45
|
const typeLibelle = (lecture.type_lecture || "").toLowerCase();
|
|
@@ -51,6 +51,11 @@ function getPhasePrefix(lecture, assemblee) {
|
|
|
51
51
|
return "SNLDEF";
|
|
52
52
|
if (typeLibelle.includes("unique"))
|
|
53
53
|
return "SNLUNI";
|
|
54
|
+
// Les propositions de résolution (ppr) suivent la procédure de lecture unique
|
|
55
|
+
// (art. 73 quinquies du Règlement du Sénat). Les données brutes encodent leur
|
|
56
|
+
// type_lecture comme "Première lecture", mais c'est toujours une lecture unique.
|
|
57
|
+
if (codeNatureDossier === "ppr")
|
|
58
|
+
return "SNLUNI";
|
|
54
59
|
if (lecture.ordre_lecture) {
|
|
55
60
|
return `SN${lecture.ordre_lecture}`;
|
|
56
61
|
}
|
|
@@ -69,7 +74,7 @@ export function buildActesLegislatifs(dossier) {
|
|
|
69
74
|
for (const lecAss of lecturesAssemblee) {
|
|
70
75
|
if (lecAss.assemblee !== "Sénat")
|
|
71
76
|
continue;
|
|
72
|
-
const phasePrefix = getPhasePrefix(lecture, lecAss.assemblee);
|
|
77
|
+
const phasePrefix = getPhasePrefix(lecture, lecAss.assemblee, dossier.code_nature_dossier);
|
|
73
78
|
if (!phasePrefix)
|
|
74
79
|
continue;
|
|
75
80
|
const textes = lecAss.textes ?? [];
|
|
@@ -144,8 +149,13 @@ export function buildActesLegislatifs(dossier) {
|
|
|
144
149
|
else if (origine.includes("devenue résolution")) {
|
|
145
150
|
libelleStatut = "Adopté";
|
|
146
151
|
}
|
|
152
|
+
// "devenu résolution du Sénat" : adoptée en commission sans séance publique (art. 73 quinquies RSN)
|
|
153
|
+
// On utilise COM-CAE-DEC plutôt que DEBATS-DEC pour refléter l'absence de séance plénière
|
|
154
|
+
const codeActeDecision = (texteFinal.origine || "").toLowerCase().includes("devenu résolution")
|
|
155
|
+
? `${phasePrefix}-COM-CAE-DEC`
|
|
156
|
+
: `${phasePrefix}-DEBATS-DEC`;
|
|
147
157
|
actes.push({
|
|
148
|
-
code_acte:
|
|
158
|
+
code_acte: codeActeDecision,
|
|
149
159
|
date: texteFinal.date,
|
|
150
160
|
libelle: `${libelleStatut === "Adopté" ? "Adoption" : "Rejet"} (Texte n°${texteFinal.numero})`,
|
|
151
161
|
id: texteFinal.id,
|
|
@@ -68,9 +68,11 @@ export interface SenateurResult {
|
|
|
68
68
|
fonctions_bureau: FonctionRow[];
|
|
69
69
|
groupe_politique: string | null;
|
|
70
70
|
groupes: MandatOrganismeRow[];
|
|
71
|
+
groupes_amitie: MandatOrganismeRow[];
|
|
71
72
|
mandats_senateur: MandatSenateurRow[];
|
|
72
73
|
matricule: string;
|
|
73
74
|
nom_usuel: string | null;
|
|
75
|
+
organismes: MandatOrganismeRow[];
|
|
74
76
|
points_contact: PointContactRow[];
|
|
75
77
|
prenom_usuel: string;
|
|
76
78
|
qualite: string;
|
|
@@ -2,9 +2,11 @@ import assert from "assert";
|
|
|
2
2
|
import { execFileSync } from "child_process";
|
|
3
3
|
import commandLineArgs from "command-line-args";
|
|
4
4
|
import fs from "fs-extra";
|
|
5
|
-
import {
|
|
6
|
-
import { makeKyselyHook } from "kanel-kysely";
|
|
5
|
+
import { createRequire } from "module";
|
|
7
6
|
import path from "path";
|
|
7
|
+
const _require = createRequire(import.meta.url);
|
|
8
|
+
const { formatWithPrettier, makePgTsGenerator, markAsGenerated, processDatabase } = _require("kanel");
|
|
9
|
+
const { makeKyselyHook } = _require("kanel-kysely");
|
|
8
10
|
import StreamZip from "node-stream-zip";
|
|
9
11
|
import readline from "readline";
|
|
10
12
|
import { pipeline, Readable } from "stream";
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
import { useKanelContext, } from "kanel";
|
|
3
|
+
import { defaultGetZodIdentifierMetadata, defaultGetZodSchemaMetadata } from "kanel-zod";
|
|
4
|
+
const _require = createRequire(import.meta.url);
|
|
5
|
+
const { escapeName, resolveType } = _require("kanel");
|
|
6
|
+
const { defaultZodTypeMap } = _require("kanel-zod");
|
|
3
7
|
const zImport = {
|
|
4
8
|
asName: undefined,
|
|
5
9
|
importAsType: false,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Pool } from "pg";
|
|
2
2
|
import { Kysely, PostgresDialect, sql as kyselySql } from "kysely";
|
|
3
|
+
import Cursor from "pg-cursor";
|
|
3
4
|
import config from "./config.js";
|
|
4
5
|
// ---------------------------------------------------------------------------
|
|
5
6
|
// Kysely instance (uses pg driver so .stream() is available)
|
|
@@ -19,7 +20,7 @@ const pool = new Pool({
|
|
|
19
20
|
* `db.withSchema("senat").selectFrom("sens_sen")`
|
|
20
21
|
*/
|
|
21
22
|
export const db = new Kysely({
|
|
22
|
-
dialect: new PostgresDialect({ pool }),
|
|
23
|
+
dialect: new PostgresDialect({ pool, cursor: Cursor }),
|
|
23
24
|
});
|
|
24
25
|
// ---------------------------------------------------------------------------
|
|
25
26
|
// Streaming helper
|
|
@@ -17,7 +17,7 @@ export async function* findAllTextes() {
|
|
|
17
17
|
sql `
|
|
18
18
|
case
|
|
19
19
|
when ${eb.ref("texte.texurl")} is not null then
|
|
20
|
-
regexp_replace(regexp_replace(trim(${eb.ref("texte.texurl")}), '#+$', ''), '^(.*/)?(.*?)(
|
|
20
|
+
regexp_replace(regexp_replace(trim(${eb.ref("texte.texurl")}), '#+$', ''), '^(.*/)?(.*?)(\\.html)?$', '\\2')
|
|
21
21
|
else null
|
|
22
22
|
end
|
|
23
23
|
`.as("id"),
|
|
@@ -78,7 +78,7 @@ export async function* findAllRapports() {
|
|
|
78
78
|
sql `
|
|
79
79
|
case
|
|
80
80
|
when ${eb.ref("rap.rapurl")} is not null then
|
|
81
|
-
regexp_replace(regexp_replace(trim(${eb.ref("rap.rapurl")}), '#+$', ''), '^(.*/)?(.*?)(
|
|
81
|
+
regexp_replace(regexp_replace(trim(${eb.ref("rap.rapurl")}), '#+$', ''), '^(.*/)?(.*?)(\\.html)?$', '\\2')
|
|
82
82
|
else null
|
|
83
83
|
end
|
|
84
84
|
`.as("id"),
|
package/lib/src/server/dosleg.js
CHANGED
|
@@ -101,7 +101,7 @@ export async function* findAllDossiers() {
|
|
|
101
101
|
when texte.texurl is not null then
|
|
102
102
|
regexp_replace(
|
|
103
103
|
regexp_replace(trim(texte.texurl), '#+$', ''),
|
|
104
|
-
'^(.*/)?(.*?)(\.html)?$', '
|
|
104
|
+
'^(.*/)?(.*?)(\.html)?$', '\\2')
|
|
105
105
|
else null
|
|
106
106
|
end
|
|
107
107
|
`.as("id"),
|
|
@@ -153,7 +153,7 @@ export async function* findAllDossiers() {
|
|
|
153
153
|
when rap.rapurl is not null then
|
|
154
154
|
regexp_replace(
|
|
155
155
|
regexp_replace(trim(rap.rapurl), '#+$', ''),
|
|
156
|
-
'^(.*/)?(.*?)(\.html)?$', '
|
|
156
|
+
'^(.*/)?(.*?)(\.html)?$', '\\2')
|
|
157
157
|
else null
|
|
158
158
|
end
|
|
159
159
|
`.as("id"),
|
package/lib/src/server/sens.js
CHANGED
|
@@ -163,6 +163,74 @@ export async function* findAll() {
|
|
|
163
163
|
])
|
|
164
164
|
.whereRef("memgrppol.senmat", "=", "sen.senmat")
|
|
165
165
|
.orderBy("memgrppol.memgrppoldatdeb", (ob) => ob.desc().nullsLast())).as("groupes"),
|
|
166
|
+
// Organismes divers (groupes d'études, missions, etc.)
|
|
167
|
+
jsonArrayFrom(eb
|
|
168
|
+
.withSchema("senat")
|
|
169
|
+
.selectFrom("sens_memorg as memorg")
|
|
170
|
+
.leftJoin("sens_org as org", "org.orgcod", "memorg.orgcod")
|
|
171
|
+
.leftJoin("sens_typorg as typorg4", "typorg4.typorgcod", "org.typorgcod")
|
|
172
|
+
.select((eb2) => [
|
|
173
|
+
"memorg.orgcod as code_organisme",
|
|
174
|
+
sql `to_char(memorg.memorgdatdeb, 'YYYY-MM-DD')`.as("date_debut"),
|
|
175
|
+
sql `to_char(memorg.memorgdatfin, 'YYYY-MM-DD')`.as("date_fin"),
|
|
176
|
+
"memorg.temvalcod as etat",
|
|
177
|
+
"org.evelib as libelle",
|
|
178
|
+
"org.typorgcod as type_code_organisme",
|
|
179
|
+
"typorg4.typorglib as type_organisme",
|
|
180
|
+
"memorg.memorgdatdeb as order_date",
|
|
181
|
+
// Fonctions dans l'organisme
|
|
182
|
+
jsonArrayFrom(eb2
|
|
183
|
+
.withSchema("senat")
|
|
184
|
+
.selectFrom("sens_fonmemorg as fonmemorg")
|
|
185
|
+
.leftJoin("sens_fonorg as fonorg", "fonorg.fonorgcod", "fonmemorg.fonorgcod")
|
|
186
|
+
.select([
|
|
187
|
+
sql `to_char(fonmemorg.fonmemorgdatdeb, 'YYYY-MM-DD')`.as("date_debut"),
|
|
188
|
+
sql `to_char(fonmemorg.fonmemorgdatfin, 'YYYY-MM-DD')`.as("date_fin"),
|
|
189
|
+
sql `coalesce(
|
|
190
|
+
nullif(fonorg.fonorglib, ''),
|
|
191
|
+
nullif(fonorg.fonorglil, ''),
|
|
192
|
+
nullif(fonorg.fonorglic, ''))`.as("libelle"),
|
|
193
|
+
"fonmemorg.fonmemorgdatdeb as order_date",
|
|
194
|
+
])
|
|
195
|
+
.whereRef("fonmemorg.memorgid", "=", "memorg.memorgid")
|
|
196
|
+
.orderBy("fonmemorg.fonmemorgdatdeb", (ob) => ob.desc().nullsLast())).as("fonctions"),
|
|
197
|
+
])
|
|
198
|
+
.whereRef("memorg.senmat", "=", "sen.senmat")
|
|
199
|
+
.orderBy("memorg.memorgdatdeb", (ob) => ob.desc().nullsLast())).as("organismes"),
|
|
200
|
+
// Groupes sénatoriaux (groupes d'amitié)
|
|
201
|
+
jsonArrayFrom(eb
|
|
202
|
+
.withSchema("senat")
|
|
203
|
+
.selectFrom("sens_memgrpsen as memgrpsen")
|
|
204
|
+
.leftJoin("sens_grpsenami as grpsenami", "grpsenami.orgcod", "memgrpsen.orgcod")
|
|
205
|
+
.leftJoin("sens_typorg as typorg5", "typorg5.typorgcod", "grpsenami.typorgcod")
|
|
206
|
+
.select((eb2) => [
|
|
207
|
+
"memgrpsen.orgcod as code_organisme",
|
|
208
|
+
sql `to_char(memgrpsen.memgrpsendatent, 'YYYY-MM-DD')`.as("date_debut"),
|
|
209
|
+
sql `to_char(memgrpsen.memgrpsendatsor, 'YYYY-MM-DD')`.as("date_fin"),
|
|
210
|
+
"memgrpsen.temvalcod as etat",
|
|
211
|
+
"grpsenami.evelib as libelle",
|
|
212
|
+
"grpsenami.typorgcod as type_code_organisme",
|
|
213
|
+
"typorg5.typorglib as type_organisme",
|
|
214
|
+
"memgrpsen.memgrpsendatent as order_date",
|
|
215
|
+
// Fonctions dans le groupe sénatorial
|
|
216
|
+
jsonArrayFrom(eb2
|
|
217
|
+
.withSchema("senat")
|
|
218
|
+
.selectFrom("sens_fonmemgrpsen as fonmemgrpsen")
|
|
219
|
+
.leftJoin("sens_fongrpsen as fongrpsen", "fongrpsen.fongrpsencod", "fonmemgrpsen.fongrpsencod")
|
|
220
|
+
.select([
|
|
221
|
+
sql `to_char(fonmemgrpsen.fonmemgrpsendatdeb, 'YYYY-MM-DD')`.as("date_debut"),
|
|
222
|
+
sql `to_char(fonmemgrpsen.fonmemgrpsendatfin, 'YYYY-MM-DD')`.as("date_fin"),
|
|
223
|
+
sql `coalesce(
|
|
224
|
+
nullif(fongrpsen.fongrpsenlib, ''),
|
|
225
|
+
nullif(fongrpsen.fongrpsenlil, ''),
|
|
226
|
+
nullif(fongrpsen.fongrpsenlic, ''))`.as("libelle"),
|
|
227
|
+
"fonmemgrpsen.fonmemgrpsendatdeb as order_date",
|
|
228
|
+
])
|
|
229
|
+
.whereRef("fonmemgrpsen.memgrpsenid", "=", "memgrpsen.memgrpsenid")
|
|
230
|
+
.orderBy("fonmemgrpsen.fonmemgrpsendatdeb", (ob) => ob.desc().nullsLast())).as("fonctions"),
|
|
231
|
+
])
|
|
232
|
+
.whereRef("memgrpsen.senmat", "=", "sen.senmat")
|
|
233
|
+
.orderBy("memgrpsen.memgrpsendatent", (ob) => ob.desc().nullsLast())).as("groupes_amitie"),
|
|
166
234
|
// Fonctions au bureau du Sénat
|
|
167
235
|
jsonArrayFrom(eb
|
|
168
236
|
.withSchema("senat")
|
|
@@ -226,7 +294,9 @@ export async function* findAll() {
|
|
|
226
294
|
delegations: row.delegations ?? [],
|
|
227
295
|
fonctions_bureau: row.fonctions_bureau ?? [],
|
|
228
296
|
groupes: row.groupes ?? [],
|
|
297
|
+
groupes_amitie: row.groupes_amitie ?? [],
|
|
229
298
|
mandats_senateur: row.mandats_senateur ?? [],
|
|
299
|
+
organismes: row.organismes ?? [],
|
|
230
300
|
points_contact: row.points_contact ?? [],
|
|
231
301
|
urls: row.urls ?? [],
|
|
232
302
|
};
|
|
@@ -50,7 +50,7 @@ export function buildReunionsByBucket(events, dossierBySenatUrl) {
|
|
|
50
50
|
for (const e of events) {
|
|
51
51
|
const kind = classifyAgendaType(e?.type);
|
|
52
52
|
if (!kind) {
|
|
53
|
-
console.warn("Can't determine type of reunion");
|
|
53
|
+
console.warn("Can't determine type of reunion ", e.type, "for event", e.id, e.titre);
|
|
54
54
|
continue;
|
|
55
55
|
}
|
|
56
56
|
const bucket = typeToSuffixStrict(kind);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tricoteuses/senat",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.3",
|
|
4
4
|
"description": "Handle French Sénat's open data",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"France",
|
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
"luxon": "^3.7.2",
|
|
85
85
|
"node-stream-zip": "^1.8.2",
|
|
86
86
|
"p-limit": "^7.2.0",
|
|
87
|
+
"pg-cursor": "^2.19.0",
|
|
87
88
|
"postgres": "^3.4.8",
|
|
88
89
|
"slug": "^11.0.0",
|
|
89
90
|
"windows-1252": "^3.0.4",
|
|
@@ -97,6 +98,7 @@
|
|
|
97
98
|
"@types/luxon": "^3.7.1",
|
|
98
99
|
"@types/node": "^24.10.1",
|
|
99
100
|
"@types/pg": "^8.20.0",
|
|
101
|
+
"@types/pg-cursor": "^2.7.2",
|
|
100
102
|
"@types/slug": "^5.0.9",
|
|
101
103
|
"@typescript-eslint/eslint-plugin": "^8.52.0",
|
|
102
104
|
"@typescript-eslint/parser": "^8.52.0",
|
package/lib/src/config.d.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import "dotenv/config";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
export declare const dbSchema: z.ZodObject<{
|
|
4
|
-
host: z.ZodString;
|
|
5
|
-
name: z.ZodString;
|
|
6
|
-
password: z.ZodString;
|
|
7
|
-
port: z.ZodCoercedNumber<unknown>;
|
|
8
|
-
user: z.ZodString;
|
|
9
|
-
}, z.core.$strip>;
|
|
10
|
-
export type DbConfig = z.infer<typeof dbSchema>;
|
|
11
|
-
export declare const configSchema: z.ZodObject<{
|
|
12
|
-
db: z.ZodObject<{
|
|
13
|
-
host: z.ZodString;
|
|
14
|
-
name: z.ZodString;
|
|
15
|
-
password: z.ZodString;
|
|
16
|
-
port: z.ZodCoercedNumber<unknown>;
|
|
17
|
-
user: z.ZodString;
|
|
18
|
-
}, z.core.$strip>;
|
|
19
|
-
stagingDb: z.ZodObject<{
|
|
20
|
-
host: z.ZodString;
|
|
21
|
-
name: z.ZodString;
|
|
22
|
-
password: z.ZodString;
|
|
23
|
-
port: z.ZodCoercedNumber<unknown>;
|
|
24
|
-
user: z.ZodString;
|
|
25
|
-
}, z.core.$strip>;
|
|
26
|
-
}, z.core.$strip>;
|
|
27
|
-
declare const _default: {
|
|
28
|
-
db: {
|
|
29
|
-
host: string;
|
|
30
|
-
name: string;
|
|
31
|
-
password: string;
|
|
32
|
-
port: number;
|
|
33
|
-
user: string;
|
|
34
|
-
};
|
|
35
|
-
stagingDb: {
|
|
36
|
-
host: string;
|
|
37
|
-
name: string;
|
|
38
|
-
password: string;
|
|
39
|
-
port: number;
|
|
40
|
-
user: string;
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
export default _default;
|
package/lib/src/config.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import "dotenv/config";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
export const dbSchema = z.object({
|
|
4
|
-
host: z.string().trim().min(1, "Must not be empty"),
|
|
5
|
-
name: z.string().trim().min(1, "Must not be empty"),
|
|
6
|
-
password: z.string().trim().min(1, "Must not be empty"),
|
|
7
|
-
port: z.coerce.number().int().min(0).max(65535),
|
|
8
|
-
user: z.string().trim().min(1, "Must not be empty"),
|
|
9
|
-
});
|
|
10
|
-
export const configSchema = z.object({
|
|
11
|
-
db: dbSchema,
|
|
12
|
-
stagingDb: dbSchema,
|
|
13
|
-
});
|
|
14
|
-
const config = {
|
|
15
|
-
db: {
|
|
16
|
-
host: process.env["DB_HOST"] || "localhost",
|
|
17
|
-
name: process.env["DB_NAME"] || "senat",
|
|
18
|
-
password: process.env["DB_PASSWORD"] || "opendata",
|
|
19
|
-
port: process.env["DB_PORT"] || 5432,
|
|
20
|
-
user: process.env["DB_USER"] || "opendata",
|
|
21
|
-
},
|
|
22
|
-
stagingDb: {
|
|
23
|
-
host: process.env["STAGING_DB_HOST"] || process.env["DB_HOST"] || "localhost",
|
|
24
|
-
name: process.env["STAGING_DB_NAME"] || "senat_staging",
|
|
25
|
-
password: process.env["STAGING_DB_PASSWORD"] || process.env["DB_PASSWORD"] || "opendata",
|
|
26
|
-
port: process.env["STAGING_DB_PORT"] || process.env["DB_PORT"] || 5432,
|
|
27
|
-
user: process.env["STAGING_DB_USER"] || process.env["DB_USER"] || "opendata",
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
const result = configSchema.safeParse(config);
|
|
31
|
-
if (!result.success) {
|
|
32
|
-
const issues = JSON.stringify(result.error.issues, null, 2);
|
|
33
|
-
const serializedConfig = JSON.stringify(config, null, 2);
|
|
34
|
-
console.error(`Error in configuration:\n${serializedConfig}\nError:\n${issues}`);
|
|
35
|
-
process.exit(-1);
|
|
36
|
-
}
|
|
37
|
-
export default result.data;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export interface SenatMetadata {
|
|
2
|
-
number: string | null;
|
|
3
|
-
session: string | null;
|
|
4
|
-
date: string | null;
|
|
5
|
-
type: string | null;
|
|
6
|
-
authors: string | null;
|
|
7
|
-
title: string | null;
|
|
8
|
-
commission: string | null;
|
|
9
|
-
}
|
|
10
|
-
export declare function extractMetadata(xmlDoc: Document): SenatMetadata;
|
|
11
|
-
export declare function convertSenatXmlToHtml(texteXml: string, outputFilePath: string): Promise<void>;
|
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
import { JSDOM } from "jsdom";
|
|
2
|
-
import fs from "fs-extra";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { DateTime } from "luxon";
|
|
5
|
-
export function extractMetadata(xmlDoc) {
|
|
6
|
-
const metadata = {
|
|
7
|
-
number: null,
|
|
8
|
-
session: null,
|
|
9
|
-
date: null,
|
|
10
|
-
type: null,
|
|
11
|
-
authors: null,
|
|
12
|
-
title: xmlDoc.querySelector("docTitle")?.textContent?.trim() || null,
|
|
13
|
-
commission: null,
|
|
14
|
-
};
|
|
15
|
-
// Extract Number
|
|
16
|
-
const docIdAlias = xmlDoc.querySelector('FRBRalias[name="signet-dossier-legislatif-senat"]');
|
|
17
|
-
if (docIdAlias) {
|
|
18
|
-
const value = docIdAlias.getAttribute("value");
|
|
19
|
-
if (value) {
|
|
20
|
-
const match = value.match(/\d+$/);
|
|
21
|
-
if (match)
|
|
22
|
-
metadata.number = match[0];
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
// Extract Session
|
|
26
|
-
const sessionUri = xmlDoc.querySelector("FRBRExpression > FRBRuri")?.getAttribute("value");
|
|
27
|
-
if (sessionUri) {
|
|
28
|
-
const match = sessionUri.match(/\d{4}-\d{4}/);
|
|
29
|
-
if (match)
|
|
30
|
-
metadata.session = match[0];
|
|
31
|
-
}
|
|
32
|
-
// Extract Date
|
|
33
|
-
const depotDate = xmlDoc.querySelector('FRBRdate[name="#depot"]')?.getAttribute("date");
|
|
34
|
-
if (depotDate) {
|
|
35
|
-
metadata.date = DateTime.fromISO(depotDate).setLocale("fr").toFormat("d MMMM yyyy");
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
const presentationDate = xmlDoc.querySelector('FRBRdate[name="#presentation"]')?.getAttribute("date");
|
|
39
|
-
if (presentationDate) {
|
|
40
|
-
metadata.date = DateTime.fromISO(presentationDate).setLocale("fr").toFormat("d MMMM yyyy");
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
// Extract Type
|
|
44
|
-
const bill = xmlDoc.querySelector("bill");
|
|
45
|
-
const typeCode = bill?.getAttribute("name");
|
|
46
|
-
if (typeCode === "ppl") {
|
|
47
|
-
metadata.type = "PROPOSITION DE LOI";
|
|
48
|
-
}
|
|
49
|
-
else if (typeCode === "pjl") {
|
|
50
|
-
metadata.type = "PROJET DE LOI";
|
|
51
|
-
}
|
|
52
|
-
// Extract Authors
|
|
53
|
-
const authorRef = xmlDoc.querySelector('FRBRWork > FRBRauthor[as="#auteur"]')?.getAttribute("href");
|
|
54
|
-
if (authorRef) {
|
|
55
|
-
const authorId = authorRef.replace(/^#/, "");
|
|
56
|
-
const authorPerson = xmlDoc.querySelector(`TLCPerson[eId="${authorId}"]`);
|
|
57
|
-
if (authorPerson) {
|
|
58
|
-
const showAs = authorPerson.getAttribute("showAs");
|
|
59
|
-
if (showAs) {
|
|
60
|
-
metadata.authors = showAs.replace(/, Sénateurs$/, ", Sénateurs et Sénatrices");
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
// Extract Commission
|
|
65
|
-
const commissionNode = xmlDoc.querySelector('TLCOrganization[eId="commission-senat"]') ||
|
|
66
|
-
xmlDoc.querySelector('TLCOrganization[eId^="commission-"]:not([eId*="assemblee"])');
|
|
67
|
-
if (commissionNode) {
|
|
68
|
-
metadata.commission = commissionNode.getAttribute("showAs");
|
|
69
|
-
}
|
|
70
|
-
return metadata;
|
|
71
|
-
}
|
|
72
|
-
export async function convertSenatXmlToHtml(texteXml, outputFilePath) {
|
|
73
|
-
let xmlDoc;
|
|
74
|
-
try {
|
|
75
|
-
xmlDoc = new JSDOM(texteXml, { contentType: "text/xml" }).window.document;
|
|
76
|
-
}
|
|
77
|
-
catch (err) {
|
|
78
|
-
if (await fs.pathExists(outputFilePath)) {
|
|
79
|
-
await fs.remove(outputFilePath);
|
|
80
|
-
}
|
|
81
|
-
throw err;
|
|
82
|
-
}
|
|
83
|
-
const metadata = extractMetadata(xmlDoc);
|
|
84
|
-
const xmlBody = xmlDoc.querySelector("body");
|
|
85
|
-
const style = `
|
|
86
|
-
body {
|
|
87
|
-
font-family: "URW Bookman", "Bookman Old Style", serif;
|
|
88
|
-
max-width: 800px;
|
|
89
|
-
margin: 40px auto;
|
|
90
|
-
line-height: 1.5;
|
|
91
|
-
color: #333;
|
|
92
|
-
}
|
|
93
|
-
.header {
|
|
94
|
-
text-align: center;
|
|
95
|
-
margin-bottom: 40px;
|
|
96
|
-
border-bottom: 2px solid #333;
|
|
97
|
-
padding-bottom: 20px;
|
|
98
|
-
}
|
|
99
|
-
.header-top {
|
|
100
|
-
font-weight: bold;
|
|
101
|
-
font-size: 1.2em;
|
|
102
|
-
margin-bottom: 10px;
|
|
103
|
-
}
|
|
104
|
-
.header-session {
|
|
105
|
-
text-transform: uppercase;
|
|
106
|
-
font-size: 0.9em;
|
|
107
|
-
margin-bottom: 5px;
|
|
108
|
-
}
|
|
109
|
-
.header-date {
|
|
110
|
-
font-size: 0.9em;
|
|
111
|
-
margin-bottom: 5px;
|
|
112
|
-
}
|
|
113
|
-
.header-number {
|
|
114
|
-
font-weight: bold;
|
|
115
|
-
font-size: 1.1em;
|
|
116
|
-
margin-bottom: 20px;
|
|
117
|
-
}
|
|
118
|
-
.header-type {
|
|
119
|
-
font-weight: bold;
|
|
120
|
-
font-size: 1.5em;
|
|
121
|
-
margin-top: 20px;
|
|
122
|
-
}
|
|
123
|
-
.header-authors {
|
|
124
|
-
margin-top: 20px;
|
|
125
|
-
font-style: italic;
|
|
126
|
-
}
|
|
127
|
-
.header-commission {
|
|
128
|
-
margin-top: 15px;
|
|
129
|
-
font-size: 0.9em;
|
|
130
|
-
}
|
|
131
|
-
h1 {
|
|
132
|
-
text-align: center;
|
|
133
|
-
font-size: 1.8em;
|
|
134
|
-
margin-top: 10px;
|
|
135
|
-
}
|
|
136
|
-
p {
|
|
137
|
-
margin: 0.6em 0;
|
|
138
|
-
}
|
|
139
|
-
p.has-alinea {
|
|
140
|
-
position: relative;
|
|
141
|
-
padding-left: 2.5em;
|
|
142
|
-
}
|
|
143
|
-
.alinea {
|
|
144
|
-
position: absolute;
|
|
145
|
-
left: 0;
|
|
146
|
-
top: 0.15em;
|
|
147
|
-
display: inline-flex;
|
|
148
|
-
align-items: center;
|
|
149
|
-
justify-content: center;
|
|
150
|
-
min-width: 1.5em;
|
|
151
|
-
height: 1.5em;
|
|
152
|
-
padding: 0 0.3em;
|
|
153
|
-
margin-right: 0.3em;
|
|
154
|
-
font-size: 0.75em;
|
|
155
|
-
font-weight: bold;
|
|
156
|
-
color: #555;
|
|
157
|
-
background-color: #f0f0f0;
|
|
158
|
-
border: 1px solid #ccc;
|
|
159
|
-
border-radius: 1em;
|
|
160
|
-
}
|
|
161
|
-
.num {
|
|
162
|
-
font-weight: bold;
|
|
163
|
-
margin-right: 0.2em;
|
|
164
|
-
}
|
|
165
|
-
.article {
|
|
166
|
-
margin-top: 2em;
|
|
167
|
-
}
|
|
168
|
-
.article h3 {
|
|
169
|
-
border-bottom: 1px solid #eee;
|
|
170
|
-
padding-bottom: 5px;
|
|
171
|
-
}
|
|
172
|
-
`;
|
|
173
|
-
const htmlDocTemplate = `<!DOCTYPE html>
|
|
174
|
-
<html lang="fr">
|
|
175
|
-
<head>
|
|
176
|
-
<meta charset="utf-8">
|
|
177
|
-
<title>${metadata.title || "Document Sénat"}</title>
|
|
178
|
-
<style>${style}</style>
|
|
179
|
-
</head>
|
|
180
|
-
<body>
|
|
181
|
-
<div class="header">
|
|
182
|
-
<div class="header-top">SÉNAT</div>
|
|
183
|
-
<div class="header-session">SESSION ORDINAIRE DE ${metadata.session || "...."}</div>
|
|
184
|
-
${metadata.date ? `<div class="header-date">Enregistré à la Présidence du Sénat le ${metadata.date}</div>` : ""}
|
|
185
|
-
<div class="header-number">N° ${metadata.number || "...."}</div>
|
|
186
|
-
<div class="header-type">${metadata.type || ""}</div>
|
|
187
|
-
<div class="header-authors">${metadata.authors || ""}</div>
|
|
188
|
-
${metadata.commission
|
|
189
|
-
? [
|
|
190
|
-
`<div class="header-commission">Envoyée à la ${metadata.commission.toLowerCase()},`,
|
|
191
|
-
"sous réserve de la constitution éventuelle d'une commission spéciale dans les conditions prévues",
|
|
192
|
-
"par le Règlement.</div>",
|
|
193
|
-
].join(" ")
|
|
194
|
-
: ""}
|
|
195
|
-
</div>
|
|
196
|
-
<h1>${metadata.title || ""}</h1>
|
|
197
|
-
</body>
|
|
198
|
-
</html>`;
|
|
199
|
-
const { document: htmlDoc } = new JSDOM(htmlDocTemplate).window;
|
|
200
|
-
const body = htmlDoc.body;
|
|
201
|
-
if (xmlBody) {
|
|
202
|
-
const processNode = (xmlNode, htmlParent, alineaData = null) => {
|
|
203
|
-
const children = Array.from(xmlNode.childNodes);
|
|
204
|
-
const alineaChildren = [];
|
|
205
|
-
const otherChildren = [];
|
|
206
|
-
for (const child of children) {
|
|
207
|
-
if (child.nodeType === 1 && child.tagName.toLowerCase() === "alinea") {
|
|
208
|
-
alineaChildren.push(child);
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
otherChildren.push(child);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
for (const child of otherChildren) {
|
|
215
|
-
if (child.nodeType === 3) {
|
|
216
|
-
htmlParent.appendChild(htmlDoc.createTextNode(child.textContent || ""));
|
|
217
|
-
}
|
|
218
|
-
else if (child.nodeType === 1) {
|
|
219
|
-
const element = child;
|
|
220
|
-
const tagName = element.tagName.toLowerCase();
|
|
221
|
-
let htmlElement = null;
|
|
222
|
-
switch (tagName) {
|
|
223
|
-
case "article": {
|
|
224
|
-
htmlElement = htmlDoc.createElement("div");
|
|
225
|
-
htmlElement.className = "article";
|
|
226
|
-
const artId = element.getAttribute("eId");
|
|
227
|
-
if (artId)
|
|
228
|
-
htmlElement.id = artId;
|
|
229
|
-
const artGuid = element.getAttribute("GUID");
|
|
230
|
-
if (artGuid)
|
|
231
|
-
htmlElement.setAttribute("data-guid", artGuid);
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
case "num": {
|
|
235
|
-
const parentTagName = element.parentElement?.tagName.toLowerCase();
|
|
236
|
-
if (parentTagName === "alinea" && alineaData) {
|
|
237
|
-
alineaData.numText = element.textContent?.trim();
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
htmlElement = htmlDoc.createElement("span");
|
|
241
|
-
htmlElement.className = "num";
|
|
242
|
-
break;
|
|
243
|
-
}
|
|
244
|
-
case "heading":
|
|
245
|
-
htmlElement = htmlDoc.createElement("h4");
|
|
246
|
-
break;
|
|
247
|
-
case "p":
|
|
248
|
-
htmlElement = htmlDoc.createElement("p");
|
|
249
|
-
if (alineaData) {
|
|
250
|
-
htmlElement.classList.add("has-alinea");
|
|
251
|
-
if (alineaData.id)
|
|
252
|
-
htmlElement.id = alineaData.id;
|
|
253
|
-
if (alineaData.guid)
|
|
254
|
-
htmlElement.setAttribute("data-guid", alineaData.guid);
|
|
255
|
-
const pastille = alineaData.pastille;
|
|
256
|
-
if (pastille) {
|
|
257
|
-
htmlElement.setAttribute("data-pastille", pastille);
|
|
258
|
-
if (!alineaData.pastilleApplied) {
|
|
259
|
-
const span = htmlDoc.createElement("span");
|
|
260
|
-
span.className = "alinea";
|
|
261
|
-
span.setAttribute("data-alinea", pastille);
|
|
262
|
-
span.textContent = pastille;
|
|
263
|
-
htmlElement.appendChild(span);
|
|
264
|
-
alineaData.pastilleApplied = true;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
if (alineaData.numText) {
|
|
268
|
-
const xmlPText = element.textContent || "";
|
|
269
|
-
const normalize = (s) => s.replace(/[\\s\\u00A0]+/g, " ").trim();
|
|
270
|
-
const normalizedNum = normalize(alineaData.numText);
|
|
271
|
-
const normalizedP = normalize(xmlPText);
|
|
272
|
-
if (normalizedNum && !normalizedP.startsWith(normalizedNum)) {
|
|
273
|
-
const numSpan = htmlDoc.createElement("span");
|
|
274
|
-
numSpan.className = "num";
|
|
275
|
-
numSpan.textContent = alineaData.numText + " ";
|
|
276
|
-
htmlElement.appendChild(numSpan);
|
|
277
|
-
}
|
|
278
|
-
alineaData.numText = null;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
break;
|
|
282
|
-
case "content":
|
|
283
|
-
processNode(element, htmlParent, alineaData);
|
|
284
|
-
continue;
|
|
285
|
-
case "doctitle":
|
|
286
|
-
continue;
|
|
287
|
-
case "i":
|
|
288
|
-
case "b":
|
|
289
|
-
case "u":
|
|
290
|
-
case "sup":
|
|
291
|
-
case "sub":
|
|
292
|
-
htmlElement = htmlDoc.createElement(tagName);
|
|
293
|
-
break;
|
|
294
|
-
default:
|
|
295
|
-
htmlElement = htmlDoc.createElement("span");
|
|
296
|
-
htmlElement.setAttribute("data-xml-tag", tagName);
|
|
297
|
-
break;
|
|
298
|
-
}
|
|
299
|
-
if (htmlElement) {
|
|
300
|
-
htmlParent.appendChild(htmlElement);
|
|
301
|
-
processNode(element, htmlElement, alineaData);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
for (const element of alineaChildren) {
|
|
306
|
-
const nextAlineaData = {
|
|
307
|
-
id: element.getAttribute("eId"),
|
|
308
|
-
guid: element.getAttribute("GUID"),
|
|
309
|
-
pastille: element.getAttribute("data:pastille"),
|
|
310
|
-
pastilleApplied: false,
|
|
311
|
-
};
|
|
312
|
-
processNode(element, htmlParent, nextAlineaData);
|
|
313
|
-
}
|
|
314
|
-
};
|
|
315
|
-
processNode(xmlBody, body);
|
|
316
|
-
}
|
|
317
|
-
const htmlContent = "<!DOCTYPE html>\n" + htmlDoc.documentElement.outerHTML;
|
|
318
|
-
await fs.ensureDir(path.dirname(outputFilePath));
|
|
319
|
-
await fs.outputFile(outputFilePath, htmlContent);
|
|
320
|
-
}
|