@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.
Files changed (328) hide show
  1. package/README.md +168 -0
  2. package/lib/aggregates.d.ts +52 -0
  3. package/lib/aggregates.js +930 -0
  4. package/lib/aggregates.mjs +713 -0
  5. package/lib/aggregates.ts +833 -0
  6. package/lib/config.d.ts +10 -0
  7. package/lib/config.js +16 -0
  8. package/lib/config.mjs +16 -0
  9. package/lib/config.ts +26 -0
  10. package/lib/databases.d.ts +2 -0
  11. package/lib/databases.js +26 -0
  12. package/lib/databases.mjs +57 -0
  13. package/lib/databases.ts +71 -0
  14. package/lib/datasets.d.ts +34 -0
  15. package/lib/datasets.js +233 -0
  16. package/lib/datasets.mjs +78 -0
  17. package/lib/datasets.ts +118 -0
  18. package/lib/fields.d.ts +10 -0
  19. package/lib/fields.js +68 -0
  20. package/lib/fields.mjs +22 -0
  21. package/lib/fields.ts +29 -0
  22. package/lib/git.d.ts +26 -0
  23. package/lib/git.js +167 -0
  24. package/lib/index.d.ts +13 -0
  25. package/lib/index.js +1 -0
  26. package/lib/index.mjs +7 -0
  27. package/lib/index.ts +64 -0
  28. package/lib/inserters.d.ts +98 -0
  29. package/lib/inserters.js +500 -0
  30. package/lib/inserters.mjs +360 -0
  31. package/lib/inserters.ts +521 -0
  32. package/lib/legislatures.json +38 -0
  33. package/lib/loaders.d.ts +58 -0
  34. package/lib/loaders.js +286 -0
  35. package/lib/loaders.mjs +158 -0
  36. package/lib/loaders.ts +271 -0
  37. package/lib/model/agenda.d.ts +6 -0
  38. package/lib/model/agenda.js +148 -0
  39. package/lib/model/ameli.d.ts +51 -0
  40. package/lib/model/ameli.js +149 -0
  41. package/lib/model/ameli.mjs +84 -0
  42. package/lib/model/ameli.ts +100 -0
  43. package/lib/model/commission.d.ts +18 -0
  44. package/lib/model/commission.js +269 -0
  45. package/lib/model/debats.d.ts +67 -0
  46. package/lib/model/debats.js +95 -0
  47. package/lib/model/debats.mjs +43 -0
  48. package/lib/model/debats.ts +68 -0
  49. package/lib/model/documents.d.ts +12 -0
  50. package/lib/model/documents.js +151 -0
  51. package/lib/model/dosleg.d.ts +7 -0
  52. package/lib/model/dosleg.js +326 -0
  53. package/lib/model/dosleg.mjs +196 -0
  54. package/lib/model/dosleg.ts +240 -0
  55. package/lib/model/index.d.ts +7 -0
  56. package/lib/model/index.js +7 -0
  57. package/lib/model/index.mjs +5 -0
  58. package/lib/model/index.ts +15 -0
  59. package/lib/model/questions.d.ts +45 -0
  60. package/lib/model/questions.js +89 -0
  61. package/lib/model/questions.mjs +71 -0
  62. package/lib/model/questions.ts +93 -0
  63. package/lib/model/scrutins.d.ts +13 -0
  64. package/lib/model/scrutins.js +114 -0
  65. package/lib/model/seance.d.ts +3 -0
  66. package/lib/model/seance.js +267 -0
  67. package/lib/model/sens.d.ts +146 -0
  68. package/lib/model/sens.js +454 -0
  69. package/lib/model/sens.mjs +415 -0
  70. package/lib/model/sens.ts +516 -0
  71. package/lib/model/texte.d.ts +7 -0
  72. package/lib/model/texte.js +256 -0
  73. package/lib/model/texte.mjs +208 -0
  74. package/lib/model/texte.ts +229 -0
  75. package/lib/model/util.d.ts +9 -0
  76. package/lib/model/util.js +38 -0
  77. package/lib/model/util.mjs +19 -0
  78. package/lib/model/util.ts +32 -0
  79. package/lib/parsers/texte.d.ts +7 -0
  80. package/lib/parsers/texte.js +228 -0
  81. package/lib/raw_types/ameli.d.ts +914 -0
  82. package/lib/raw_types/ameli.js +5 -0
  83. package/lib/raw_types/ameli.mjs +163 -0
  84. package/lib/raw_types/debats.d.ts +207 -0
  85. package/lib/raw_types/debats.js +5 -0
  86. package/lib/raw_types/debats.mjs +58 -0
  87. package/lib/raw_types/dosleg.d.ts +1619 -0
  88. package/lib/raw_types/dosleg.js +5 -0
  89. package/lib/raw_types/dosleg.mjs +438 -0
  90. package/lib/raw_types/questions.d.ts +419 -0
  91. package/lib/raw_types/questions.js +5 -0
  92. package/lib/raw_types/questions.mjs +11 -0
  93. package/lib/raw_types/senat.d.ts +11368 -0
  94. package/lib/raw_types/senat.js +5 -0
  95. package/lib/raw_types/sens.d.ts +8248 -0
  96. package/lib/raw_types/sens.js +5 -0
  97. package/lib/raw_types/sens.mjs +508 -0
  98. package/lib/raw_types_kysely/ameli.d.ts +915 -0
  99. package/lib/raw_types_kysely/ameli.js +7 -0
  100. package/lib/raw_types_kysely/ameli.mjs +5 -0
  101. package/lib/raw_types_kysely/ameli.ts +951 -0
  102. package/lib/raw_types_kysely/debats.d.ts +207 -0
  103. package/lib/raw_types_kysely/debats.js +7 -0
  104. package/lib/raw_types_kysely/debats.mjs +5 -0
  105. package/lib/raw_types_kysely/debats.ts +222 -0
  106. package/lib/raw_types_kysely/dosleg.d.ts +3532 -0
  107. package/lib/raw_types_kysely/dosleg.js +7 -0
  108. package/lib/raw_types_kysely/dosleg.mjs +5 -0
  109. package/lib/raw_types_kysely/dosleg.ts +3621 -0
  110. package/lib/raw_types_kysely/questions.d.ts +414 -0
  111. package/lib/raw_types_kysely/questions.js +7 -0
  112. package/lib/raw_types_kysely/questions.mjs +5 -0
  113. package/lib/raw_types_kysely/questions.ts +426 -0
  114. package/lib/raw_types_kysely/sens.d.ts +4394 -0
  115. package/lib/raw_types_kysely/sens.js +7 -0
  116. package/lib/raw_types_kysely/sens.mjs +5 -0
  117. package/lib/raw_types_kysely/sens.ts +4499 -0
  118. package/lib/raw_types_schemats/ameli.d.ts +539 -0
  119. package/lib/raw_types_schemats/ameli.js +2 -0
  120. package/lib/raw_types_schemats/ameli.mjs +2 -0
  121. package/lib/raw_types_schemats/ameli.ts +601 -0
  122. package/lib/raw_types_schemats/debats.d.ts +127 -0
  123. package/lib/raw_types_schemats/debats.js +2 -0
  124. package/lib/raw_types_schemats/debats.mjs +2 -0
  125. package/lib/raw_types_schemats/debats.ts +145 -0
  126. package/lib/raw_types_schemats/dosleg.d.ts +977 -0
  127. package/lib/raw_types_schemats/dosleg.js +2 -0
  128. package/lib/raw_types_schemats/dosleg.mjs +2 -0
  129. package/lib/raw_types_schemats/dosleg.ts +2193 -0
  130. package/lib/raw_types_schemats/questions.d.ts +235 -0
  131. package/lib/raw_types_schemats/questions.js +2 -0
  132. package/lib/raw_types_schemats/questions.mjs +2 -0
  133. package/lib/raw_types_schemats/questions.ts +249 -0
  134. package/lib/raw_types_schemats/sens.d.ts +6915 -0
  135. package/lib/raw_types_schemats/sens.js +2 -0
  136. package/lib/raw_types_schemats/sens.mjs +2 -0
  137. package/lib/raw_types_schemats/sens.ts +2907 -0
  138. package/lib/scripts/convert_data.d.ts +1 -0
  139. package/lib/scripts/convert_data.js +354 -0
  140. package/lib/scripts/convert_data.mjs +181 -0
  141. package/lib/scripts/convert_data.ts +243 -0
  142. package/lib/scripts/data-download.d.ts +1 -0
  143. package/lib/scripts/data-download.js +12 -0
  144. package/lib/scripts/datautil.d.ts +8 -0
  145. package/lib/scripts/datautil.js +34 -0
  146. package/lib/scripts/datautil.mjs +16 -0
  147. package/lib/scripts/datautil.ts +19 -0
  148. package/lib/scripts/images/transparent_150x192.jpg +0 -0
  149. package/lib/scripts/images/transparent_155x225.jpg +0 -0
  150. package/lib/scripts/parse_textes.d.ts +1 -0
  151. package/lib/scripts/parse_textes.js +44 -0
  152. package/lib/scripts/parse_textes.mjs +46 -0
  153. package/lib/scripts/parse_textes.ts +65 -0
  154. package/lib/scripts/retrieve_agenda.d.ts +1 -0
  155. package/lib/scripts/retrieve_agenda.js +132 -0
  156. package/lib/scripts/retrieve_cr_commission.d.ts +1 -0
  157. package/lib/scripts/retrieve_cr_commission.js +364 -0
  158. package/lib/scripts/retrieve_cr_seance.d.ts +6 -0
  159. package/lib/scripts/retrieve_cr_seance.js +347 -0
  160. package/lib/scripts/retrieve_documents.d.ts +3 -0
  161. package/lib/scripts/retrieve_documents.js +219 -0
  162. package/lib/scripts/retrieve_documents.mjs +249 -0
  163. package/lib/scripts/retrieve_documents.ts +298 -0
  164. package/lib/scripts/retrieve_open_data.d.ts +1 -0
  165. package/lib/scripts/retrieve_open_data.js +315 -0
  166. package/lib/scripts/retrieve_open_data.mjs +217 -0
  167. package/lib/scripts/retrieve_open_data.ts +268 -0
  168. package/lib/scripts/retrieve_senateurs_photos.d.ts +1 -0
  169. package/lib/scripts/retrieve_senateurs_photos.js +147 -0
  170. package/lib/scripts/retrieve_senateurs_photos.mjs +147 -0
  171. package/lib/scripts/retrieve_senateurs_photos.ts +177 -0
  172. package/lib/scripts/retrieve_videos.d.ts +1 -0
  173. package/lib/scripts/retrieve_videos.js +461 -0
  174. package/lib/scripts/shared/cli_helpers.d.ts +95 -0
  175. package/lib/scripts/shared/cli_helpers.js +91 -0
  176. package/lib/scripts/shared/cli_helpers.ts +36 -0
  177. package/lib/scripts/shared/util.d.ts +4 -0
  178. package/lib/scripts/shared/util.js +35 -0
  179. package/lib/scripts/shared/util.ts +33 -0
  180. package/lib/scripts/test_iter_load.d.ts +1 -0
  181. package/lib/scripts/test_iter_load.js +12 -0
  182. package/lib/src/config.d.ts +22 -0
  183. package/lib/src/config.js +17 -7
  184. package/lib/src/conversion_textes.js +5 -1
  185. package/lib/src/databases.d.ts +2 -1
  186. package/lib/src/databases_postgres.d.ts +4 -0
  187. package/lib/src/databases_postgres.js +23 -0
  188. package/lib/src/datasets.d.ts +4 -0
  189. package/lib/src/datasets.js +16 -2
  190. package/lib/src/git.d.ts +1 -0
  191. package/lib/src/git.js +45 -11
  192. package/lib/src/loaders.js +10 -4
  193. package/lib/src/model/agenda.js +2 -2
  194. package/lib/src/model/ameli.d.ts +64 -52
  195. package/lib/src/model/ameli.js +147 -145
  196. package/lib/src/model/ameli_postgres.d.ts +67 -0
  197. package/lib/src/model/ameli_postgres.js +150 -0
  198. package/lib/src/model/commission.d.ts +3 -2
  199. package/lib/src/model/commission.js +2 -2
  200. package/lib/src/model/debats.d.ts +38 -66
  201. package/lib/src/model/debats.js +110 -93
  202. package/lib/src/model/documents.d.ts +32 -12
  203. package/lib/src/model/documents.js +171 -130
  204. package/lib/src/model/dosleg.d.ts +142 -5
  205. package/lib/src/model/dosleg.js +298 -156
  206. package/lib/src/model/questions.d.ts +54 -45
  207. package/lib/src/model/questions.js +89 -87
  208. package/lib/src/model/scrutins.d.ts +48 -13
  209. package/lib/src/model/scrutins.js +118 -111
  210. package/lib/src/model/seance.js +3 -3
  211. package/lib/src/model/sens.d.ts +109 -179
  212. package/lib/src/model/sens.js +384 -484
  213. package/lib/src/model/util.d.ts +0 -8
  214. package/lib/src/model/util.js +0 -23
  215. package/lib/src/parsers/texte.js +7 -7
  216. package/lib/src/raw_types_schemats/ameli.d.ts +4 -2
  217. package/lib/src/raw_types_schemats/debats.d.ts +2 -2
  218. package/lib/src/raw_types_schemats/dosleg.d.ts +2 -2
  219. package/lib/src/raw_types_schemats/questions.d.ts +2 -2
  220. package/lib/src/raw_types_schemats/sens.d.ts +10 -4216
  221. package/lib/src/scripts/convert_data.js +7 -6
  222. package/lib/src/scripts/convert_xml_to_html.js +2 -2
  223. package/lib/src/scripts/data-download.js +3 -2
  224. package/lib/src/scripts/retrieve_agenda.js +21 -9
  225. package/lib/src/scripts/retrieve_cr_commission.js +17 -17
  226. package/lib/src/scripts/retrieve_cr_seance.d.ts +14 -1
  227. package/lib/src/scripts/retrieve_cr_seance.js +10 -11
  228. package/lib/src/scripts/retrieve_documents.d.ts +11 -2
  229. package/lib/src/scripts/retrieve_documents.js +25 -14
  230. package/lib/src/scripts/retrieve_open_data.js +400 -145
  231. package/lib/src/scripts/retrieve_senateurs_photos.js +25 -11
  232. package/lib/src/scripts/retrieve_videos.js +12 -11
  233. package/lib/src/scripts/shared/cli_helpers.d.ts +1 -6
  234. package/lib/src/scripts/shared/cli_helpers.js +9 -8
  235. package/lib/src/scripts/shared/incremental_import_sql.d.ts +2 -0
  236. package/lib/src/scripts/shared/incremental_import_sql.js +894 -0
  237. package/lib/src/scripts/shared/prefixed_tables.d.ts +7 -0
  238. package/lib/src/scripts/shared/prefixed_tables.js +30 -0
  239. package/lib/src/scripts/shared/schema_version.d.ts +3 -0
  240. package/lib/src/scripts/shared/schema_version.js +97 -0
  241. package/lib/src/scripts/shared/staging_import.d.ts +3 -0
  242. package/lib/src/scripts/shared/staging_import.js +80 -0
  243. package/lib/src/scripts/shared/staging_metadata_sql.d.ts +1 -0
  244. package/lib/src/scripts/shared/staging_metadata_sql.js +221 -0
  245. package/lib/src/scripts/validate_prefixed_tables.d.ts +1 -0
  246. package/lib/src/scripts/validate_prefixed_tables.js +102 -0
  247. package/lib/src/types/texte.d.ts +1 -1
  248. package/lib/src/utils/cr_spliting.d.ts +9 -6
  249. package/lib/src/utils/cr_spliting.js +6 -101
  250. package/lib/src/utils/reunion_odj_building.d.ts +7 -3
  251. package/lib/src/utils/reunion_parsing.d.ts +2 -1
  252. package/lib/src/utils/reunion_parsing.js +2 -2
  253. package/lib/src/videos/match.js +8 -5
  254. package/lib/src/videos/pipeline.d.ts +6 -2
  255. package/lib/src/videos/pipeline.js +21 -8
  256. package/lib/src/videos/search.js +6 -2
  257. package/lib/strings.d.ts +1 -0
  258. package/lib/strings.js +18 -0
  259. package/lib/strings.mjs +18 -0
  260. package/lib/strings.ts +26 -0
  261. package/lib/tests/incrementalImportSql.test.d.ts +1 -0
  262. package/lib/tests/incrementalImportSql.test.js +155 -0
  263. package/lib/tests/prefixedTables.test.d.ts +1 -0
  264. package/lib/tests/prefixedTables.test.js +29 -0
  265. package/lib/tests/schemaVersion.test.d.ts +1 -0
  266. package/lib/tests/schemaVersion.test.js +23 -0
  267. package/lib/tests/validatePrefixedTables.test.d.ts +1 -0
  268. package/lib/tests/validatePrefixedTables.test.js +14 -0
  269. package/lib/types/agenda.d.ts +44 -0
  270. package/lib/types/agenda.js +1 -0
  271. package/lib/types/ameli.d.ts +5 -0
  272. package/lib/types/ameli.js +1 -0
  273. package/lib/types/ameli.mjs +13 -0
  274. package/lib/types/ameli.ts +21 -0
  275. package/lib/types/compte_rendu.d.ts +83 -0
  276. package/lib/types/compte_rendu.js +1 -0
  277. package/lib/types/debats.d.ts +2 -0
  278. package/lib/types/debats.js +1 -0
  279. package/lib/types/debats.mjs +2 -0
  280. package/lib/types/debats.ts +6 -0
  281. package/lib/types/dosleg.d.ts +70 -0
  282. package/lib/types/dosleg.js +1 -0
  283. package/lib/types/dosleg.mjs +151 -0
  284. package/lib/types/dosleg.ts +284 -0
  285. package/lib/types/questions.d.ts +2 -0
  286. package/lib/types/questions.js +1 -0
  287. package/lib/types/questions.mjs +1 -0
  288. package/lib/types/questions.ts +3 -0
  289. package/lib/types/sens.d.ts +10 -0
  290. package/lib/types/sens.js +1 -0
  291. package/lib/types/sens.mjs +1 -0
  292. package/lib/types/sens.ts +12 -0
  293. package/lib/types/sessions.d.ts +5 -0
  294. package/lib/types/sessions.js +84 -0
  295. package/lib/types/sessions.mjs +43 -0
  296. package/lib/types/sessions.ts +42 -0
  297. package/lib/types/texte.d.ts +74 -0
  298. package/lib/types/texte.js +16 -0
  299. package/lib/types/texte.mjs +16 -0
  300. package/lib/types/texte.ts +76 -0
  301. package/lib/typings/windows-1252.d.js +2 -0
  302. package/lib/typings/windows-1252.d.mjs +2 -0
  303. package/lib/typings/windows-1252.d.ts +11 -0
  304. package/lib/utils/cr_spliting.d.ts +28 -0
  305. package/lib/utils/cr_spliting.js +265 -0
  306. package/lib/utils/date.d.ts +10 -0
  307. package/lib/utils/date.js +100 -0
  308. package/lib/utils/nvs-timecode.d.ts +7 -0
  309. package/lib/utils/nvs-timecode.js +79 -0
  310. package/lib/utils/reunion_grouping.d.ts +9 -0
  311. package/lib/utils/reunion_grouping.js +361 -0
  312. package/lib/utils/reunion_odj_building.d.ts +5 -0
  313. package/lib/utils/reunion_odj_building.js +154 -0
  314. package/lib/utils/reunion_parsing.d.ts +23 -0
  315. package/lib/utils/reunion_parsing.js +209 -0
  316. package/lib/utils/scoring.d.ts +14 -0
  317. package/lib/utils/scoring.js +147 -0
  318. package/lib/utils/string_cleaning.d.ts +7 -0
  319. package/lib/utils/string_cleaning.js +57 -0
  320. package/lib/validators/config.d.ts +9 -0
  321. package/lib/validators/config.js +10 -0
  322. package/lib/validators/config.mjs +54 -0
  323. package/lib/validators/config.ts +79 -0
  324. package/lib/validators/senat.d.ts +0 -0
  325. package/lib/validators/senat.js +28 -0
  326. package/lib/validators/senat.mjs +24 -0
  327. package/lib/validators/senat.ts +26 -0
  328. 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 o && typeof o === "object" && typeof o.uid === "string" && typeof o.date === "string";
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
- export declare function buildOdj(events: AgendaEvent[], dossierBySenatUrl: Record<string, DossierLegislatifResult>): ReunionOdj | undefined;
5
- export declare function buildSenatDossierIndex(options: commandLineArgs.CommandLineOptions): Record<string, DossierLegislatifResult>;
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<any, number>): SommaireBlock[];
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 (/\instances\b|\decisionelles\b/.test(s))
86
+ if (/instances\b|decisionelles\b/.test(s))
87
87
  return "ID";
88
88
  return null;
89
89
  }
@@ -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
- const minAcceptBase = weights?.minAccept;
12
- const margin = weights?.margin;
13
- const titleDominance = weights?.titleDominance;
14
- const orgUncertainPenalty = weights?.orgUncertainPenalty; // 1 disables
15
- const orgSkipDice = weights?.orgSkipDice;
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: Record<string, any>;
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: Record<string, any>;
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 (e) {
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(`[warn] timecode order inversion on same video: ` +
47
- `prev=${prev.agendaUid}(${prev.start}s) -> cur=${agenda.uid}(${timecodeDebutVideo}s). ` +
48
- `Skip closing prev to avoid negative segment.`);
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 (e) {
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, options });
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, options } = args;
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}, got ${Array.isArray(obj) ? "array" : typeof obj}`);
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
  }
@@ -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 (videotype=${searchParams.videotype}, organe=${searchParams.organe || "-"}, date=${searchParams.begin || "-"})`);
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 (videotype=${searchParams.videotype}, organe=${searchParams.organe || "-"}, date=${searchParams.begin || "-"})`);
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
  }
@@ -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
+ }
@@ -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
+ });