@tricoteuses/senat 3.1.21 → 3.1.23
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 +0 -7
- package/lib/src/scripts/convert_data.js +4 -5
- package/lib/src/scripts/retrieve_cr_commission.js +1 -1
- package/lib/src/scripts/retrieve_cr_seance.js +34 -25
- package/lib/src/scripts/retrieve_open_data.js +39 -0
- package/lib/src/scripts/retrieve_videos.js +14 -10
- package/lib/src/scripts/shared/incremental_import_sql.js +7 -0
- package/lib/src/server/ameli.js +3 -4
- package/lib/src/server/conversion_textes.js +1 -1
- package/lib/src/server/debats.js +0 -5
- package/lib/src/server/documents.js +0 -3
- package/lib/src/server/dosleg.js +0 -11
- package/lib/src/server/questions.js +0 -1
- package/lib/src/server/scrutins.js +0 -4
- package/lib/src/server/sens.js +0 -17
- package/lib/src/utils/reunion_odj_building.js +0 -4
- package/lib/src/videos/match.js +3 -1
- package/lib/src/videos/pipeline.js +26 -19
- package/lib/tests/incrementalImportSql.test.js +4 -1
- package/package.json +30 -28
|
@@ -39,13 +39,6 @@ function getDateSortValue(value) {
|
|
|
39
39
|
function compareByDate(left, right) {
|
|
40
40
|
return getDateSortValue(left.date) - getDateSortValue(right.date);
|
|
41
41
|
}
|
|
42
|
-
function getCommissionCodeActe(phasePrefix, rapportType) {
|
|
43
|
-
const normalizedType = (rapportType || "").toLowerCase();
|
|
44
|
-
if (normalizedType.includes("avis") || normalizedType.includes("information")) {
|
|
45
|
-
return `${phasePrefix}-COM-AVIS`;
|
|
46
|
-
}
|
|
47
|
-
return `${phasePrefix}-COM-FOND`;
|
|
48
|
-
}
|
|
49
42
|
function getPhasePrefix(lecture, assemblee, codeNatureDossier) {
|
|
50
43
|
if (assemblee !== "Sénat")
|
|
51
44
|
return null;
|
|
@@ -154,12 +154,11 @@ async function convertDatasetDosLeg(dataDir, options) {
|
|
|
154
154
|
if (options["verbose"]) {
|
|
155
155
|
console.log(`Converting ${dossier["signet"]} file…`);
|
|
156
156
|
}
|
|
157
|
-
let dossierReorganizedDir = path.join(dossiersReorganizedDir, String(UNDEFINED_SESSION));
|
|
158
157
|
const session = getSessionFromSignet(dossier["signet"]) || UNDEFINED_SESSION;
|
|
159
158
|
if (options["fromSession"] && session < options["fromSession"]) {
|
|
160
159
|
continue;
|
|
161
160
|
}
|
|
162
|
-
dossierReorganizedDir = path.join(dossiersReorganizedDir, String(session));
|
|
161
|
+
const dossierReorganizedDir = path.join(dossiersReorganizedDir, String(session));
|
|
163
162
|
const actesBrutsNormalises = buildActesLegislatifs(dossier);
|
|
164
163
|
const dossierWithActes = {
|
|
165
164
|
...dossier,
|
|
@@ -184,9 +183,8 @@ async function convertDatasetScrutins(dataDir, options) {
|
|
|
184
183
|
if (options["verbose"]) {
|
|
185
184
|
console.log(`Converting ${scrutin["numero"]} file…`);
|
|
186
185
|
}
|
|
187
|
-
let scrutinReorganizedDir = path.join(scrutinsReorganizedDir, String(UNDEFINED_SESSION));
|
|
188
186
|
const session = scrutin["session"] || UNDEFINED_SESSION;
|
|
189
|
-
scrutinReorganizedDir = path.join(scrutinsReorganizedDir, String(session));
|
|
187
|
+
const scrutinReorganizedDir = path.join(scrutinsReorganizedDir, String(session));
|
|
190
188
|
const scrutinFileName = `${scrutin["numero"]}.json`;
|
|
191
189
|
await fs.outputJSON(path.join(scrutinReorganizedDir, scrutinFileName), scrutin, {
|
|
192
190
|
spaces: 2,
|
|
@@ -339,7 +337,8 @@ async function convertDatasetSens(dataDir, options) {
|
|
|
339
337
|
for await (const organisme of findAllOrganismes()) {
|
|
340
338
|
const sanitizedCode = organisme.code.trim().replace(/\s+/g, "-");
|
|
341
339
|
if (sanitizedCode !== organisme.code) {
|
|
342
|
-
console.warn(`Warning: code "${organisme.code}" contains leading/trailing or multiple consecutive whitespace characters.
|
|
340
|
+
console.warn(`Warning: code "${organisme.code}" contains leading/trailing or multiple consecutive whitespace characters. ` +
|
|
341
|
+
`It has been sanitized to "${sanitizedCode}". Please check the original code for potential issues.`);
|
|
343
342
|
}
|
|
344
343
|
if (options["verbose"]) {
|
|
345
344
|
console.log(`Converting ${sanitizedCode} file…`);
|
|
@@ -263,7 +263,7 @@ async function retrieveCommissionCRs(options = {}) {
|
|
|
263
263
|
for (const f of htmlFiles) {
|
|
264
264
|
const htmlPath = path.join(commissionDir, f);
|
|
265
265
|
let meta;
|
|
266
|
-
let raw
|
|
266
|
+
let raw;
|
|
267
267
|
try {
|
|
268
268
|
raw = await fs.readFile(htmlPath, "utf8");
|
|
269
269
|
meta = parseCommissionMetadataFromHtml(raw, f);
|
|
@@ -28,18 +28,25 @@ const optionsDefinitions = [
|
|
|
28
28
|
const options = commandLineArgs(optionsDefinitions);
|
|
29
29
|
const CRI_ZIP_URL = "https://data.senat.fr/data/debats/cri.zip";
|
|
30
30
|
let exitCode = 10; // 0: some data changed, 10: no modification
|
|
31
|
+
function log(options, ...args) {
|
|
32
|
+
if (!options["silent"])
|
|
33
|
+
console.log(...args);
|
|
34
|
+
}
|
|
35
|
+
function warn(options, ...args) {
|
|
36
|
+
if (!options["silent"])
|
|
37
|
+
console.warn(...args);
|
|
38
|
+
}
|
|
31
39
|
class CompteRenduError extends Error {
|
|
32
40
|
constructor(message, url) {
|
|
33
41
|
super(`An error occurred while retrieving ${url}: ${message}`);
|
|
34
42
|
}
|
|
35
43
|
}
|
|
36
|
-
async function downloadCriZip(zipPath) {
|
|
37
|
-
|
|
38
|
-
console.log(`Downloading CRI zip ${CRI_ZIP_URL}…`);
|
|
44
|
+
async function downloadCriZip(zipPath, options) {
|
|
45
|
+
log(options, `Downloading CRI zip ${CRI_ZIP_URL}…`);
|
|
39
46
|
const response = await fetchWithRetry(CRI_ZIP_URL);
|
|
40
47
|
if (!response.ok) {
|
|
41
48
|
if (response.status === 404) {
|
|
42
|
-
|
|
49
|
+
warn(options, `CRI zip ${CRI_ZIP_URL} not found`);
|
|
43
50
|
return;
|
|
44
51
|
}
|
|
45
52
|
throw new CompteRenduError(String(response.status), CRI_ZIP_URL);
|
|
@@ -48,7 +55,7 @@ async function downloadCriZip(zipPath) {
|
|
|
48
55
|
await fs.writeFile(zipPath, buf);
|
|
49
56
|
if (!options["silent"]) {
|
|
50
57
|
const mb = (buf.length / (1024 * 1024)).toFixed(1);
|
|
51
|
-
|
|
58
|
+
log(options, `[CRI] Downloaded ${mb} MB → ${zipPath}`);
|
|
52
59
|
}
|
|
53
60
|
}
|
|
54
61
|
async function extractAndDistributeXmlBySession(zipPath, originalRoot) {
|
|
@@ -97,9 +104,9 @@ export async function retrieveCriXmlDump(dataDir, options = {}) {
|
|
|
97
104
|
const sessions = getSessionsFromStart((options["fromSession"] ?? UNDEFINED_SESSION));
|
|
98
105
|
// 1) Download ZIP global + distribut by session
|
|
99
106
|
const zipPath = path.join(dataDir, "cri.zip");
|
|
100
|
-
|
|
101
|
-
await downloadCriZip(zipPath);
|
|
102
|
-
|
|
107
|
+
log(options, "[CRI] Downloading global CRI zip…");
|
|
108
|
+
await downloadCriZip(zipPath, options);
|
|
109
|
+
log(options, "[CRI] Extracting + distributing XMLs by session…");
|
|
103
110
|
for (const session of sessions) {
|
|
104
111
|
const dir = path.join(originalRoot, String(session));
|
|
105
112
|
if (await fs.pathExists(dir)) {
|
|
@@ -110,13 +117,13 @@ export async function retrieveCriXmlDump(dataDir, options = {}) {
|
|
|
110
117
|
}
|
|
111
118
|
const n = await extractAndDistributeXmlBySession(zipPath, originalRoot);
|
|
112
119
|
if (n === 0) {
|
|
113
|
-
|
|
120
|
+
warn(options, "[CRI] No XML extracted. Archive empty or layout changed?");
|
|
114
121
|
}
|
|
115
122
|
else {
|
|
116
|
-
|
|
123
|
+
log(options, `[CRI] Distributed ${n} XML file(s) into session folders.`);
|
|
117
124
|
}
|
|
118
125
|
if (!options["parseDebats"]) {
|
|
119
|
-
|
|
126
|
+
log(options, "[CRI] parseDebats not requested → done.");
|
|
120
127
|
return;
|
|
121
128
|
}
|
|
122
129
|
for (const session of sessions) {
|
|
@@ -147,10 +154,10 @@ export async function retrieveCriXmlDump(dataDir, options = {}) {
|
|
|
147
154
|
const crPath = path.join(transformedSessionDir, fn);
|
|
148
155
|
try {
|
|
149
156
|
const cr = await fs.readJSON(crPath);
|
|
150
|
-
await linkCriEventIntoAgenda(dataDir, yyyymmdd, eventId, cr.uid, cr, session);
|
|
157
|
+
await linkCriEventIntoAgenda(dataDir, yyyymmdd, eventId, cr.uid, cr, session, options);
|
|
151
158
|
}
|
|
152
159
|
catch (e) {
|
|
153
|
-
|
|
160
|
+
warn(options, `[CR] [${session}] Could not relink existing CR into a reunion for ${yyyymmdd} event=${eventId}:`, e);
|
|
154
161
|
}
|
|
155
162
|
}
|
|
156
163
|
continue;
|
|
@@ -160,7 +167,7 @@ export async function retrieveCriXmlDump(dataDir, options = {}) {
|
|
|
160
167
|
// === Charger les events SP du jour depuis les agendas groupés ===
|
|
161
168
|
const dayEvents = await loadAgendaSpEventsForDate(dataDir, yyyymmdd, session);
|
|
162
169
|
if (dayEvents.length === 0) {
|
|
163
|
-
|
|
170
|
+
warn(options, `[CRI] [${session}] No agenda SP events found for ${yyyymmdd} → skip split/link`);
|
|
164
171
|
continue;
|
|
165
172
|
}
|
|
166
173
|
// === Lire XML + construire index DOM ===
|
|
@@ -175,14 +182,14 @@ export async function retrieveCriXmlDump(dataDir, options = {}) {
|
|
|
175
182
|
idx = new Map(order.map((el, i) => [el, i]));
|
|
176
183
|
}
|
|
177
184
|
catch (e) {
|
|
178
|
-
|
|
185
|
+
warn(options, `[CRI] [${session}] Cannot read/parse ${f}:`, e);
|
|
179
186
|
continue;
|
|
180
187
|
}
|
|
181
188
|
// === Extraire sommaire + matcher vers events agenda ===
|
|
182
189
|
const blocks = extractSommaireBlocks($, idx);
|
|
183
190
|
const intervals = buildIntervalsByAgendaEvents($, idx, order, blocks, dayEvents);
|
|
184
191
|
if (!intervals.length) {
|
|
185
|
-
|
|
192
|
+
warn(options, `[CRI] [${session}] No confident split intervals for ${yyyymmdd} → skip`);
|
|
186
193
|
continue;
|
|
187
194
|
}
|
|
188
195
|
// === Parser / écrire / linker chaque segment par event ===
|
|
@@ -191,16 +198,16 @@ export async function retrieveCriXmlDump(dataDir, options = {}) {
|
|
|
191
198
|
const outPath = path.join(transformedSessionDir, outName);
|
|
192
199
|
const cr = await parseCompteRenduIntervalFromFile(xmlPath, iv.startIndex, iv.endIndex, iv.agendaEventId);
|
|
193
200
|
if (!cr) {
|
|
194
|
-
|
|
201
|
+
warn(options, `[CRI] [${session}] Empty or no points for ${yyyymmdd} event=${iv.agendaEventId} → skip`);
|
|
195
202
|
continue;
|
|
196
203
|
}
|
|
197
204
|
await fs.ensureDir(transformedSessionDir);
|
|
198
205
|
await fs.writeJSON(outPath, cr, { spaces: 2 });
|
|
199
206
|
try {
|
|
200
|
-
await linkCriEventIntoAgenda(dataDir, yyyymmdd, iv.agendaEventId, cr.uid, cr, session);
|
|
207
|
+
await linkCriEventIntoAgenda(dataDir, yyyymmdd, iv.agendaEventId, cr.uid, cr, session, options);
|
|
201
208
|
}
|
|
202
209
|
catch (e) {
|
|
203
|
-
|
|
210
|
+
warn(options, `[CR] [${session}] Could not link CR into agenda for ${yyyymmdd} event=${iv.agendaEventId}:`, e);
|
|
204
211
|
}
|
|
205
212
|
}
|
|
206
213
|
}
|
|
@@ -218,7 +225,7 @@ function commitAndPushGit(datasetDir, options) {
|
|
|
218
225
|
}
|
|
219
226
|
}
|
|
220
227
|
}
|
|
221
|
-
async function linkCriEventIntoAgenda(dataDir, yyyymmdd, agendaEventId, crUid, cr, session) {
|
|
228
|
+
async function linkCriEventIntoAgenda(dataDir, yyyymmdd, agendaEventId, crUid, cr, session, options) {
|
|
222
229
|
const agendadDir = path.join(dataDir, AGENDA_FOLDER, DATA_TRANSFORMED_FOLDER, session.toString());
|
|
223
230
|
fs.ensureDirSync(agendadDir);
|
|
224
231
|
const dateISO = `${yyyymmdd.slice(0, 4)}-${yyyymmdd.slice(4, 6)}-${yyyymmdd.slice(6, 8)}`;
|
|
@@ -230,17 +237,17 @@ async function linkCriEventIntoAgenda(dataDir, yyyymmdd, agendaEventId, crUid, c
|
|
|
230
237
|
agenda = await fs.readJSON(agendaPath);
|
|
231
238
|
}
|
|
232
239
|
catch (e) {
|
|
233
|
-
|
|
240
|
+
warn(options, `[CR] unreadable reunion JSON → ${agendaPath} (${e})`);
|
|
234
241
|
agenda = null;
|
|
235
242
|
}
|
|
236
243
|
}
|
|
237
244
|
if (!agenda) {
|
|
238
|
-
|
|
245
|
+
warn(options, `[CR] Missing reunion file for SP event=${agendaEventId}: ${agendaPath}`);
|
|
239
246
|
return;
|
|
240
247
|
}
|
|
241
248
|
agenda.compteRenduRefUid = crUid;
|
|
242
249
|
await fs.writeJSON(agendaPath, agenda, { spaces: 2 });
|
|
243
|
-
|
|
250
|
+
log(options, `[CR] Linked CR ${crUid} → ${path.basename(agendaPath)} (event=${agendaEventId})`);
|
|
244
251
|
}
|
|
245
252
|
function buildIntervalsByAgendaEvents($, idx, order, blocks, dayEvents) {
|
|
246
253
|
const MIN_SCORE = 0.65;
|
|
@@ -348,9 +355,11 @@ function resolveTargetIndex($, idx, targetId) {
|
|
|
348
355
|
}
|
|
349
356
|
async function main() {
|
|
350
357
|
const dataDir = assertExistingDirectory(options["dataDir"], "data directory");
|
|
351
|
-
|
|
358
|
+
if (!options["silent"])
|
|
359
|
+
console.time("CRI processing time");
|
|
352
360
|
await retrieveCriXmlDump(dataDir, options);
|
|
353
|
-
|
|
361
|
+
if (!options["silent"])
|
|
362
|
+
console.timeEnd("CRI processing time");
|
|
354
363
|
}
|
|
355
364
|
main()
|
|
356
365
|
.then(() => process.exit(exitCode))
|
|
@@ -185,10 +185,49 @@ function runPsqlQuery(command, dataDir, options, connection, stopOnError = true)
|
|
|
185
185
|
stdio: ["ignore", "pipe", "pipe"],
|
|
186
186
|
});
|
|
187
187
|
}
|
|
188
|
+
function sourceSchemaNames(dataset) {
|
|
189
|
+
if (dataset.database === "sens") {
|
|
190
|
+
return ["sens", "senat"];
|
|
191
|
+
}
|
|
192
|
+
return [dataset.database];
|
|
193
|
+
}
|
|
194
|
+
function inspectSourceDump(dataset, dataDir) {
|
|
195
|
+
const sqlFilePath = path.join(dataDir, `${dataset.database}.sql`);
|
|
196
|
+
if (!fs.existsSync(sqlFilePath)) {
|
|
197
|
+
return {
|
|
198
|
+
copyStatements: 0,
|
|
199
|
+
createTableStatements: 0,
|
|
200
|
+
fileSizeBytes: null,
|
|
201
|
+
schemaQualifiedTableStatements: 0,
|
|
202
|
+
sqlFilePath,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const sqlContent = fs.readFileSync(sqlFilePath, { encoding: "utf8" });
|
|
206
|
+
const schemaPattern = sourceSchemaNames(dataset)
|
|
207
|
+
.map((schemaName) => schemaName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
208
|
+
.join("|");
|
|
209
|
+
return {
|
|
210
|
+
copyStatements: (sqlContent.match(/^COPY\s+/gim) ?? []).length,
|
|
211
|
+
createTableStatements: (sqlContent.match(/^CREATE\s+TABLE\s+/gim) ?? []).length,
|
|
212
|
+
fileSizeBytes: fs.statSync(sqlFilePath).size,
|
|
213
|
+
schemaQualifiedTableStatements: (sqlContent.match(new RegExp(`\\b(?:${schemaPattern})\\.`, "g")) ?? []).length,
|
|
214
|
+
sqlFilePath,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
188
217
|
function ensureStagingSchemaHasTables(dataset, dataDir, options, connection) {
|
|
189
218
|
const stagingSchema = stagingSchemaName(dataset.database);
|
|
190
219
|
const tableCount = Number.parseInt(runPsqlQuery(`SELECT count(*) FROM pg_tables WHERE schemaname = '${escapeSqlLiteral(stagingSchema)}'`, dataDir, options, connection).trim(), 10);
|
|
220
|
+
if (tableCount === 0) {
|
|
221
|
+
const dumpInspection = inspectSourceDump(dataset, dataDir);
|
|
222
|
+
const fileSizeLabel = dumpInspection.fileSizeBytes === null ? "missing" : `${dumpInspection.fileSizeBytes} bytes`;
|
|
223
|
+
console.error(`Source dump diagnostics for ${dataset.database}: file=${dumpInspection.sqlFilePath}, ` +
|
|
224
|
+
`size=${fileSizeLabel}, create_table=${dumpInspection.createTableStatements}, ` +
|
|
225
|
+
`copy=${dumpInspection.copyStatements}, ` +
|
|
226
|
+
`schema_refs=${dumpInspection.schemaQualifiedTableStatements}`);
|
|
227
|
+
}
|
|
191
228
|
assert(tableCount > 0, `Staging schema ${stagingSchema} is empty after importing ${dataset.database}. ` +
|
|
229
|
+
"Source dump may be empty or malformed. " +
|
|
230
|
+
`Check /home/gitlab-runner/senat-data/${dataset.database}.sql and the upstream archive before retrying. ` +
|
|
192
231
|
`Aborting incremental merge to protect ${senatSchemaName}.`);
|
|
193
232
|
}
|
|
194
233
|
function ensureStagingDatabase(dataDir, options, runtime) {
|
|
@@ -17,6 +17,10 @@ import { processBisIfNeeded, processOneReunionMatch, writeIfChanged } from "../v
|
|
|
17
17
|
const optionsDefinitions = [...commonOptions];
|
|
18
18
|
const options = commandLineArgs(optionsDefinitions);
|
|
19
19
|
let exitCode = 10; // 0: some data changed, 10: no modification
|
|
20
|
+
function log(...args) {
|
|
21
|
+
if (!options["silent"])
|
|
22
|
+
console.log(...args);
|
|
23
|
+
}
|
|
20
24
|
function shouldSkipAgenda(agenda) {
|
|
21
25
|
if (!agenda.date || !agenda.startTime)
|
|
22
26
|
return true;
|
|
@@ -106,7 +110,6 @@ async function processGroupedReunion(agenda, session, dataDir, lastByVideo) {
|
|
|
106
110
|
return;
|
|
107
111
|
const ctx = await computeContext(agenda, session, dataDir);
|
|
108
112
|
const skipDownload = shouldSkipDownload(agenda, ctx.baseDir);
|
|
109
|
-
let match = null;
|
|
110
113
|
let best = null;
|
|
111
114
|
let secondBest = null;
|
|
112
115
|
// 2) Match + download artifacts (only if not skipped)
|
|
@@ -114,12 +117,12 @@ async function processGroupedReunion(agenda, session, dataDir, lastByVideo) {
|
|
|
114
117
|
STATS.total++;
|
|
115
118
|
const candidates = await fetchCandidatesForAgenda(agenda, options);
|
|
116
119
|
if (!candidates) {
|
|
117
|
-
|
|
120
|
+
log(`[warn] ${agenda.uid} No candidate found for this reunion. Probably VOD not published yet.`);
|
|
118
121
|
return;
|
|
119
122
|
}
|
|
120
|
-
match = await matchAgendaToVideo({ agenda, agendaTs: ctx.agendaTs, candidates, options });
|
|
123
|
+
const match = await matchAgendaToVideo({ agenda, agendaTs: ctx.agendaTs, candidates, options });
|
|
121
124
|
if (!match) {
|
|
122
|
-
|
|
125
|
+
log(`[miss] ${agenda.uid} No match found for this reunion`);
|
|
123
126
|
return;
|
|
124
127
|
}
|
|
125
128
|
;
|
|
@@ -128,8 +131,7 @@ async function processGroupedReunion(agenda, session, dataDir, lastByVideo) {
|
|
|
128
131
|
await writeMatchArtifacts({ agenda, ctx, best, secondBest });
|
|
129
132
|
}
|
|
130
133
|
if (best && isAmbiguousTimeOriginal(agenda.events[0].timeOriginal)) {
|
|
131
|
-
|
|
132
|
-
console.log("If the time is ambiguous, update agenda startTime from matched video");
|
|
134
|
+
log("If the time is ambiguous, update agenda startTime from matched video");
|
|
133
135
|
agenda = { ...agenda, startTime: epochToParisDateTime(best.epoch)?.startTime ?? agenda.startTime };
|
|
134
136
|
}
|
|
135
137
|
// 3) Always update BEST agenda JSON from local NVS
|
|
@@ -159,7 +161,7 @@ async function processGroupedReunion(agenda, session, dataDir, lastByVideo) {
|
|
|
159
161
|
});
|
|
160
162
|
}
|
|
161
163
|
async function processAll(dataDir, sessions) {
|
|
162
|
-
|
|
164
|
+
log("Process all Agendas and fetch video's url");
|
|
163
165
|
for (const session of sessions) {
|
|
164
166
|
const lastByVideo = new Map();
|
|
165
167
|
for (const { item: agenda } of iterLoadSenatAgendas(dataDir, session)) {
|
|
@@ -187,12 +189,14 @@ async function main() {
|
|
|
187
189
|
const dataDir = assertExistingDirectory(options["dataDir"], "data directory");
|
|
188
190
|
const sessions = getSessionsFromStart((options["fromSession"] ?? UNDEFINED_SESSION));
|
|
189
191
|
const TIMER = "senat-agendas→videos processing time";
|
|
190
|
-
|
|
192
|
+
if (!options["silent"])
|
|
193
|
+
console.time(TIMER);
|
|
191
194
|
await processAll(dataDir, sessions);
|
|
192
|
-
|
|
195
|
+
if (!options["silent"])
|
|
196
|
+
console.timeEnd(TIMER);
|
|
193
197
|
const { total, accepted } = STATS;
|
|
194
198
|
const ratio = total ? ((accepted / total) * 100).toFixed(1) : "0.0";
|
|
195
|
-
|
|
199
|
+
log(`[summary] accepted=${accepted} / total=${total} (${ratio}%)`);
|
|
196
200
|
}
|
|
197
201
|
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
198
202
|
main()
|
|
@@ -283,6 +283,13 @@ BEGIN
|
|
|
283
283
|
)
|
|
284
284
|
INTO use_row_multiset_merge;
|
|
285
285
|
|
|
286
|
+
-- The synchronization below temporarily drops primary/unique constraints before
|
|
287
|
+
-- issuing DELETE/UPDATE statements. On tables that belong to a publication with
|
|
288
|
+
-- deletes/updates enabled, PostgreSQL then refuses those statements unless the
|
|
289
|
+
-- table has an explicit replica identity. FULL keeps logical replication valid
|
|
290
|
+
-- while the constraints are being rebuilt.
|
|
291
|
+
EXECUTE format('ALTER TABLE ${senatSchemaName}.%I REPLICA IDENTITY FULL', table_row.tablename);
|
|
292
|
+
|
|
286
293
|
FOR constraint_row IN
|
|
287
294
|
SELECT con.conname
|
|
288
295
|
FROM pg_constraint con
|
package/lib/src/server/ameli.js
CHANGED
|
@@ -16,6 +16,7 @@ export async function* findAllAmendements(fromSession) {
|
|
|
16
16
|
.leftJoin("ameli_lec_ameli as lec_ameli", "lec_ameli.id", "txt_ameli.lecid")
|
|
17
17
|
.leftJoin("dosleg_texte as texte", (join) => join.onRef("ses.ann", "=", "texte.sesann").onRef("txt_ameli.numabs", "=", "texte.texnum"))
|
|
18
18
|
.leftJoin("dosleg_lecass as lecass", "lecass.lecassidt", "texte.lecassidt")
|
|
19
|
+
.leftJoin("dosleg_org as org_lecass", "org_lecass.orgcod", "lecass.orgcod")
|
|
19
20
|
.leftJoin("ameli_mot as mot", "mot.id", "amd.motid")
|
|
20
21
|
.leftJoin("ameli_avicom as avicom", "avicom.id", "amd.avcid")
|
|
21
22
|
.leftJoin("ameli_avigvt as avigvt", "avigvt.id", "amd.avgid")
|
|
@@ -107,12 +108,11 @@ export async function* findAllAmendements(fromSession) {
|
|
|
107
108
|
)
|
|
108
109
|
`.as("url"),
|
|
109
110
|
"grppol_ameli.lilcou as au_nom_de_groupe_politique",
|
|
110
|
-
sql `rtrim(${eb.ref("com_ameli.lil")})`.as("au_nom_de_commission"),
|
|
111
|
-
sql `rtrim(${eb.ref("com_ameli.cod")})`.as("code_commission"),
|
|
111
|
+
sql `coalesce(rtrim(${eb.ref("com_ameli.lil")}), ${eb.ref("org_lecass.orgnom")})`.as("au_nom_de_commission"),
|
|
112
|
+
sql `coalesce(rtrim(${eb.ref("com_ameli.cod")}), ${eb.ref("org_lecass.senorgcod")})`.as("code_commission"),
|
|
112
113
|
sql `(${eb.ref("cab.entid")} is not null)`.as("auteur_est_gouvernement"),
|
|
113
114
|
// Sous-requête scalaire: numéro de scrutin
|
|
114
115
|
eb
|
|
115
|
-
.withSchema("senat")
|
|
116
116
|
.selectFrom("dosleg_amescr as amescr")
|
|
117
117
|
.leftJoin("dosleg_scr as scr", (join) => join.onRef("amescr.scrnum", "=", "scr.scrnum").onRef("amescr.sesann", "=", "scr.sesann"))
|
|
118
118
|
.leftJoin("dosleg_date_seance as date_seance", "date_seance.code", "scr.code")
|
|
@@ -124,7 +124,6 @@ export async function* findAllAmendements(fromSession) {
|
|
|
124
124
|
.as("scrutin_num"),
|
|
125
125
|
// Sous-requête pour les auteurs
|
|
126
126
|
jsonArrayFrom(eb
|
|
127
|
-
.withSchema("senat")
|
|
128
127
|
.selectFrom("ameli_amdsen as amdsen")
|
|
129
128
|
.leftJoin("ameli_sen_ameli as sen_ameli", "sen_ameli.entid", "amdsen.senid")
|
|
130
129
|
.leftJoin("ameli_grppol_ameli as grppol_ameli2", "grppol_ameli2.entid", "amdsen.grpid")
|
|
@@ -218,7 +218,7 @@ p.has-alinea {
|
|
|
218
218
|
else if (child.nodeType === 1) {
|
|
219
219
|
const element = child;
|
|
220
220
|
const tagName = element.tagName.toLowerCase();
|
|
221
|
-
let htmlElement
|
|
221
|
+
let htmlElement;
|
|
222
222
|
switch (tagName) {
|
|
223
223
|
case "article": {
|
|
224
224
|
htmlElement = htmlDoc.createElement("div");
|
package/lib/src/server/debats.js
CHANGED
|
@@ -13,7 +13,6 @@ export async function* findAll() {
|
|
|
13
13
|
"debats.debsyn as etat_synchronisation",
|
|
14
14
|
// Sections principales (PJL)
|
|
15
15
|
jsonArrayFrom(eb
|
|
16
|
-
.withSchema("senat")
|
|
17
16
|
.selectFrom("debats_secdis as secdis")
|
|
18
17
|
.leftJoin("debats_typsec as typsec", "typsec.typseccod", "secdis.typseccod")
|
|
19
18
|
.select((eb2) => [
|
|
@@ -26,7 +25,6 @@ export async function* findAll() {
|
|
|
26
25
|
"secdis.lecassidt as lecture_id",
|
|
27
26
|
// Interventions de la section
|
|
28
27
|
jsonArrayFrom(eb2
|
|
29
|
-
.withSchema("senat")
|
|
30
28
|
.selectFrom("debats_intpjl as intpjl")
|
|
31
29
|
.leftJoin("dosleg_auteur as auteur", "auteur.autcod", "intpjl.autcod")
|
|
32
30
|
.select([
|
|
@@ -49,7 +47,6 @@ export async function* findAll() {
|
|
|
49
47
|
.orderBy("secdis.secdisordid", "asc")).as("sections"),
|
|
50
48
|
// Sections diverses
|
|
51
49
|
jsonArrayFrom(eb
|
|
52
|
-
.withSchema("senat")
|
|
53
50
|
.selectFrom("debats_secdivers as secdivers")
|
|
54
51
|
.leftJoin("debats_typsec as typsec", "typsec.typseccod", "secdivers.typseccod")
|
|
55
52
|
.select((eb2) => [
|
|
@@ -59,7 +56,6 @@ export async function* findAll() {
|
|
|
59
56
|
"typsec.typseccat as categorie",
|
|
60
57
|
// Interventions diverses
|
|
61
58
|
jsonArrayFrom(eb2
|
|
62
|
-
.withSchema("senat")
|
|
63
59
|
.selectFrom("debats_intdivers as intdivers")
|
|
64
60
|
.leftJoin("dosleg_auteur as auteur", "auteur.autcod", "intdivers.autcod")
|
|
65
61
|
.select([
|
|
@@ -81,7 +77,6 @@ export async function* findAll() {
|
|
|
81
77
|
.whereRef("secdivers.datsea", "=", "debats.datsea")).as("sections_divers"),
|
|
82
78
|
// Lectures
|
|
83
79
|
jsonArrayFrom(eb
|
|
84
|
-
.withSchema("senat")
|
|
85
80
|
.selectFrom("debats_lecassdeb as lecassdeb")
|
|
86
81
|
.select(["lecassdeb.lecassidt as id"])
|
|
87
82
|
.whereRef("lecassdeb.datsea", "=", "debats.datsea")).as("lectures"),
|
|
@@ -36,7 +36,6 @@ export async function* findAllTextes() {
|
|
|
36
36
|
sql `${eb.ref("texte.sesann")}::int`.as("session"),
|
|
37
37
|
// Auteurs
|
|
38
38
|
jsonArrayFrom(eb
|
|
39
|
-
.withSchema("senat")
|
|
40
39
|
.selectFrom("dosleg_auteur as auteur")
|
|
41
40
|
.leftJoin("dosleg_ecr as ecr", "ecr.autcod", "auteur.autcod")
|
|
42
41
|
.leftJoin("dosleg_rolsig as rolsig", "rolsig.signataire", "ecr.signataire")
|
|
@@ -95,7 +94,6 @@ export async function* findAllRapports() {
|
|
|
95
94
|
sql `${eb.ref("rap.sesann")}::int`.as("session"),
|
|
96
95
|
// Auteurs
|
|
97
96
|
jsonArrayFrom(eb
|
|
98
|
-
.withSchema("senat")
|
|
99
97
|
.selectFrom("dosleg_auteur as auteur")
|
|
100
98
|
.leftJoin("dosleg_ecr as ecr", "ecr.autcod", "auteur.autcod")
|
|
101
99
|
.leftJoin("dosleg_rolsig as rolsig", "rolsig.signataire", "ecr.signataire")
|
|
@@ -111,7 +109,6 @@ export async function* findAllRapports() {
|
|
|
111
109
|
.whereRef("ecr.rapcod", "=", "rap.rapcod")).as("auteurs"),
|
|
112
110
|
// Documents annexes
|
|
113
111
|
jsonArrayFrom(eb
|
|
114
|
-
.withSchema("senat")
|
|
115
112
|
.selectFrom("dosleg_docatt as docatt")
|
|
116
113
|
.leftJoin("dosleg_typatt as typatt", "typatt.typattcod", "docatt.typattcod")
|
|
117
114
|
.select(["docatt.docatturl as url", "typatt.typattlib as type_document"])
|
package/lib/src/server/dosleg.js
CHANGED
|
@@ -58,7 +58,6 @@ export async function* findAllDossiers() {
|
|
|
58
58
|
"loi.url_jo3 as url_JO_correctif_2",
|
|
59
59
|
// Lectures
|
|
60
60
|
jsonArrayFrom(eb
|
|
61
|
-
.withSchema("senat")
|
|
62
61
|
.selectFrom("dosleg_lecture as lecture")
|
|
63
62
|
.leftJoin("dosleg_typlec as typlec", "typlec.typleccod", "lecture.typleccod")
|
|
64
63
|
.select((eb2) => [
|
|
@@ -68,7 +67,6 @@ export async function* findAllDossiers() {
|
|
|
68
67
|
"typlec.typlecord as ordre_lecture_num",
|
|
69
68
|
// Lectures par assemblée
|
|
70
69
|
jsonArrayFrom(eb2
|
|
71
|
-
.withSchema("senat")
|
|
72
70
|
.selectFrom("dosleg_lecass as lecass")
|
|
73
71
|
.leftJoin("dosleg_ass as ass", "ass.codass", "lecass.codass")
|
|
74
72
|
.leftJoin("dosleg_org as org", "org.orgcod", "lecass.orgcod")
|
|
@@ -114,7 +112,6 @@ export async function* findAllDossiers() {
|
|
|
114
112
|
sql `lecass.lecassamecomadoses::int`.as("session_liasse_amendements_adoptes_commission"),
|
|
115
113
|
// Textes
|
|
116
114
|
jsonArrayFrom(eb3
|
|
117
|
-
.withSchema("senat")
|
|
118
115
|
.selectFrom("dosleg_texte as texte")
|
|
119
116
|
.leftJoin("dosleg_oritxt as oritxt", "oritxt.oritxtcod", "texte.oritxtcod")
|
|
120
117
|
.leftJoin("dosleg_typtxt as typtxt", "typtxt.typtxtcod", "texte.typtxtcod")
|
|
@@ -158,7 +155,6 @@ export async function* findAllDossiers() {
|
|
|
158
155
|
"texte.prix as prix",
|
|
159
156
|
// Auteurs du texte
|
|
160
157
|
jsonArrayFrom(eb4
|
|
161
|
-
.withSchema("senat")
|
|
162
158
|
.selectFrom("dosleg_auteur as auteur")
|
|
163
159
|
.leftJoin("dosleg_ecr as ecr", "ecr.autcod", "auteur.autcod")
|
|
164
160
|
.leftJoin("dosleg_rolsig as rolsig", "rolsig.signataire", "ecr.signataire")
|
|
@@ -178,7 +174,6 @@ export async function* findAllDossiers() {
|
|
|
178
174
|
.orderBy(sql `array_position(array['0','2','1'], oritxt.oriordre)`)).as("textes"),
|
|
179
175
|
// Rapports
|
|
180
176
|
jsonArrayFrom(eb3
|
|
181
|
-
.withSchema("senat")
|
|
182
177
|
.selectFrom("dosleg_rap as rap")
|
|
183
178
|
.leftJoin("dosleg_raporg as raporg", "raporg.rapcod", "rap.rapcod")
|
|
184
179
|
.leftJoin("dosleg_denrap as denrap", "denrap.coddenrap", "rap.coddenrap")
|
|
@@ -228,7 +223,6 @@ export async function* findAllDossiers() {
|
|
|
228
223
|
"lecassrap.lecassrapord as ordre_rapport",
|
|
229
224
|
// Auteurs du rapport
|
|
230
225
|
jsonArrayFrom(eb4
|
|
231
|
-
.withSchema("senat")
|
|
232
226
|
.selectFrom("dosleg_auteur as auteur")
|
|
233
227
|
.leftJoin("dosleg_ecr as ecr", "ecr.autcod", "auteur.autcod")
|
|
234
228
|
.leftJoin("dosleg_rolsig as rolsig", "rolsig.signataire", "ecr.signataire")
|
|
@@ -244,7 +238,6 @@ export async function* findAllDossiers() {
|
|
|
244
238
|
.whereRef("ecr.rapcod", "=", "rap.rapcod")).as("auteurs"),
|
|
245
239
|
// Documents annexes
|
|
246
240
|
jsonArrayFrom(eb4
|
|
247
|
-
.withSchema("senat")
|
|
248
241
|
.selectFrom("dosleg_docatt as docatt")
|
|
249
242
|
.leftJoin("dosleg_typatt as typatt", "typatt.typattcod", "docatt.typattcod")
|
|
250
243
|
.select(["docatt.docatturl as url", "typatt.typattlib as type_document"])
|
|
@@ -253,7 +246,6 @@ export async function* findAllDossiers() {
|
|
|
253
246
|
.whereRef("lecassrap.lecassidt", "=", "lecass.lecassidt")).as("rapports"),
|
|
254
247
|
// Dates de séances
|
|
255
248
|
jsonArrayFrom(eb3
|
|
256
|
-
.withSchema("senat")
|
|
257
249
|
.selectFrom("dosleg_date_seance as date_seance")
|
|
258
250
|
.select([
|
|
259
251
|
"date_seance.code as code",
|
|
@@ -264,7 +256,6 @@ export async function* findAllDossiers() {
|
|
|
264
256
|
// Commissions saisies (source : ameli.sai)
|
|
265
257
|
// Fusionnées avec la commission au fond (lecass.orgcod) en post-traitement
|
|
266
258
|
jsonArrayFrom(eb3
|
|
267
|
-
.withSchema("senat")
|
|
268
259
|
.selectFrom("ameli_sai as sai")
|
|
269
260
|
.innerJoin("ameli_txt_ameli as txt_a", "txt_a.id", "sai.txtid")
|
|
270
261
|
.innerJoin("ameli_com_ameli as com_a", "com_a.entid", "sai.comid")
|
|
@@ -285,7 +276,6 @@ export async function* findAllDossiers() {
|
|
|
285
276
|
.groupBy(["com_a.lib", "com_a.lil", "sai.saityp", "sai.isdelegfond"])).as("commissions_saisies_ameli"),
|
|
286
277
|
// Réunions de commission (source : dosleg_aud)
|
|
287
278
|
jsonArrayFrom(eb3
|
|
288
|
-
.withSchema("senat")
|
|
289
279
|
.selectFrom("dosleg_aud as aud")
|
|
290
280
|
.innerJoin("dosleg_org as org_a", "org_a.orgcod", "aud.orgcod")
|
|
291
281
|
.select([
|
|
@@ -305,7 +295,6 @@ export async function* findAllDossiers() {
|
|
|
305
295
|
.orderBy(sql `COALESCE(typlec.typlecord::int, 9999)`, "asc")).as("lectures"),
|
|
306
296
|
// Thèmes
|
|
307
297
|
jsonArrayFrom(eb
|
|
308
|
-
.withSchema("senat")
|
|
309
298
|
.selectFrom("dosleg_the as the")
|
|
310
299
|
.leftJoin("dosleg_loithe as loithe", "loithe.thecle", "the.thecle")
|
|
311
300
|
.select(["the.thelib as libelle"])
|
|
@@ -33,7 +33,6 @@ export async function* findAllScrutins(fromSession) {
|
|
|
33
33
|
sql `${eb.ref("scr.scrpousea")}::text`.as("nombre_pour_seance"),
|
|
34
34
|
// Votes individuels
|
|
35
35
|
jsonArrayFrom(eb
|
|
36
|
-
.withSchema("senat")
|
|
37
36
|
.selectFrom("dosleg_votsen as votsen")
|
|
38
37
|
.leftJoin("dosleg_titsen as titsen", "titsen.titsencod", "votsen.titsencod")
|
|
39
38
|
.leftJoin("dosleg_stavot as stavot", "stavot.stavotidt", "votsen.stavotidt")
|
|
@@ -57,9 +56,7 @@ export async function* findAllScrutins(fromSession) {
|
|
|
57
56
|
.orderBy("memgrppol.memgrppoldatdeb", (ob) => ob.desc().nullsLast())).as("votes"),
|
|
58
57
|
// Groupes votants (avec déduplication via window function)
|
|
59
58
|
jsonArrayFrom(eb
|
|
60
|
-
.withSchema("senat")
|
|
61
59
|
.selectFrom(eb
|
|
62
|
-
.withSchema("senat")
|
|
63
60
|
.selectFrom("dosleg_votsen as votsen")
|
|
64
61
|
.leftJoin("dosleg_posvot as posvot", "posvot.posvotcod", "votsen.posvotcod")
|
|
65
62
|
.leftJoin("dosleg_stavot as stavot", "stavot.stavotidt", "votsen.stavotidt")
|
|
@@ -93,7 +90,6 @@ export async function* findAllScrutins(fromSession) {
|
|
|
93
90
|
.groupBy("unique_votes.grppolcod")).as("groupes_votants"),
|
|
94
91
|
// Mises au point (corrections)
|
|
95
92
|
jsonArrayFrom(eb
|
|
96
|
-
.withSchema("senat")
|
|
97
93
|
.selectFrom("dosleg_corscr as corscr")
|
|
98
94
|
.select([
|
|
99
95
|
"corscr.corscrtxt as texte",
|
package/lib/src/server/sens.js
CHANGED
|
@@ -37,14 +37,12 @@ export async function* findAll() {
|
|
|
37
37
|
"sen.sendaiurl as url_hatvp",
|
|
38
38
|
// URLs
|
|
39
39
|
jsonArrayFrom(eb
|
|
40
|
-
.withSchema("senat")
|
|
41
40
|
.selectFrom("sens_senurl as senurl")
|
|
42
41
|
.select(["senurl.typurlcod as code_url", "senurl.senurlurl as url", "senurl.senurlnumtri as order_num"])
|
|
43
42
|
.whereRef("senurl.senmat", "=", "sen.senmat")
|
|
44
43
|
.orderBy("senurl.senurlnumtri", "asc")).as("urls"),
|
|
45
44
|
// Mandats sénatoriaux
|
|
46
45
|
jsonArrayFrom(eb
|
|
47
|
-
.withSchema("senat")
|
|
48
46
|
.selectFrom("sens_elusen as elusen")
|
|
49
47
|
.leftJoin("sens_etadebman as etadebman", "etadebman.etadebmancod", "elusen.etadebmancod")
|
|
50
48
|
.leftJoin("sens_etafinman as etafinman", "etafinman.etafinmancod", "elusen.etafinmancod")
|
|
@@ -61,7 +59,6 @@ export async function* findAll() {
|
|
|
61
59
|
.orderBy("elusen.eludatdeb", (ob) => ob.desc().nullsLast())).as("mandats_senateur"),
|
|
62
60
|
// Commissions
|
|
63
61
|
jsonArrayFrom(eb
|
|
64
|
-
.withSchema("senat")
|
|
65
62
|
.selectFrom("sens_memcom as memcom")
|
|
66
63
|
.leftJoin("sens_com as com2", "com2.orgcod", "memcom.orgcod")
|
|
67
64
|
.leftJoin("sens_typorg as typorg", "typorg.typorgcod", "com2.typorgcod")
|
|
@@ -76,7 +73,6 @@ export async function* findAll() {
|
|
|
76
73
|
"memcom.memcomdatdeb as order_date",
|
|
77
74
|
// Fonctions dans la commission
|
|
78
75
|
jsonArrayFrom(eb2
|
|
79
|
-
.withSchema("senat")
|
|
80
76
|
.selectFrom("sens_fonmemcom as fonmemcom")
|
|
81
77
|
.leftJoin("sens_foncom as foncom", "foncom.foncomcod", "fonmemcom.foncomcod")
|
|
82
78
|
.select([
|
|
@@ -95,7 +91,6 @@ export async function* findAll() {
|
|
|
95
91
|
.orderBy("memcom.memcomdatdeb", (ob) => ob.desc().nullsLast())).as("commissions"),
|
|
96
92
|
// Délégations
|
|
97
93
|
jsonArrayFrom(eb
|
|
98
|
-
.withSchema("senat")
|
|
99
94
|
.selectFrom("sens_memdelega as memdelega")
|
|
100
95
|
.leftJoin("sens_delega as delega", "delega.orgcod", "memdelega.orgcod")
|
|
101
96
|
.leftJoin("sens_designorg as designorg", "designorg.designcod", "memdelega.designcod")
|
|
@@ -111,7 +106,6 @@ export async function* findAll() {
|
|
|
111
106
|
"memdelega.memdelegadatdeb as order_date",
|
|
112
107
|
// Fonctions dans la délégation
|
|
113
108
|
jsonArrayFrom(eb2
|
|
114
|
-
.withSchema("senat")
|
|
115
109
|
.selectFrom("sens_fonmemdelega as fonmemdelega")
|
|
116
110
|
.leftJoin("sens_fondelega as fondelega", "fondelega.fondelcod", "fonmemdelega.fondelcod")
|
|
117
111
|
.select([
|
|
@@ -130,7 +124,6 @@ export async function* findAll() {
|
|
|
130
124
|
.orderBy("memdelega.memdelegadatdeb", (ob) => ob.desc().nullsLast())).as("delegations"),
|
|
131
125
|
// Groupes politiques
|
|
132
126
|
jsonArrayFrom(eb
|
|
133
|
-
.withSchema("senat")
|
|
134
127
|
.selectFrom("sens_memgrppol as memgrppol")
|
|
135
128
|
.leftJoin("sens_grppol as grppol2", "grppol2.grppolcod", "memgrppol.grppolcod")
|
|
136
129
|
.leftJoin("sens_typapppol as typapppol", "typapppol.typapppolcod", "memgrppol.typapppolcod")
|
|
@@ -146,7 +139,6 @@ export async function* findAll() {
|
|
|
146
139
|
"memgrppol.memgrppoldatdeb as order_date",
|
|
147
140
|
// Fonctions dans le groupe politique
|
|
148
141
|
jsonArrayFrom(eb2
|
|
149
|
-
.withSchema("senat")
|
|
150
142
|
.selectFrom("sens_fonmemgrppol as fonmemgrppol")
|
|
151
143
|
.leftJoin("sens_fongrppol as fongrppol", "fongrppol.fongrppolcod", "fonmemgrppol.fongrppolcod")
|
|
152
144
|
.select([
|
|
@@ -165,7 +157,6 @@ export async function* findAll() {
|
|
|
165
157
|
.orderBy("memgrppol.memgrppoldatdeb", (ob) => ob.desc().nullsLast())).as("groupes"),
|
|
166
158
|
// Organismes divers (groupes d'études, missions, etc.)
|
|
167
159
|
jsonArrayFrom(eb
|
|
168
|
-
.withSchema("senat")
|
|
169
160
|
.selectFrom("sens_memorg as memorg")
|
|
170
161
|
.leftJoin("sens_org as org", "org.orgcod", "memorg.orgcod")
|
|
171
162
|
.leftJoin("sens_typorg as typorg4", "typorg4.typorgcod", "org.typorgcod")
|
|
@@ -180,7 +171,6 @@ export async function* findAll() {
|
|
|
180
171
|
"memorg.memorgdatdeb as order_date",
|
|
181
172
|
// Fonctions dans l'organisme
|
|
182
173
|
jsonArrayFrom(eb2
|
|
183
|
-
.withSchema("senat")
|
|
184
174
|
.selectFrom("sens_fonmemorg as fonmemorg")
|
|
185
175
|
.leftJoin("sens_fonorg as fonorg", "fonorg.fonorgcod", "fonmemorg.fonorgcod")
|
|
186
176
|
.select([
|
|
@@ -199,9 +189,7 @@ export async function* findAll() {
|
|
|
199
189
|
.orderBy("memorg.memorgdatdeb", (ob) => ob.desc().nullsLast())).as("organismes"),
|
|
200
190
|
// Groupes sénatoriaux (groupes d'amitié)
|
|
201
191
|
jsonArrayFrom(eb
|
|
202
|
-
.withSchema("senat")
|
|
203
192
|
.selectFrom(eb
|
|
204
|
-
.withSchema("senat")
|
|
205
193
|
.selectFrom("sens_memgrpsen as memgrpsen_raw")
|
|
206
194
|
.select([
|
|
207
195
|
"memgrpsen_raw.orgcod as orgcod",
|
|
@@ -231,7 +219,6 @@ export async function* findAll() {
|
|
|
231
219
|
"memgrpsen.memgrpsendatent as order_date",
|
|
232
220
|
// Fonctions dans le groupe sénatorial
|
|
233
221
|
jsonArrayFrom(eb2
|
|
234
|
-
.withSchema("senat")
|
|
235
222
|
.selectFrom("sens_fonmemgrpsen as fonmemgrpsen")
|
|
236
223
|
.leftJoin("sens_fongrpsen as fongrpsen", "fongrpsen.fongrpsencod", "fonmemgrpsen.fongrpsencod")
|
|
237
224
|
.select([
|
|
@@ -249,7 +236,6 @@ export async function* findAll() {
|
|
|
249
236
|
.orderBy("memgrpsen.memgrpsendatent", (ob) => ob.desc().nullsLast())).as("groupes_senatoriaux"),
|
|
250
237
|
// Fonctions au bureau du Sénat
|
|
251
238
|
jsonArrayFrom(eb
|
|
252
|
-
.withSchema("senat")
|
|
253
239
|
.selectFrom("sens_senbur as senbur")
|
|
254
240
|
.leftJoin("sens_bur as bur", "bur.burcod", "senbur.burcod")
|
|
255
241
|
.select([
|
|
@@ -266,7 +252,6 @@ export async function* findAll() {
|
|
|
266
252
|
.orderBy("senbur.senburdatdeb", (ob) => ob.desc().nullsLast())).as("fonctions_bureau"),
|
|
267
253
|
// Points de contact
|
|
268
254
|
jsonArrayFrom(eb
|
|
269
|
-
.withSchema("senat")
|
|
270
255
|
.selectFrom("sens_poicon as poicon")
|
|
271
256
|
.select((eb2) => [
|
|
272
257
|
sql `poicon.poiconid::text`.as("id"),
|
|
@@ -275,7 +260,6 @@ export async function* findAll() {
|
|
|
275
260
|
"poicon.poiconnumtri as order_num",
|
|
276
261
|
// Adresses
|
|
277
262
|
jsonArrayFrom(eb2
|
|
278
|
-
.withSchema("senat")
|
|
279
263
|
.selectFrom("sens_adresse as adresse")
|
|
280
264
|
.select([
|
|
281
265
|
"adresse.adrnumvoi as numero_voie",
|
|
@@ -293,7 +277,6 @@ export async function* findAll() {
|
|
|
293
277
|
.orderBy("adresse.adrnumtri", "asc")).as("adresses"),
|
|
294
278
|
// Téléphones
|
|
295
279
|
jsonArrayFrom(eb2
|
|
296
|
-
.withSchema("senat")
|
|
297
280
|
.selectFrom("sens_telephone as telephone")
|
|
298
281
|
.select([
|
|
299
282
|
"telephone.typtelcod as type",
|
|
@@ -265,10 +265,6 @@ function computeCodeEtape(ev, dossier, flatActes) {
|
|
|
265
265
|
return cleanCode(genericActe.codeActe);
|
|
266
266
|
}
|
|
267
267
|
}
|
|
268
|
-
console.log(`✖ No stage code found for ev=${ev.id} (Date: ${evDate}, Nature: ${nature}, Lecture: ${lecture})`, {
|
|
269
|
-
totalActsInDossier: dossier["actes_legislatifs"]?.length || 0,
|
|
270
|
-
firstActDate: flat[0]?.date,
|
|
271
|
-
});
|
|
272
268
|
return null;
|
|
273
269
|
}
|
|
274
270
|
function buildFlatActes(dossier) {
|
package/lib/src/videos/match.js
CHANGED
|
@@ -114,7 +114,9 @@ export async function matchOneReunion(args) {
|
|
|
114
114
|
if (isSP && timeAmbigious) {
|
|
115
115
|
// Optional safety: second must also be "acceptable"
|
|
116
116
|
if (second.score >= minAccept) {
|
|
117
|
-
|
|
117
|
+
if (!options["silent"]) {
|
|
118
|
+
console.log(" - special case: séance publique + time ambigious => accepting best + secondBest (bis)");
|
|
119
|
+
}
|
|
118
120
|
return { best, secondBest: second, reason: "margin_ambiguous_time_sp" };
|
|
119
121
|
}
|
|
120
122
|
}
|
|
@@ -5,6 +5,14 @@ import { fetchText } from "./search.js";
|
|
|
5
5
|
import fs from "fs-extra";
|
|
6
6
|
import fsp from "fs/promises";
|
|
7
7
|
import path from "path";
|
|
8
|
+
function log(options, ...args) {
|
|
9
|
+
if (!options["silent"])
|
|
10
|
+
console.log(...args);
|
|
11
|
+
}
|
|
12
|
+
function warn(options, ...args) {
|
|
13
|
+
if (!options["silent"])
|
|
14
|
+
console.warn(...args);
|
|
15
|
+
}
|
|
8
16
|
function isReunion(value) {
|
|
9
17
|
return typeof value === "object" && value !== null && "uid" in value;
|
|
10
18
|
}
|
|
@@ -18,18 +26,18 @@ export async function processOneReunionMatch(args) {
|
|
|
18
26
|
finalTxt = await fsp.readFile(path.join(baseDir, "finalplayer.nvs"), "utf-8");
|
|
19
27
|
}
|
|
20
28
|
catch {
|
|
21
|
-
|
|
29
|
+
warn(options, `[skip] Missing NVS files for reunion ${reunionUid}`);
|
|
22
30
|
return;
|
|
23
31
|
}
|
|
24
32
|
const master = buildSenatVodMasterM3u8FromNvs(dataTxt);
|
|
25
33
|
if (!master) {
|
|
26
|
-
|
|
34
|
+
warn(options, `[warn] Cannot build m3u8 for reunion ${reunionUid}`);
|
|
27
35
|
return;
|
|
28
36
|
}
|
|
29
37
|
const agendaJsonPath = path.join(dataDir, AGENDA_FOLDER, DATA_TRANSFORMED_FOLDER, String(session), `${agenda.uid}.json`);
|
|
30
38
|
// Ensure it exists first.
|
|
31
39
|
if (!(await fs.pathExists(agendaJsonPath))) {
|
|
32
|
-
|
|
40
|
+
warn(options, `[warn] agenda file not found: ${agendaJsonPath}`);
|
|
33
41
|
return;
|
|
34
42
|
}
|
|
35
43
|
let timecodeDebutVideo = null;
|
|
@@ -46,7 +54,7 @@ export async function processOneReunionMatch(args) {
|
|
|
46
54
|
if (prev && prev.agendaJsonPath !== agendaJsonPath) {
|
|
47
55
|
// micro-safety: do not close with an earlier timecode
|
|
48
56
|
if (timecodeDebutVideo <= prev.start) {
|
|
49
|
-
|
|
57
|
+
warn(options, "[warn] timecode order inversion on same video: " +
|
|
50
58
|
`prev=${prev.agendaUid}(${prev.start}s) -> ` +
|
|
51
59
|
`cur=${agenda.uid}(${timecodeDebutVideo}s). ` +
|
|
52
60
|
"Skip closing prev to avoid negative segment.");
|
|
@@ -55,6 +63,7 @@ export async function processOneReunionMatch(args) {
|
|
|
55
63
|
await patchAgendaTimecodeFin({
|
|
56
64
|
agendaJsonPath: prev.agendaJsonPath,
|
|
57
65
|
timecodeFinVideo: timecodeDebutVideo,
|
|
66
|
+
options,
|
|
58
67
|
writeIfChanged,
|
|
59
68
|
});
|
|
60
69
|
}
|
|
@@ -68,11 +77,11 @@ export async function processOneReunionMatch(args) {
|
|
|
68
77
|
obj = JSON.parse(raw);
|
|
69
78
|
}
|
|
70
79
|
catch {
|
|
71
|
-
|
|
80
|
+
warn(options, `[warn] invalid JSON in ${agendaJsonPath}`);
|
|
72
81
|
return;
|
|
73
82
|
}
|
|
74
83
|
if (!isReunion(obj)) {
|
|
75
|
-
|
|
84
|
+
warn(options, `[warn] invalid reunion payload in ${agendaJsonPath}`);
|
|
76
85
|
return;
|
|
77
86
|
}
|
|
78
87
|
const next = { ...obj, urlVideo: master, startTime: agenda.startTime, urlPageVideo: best?.pageUrl };
|
|
@@ -84,10 +93,8 @@ export async function processOneReunionMatch(args) {
|
|
|
84
93
|
delete next.timecodeFinVideo;
|
|
85
94
|
}
|
|
86
95
|
await writeIfChanged(agendaJsonPath, JSON.stringify(next, null, 2));
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
(timecodeDebutVideo != null ? ` (timecodeDebutVideo ← ${timecodeDebutVideo}s)` : ""));
|
|
90
|
-
}
|
|
96
|
+
log(options, `[write] ${agenda.uid} urlVideo ← ${master}` +
|
|
97
|
+
(timecodeDebutVideo != null ? ` (timecodeDebutVideo ← ${timecodeDebutVideo}s)` : ""));
|
|
91
98
|
}
|
|
92
99
|
export async function processBisIfNeeded(args) {
|
|
93
100
|
const { agenda, secondBest, ctx, skipDownload, options, lastByVideo, writeIfChanged, processOneReunionMatch, getAgendaSegmentTimecodes, buildSenatVodMasterM3u8FromNvs, } = args;
|
|
@@ -106,7 +113,7 @@ export async function processBisIfNeeded(args) {
|
|
|
106
113
|
};
|
|
107
114
|
const baseDirBis = path.join(path.dirname(ctx.baseDir), bisUid);
|
|
108
115
|
await fs.ensureDir(baseDirBis);
|
|
109
|
-
await ensureBisAgendaJson({ agenda, agendaBis, dataDir: ctx.dataDir, session: ctx.session });
|
|
116
|
+
await ensureBisAgendaJson({ agenda, agendaBis, dataDir: ctx.dataDir, session: ctx.session, options });
|
|
110
117
|
await downloadNvsForMatch(secondBest, baseDirBis);
|
|
111
118
|
await processOneReunionMatch({
|
|
112
119
|
agenda: agendaBis,
|
|
@@ -122,26 +129,26 @@ export async function processBisIfNeeded(args) {
|
|
|
122
129
|
});
|
|
123
130
|
}
|
|
124
131
|
async function ensureBisAgendaJson(args) {
|
|
125
|
-
const { agenda, agendaBis, dataDir, session } = args;
|
|
132
|
+
const { agenda, agendaBis, dataDir, session, options } = args;
|
|
126
133
|
const agendaJsonPath = path.join(dataDir, AGENDA_FOLDER, DATA_TRANSFORMED_FOLDER, String(session), `${agenda.uid}.json`);
|
|
127
134
|
const agendaBisJsonPath = path.join(dataDir, AGENDA_FOLDER, DATA_TRANSFORMED_FOLDER, String(session), `${agendaBis.uid}.json`);
|
|
128
135
|
if (!(await fs.pathExists(agendaJsonPath))) {
|
|
129
|
-
|
|
136
|
+
warn(options, `[bis] original agenda json not found to clone: ${agendaJsonPath}`);
|
|
130
137
|
return;
|
|
131
138
|
}
|
|
132
139
|
try {
|
|
133
140
|
const raw = await fsp.readFile(agendaJsonPath, "utf-8");
|
|
134
141
|
const obj = JSON.parse(raw);
|
|
135
142
|
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
|
|
136
|
-
|
|
143
|
+
warn(options, `[bis] cannot clone agenda json: expected object in ${agendaJsonPath}, ` +
|
|
137
144
|
`got ${Array.isArray(obj) ? "array" : typeof obj}`);
|
|
138
145
|
return;
|
|
139
146
|
}
|
|
140
147
|
await writeIfChanged(agendaBisJsonPath, JSON.stringify(agendaBis, null, 2));
|
|
141
|
-
|
|
148
|
+
log(options, `[bis] created agenda json ${agendaBis.uid}.json from ${agenda.uid}.json`);
|
|
142
149
|
}
|
|
143
150
|
catch (e) {
|
|
144
|
-
|
|
151
|
+
warn(options, `[bis] cannot clone agenda json ${agenda.uid}.json -> ${agendaBis.uid}.json:`, e?.message);
|
|
145
152
|
}
|
|
146
153
|
}
|
|
147
154
|
async function downloadNvsForMatch(best, baseDir) {
|
|
@@ -164,7 +171,7 @@ export async function writeIfChanged(p, content) {
|
|
|
164
171
|
await fsp.writeFile(p, content, "utf-8");
|
|
165
172
|
}
|
|
166
173
|
async function patchAgendaTimecodeFin(args) {
|
|
167
|
-
const { agendaJsonPath, timecodeFinVideo, writeIfChanged } = args;
|
|
174
|
+
const { agendaJsonPath, timecodeFinVideo, options, writeIfChanged } = args;
|
|
168
175
|
if (!(await fs.pathExists(agendaJsonPath)))
|
|
169
176
|
return;
|
|
170
177
|
const raw = await fsp.readFile(agendaJsonPath, "utf-8");
|
|
@@ -173,11 +180,11 @@ async function patchAgendaTimecodeFin(args) {
|
|
|
173
180
|
obj = JSON.parse(raw);
|
|
174
181
|
}
|
|
175
182
|
catch {
|
|
176
|
-
|
|
183
|
+
warn(options, `[warn] invalid JSON in ${agendaJsonPath}`);
|
|
177
184
|
return;
|
|
178
185
|
}
|
|
179
186
|
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
|
|
180
|
-
|
|
187
|
+
warn(options, `[warn] invalid reunion payload in ${agendaJsonPath}`);
|
|
181
188
|
return;
|
|
182
189
|
}
|
|
183
190
|
const next = { ...obj, timecodeFinVideo };
|
|
@@ -33,6 +33,7 @@ describe("incremental import SQL", () => {
|
|
|
33
33
|
expect(sql).toContain("t.%1$I IS NOT DISTINCT FROM s.%1$I");
|
|
34
34
|
expect(sql).toContain("jsonb_build_array(");
|
|
35
35
|
expect(sql).toContain("Sync strategy for %.%: %");
|
|
36
|
+
expect(sql).toContain("ALTER TABLE senat.%I REPLICA IDENTITY FULL");
|
|
36
37
|
expect(sql).toContain("Rejected sync key for %.% from % because only %/% columns are present in both staging and target: %");
|
|
37
38
|
expect(sql).toContain("WHERE table_namespace.nspname IN ('dosleg_staging', 'senat')");
|
|
38
39
|
expect(sql).toContain("CASE WHEN table_namespace.nspname = 'dosleg_staging' THEN 0 ELSE 1 END AS schema_priority");
|
|
@@ -42,7 +43,9 @@ describe("incremental import SQL", () => {
|
|
|
42
43
|
expect(sql).toContain("LEFT JOIN target_counts reference_row USING (row_signature)");
|
|
43
44
|
expect(sql).toContain("DELETE FROM senat.%1$I t WHERE NOT EXISTS (SELECT 1 FROM %2$I.%1$I s WHERE %3$s)");
|
|
44
45
|
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)
|
|
46
|
+
expect(sql).toContain("INSERT INTO senat.%1$I (%2$s) '");
|
|
47
|
+
expect(sql).toContain("SELECT %3$s FROM %4$I.%1$I s '");
|
|
48
|
+
expect(sql).toContain("(SELECT 1 FROM senat.%1$I t WHERE %5$s)");
|
|
46
49
|
expect(sql).toContain("ALTER TABLE senat.%I ADD CONSTRAINT %I %s");
|
|
47
50
|
expect(sql).toContain("DROP SEQUENCE IF EXISTS senat.%I CASCADE");
|
|
48
51
|
expect(sql).not.toContain("DROP SCHEMA IF EXISTS dosleg_staging CASCADE;");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tricoteuses/senat",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.23",
|
|
4
4
|
"description": "Handle French Sénat's open data",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"France",
|
|
@@ -75,43 +75,45 @@
|
|
|
75
75
|
"test:prefixed-tables": "vitest tests/prefixedTables.test.ts tests/validatePrefixedTables.test.ts"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"cheerio": "^1.
|
|
79
|
-
"command-line-args": "^6.0.
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
78
|
+
"cheerio": "^1.2.0",
|
|
79
|
+
"command-line-args": "^6.0.2",
|
|
80
|
+
"domhandler": "^6.0.1",
|
|
81
|
+
"dotenv": "^17.4.2",
|
|
82
|
+
"fast-xml-parser": "^5.9.3",
|
|
83
|
+
"fs-extra": "^11.3.6",
|
|
84
|
+
"jsdom": "^29.1.1",
|
|
84
85
|
"luxon": "^3.7.2",
|
|
85
|
-
"node-stream-zip": "^1.
|
|
86
|
-
"p-limit": "^7.
|
|
87
|
-
"pg
|
|
88
|
-
"
|
|
89
|
-
"slug": "^11.0.
|
|
86
|
+
"node-stream-zip": "^1.15.0",
|
|
87
|
+
"p-limit": "^7.3.0",
|
|
88
|
+
"pg": "^8.22.0",
|
|
89
|
+
"pg-cursor": "^2.21.0",
|
|
90
|
+
"slug": "^11.0.1",
|
|
90
91
|
"windows-1252": "^3.0.4",
|
|
91
|
-
"zod": "^4.3
|
|
92
|
+
"zod": "^4.4.3"
|
|
92
93
|
},
|
|
93
94
|
"devDependencies": {
|
|
94
|
-
"@
|
|
95
|
-
"@types/command-line-args": "^5.
|
|
95
|
+
"@eslint/js": "^10.0.1",
|
|
96
|
+
"@types/command-line-args": "^5.2.3",
|
|
96
97
|
"@types/fs-extra": "^11.0.4",
|
|
97
|
-
"@types/jsdom": "^
|
|
98
|
-
"@types/luxon": "^3.7.
|
|
99
|
-
"@types/node": "^
|
|
98
|
+
"@types/jsdom": "^28.0.3",
|
|
99
|
+
"@types/luxon": "^3.7.2",
|
|
100
|
+
"@types/node": "^26.1.1",
|
|
100
101
|
"@types/pg": "^8.20.0",
|
|
101
102
|
"@types/pg-cursor": "^2.7.2",
|
|
102
103
|
"@types/slug": "^5.0.9",
|
|
103
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
104
|
-
"@typescript-eslint/parser": "^8.
|
|
104
|
+
"@typescript-eslint/eslint-plugin": "^8.63.0",
|
|
105
|
+
"@typescript-eslint/parser": "^8.63.0",
|
|
105
106
|
"cross-env": "^10.1.0",
|
|
106
|
-
"eslint": "^
|
|
107
|
-
"
|
|
108
|
-
"
|
|
107
|
+
"eslint": "^10.6.0",
|
|
108
|
+
"extract-pg-schema": "^5.8.2",
|
|
109
|
+
"globals": "^17.7.0",
|
|
110
|
+
"kanel": "^4.0.2",
|
|
109
111
|
"kanel-kysely": "^4.0.0",
|
|
110
112
|
"kanel-zod": "^4.0.0",
|
|
111
|
-
"kysely": "^0.
|
|
112
|
-
"prettier": "^3.
|
|
113
|
-
"tsx": "^4.
|
|
114
|
-
"typescript": "^
|
|
115
|
-
"vitest": "^4.
|
|
113
|
+
"kysely": "^0.29.3",
|
|
114
|
+
"prettier": "^3.9.4",
|
|
115
|
+
"tsx": "^4.23.0",
|
|
116
|
+
"typescript": "^6.0.3",
|
|
117
|
+
"vitest": "^4.1.10"
|
|
116
118
|
}
|
|
117
119
|
}
|