@tricoteuses/senat 2.22.16 → 2.23.0
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/README.md +168 -0
- package/lib/aggregates.d.ts +52 -0
- package/lib/aggregates.js +930 -0
- package/lib/aggregates.mjs +713 -0
- package/lib/aggregates.ts +833 -0
- package/lib/config.d.ts +10 -0
- package/lib/config.js +16 -0
- package/lib/config.mjs +16 -0
- package/lib/config.ts +26 -0
- package/lib/databases.d.ts +2 -0
- package/lib/databases.js +26 -0
- package/lib/databases.mjs +57 -0
- package/lib/databases.ts +71 -0
- package/lib/datasets.d.ts +34 -0
- package/lib/datasets.js +233 -0
- package/lib/datasets.mjs +78 -0
- package/lib/datasets.ts +118 -0
- package/lib/fields.d.ts +10 -0
- package/lib/fields.js +68 -0
- package/lib/fields.mjs +22 -0
- package/lib/fields.ts +29 -0
- package/lib/git.d.ts +26 -0
- package/lib/git.js +167 -0
- package/lib/index.d.ts +13 -0
- package/lib/index.js +1 -0
- package/lib/index.mjs +7 -0
- package/lib/index.ts +64 -0
- package/lib/inserters.d.ts +98 -0
- package/lib/inserters.js +500 -0
- package/lib/inserters.mjs +360 -0
- package/lib/inserters.ts +521 -0
- package/lib/legislatures.json +38 -0
- package/lib/loaders.d.ts +58 -0
- package/lib/loaders.js +286 -0
- package/lib/loaders.mjs +158 -0
- package/lib/loaders.ts +271 -0
- package/lib/model/agenda.d.ts +6 -0
- package/lib/model/agenda.js +148 -0
- package/lib/model/ameli.d.ts +51 -0
- package/lib/model/ameli.js +149 -0
- package/lib/model/ameli.mjs +84 -0
- package/lib/model/ameli.ts +100 -0
- package/lib/model/commission.d.ts +18 -0
- package/lib/model/commission.js +269 -0
- package/lib/model/debats.d.ts +67 -0
- package/lib/model/debats.js +95 -0
- package/lib/model/debats.mjs +43 -0
- package/lib/model/debats.ts +68 -0
- package/lib/model/documents.d.ts +12 -0
- package/lib/model/documents.js +151 -0
- package/lib/model/dosleg.d.ts +7 -0
- package/lib/model/dosleg.js +326 -0
- package/lib/model/dosleg.mjs +196 -0
- package/lib/model/dosleg.ts +240 -0
- package/lib/model/index.d.ts +7 -0
- package/lib/model/index.js +7 -0
- package/lib/model/index.mjs +5 -0
- package/lib/model/index.ts +15 -0
- package/lib/model/questions.d.ts +45 -0
- package/lib/model/questions.js +89 -0
- package/lib/model/questions.mjs +71 -0
- package/lib/model/questions.ts +93 -0
- package/lib/model/scrutins.d.ts +13 -0
- package/lib/model/scrutins.js +114 -0
- package/lib/model/seance.d.ts +3 -0
- package/lib/model/seance.js +267 -0
- package/lib/model/sens.d.ts +146 -0
- package/lib/model/sens.js +454 -0
- package/lib/model/sens.mjs +415 -0
- package/lib/model/sens.ts +516 -0
- package/lib/model/texte.d.ts +7 -0
- package/lib/model/texte.js +256 -0
- package/lib/model/texte.mjs +208 -0
- package/lib/model/texte.ts +229 -0
- package/lib/model/util.d.ts +9 -0
- package/lib/model/util.js +38 -0
- package/lib/model/util.mjs +19 -0
- package/lib/model/util.ts +32 -0
- package/lib/parsers/texte.d.ts +7 -0
- package/lib/parsers/texte.js +228 -0
- package/lib/raw_types/ameli.d.ts +914 -0
- package/lib/raw_types/ameli.js +5 -0
- package/lib/raw_types/ameli.mjs +163 -0
- package/lib/raw_types/debats.d.ts +207 -0
- package/lib/raw_types/debats.js +5 -0
- package/lib/raw_types/debats.mjs +58 -0
- package/lib/raw_types/dosleg.d.ts +1619 -0
- package/lib/raw_types/dosleg.js +5 -0
- package/lib/raw_types/dosleg.mjs +438 -0
- package/lib/raw_types/questions.d.ts +419 -0
- package/lib/raw_types/questions.js +5 -0
- package/lib/raw_types/questions.mjs +11 -0
- package/lib/raw_types/senat.d.ts +11368 -0
- package/lib/raw_types/senat.js +5 -0
- package/lib/raw_types/sens.d.ts +8248 -0
- package/lib/raw_types/sens.js +5 -0
- package/lib/raw_types/sens.mjs +508 -0
- package/lib/raw_types_kysely/ameli.d.ts +915 -0
- package/lib/raw_types_kysely/ameli.js +7 -0
- package/lib/raw_types_kysely/ameli.mjs +5 -0
- package/lib/raw_types_kysely/ameli.ts +951 -0
- package/lib/raw_types_kysely/debats.d.ts +207 -0
- package/lib/raw_types_kysely/debats.js +7 -0
- package/lib/raw_types_kysely/debats.mjs +5 -0
- package/lib/raw_types_kysely/debats.ts +222 -0
- package/lib/raw_types_kysely/dosleg.d.ts +3532 -0
- package/lib/raw_types_kysely/dosleg.js +7 -0
- package/lib/raw_types_kysely/dosleg.mjs +5 -0
- package/lib/raw_types_kysely/dosleg.ts +3621 -0
- package/lib/raw_types_kysely/questions.d.ts +414 -0
- package/lib/raw_types_kysely/questions.js +7 -0
- package/lib/raw_types_kysely/questions.mjs +5 -0
- package/lib/raw_types_kysely/questions.ts +426 -0
- package/lib/raw_types_kysely/sens.d.ts +4394 -0
- package/lib/raw_types_kysely/sens.js +7 -0
- package/lib/raw_types_kysely/sens.mjs +5 -0
- package/lib/raw_types_kysely/sens.ts +4499 -0
- package/lib/raw_types_schemats/ameli.d.ts +539 -0
- package/lib/raw_types_schemats/ameli.js +2 -0
- package/lib/raw_types_schemats/ameli.mjs +2 -0
- package/lib/raw_types_schemats/ameli.ts +601 -0
- package/lib/raw_types_schemats/debats.d.ts +127 -0
- package/lib/raw_types_schemats/debats.js +2 -0
- package/lib/raw_types_schemats/debats.mjs +2 -0
- package/lib/raw_types_schemats/debats.ts +145 -0
- package/lib/raw_types_schemats/dosleg.d.ts +977 -0
- package/lib/raw_types_schemats/dosleg.js +2 -0
- package/lib/raw_types_schemats/dosleg.mjs +2 -0
- package/lib/raw_types_schemats/dosleg.ts +2193 -0
- package/lib/raw_types_schemats/questions.d.ts +235 -0
- package/lib/raw_types_schemats/questions.js +2 -0
- package/lib/raw_types_schemats/questions.mjs +2 -0
- package/lib/raw_types_schemats/questions.ts +249 -0
- package/lib/raw_types_schemats/sens.d.ts +6915 -0
- package/lib/raw_types_schemats/sens.js +2 -0
- package/lib/raw_types_schemats/sens.mjs +2 -0
- package/lib/raw_types_schemats/sens.ts +2907 -0
- package/lib/scripts/convert_data.d.ts +1 -0
- package/lib/scripts/convert_data.js +354 -0
- package/lib/scripts/convert_data.mjs +181 -0
- package/lib/scripts/convert_data.ts +243 -0
- package/lib/scripts/data-download.d.ts +1 -0
- package/lib/scripts/data-download.js +12 -0
- package/lib/scripts/datautil.d.ts +8 -0
- package/lib/scripts/datautil.js +34 -0
- package/lib/scripts/datautil.mjs +16 -0
- package/lib/scripts/datautil.ts +19 -0
- package/lib/scripts/images/transparent_150x192.jpg +0 -0
- package/lib/scripts/images/transparent_155x225.jpg +0 -0
- package/lib/scripts/parse_textes.d.ts +1 -0
- package/lib/scripts/parse_textes.js +44 -0
- package/lib/scripts/parse_textes.mjs +46 -0
- package/lib/scripts/parse_textes.ts +65 -0
- package/lib/scripts/retrieve_agenda.d.ts +1 -0
- package/lib/scripts/retrieve_agenda.js +132 -0
- package/lib/scripts/retrieve_cr_commission.d.ts +1 -0
- package/lib/scripts/retrieve_cr_commission.js +364 -0
- package/lib/scripts/retrieve_cr_seance.d.ts +6 -0
- package/lib/scripts/retrieve_cr_seance.js +347 -0
- package/lib/scripts/retrieve_documents.d.ts +3 -0
- package/lib/scripts/retrieve_documents.js +219 -0
- package/lib/scripts/retrieve_documents.mjs +249 -0
- package/lib/scripts/retrieve_documents.ts +298 -0
- package/lib/scripts/retrieve_open_data.d.ts +1 -0
- package/lib/scripts/retrieve_open_data.js +315 -0
- package/lib/scripts/retrieve_open_data.mjs +217 -0
- package/lib/scripts/retrieve_open_data.ts +268 -0
- package/lib/scripts/retrieve_senateurs_photos.d.ts +1 -0
- package/lib/scripts/retrieve_senateurs_photos.js +147 -0
- package/lib/scripts/retrieve_senateurs_photos.mjs +147 -0
- package/lib/scripts/retrieve_senateurs_photos.ts +177 -0
- package/lib/scripts/retrieve_videos.d.ts +1 -0
- package/lib/scripts/retrieve_videos.js +461 -0
- package/lib/scripts/shared/cli_helpers.d.ts +95 -0
- package/lib/scripts/shared/cli_helpers.js +91 -0
- package/lib/scripts/shared/cli_helpers.ts +36 -0
- package/lib/scripts/shared/util.d.ts +4 -0
- package/lib/scripts/shared/util.js +35 -0
- package/lib/scripts/shared/util.ts +33 -0
- package/lib/scripts/test_iter_load.d.ts +1 -0
- package/lib/scripts/test_iter_load.js +12 -0
- package/lib/src/config.d.ts +22 -0
- package/lib/src/config.js +17 -7
- package/lib/src/conversion_textes.js +5 -1
- package/lib/src/databases.d.ts +2 -1
- package/lib/src/databases_postgres.d.ts +4 -0
- package/lib/src/databases_postgres.js +23 -0
- package/lib/src/datasets.d.ts +4 -0
- package/lib/src/datasets.js +16 -2
- package/lib/src/git.d.ts +1 -0
- package/lib/src/git.js +45 -11
- package/lib/src/loaders.js +10 -4
- package/lib/src/model/agenda.js +2 -2
- package/lib/src/model/ameli.d.ts +64 -52
- package/lib/src/model/ameli.js +147 -145
- package/lib/src/model/ameli_postgres.d.ts +67 -0
- package/lib/src/model/ameli_postgres.js +150 -0
- package/lib/src/model/commission.d.ts +3 -2
- package/lib/src/model/commission.js +2 -2
- package/lib/src/model/debats.d.ts +38 -66
- package/lib/src/model/debats.js +110 -93
- package/lib/src/model/documents.d.ts +32 -12
- package/lib/src/model/documents.js +171 -130
- package/lib/src/model/dosleg.d.ts +142 -5
- package/lib/src/model/dosleg.js +298 -156
- package/lib/src/model/questions.d.ts +54 -45
- package/lib/src/model/questions.js +89 -87
- package/lib/src/model/scrutins.d.ts +48 -13
- package/lib/src/model/scrutins.js +118 -111
- package/lib/src/model/seance.js +3 -3
- package/lib/src/model/sens.d.ts +109 -179
- package/lib/src/model/sens.js +384 -484
- package/lib/src/model/util.d.ts +0 -8
- package/lib/src/model/util.js +0 -23
- package/lib/src/parsers/texte.js +7 -7
- package/lib/src/raw_types_schemats/ameli.d.ts +4 -2
- package/lib/src/raw_types_schemats/debats.d.ts +2 -2
- package/lib/src/raw_types_schemats/dosleg.d.ts +2 -2
- package/lib/src/raw_types_schemats/questions.d.ts +2 -2
- package/lib/src/raw_types_schemats/sens.d.ts +10 -4216
- package/lib/src/scripts/convert_data.js +7 -6
- package/lib/src/scripts/convert_xml_to_html.js +2 -2
- package/lib/src/scripts/data-download.js +3 -2
- package/lib/src/scripts/retrieve_agenda.js +21 -9
- package/lib/src/scripts/retrieve_cr_commission.js +17 -17
- package/lib/src/scripts/retrieve_cr_seance.d.ts +14 -1
- package/lib/src/scripts/retrieve_cr_seance.js +10 -11
- package/lib/src/scripts/retrieve_documents.d.ts +11 -2
- package/lib/src/scripts/retrieve_documents.js +25 -14
- package/lib/src/scripts/retrieve_open_data.js +400 -145
- package/lib/src/scripts/retrieve_senateurs_photos.js +25 -11
- package/lib/src/scripts/retrieve_videos.js +12 -11
- package/lib/src/scripts/shared/cli_helpers.d.ts +1 -6
- package/lib/src/scripts/shared/cli_helpers.js +9 -8
- package/lib/src/scripts/shared/incremental_import_sql.d.ts +2 -0
- package/lib/src/scripts/shared/incremental_import_sql.js +894 -0
- package/lib/src/scripts/shared/prefixed_tables.d.ts +7 -0
- package/lib/src/scripts/shared/prefixed_tables.js +30 -0
- package/lib/src/scripts/shared/schema_version.d.ts +3 -0
- package/lib/src/scripts/shared/schema_version.js +97 -0
- package/lib/src/scripts/shared/staging_import.d.ts +3 -0
- package/lib/src/scripts/shared/staging_import.js +80 -0
- package/lib/src/scripts/shared/staging_metadata_sql.d.ts +1 -0
- package/lib/src/scripts/shared/staging_metadata_sql.js +221 -0
- package/lib/src/scripts/validate_prefixed_tables.d.ts +1 -0
- package/lib/src/scripts/validate_prefixed_tables.js +102 -0
- package/lib/src/types/texte.d.ts +1 -1
- package/lib/src/utils/cr_spliting.d.ts +9 -6
- package/lib/src/utils/cr_spliting.js +6 -101
- package/lib/src/utils/reunion_odj_building.d.ts +7 -3
- package/lib/src/utils/reunion_parsing.d.ts +2 -1
- package/lib/src/utils/reunion_parsing.js +2 -2
- package/lib/src/videos/match.js +8 -5
- package/lib/src/videos/pipeline.d.ts +6 -2
- package/lib/src/videos/pipeline.js +21 -8
- package/lib/src/videos/search.js +6 -2
- package/lib/strings.d.ts +1 -0
- package/lib/strings.js +18 -0
- package/lib/strings.mjs +18 -0
- package/lib/strings.ts +26 -0
- package/lib/tests/incrementalImportSql.test.d.ts +1 -0
- package/lib/tests/incrementalImportSql.test.js +155 -0
- package/lib/tests/prefixedTables.test.d.ts +1 -0
- package/lib/tests/prefixedTables.test.js +29 -0
- package/lib/tests/schemaVersion.test.d.ts +1 -0
- package/lib/tests/schemaVersion.test.js +23 -0
- package/lib/tests/validatePrefixedTables.test.d.ts +1 -0
- package/lib/tests/validatePrefixedTables.test.js +14 -0
- package/lib/types/agenda.d.ts +44 -0
- package/lib/types/agenda.js +1 -0
- package/lib/types/ameli.d.ts +5 -0
- package/lib/types/ameli.js +1 -0
- package/lib/types/ameli.mjs +13 -0
- package/lib/types/ameli.ts +21 -0
- package/lib/types/compte_rendu.d.ts +83 -0
- package/lib/types/compte_rendu.js +1 -0
- package/lib/types/debats.d.ts +2 -0
- package/lib/types/debats.js +1 -0
- package/lib/types/debats.mjs +2 -0
- package/lib/types/debats.ts +6 -0
- package/lib/types/dosleg.d.ts +70 -0
- package/lib/types/dosleg.js +1 -0
- package/lib/types/dosleg.mjs +151 -0
- package/lib/types/dosleg.ts +284 -0
- package/lib/types/questions.d.ts +2 -0
- package/lib/types/questions.js +1 -0
- package/lib/types/questions.mjs +1 -0
- package/lib/types/questions.ts +3 -0
- package/lib/types/sens.d.ts +10 -0
- package/lib/types/sens.js +1 -0
- package/lib/types/sens.mjs +1 -0
- package/lib/types/sens.ts +12 -0
- package/lib/types/sessions.d.ts +5 -0
- package/lib/types/sessions.js +84 -0
- package/lib/types/sessions.mjs +43 -0
- package/lib/types/sessions.ts +42 -0
- package/lib/types/texte.d.ts +74 -0
- package/lib/types/texte.js +16 -0
- package/lib/types/texte.mjs +16 -0
- package/lib/types/texte.ts +76 -0
- package/lib/typings/windows-1252.d.js +2 -0
- package/lib/typings/windows-1252.d.mjs +2 -0
- package/lib/typings/windows-1252.d.ts +11 -0
- package/lib/utils/cr_spliting.d.ts +28 -0
- package/lib/utils/cr_spliting.js +265 -0
- package/lib/utils/date.d.ts +10 -0
- package/lib/utils/date.js +100 -0
- package/lib/utils/nvs-timecode.d.ts +7 -0
- package/lib/utils/nvs-timecode.js +79 -0
- package/lib/utils/reunion_grouping.d.ts +9 -0
- package/lib/utils/reunion_grouping.js +361 -0
- package/lib/utils/reunion_odj_building.d.ts +5 -0
- package/lib/utils/reunion_odj_building.js +154 -0
- package/lib/utils/reunion_parsing.d.ts +23 -0
- package/lib/utils/reunion_parsing.js +209 -0
- package/lib/utils/scoring.d.ts +14 -0
- package/lib/utils/scoring.js +147 -0
- package/lib/utils/string_cleaning.d.ts +7 -0
- package/lib/utils/string_cleaning.js +57 -0
- package/lib/validators/config.d.ts +9 -0
- package/lib/validators/config.js +10 -0
- package/lib/validators/config.mjs +54 -0
- package/lib/validators/config.ts +79 -0
- package/lib/validators/senat.d.ts +0 -0
- package/lib/validators/senat.js +28 -0
- package/lib/validators/senat.mjs +24 -0
- package/lib/validators/senat.ts +26 -0
- package/package.json +6 -10
|
@@ -5,106 +5,6 @@ import fs from "fs-extra";
|
|
|
5
5
|
import { sessionStartYearFromDate } from "../model/seance";
|
|
6
6
|
import { frDateToISO, hourShortToStartTime } from "./date";
|
|
7
7
|
import { normalizeSpaces } from "./string_cleaning";
|
|
8
|
-
// Convert "quinze heures trente", "15 heures 30", "dix-sept heures moins le quart", etc. en "HHMM"
|
|
9
|
-
function parseFrenchClockToHHMM(input) {
|
|
10
|
-
const s = (input || "")
|
|
11
|
-
.toLowerCase()
|
|
12
|
-
.normalize("NFKD")
|
|
13
|
-
.replace(/[\u0300-\u036f]/g, "")
|
|
14
|
-
.trim();
|
|
15
|
-
if (!s)
|
|
16
|
-
return undefined;
|
|
17
|
-
const digitMatch = s.match(/(\d{1,2})\s*heures?(?:\s*(\d{1,2}))?/);
|
|
18
|
-
if (digitMatch) {
|
|
19
|
-
const h = Math.min(24, Math.max(0, parseInt(digitMatch[1], 10)));
|
|
20
|
-
const m = digitMatch[2] ? Math.min(59, Math.max(0, parseInt(digitMatch[2], 10))) : 0;
|
|
21
|
-
return `${String(h).padStart(2, "0")}${String(m).padStart(2, "0")}`;
|
|
22
|
-
}
|
|
23
|
-
const NUM = new Map([
|
|
24
|
-
["zero", 0],
|
|
25
|
-
["une", 1],
|
|
26
|
-
["un", 1],
|
|
27
|
-
["deux", 2],
|
|
28
|
-
["trois", 3],
|
|
29
|
-
["quatre", 4],
|
|
30
|
-
["cinq", 5],
|
|
31
|
-
["six", 6],
|
|
32
|
-
["sept", 7],
|
|
33
|
-
["huit", 8],
|
|
34
|
-
["neuf", 9],
|
|
35
|
-
["dix", 10],
|
|
36
|
-
["onze", 11],
|
|
37
|
-
["douze", 12],
|
|
38
|
-
["treize", 13],
|
|
39
|
-
["quatorze", 14],
|
|
40
|
-
["quinze", 15],
|
|
41
|
-
["seize", 16],
|
|
42
|
-
["dix-sept", 17],
|
|
43
|
-
["dix sept", 17],
|
|
44
|
-
["dix-huit", 18],
|
|
45
|
-
["dix huit", 18],
|
|
46
|
-
["dix-neuf", 19],
|
|
47
|
-
["dix neuf", 19],
|
|
48
|
-
["vingt", 20],
|
|
49
|
-
["vingt et une", 21],
|
|
50
|
-
["vingt-et-une", 21],
|
|
51
|
-
["vingt et un", 21],
|
|
52
|
-
["vingt-et-un", 21],
|
|
53
|
-
["vingt-deux", 22],
|
|
54
|
-
["vingt deux", 22],
|
|
55
|
-
["vingt-trois", 23],
|
|
56
|
-
["vingt trois", 23],
|
|
57
|
-
["vingt-quatre", 24],
|
|
58
|
-
["vingt quatre", 24],
|
|
59
|
-
]);
|
|
60
|
-
const hourWordMatch = s.match(/([a-z\- ]+?)\s*heures?/);
|
|
61
|
-
if (!hourWordMatch)
|
|
62
|
-
return undefined;
|
|
63
|
-
const hourWord = hourWordMatch[1].trim();
|
|
64
|
-
let hour = NUM.get(hourWord);
|
|
65
|
-
if (hour == null) {
|
|
66
|
-
const cleaned = hourWord.replace(/\s+/g, " ");
|
|
67
|
-
hour = NUM.get(cleaned);
|
|
68
|
-
}
|
|
69
|
-
if (hour == null)
|
|
70
|
-
return undefined;
|
|
71
|
-
let minutes = 0;
|
|
72
|
-
if (/\bet (demie|demi)\b/.test(s))
|
|
73
|
-
minutes = 30;
|
|
74
|
-
else if (/\bet quart\b/.test(s))
|
|
75
|
-
minutes = 15;
|
|
76
|
-
else if (/\bmoins le quart\b/.test(s)) {
|
|
77
|
-
hour = (hour + 23) % 24;
|
|
78
|
-
minutes = 45;
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
const MIN = new Map([
|
|
82
|
-
["cinq", 5],
|
|
83
|
-
["dix", 10],
|
|
84
|
-
["quinze", 15],
|
|
85
|
-
["vingt", 20],
|
|
86
|
-
["vingt-cinq", 25],
|
|
87
|
-
["vingt cinq", 25],
|
|
88
|
-
["trente", 30],
|
|
89
|
-
["trente-cinq", 35],
|
|
90
|
-
["trente cinq", 35],
|
|
91
|
-
["quarante", 40],
|
|
92
|
-
["quarante-cinq", 45],
|
|
93
|
-
["quarante cinq", 45],
|
|
94
|
-
["cinquante", 50],
|
|
95
|
-
["cinquante-cinq", 55],
|
|
96
|
-
["cinquante cinq", 55],
|
|
97
|
-
]);
|
|
98
|
-
const minWordMatch = s.match(/heures?\s+([a-z\- ]+?)(?:[).,;]|$)/);
|
|
99
|
-
if (minWordMatch) {
|
|
100
|
-
const mw = minWordMatch[1].trim();
|
|
101
|
-
const m1 = MIN.get(mw);
|
|
102
|
-
if (m1 != null)
|
|
103
|
-
minutes = m1;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return `${String(hour).padStart(2, "0")}${String(minutes).padStart(2, "0")}`;
|
|
107
|
-
}
|
|
108
8
|
function extractWeekStartFromHead($) {
|
|
109
9
|
const og = $('meta[property="og:title"]').attr("content") || $("title").text();
|
|
110
10
|
const m = (og ?? "").toLowerCase().match(/semaine du\s+(\d{1,2}\s+\w+\s+\d{4})/i);
|
|
@@ -181,7 +81,12 @@ export function parseCommissionMetadataFromHtml(html, sourceFileName) {
|
|
|
181
81
|
};
|
|
182
82
|
}
|
|
183
83
|
function isGroupedReunion(o) {
|
|
184
|
-
return
|
|
84
|
+
return (typeof o === "object" &&
|
|
85
|
+
o !== null &&
|
|
86
|
+
"uid" in o &&
|
|
87
|
+
typeof o.uid === "string" &&
|
|
88
|
+
"date" in o &&
|
|
89
|
+
typeof o.date === "string");
|
|
185
90
|
}
|
|
186
91
|
export async function loadAgendaForDate(dataDir, yyyymmdd, session) {
|
|
187
92
|
const baseDir = path.join(dataDir, AGENDA_FOLDER, DATA_TRANSFORMED_FOLDER, String(session));
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import commandLineArgs from "command-line-args";
|
|
2
|
-
import { DossierLegislatifResult } from "../model/dosleg";
|
|
2
|
+
import { ActeLegislatif, DossierLegislatifResult } from "../model/dosleg";
|
|
3
3
|
import { AgendaEvent, ReunionOdj } from "../types/agenda";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
type DossierWithActes = DossierLegislatifResult & {
|
|
5
|
+
actes_legislatifs?: ActeLegislatif[] | null;
|
|
6
|
+
};
|
|
7
|
+
export declare function buildOdj(events: AgendaEvent[], dossierBySenatUrl: Record<string, DossierWithActes>): ReunionOdj | undefined;
|
|
8
|
+
export declare function buildSenatDossierIndex(options: commandLineArgs.CommandLineOptions): Record<string, DossierWithActes>;
|
|
9
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DateTime } from "luxon";
|
|
2
|
+
import type { AnyNode } from "domhandler";
|
|
2
3
|
import { AgendaEvent, Reunion } from "../types/agenda";
|
|
3
4
|
import { DossierLegislatifResult } from "../model/dosleg";
|
|
4
5
|
import * as cheerio from "cheerio";
|
|
@@ -18,6 +19,6 @@ export type SommaireBlock = {
|
|
|
18
19
|
startIndex: number;
|
|
19
20
|
targetId?: string | null;
|
|
20
21
|
};
|
|
21
|
-
export declare function extractSommaireBlocks($: cheerio.CheerioAPI, idx: Map<
|
|
22
|
+
export declare function extractSommaireBlocks($: cheerio.CheerioAPI, idx: Map<AnyNode, number>): SommaireBlock[];
|
|
22
23
|
export declare function parseISO(iso: string | null | undefined): DateTime | null;
|
|
23
24
|
export {};
|
|
@@ -50,8 +50,8 @@ export function buildReunionsByBucket(events, dossierBySenatUrl) {
|
|
|
50
50
|
for (const e of events) {
|
|
51
51
|
const kind = classifyAgendaType(e?.type);
|
|
52
52
|
if (!kind) {
|
|
53
|
-
continue;
|
|
54
53
|
console.warn("Can't determine type of reunion");
|
|
54
|
+
continue;
|
|
55
55
|
}
|
|
56
56
|
const bucket = typeToSuffixStrict(kind);
|
|
57
57
|
const uid = makeReunionUid(e.date, kind, e.id, e.organe ?? null);
|
|
@@ -83,7 +83,7 @@ function classifyAgendaType(typeLabel) {
|
|
|
83
83
|
return "MC";
|
|
84
84
|
if (/\boffices\b|\bdelegations\b/.test(s))
|
|
85
85
|
return "OD";
|
|
86
|
-
if (
|
|
86
|
+
if (/instances\b|decisionelles\b/.test(s))
|
|
87
87
|
return "ID";
|
|
88
88
|
return null;
|
|
89
89
|
}
|
package/lib/src/videos/match.js
CHANGED
|
@@ -8,11 +8,14 @@ export async function matchOneReunion(args) {
|
|
|
8
8
|
if (!options["silent"])
|
|
9
9
|
console.log(`Matching video for reunion: ${agenda?.uid}`);
|
|
10
10
|
const isSP = (agenda.type ?? "").toLowerCase().includes("séance publique");
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
const
|
|
11
|
+
if (!weights) {
|
|
12
|
+
throw new Error("matchOneReunion: missing weights");
|
|
13
|
+
}
|
|
14
|
+
const minAcceptBase = weights.minAccept;
|
|
15
|
+
const margin = weights.margin;
|
|
16
|
+
const titleDominance = weights.titleDominance ?? 0;
|
|
17
|
+
const orgUncertainPenalty = weights.orgUncertainPenalty ?? 1; // 1 disables
|
|
18
|
+
const orgSkipDice = weights.orgSkipDice ?? 0;
|
|
16
19
|
const minAccept = minAcceptBase + (candidates.length >= 20 ? 0.08 : candidates.length >= 10 ? 0.05 : 0);
|
|
17
20
|
const vw = weights
|
|
18
21
|
? {
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { Reunion } from "../types/agenda";
|
|
2
2
|
import { BestMatch, LastForVideo, MatchContext } from "./types";
|
|
3
3
|
import { CommandLineOptions } from "command-line-args";
|
|
4
|
+
type VideoPipelineOptions = CommandLineOptions & {
|
|
5
|
+
silent?: boolean;
|
|
6
|
+
};
|
|
4
7
|
export declare function processOneReunionMatch(args: {
|
|
5
8
|
agenda: Reunion;
|
|
6
9
|
best: BestMatch | null;
|
|
7
10
|
baseDir: string;
|
|
8
11
|
dataDir: string;
|
|
9
12
|
session: number;
|
|
10
|
-
options:
|
|
13
|
+
options: VideoPipelineOptions;
|
|
11
14
|
writeIfChanged: (p: string, content: string) => Promise<void>;
|
|
12
15
|
lastByVideo: Map<string, {
|
|
13
16
|
agendaUid: string;
|
|
@@ -34,7 +37,7 @@ export declare function processBisIfNeeded(args: {
|
|
|
34
37
|
baseDir: string;
|
|
35
38
|
dataDir: string;
|
|
36
39
|
session: number;
|
|
37
|
-
options:
|
|
40
|
+
options: VideoPipelineOptions;
|
|
38
41
|
writeIfChanged: (p: string, content: string) => Promise<void>;
|
|
39
42
|
lastByVideo: Map<string, LastForVideo>;
|
|
40
43
|
getAgendaSegmentTimecodes: (dataNvs: string, finalNvs: string, agendaKey: string) => {
|
|
@@ -50,3 +53,4 @@ export declare function processBisIfNeeded(args: {
|
|
|
50
53
|
buildSenatVodMasterM3u8FromNvs: (dataNvs: string) => string | null;
|
|
51
54
|
}): Promise<void>;
|
|
52
55
|
export declare function writeIfChanged(p: string, content: string): Promise<void>;
|
|
56
|
+
export {};
|
|
@@ -5,6 +5,9 @@ import { fetchText } from "./search";
|
|
|
5
5
|
import fs from "fs-extra";
|
|
6
6
|
import fsp from "fs/promises";
|
|
7
7
|
import path from "path";
|
|
8
|
+
function isReunion(value) {
|
|
9
|
+
return typeof value === "object" && value !== null && "uid" in value;
|
|
10
|
+
}
|
|
8
11
|
export async function processOneReunionMatch(args) {
|
|
9
12
|
const { agenda, best, baseDir, dataDir, session, options, writeIfChanged, lastByVideo, getAgendaSegmentTimecodes, buildSenatVodMasterM3u8FromNvs, } = args;
|
|
10
13
|
const reunionUid = agenda.uid;
|
|
@@ -14,7 +17,7 @@ export async function processOneReunionMatch(args) {
|
|
|
14
17
|
dataTxt = await fsp.readFile(path.join(baseDir, "data.nvs"), "utf-8");
|
|
15
18
|
finalTxt = await fsp.readFile(path.join(baseDir, "finalplayer.nvs"), "utf-8");
|
|
16
19
|
}
|
|
17
|
-
catch
|
|
20
|
+
catch {
|
|
18
21
|
console.warn(`[skip] Missing NVS files for reunion ${reunionUid}`);
|
|
19
22
|
return;
|
|
20
23
|
}
|
|
@@ -43,9 +46,10 @@ export async function processOneReunionMatch(args) {
|
|
|
43
46
|
if (prev && prev.agendaJsonPath !== agendaJsonPath) {
|
|
44
47
|
// micro-safety: do not close with an earlier timecode
|
|
45
48
|
if (timecodeDebutVideo <= prev.start) {
|
|
46
|
-
console.warn(
|
|
47
|
-
`prev=${prev.agendaUid}(${prev.start}s) ->
|
|
48
|
-
`
|
|
49
|
+
console.warn("[warn] timecode order inversion on same video: " +
|
|
50
|
+
`prev=${prev.agendaUid}(${prev.start}s) -> ` +
|
|
51
|
+
`cur=${agenda.uid}(${timecodeDebutVideo}s). ` +
|
|
52
|
+
"Skip closing prev to avoid negative segment.");
|
|
49
53
|
}
|
|
50
54
|
else {
|
|
51
55
|
await patchAgendaTimecodeFin({
|
|
@@ -63,10 +67,14 @@ export async function processOneReunionMatch(args) {
|
|
|
63
67
|
try {
|
|
64
68
|
obj = JSON.parse(raw);
|
|
65
69
|
}
|
|
66
|
-
catch
|
|
70
|
+
catch {
|
|
67
71
|
console.warn(`[warn] invalid JSON in ${agendaJsonPath}`);
|
|
68
72
|
return;
|
|
69
73
|
}
|
|
74
|
+
if (!isReunion(obj)) {
|
|
75
|
+
console.warn(`[warn] invalid reunion payload in ${agendaJsonPath}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
70
78
|
const next = { ...obj, urlVideo: master, startTime: agenda.startTime, urlPageVideo: best?.pageUrl };
|
|
71
79
|
if (timecodeDebutVideo != null) {
|
|
72
80
|
next.timecodeDebutVideo = timecodeDebutVideo;
|
|
@@ -98,7 +106,7 @@ export async function processBisIfNeeded(args) {
|
|
|
98
106
|
};
|
|
99
107
|
const baseDirBis = path.join(path.dirname(ctx.baseDir), bisUid);
|
|
100
108
|
await fs.ensureDir(baseDirBis);
|
|
101
|
-
await ensureBisAgendaJson({ agenda, agendaBis, dataDir: ctx.dataDir, session: ctx.session
|
|
109
|
+
await ensureBisAgendaJson({ agenda, agendaBis, dataDir: ctx.dataDir, session: ctx.session });
|
|
102
110
|
await downloadNvsForMatch(secondBest, baseDirBis);
|
|
103
111
|
await processOneReunionMatch({
|
|
104
112
|
agenda: agendaBis,
|
|
@@ -114,7 +122,7 @@ export async function processBisIfNeeded(args) {
|
|
|
114
122
|
});
|
|
115
123
|
}
|
|
116
124
|
async function ensureBisAgendaJson(args) {
|
|
117
|
-
const { agenda, agendaBis, dataDir, session
|
|
125
|
+
const { agenda, agendaBis, dataDir, session } = args;
|
|
118
126
|
const agendaJsonPath = path.join(dataDir, AGENDA_FOLDER, DATA_TRANSFORMED_FOLDER, String(session), `${agenda.uid}.json`);
|
|
119
127
|
const agendaBisJsonPath = path.join(dataDir, AGENDA_FOLDER, DATA_TRANSFORMED_FOLDER, String(session), `${agendaBis.uid}.json`);
|
|
120
128
|
if (!(await fs.pathExists(agendaJsonPath))) {
|
|
@@ -125,7 +133,8 @@ async function ensureBisAgendaJson(args) {
|
|
|
125
133
|
const raw = await fsp.readFile(agendaJsonPath, "utf-8");
|
|
126
134
|
const obj = JSON.parse(raw);
|
|
127
135
|
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
|
|
128
|
-
console.warn(`[bis] cannot clone agenda json: expected object in ${agendaJsonPath},
|
|
136
|
+
console.warn(`[bis] cannot clone agenda json: expected object in ${agendaJsonPath}, ` +
|
|
137
|
+
`got ${Array.isArray(obj) ? "array" : typeof obj}`);
|
|
129
138
|
return;
|
|
130
139
|
}
|
|
131
140
|
await writeIfChanged(agendaBisJsonPath, JSON.stringify(agendaBis, null, 2));
|
|
@@ -167,6 +176,10 @@ async function patchAgendaTimecodeFin(args) {
|
|
|
167
176
|
console.warn(`[warn] invalid JSON in ${agendaJsonPath}`);
|
|
168
177
|
return;
|
|
169
178
|
}
|
|
179
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
|
|
180
|
+
console.warn(`[warn] invalid reunion payload in ${agendaJsonPath}`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
170
183
|
const next = { ...obj, timecodeFinVideo };
|
|
171
184
|
await writeIfChanged(agendaJsonPath, JSON.stringify(next, null, 2));
|
|
172
185
|
}
|
package/lib/src/videos/search.js
CHANGED
|
@@ -55,7 +55,9 @@ export async function fetchCandidatesForAgenda(agenda, options) {
|
|
|
55
55
|
const pages = await fetchAllSearchPages(searchParams);
|
|
56
56
|
if (!pages.length) {
|
|
57
57
|
if (!options["silent"]) {
|
|
58
|
-
console.log(`[miss] ${agenda.uid} no candidates
|
|
58
|
+
console.log(`[miss] ${agenda.uid} no candidates ` +
|
|
59
|
+
`(videotype=${searchParams.videotype}, organe=${searchParams.organe || "-"}, ` +
|
|
60
|
+
`date=${searchParams.begin || "-"})`);
|
|
59
61
|
}
|
|
60
62
|
return null;
|
|
61
63
|
}
|
|
@@ -63,7 +65,9 @@ export async function fetchCandidatesForAgenda(agenda, options) {
|
|
|
63
65
|
const candidates = extractCandidatesFromSearchHtml(combinedHtml).slice(0, MAX_CANDIDATES);
|
|
64
66
|
if (!candidates.length) {
|
|
65
67
|
if (!options["silent"]) {
|
|
66
|
-
console.log(`[miss] ${agenda.uid} no candidates after parse
|
|
68
|
+
console.log(`[miss] ${agenda.uid} no candidates after parse ` +
|
|
69
|
+
`(videotype=${searchParams.videotype}, organe=${searchParams.organe || "-"}, ` +
|
|
70
|
+
`date=${searchParams.begin || "-"})`);
|
|
67
71
|
}
|
|
68
72
|
return null;
|
|
69
73
|
}
|
package/lib/strings.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function slugify(string: string, replacement?: string): string;
|
package/lib/strings.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import originalSlugify from "slug";
|
|
2
|
+
const slugifyCharmap = {
|
|
3
|
+
...originalSlugify.defaults.charmap,
|
|
4
|
+
"'": " ",
|
|
5
|
+
"@": " ",
|
|
6
|
+
".": " ",
|
|
7
|
+
"-": "_",
|
|
8
|
+
};
|
|
9
|
+
export function slugify(string, replacement) {
|
|
10
|
+
const options = {
|
|
11
|
+
charmap: slugifyCharmap,
|
|
12
|
+
mode: "rfc3986",
|
|
13
|
+
};
|
|
14
|
+
if (replacement) {
|
|
15
|
+
options.replacement = replacement;
|
|
16
|
+
}
|
|
17
|
+
return originalSlugify(string, options);
|
|
18
|
+
}
|
package/lib/strings.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import originalSlugify from "slug";
|
|
2
|
+
const slugifyCharmap = {
|
|
3
|
+
...originalSlugify.defaults.charmap,
|
|
4
|
+
"'": " ",
|
|
5
|
+
"@": " ",
|
|
6
|
+
".": " ",
|
|
7
|
+
"-": "_",
|
|
8
|
+
};
|
|
9
|
+
export function slugify(string, replacement) {
|
|
10
|
+
const options = {
|
|
11
|
+
charmap: slugifyCharmap,
|
|
12
|
+
mode: "rfc3986",
|
|
13
|
+
};
|
|
14
|
+
if (replacement) {
|
|
15
|
+
options.replacement = replacement;
|
|
16
|
+
}
|
|
17
|
+
return originalSlugify(string, options);
|
|
18
|
+
}
|
package/lib/strings.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import originalSlugify from "slug"
|
|
2
|
+
|
|
3
|
+
const slugifyCharmap = {
|
|
4
|
+
...originalSlugify.defaults.charmap,
|
|
5
|
+
"'": " ",
|
|
6
|
+
"@": " ",
|
|
7
|
+
".": " ",
|
|
8
|
+
"-": "_",
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Options {
|
|
12
|
+
charmap: { [c: string]: string }
|
|
13
|
+
mode: string
|
|
14
|
+
replacement?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function slugify(string: string, replacement?: string): string {
|
|
18
|
+
const options: Options = {
|
|
19
|
+
charmap: slugifyCharmap,
|
|
20
|
+
mode: "rfc3986",
|
|
21
|
+
}
|
|
22
|
+
if (replacement) {
|
|
23
|
+
options.replacement = replacement
|
|
24
|
+
}
|
|
25
|
+
return originalSlugify(string, options)
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildIncrementalDatasetImportSql, buildNormalizeStagingSchemaSql, } from "../src/scripts/shared/incremental_import_sql";
|
|
3
|
+
import { stagingSchemaName } from "../src/scripts/shared/prefixed_tables";
|
|
4
|
+
import { buildExportStagingMetadataStatementsQuery } from "../src/scripts/shared/staging_metadata_sql";
|
|
5
|
+
import { isCopyFromStdinLine, rewriteLineForStagingImport } from "../src/scripts/shared/staging_import";
|
|
6
|
+
describe("incremental import SQL", () => {
|
|
7
|
+
it("normalizes staging object names before merge", () => {
|
|
8
|
+
const sql = buildNormalizeStagingSchemaSql("dosleg");
|
|
9
|
+
expect(sql).toContain("WHERE n.nspname = 'dosleg_staging'");
|
|
10
|
+
expect(sql).toContain("AND c.relname NOT LIKE 'dosleg_' || '%'");
|
|
11
|
+
expect(sql).toContain("ALTER INDEX %I.%I RENAME TO %I");
|
|
12
|
+
expect(sql).toContain("ALTER SEQUENCE %I.%I RENAME TO %I");
|
|
13
|
+
expect(sql).toContain("ALTER TABLE %I.%I RENAME TO %I");
|
|
14
|
+
});
|
|
15
|
+
it("builds merge SQL for incremental synchronization into senat", () => {
|
|
16
|
+
const sql = buildIncrementalDatasetImportSql("dosleg", "opendata", {
|
|
17
|
+
corscr: ["sesann", "scrnum", "corscrord"],
|
|
18
|
+
docsea: ["evtseacle", "docseaord"],
|
|
19
|
+
}, ["amescr"]);
|
|
20
|
+
expect(sql).toContain("WHERE n.nspname = 'dosleg_staging'");
|
|
21
|
+
expect(sql).toContain("AND c.relkind IN ('r', 'p', 'f')");
|
|
22
|
+
expect(sql).toContain("CREATE TABLE senat.%I (LIKE %I.%I INCLUDING IDENTITY INCLUDING GENERATED INCLUDING DEFAULTS)");
|
|
23
|
+
expect(sql).toContain("ALTER TABLE senat.%I DROP COLUMN IF EXISTS %I CASCADE");
|
|
24
|
+
expect(sql).toContain("ALTER TABLE senat.%I ADD COLUMN %I %s");
|
|
25
|
+
expect(sql).toContain("ALTER TABLE senat.%I ALTER COLUMN %I TYPE %s%s USING %I::%s");
|
|
26
|
+
expect(sql).toContain("SELECT obj_description(c.oid, 'pg_class')");
|
|
27
|
+
expect(sql).toContain("COMMENT ON TABLE senat.%I IS %L");
|
|
28
|
+
expect(sql).toContain("col_description(staging_class.oid, staging_attribute.attnum) AS staging_comment");
|
|
29
|
+
expect(sql).toContain("COMMENT ON COLUMN senat.%I.%I IS %L");
|
|
30
|
+
expect(sql).toContain("SELECT 'dosleg_corscr' AS tablename, ARRAY['sesann', 'scrnum', 'corscrord']::text[] AS key_columns");
|
|
31
|
+
expect(sql).toContain("SELECT 'dosleg_docsea' AS tablename, ARRAY['evtseacle', 'docseaord']::text[] AS key_columns");
|
|
32
|
+
expect(sql).toContain("SELECT 'dosleg_amescr' AS tablename");
|
|
33
|
+
expect(sql).toContain("t.%1$I IS NOT DISTINCT FROM s.%1$I");
|
|
34
|
+
expect(sql).toContain("jsonb_build_array(");
|
|
35
|
+
expect(sql).toContain("Sync strategy for %.%: %");
|
|
36
|
+
expect(sql).toContain("Rejected sync key for %.% from % because only %/% columns are present in both staging and target: %");
|
|
37
|
+
expect(sql).toContain("WHERE table_namespace.nspname IN ('dosleg_staging', 'senat')");
|
|
38
|
+
expect(sql).toContain("CASE WHEN table_namespace.nspname = 'dosleg_staging' THEN 0 ELSE 1 END AS schema_priority");
|
|
39
|
+
expect(sql).toContain("No primary key or all-NOT-NULL unique key found for %.%; falling back to full refresh");
|
|
40
|
+
expect(sql).toContain("incremental_row_multiset");
|
|
41
|
+
expect(sql).toContain("LEFT JOIN staging_counts reference_row USING (row_signature)");
|
|
42
|
+
expect(sql).toContain("LEFT JOIN target_counts reference_row USING (row_signature)");
|
|
43
|
+
expect(sql).toContain("DELETE FROM senat.%1$I t WHERE NOT EXISTS (SELECT 1 FROM %2$I.%1$I s WHERE %3$s)");
|
|
44
|
+
expect(sql).toContain("UPDATE senat.%1$I t SET %2$s FROM %3$I.%1$I s WHERE %4$s AND (%5$s)");
|
|
45
|
+
expect(sql).toContain("INSERT INTO senat.%1$I (%2$s) SELECT %3$s FROM %4$I.%1$I s WHERE NOT EXISTS (SELECT 1 FROM senat.%1$I t WHERE %5$s)");
|
|
46
|
+
expect(sql).toContain("ALTER TABLE senat.%I ADD CONSTRAINT %I %s");
|
|
47
|
+
expect(sql).toContain("DROP SEQUENCE IF EXISTS senat.%I CASCADE");
|
|
48
|
+
expect(sql).not.toContain("DROP SCHEMA IF EXISTS dosleg_staging CASCADE;");
|
|
49
|
+
});
|
|
50
|
+
it("prefers row multiset strategy over configured keys when both are provided", () => {
|
|
51
|
+
const sql = buildIncrementalDatasetImportSql("questions", "opendata", {
|
|
52
|
+
tam_reponses: ["idque"],
|
|
53
|
+
}, ["tam_reponses"]);
|
|
54
|
+
expect(sql).toContain("IF use_row_multiset_merge THEN");
|
|
55
|
+
expect(sql).toContain("ELSIF key_columns IS NULL THEN");
|
|
56
|
+
expect(sql).toContain("SELECT 'questions_tam_reponses' AS tablename");
|
|
57
|
+
expect(sql).toContain("incremental_row_multiset");
|
|
58
|
+
});
|
|
59
|
+
it("escapes single quotes in SQL literals produced for staging names", () => {
|
|
60
|
+
const normalizeSql = buildNormalizeStagingSchemaSql("quo'te");
|
|
61
|
+
const incrementalSql = buildIncrementalDatasetImportSql("quo'te", "opendata", {
|
|
62
|
+
table: ["col'umn"],
|
|
63
|
+
}, ["ta'ble"]);
|
|
64
|
+
expect(normalizeSql).toContain("WHERE n.nspname = 'quo''te_staging'");
|
|
65
|
+
expect(normalizeSql).toContain("AND c.relname NOT LIKE 'quo''te_' || '%'");
|
|
66
|
+
expect(incrementalSql).toContain("WHERE n.nspname = 'quo''te_staging'");
|
|
67
|
+
expect(incrementalSql).toContain("tablename LIKE 'quo''te_' || '%'");
|
|
68
|
+
expect(incrementalSql).toContain("SELECT 'quo''te_table' AS tablename, ARRAY['col''umn']::text[] AS key_columns");
|
|
69
|
+
expect(incrementalSql).toContain("SELECT 'quo''te_ta''ble' AS tablename");
|
|
70
|
+
});
|
|
71
|
+
it("builds an unlogged staging schema name usable by import rewriting", () => {
|
|
72
|
+
expect(stagingSchemaName("sens")).toBe("sens_staging");
|
|
73
|
+
});
|
|
74
|
+
it("exports metadata synchronization statements from the real staging database", () => {
|
|
75
|
+
const sql = buildExportStagingMetadataStatementsQuery("sens_staging", "senat");
|
|
76
|
+
expect(sql).toContain("CREATE SEQUENCE IF NOT EXISTS %I.%I");
|
|
77
|
+
expect(sql).toContain("ALTER TABLE %I.%I ALTER COLUMN %I SET DEFAULT %s");
|
|
78
|
+
expect(sql).toContain("COMMENT ON TABLE %I.%I IS %L");
|
|
79
|
+
expect(sql).toContain("COMMENT ON COLUMN %I.%I.%I IS %L");
|
|
80
|
+
expect(sql).toContain("ALTER TABLE %I.%I ADD CONSTRAINT %I %s");
|
|
81
|
+
expect(sql).toContain("WHEN 'p' THEN 1");
|
|
82
|
+
expect(sql).toContain("WHEN 'f' THEN 4");
|
|
83
|
+
expect(sql).toContain("pg_get_indexdef(index_data.indexrelid)");
|
|
84
|
+
expect(sql).toContain("SELECT setval(%L, %s, %s)");
|
|
85
|
+
expect(sql).toContain("encode(convert_to(statement, 'UTF8'), 'hex')");
|
|
86
|
+
expect(sql).toContain("ORDER BY group_no, order_no, object_name");
|
|
87
|
+
});
|
|
88
|
+
it("rewrites staging DDL toward the staging schema and unlogged objects", () => {
|
|
89
|
+
const dataset = {
|
|
90
|
+
database: "dosleg",
|
|
91
|
+
repairEncoding: false,
|
|
92
|
+
title: "demo",
|
|
93
|
+
url: "https://example.test/demo.zip",
|
|
94
|
+
};
|
|
95
|
+
expect(rewriteLineForStagingImport("SET search_path = dosleg, pg_catalog;", dataset, "dosleg_staging")).toBe("SET search_path = dosleg_staging, pg_catalog;");
|
|
96
|
+
expect(rewriteLineForStagingImport("CREATE TABLE texte (id integer);", dataset, "dosleg_staging")).toBe("CREATE UNLOGGED TABLE texte (id integer);");
|
|
97
|
+
expect(rewriteLineForStagingImport("CREATE SEQUENCE texte_id_seq;", dataset, "dosleg_staging")).toBe("CREATE UNLOGGED SEQUENCE texte_id_seq;");
|
|
98
|
+
expect(rewriteLineForStagingImport("SELECT * FROM dosleg.texte;", dataset, "dosleg_staging")).toBe("SELECT * FROM dosleg_staging.texte;");
|
|
99
|
+
expect(rewriteLineForStagingImport("SELECT 'dosleg.texte'::text;", dataset, "dosleg_staging")).toBe("SELECT 'dosleg.texte'::text;");
|
|
100
|
+
});
|
|
101
|
+
it("keeps two-part COMMENT ON COLUMN statements when the table name matches the dataset name", () => {
|
|
102
|
+
const dataset = {
|
|
103
|
+
database: "debats",
|
|
104
|
+
repairEncoding: false,
|
|
105
|
+
title: "demo",
|
|
106
|
+
url: "https://example.test/demo.zip",
|
|
107
|
+
};
|
|
108
|
+
expect(rewriteLineForStagingImport("COMMENT ON COLUMN debats.datsea IS 'Date de la séance (clé)';", dataset, "debats_staging")).toBe("COMMENT ON COLUMN debats.datsea IS 'Date de la séance (clé)';");
|
|
109
|
+
});
|
|
110
|
+
it("rewrites sens dump references that still use the senat schema", () => {
|
|
111
|
+
const dataset = {
|
|
112
|
+
database: "sens",
|
|
113
|
+
repairEncoding: false,
|
|
114
|
+
title: "demo",
|
|
115
|
+
url: "https://example.test/demo.zip",
|
|
116
|
+
};
|
|
117
|
+
expect(rewriteLineForStagingImport("SET search_path = senat, pg_catalog;", dataset, "sens_staging")).toBe("SET search_path = sens_staging, pg_catalog;");
|
|
118
|
+
expect(rewriteLineForStagingImport("CREATE SCHEMA senat;", dataset, "sens_staging")).toBe("CREATE SCHEMA IF NOT EXISTS sens_staging;");
|
|
119
|
+
expect(rewriteLineForStagingImport("ALTER TABLE senat.senateur ADD COLUMN test integer;", dataset, "sens_staging")).toBe("ALTER TABLE sens_staging.senateur ADD COLUMN test integer;");
|
|
120
|
+
});
|
|
121
|
+
it("skips privilege statements during staging import", () => {
|
|
122
|
+
const dataset = {
|
|
123
|
+
database: "ameli",
|
|
124
|
+
repairEncoding: false,
|
|
125
|
+
title: "demo",
|
|
126
|
+
url: "https://example.test/demo.zip",
|
|
127
|
+
};
|
|
128
|
+
expect(rewriteLineForStagingImport("GRANT ALL ON TABLE amd TO opendata;", dataset, "ameli_staging")).toBe("-- skipped GRANT/REVOKE during staging import");
|
|
129
|
+
expect(rewriteLineForStagingImport("REVOKE ALL ON TABLE amd FROM opendata;", dataset, "ameli_staging")).toBe("-- skipped GRANT/REVOKE during staging import");
|
|
130
|
+
});
|
|
131
|
+
it("skips redundant dump cleanup statements during staging import", () => {
|
|
132
|
+
const dataset = {
|
|
133
|
+
database: "ameli",
|
|
134
|
+
repairEncoding: false,
|
|
135
|
+
title: "demo",
|
|
136
|
+
url: "https://example.test/demo.zip",
|
|
137
|
+
};
|
|
138
|
+
expect(rewriteLineForStagingImport("ALTER TABLE ONLY ameli_staging.txt_ameli DROP CONSTRAINT IF EXISTS txt_ameli_pkey;", dataset, "ameli_staging")).toBe("-- skipped redundant DROP CONSTRAINT during staging import");
|
|
139
|
+
expect(rewriteLineForStagingImport("DROP INDEX IF EXISTS ameli_staging.pk_txt;", dataset, "ameli_staging")).toBe("-- skipped redundant DROP INDEX during staging import");
|
|
140
|
+
expect(rewriteLineForStagingImport("DROP TABLE IF EXISTS ameli_staging.txt_ameli;", dataset, "ameli_staging")).toBe("-- skipped redundant DROP TABLE during staging import");
|
|
141
|
+
expect(rewriteLineForStagingImport("DROP SCHEMA public;", dataset, "ameli_staging")).toBe("-- skipped redundant DROP SCHEMA public during staging import");
|
|
142
|
+
expect(rewriteLineForStagingImport("CREATE SCHEMA public;", dataset, "ameli_staging")).toBe("-- skipped redundant CREATE SCHEMA public during staging import");
|
|
143
|
+
expect(rewriteLineForStagingImport("COMMENT ON SCHEMA public IS 'standard public schema';", dataset, "ameli_staging")).toBe("-- skipped redundant COMMENT ON SCHEMA public during staging import");
|
|
144
|
+
});
|
|
145
|
+
it("does not rewrite COPY payload lines", () => {
|
|
146
|
+
const dataset = {
|
|
147
|
+
database: "ameli",
|
|
148
|
+
repairEncoding: false,
|
|
149
|
+
title: "demo",
|
|
150
|
+
url: "https://example.test/demo.zip",
|
|
151
|
+
};
|
|
152
|
+
expect(isCopyFromStdinLine("COPY amd (id, obj) FROM stdin;")).toBe(true);
|
|
153
|
+
expect(rewriteLineForStagingImport("1\t<body><p>consultation du public.</p></body>\t<body><p>Mayotte public.</p></body>", dataset, "ameli_staging", true)).toBe("1\t<body><p>consultation du public.</p></body>\t<body><p>Mayotte public.</p></body>");
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { extractPrefixedTableNamesFromGeneratedDefinition, normalizeGeneratedDefinition, prefixedName, senatSchemaName, stagingSchemaName, } from "../src/scripts/shared/prefixed_tables";
|
|
3
|
+
describe("prefixed table helpers", () => {
|
|
4
|
+
it("builds prefixed table and staging schema names", () => {
|
|
5
|
+
expect(prefixedName("dosleg", "texte")).toBe("dosleg_texte");
|
|
6
|
+
expect(stagingSchemaName("questions")).toBe("questions_staging");
|
|
7
|
+
expect(senatSchemaName).toBe("senat");
|
|
8
|
+
});
|
|
9
|
+
it("normalizes generated definitions by removing dataset prefixes", () => {
|
|
10
|
+
const definition = "/* tslint:disable */\n/**\n * AUTO-GENERATED FILE @ 2026-03-20 16:00:00\n" +
|
|
11
|
+
" * $ schemats generate -c postgres://demo -t ameli_amd -t ameli_ses -s senat\n" +
|
|
12
|
+
" */\nexport namespace ameli_amdFields {}\nexport interface ameli_amd {}\nexport interface ameli_ses {}\n";
|
|
13
|
+
const normalized = normalizeGeneratedDefinition(definition, "ameli");
|
|
14
|
+
expect(normalized).toContain("AUTO-GENERATED FILE");
|
|
15
|
+
expect(normalized).not.toContain("AUTO-GENERATED FILE @");
|
|
16
|
+
expect(normalized).toContain("export namespace amdFields");
|
|
17
|
+
expect(normalized).toContain("export interface amd");
|
|
18
|
+
expect(normalized).toContain("export interface ses");
|
|
19
|
+
expect(normalized).not.toContain("ameli_amd");
|
|
20
|
+
});
|
|
21
|
+
it("extracts expected prefixed public tables from generated definitions", () => {
|
|
22
|
+
const definition = "/**\n * $ schemats generate -c postgres://demo -t amd -t ses -t txt_ameli -s ameli\n */";
|
|
23
|
+
expect(extractPrefixedTableNamesFromGeneratedDefinition(definition, "ameli")).toEqual([
|
|
24
|
+
"ameli_amd",
|
|
25
|
+
"ameli_ses",
|
|
26
|
+
"ameli_txt_ameli",
|
|
27
|
+
]);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildEnsureSchemaVersionTableSql, buildIncrementSchemaVersionSql, buildSchemaStructureFingerprintQuery, } from "../src/scripts/shared/schema_version";
|
|
3
|
+
describe("schema version SQL", () => {
|
|
4
|
+
it("creates the senat.version table and seeds version zero", () => {
|
|
5
|
+
const sql = buildEnsureSchemaVersionTableSql("senat");
|
|
6
|
+
expect(sql).toContain("CREATE TABLE IF NOT EXISTS senat.version(");
|
|
7
|
+
expect(sql).toContain("number integer PRIMARY KEY");
|
|
8
|
+
expect(sql).toContain("COMMENT ON TABLE senat.version IS 'version of database'");
|
|
9
|
+
expect(sql).toContain("INSERT INTO senat.version(number)");
|
|
10
|
+
expect(sql).toContain("SELECT 0");
|
|
11
|
+
});
|
|
12
|
+
it("builds a fingerprint query that ignores the version table itself", () => {
|
|
13
|
+
const sql = buildSchemaStructureFingerprintQuery("senat");
|
|
14
|
+
expect(sql).toContain("table_class.relname <> 'version'");
|
|
15
|
+
expect(sql).toContain("columns.table_name <> 'version'");
|
|
16
|
+
expect(sql).toContain("constraint_data.contype <> 'n'");
|
|
17
|
+
expect(sql).toContain("pg_get_indexdef(idx.indexrelid)");
|
|
18
|
+
expect(sql).toContain("SELECT coalesce(md5(string_agg(entry, E'\\n' ORDER BY entry)), '')");
|
|
19
|
+
});
|
|
20
|
+
it("increments the schema version with a simple update", () => {
|
|
21
|
+
expect(buildIncrementSchemaVersionSql("senat")).toBe("UPDATE senat.version SET number = number + 1");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { datasets } from "../src/datasets";
|
|
4
|
+
import { extractPrefixedTableNamesFromGeneratedDefinition } from "../src/scripts/shared/prefixed_tables";
|
|
5
|
+
describe("generated raw types coverage", () => {
|
|
6
|
+
it("maps each generated dataset definition to prefixed senat tables", () => {
|
|
7
|
+
for (const dataset of Object.values(datasets)) {
|
|
8
|
+
const definition = readFileSync(`src/raw_types_schemats/${dataset.database}.ts`, { encoding: "utf8" });
|
|
9
|
+
const tableNames = extractPrefixedTableNamesFromGeneratedDefinition(definition, dataset.database);
|
|
10
|
+
expect(tableNames.length).toBeGreaterThan(0);
|
|
11
|
+
expect(tableNames.every((tableName) => tableName.startsWith(`${dataset.database}_`))).toBe(true);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
});
|