@tricoteuses/senat 2.10.5 → 2.11.1

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 (54) hide show
  1. package/lib/databases.d.ts +1 -28
  2. package/lib/databases.js +0 -6
  3. package/lib/datasets.d.ts +6 -0
  4. package/lib/datasets.js +233 -0
  5. package/lib/loaders.d.ts +5 -0
  6. package/lib/loaders.js +14 -9
  7. package/lib/model/ameli.d.ts +31 -143
  8. package/lib/model/ameli.js +102 -95
  9. package/lib/model/commission.d.ts +5 -0
  10. package/lib/model/commission.js +263 -0
  11. package/lib/model/debats.d.ts +13 -51
  12. package/lib/model/documents.d.ts +2 -0
  13. package/lib/model/documents.js +37 -0
  14. package/lib/model/dosleg.d.ts +9 -104
  15. package/lib/model/dosleg.js +76 -108
  16. package/lib/model/index.d.ts +4 -2
  17. package/lib/model/index.js +4 -2
  18. package/lib/model/questions.d.ts +10 -458
  19. package/lib/model/scrutins.d.ts +3 -0
  20. package/lib/model/scrutins.js +74 -0
  21. package/lib/model/{compte_rendu.js → seance.js} +47 -28
  22. package/lib/model/sens.d.ts +28 -1002
  23. package/lib/model/sens.js +65 -33
  24. package/lib/model/util.d.ts +1 -0
  25. package/lib/model/util.js +19 -1
  26. package/lib/raw_types/ameli.d.ts +778 -1521
  27. package/lib/raw_types/ameli.js +5 -345
  28. package/lib/raw_types/debats.d.ts +163 -306
  29. package/lib/raw_types/debats.js +5 -84
  30. package/lib/raw_types/dosleg.d.ts +1349 -2293
  31. package/lib/raw_types/dosleg.js +5 -550
  32. package/lib/raw_types/questions.d.ts +374 -519
  33. package/lib/raw_types/questions.js +5 -84
  34. package/lib/raw_types/senat.d.ts +11389 -0
  35. package/lib/raw_types/senat.js +5 -0
  36. package/lib/raw_types/sens.d.ts +6729 -12571
  37. package/lib/raw_types/sens.js +5 -2944
  38. package/lib/raw_types_schemats/ameli.d.ts +2 -2
  39. package/lib/raw_types_schemats/debats.d.ts +2 -2
  40. package/lib/raw_types_schemats/dosleg.d.ts +2 -2
  41. package/lib/raw_types_schemats/questions.d.ts +2 -2
  42. package/lib/raw_types_schemats/sens.d.ts +2 -2
  43. package/lib/scripts/convert_data.js +37 -31
  44. package/lib/scripts/retrieve_cr_commission.d.ts +1 -0
  45. package/lib/scripts/retrieve_cr_commission.js +291 -0
  46. package/lib/scripts/{retrieve_comptes_rendus.js → retrieve_cr_seance.js} +1 -1
  47. package/lib/scripts/retrieve_open_data.js +35 -1
  48. package/lib/utils/cr_spliting.d.ts +22 -1
  49. package/lib/utils/cr_spliting.js +273 -12
  50. package/lib/utils/reunion_grouping.d.ts +3 -0
  51. package/lib/utils/reunion_grouping.js +1 -1
  52. package/package.json +12 -11
  53. /package/lib/model/{compte_rendu.d.ts → seance.d.ts} +0 -0
  54. /package/lib/scripts/{retrieve_comptes_rendus.d.ts → retrieve_cr_seance.d.ts} +0 -0
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * AUTO-GENERATED FILE - DO NOT EDIT!
3
3
  *
4
- * This file was automatically generated by schemats v.2.9.8
5
- * $ schemats generate -c postgres://username:password@localhost:5433/ameli -t amd -t amdsen -t avicom -t avigvt -t cab -t com_ameli -t ent -t etatxt -t fbu -t grppol_ameli -t gvt -t intora -t irr -t lec_ameli -t mot -t nat -t orarol -t sai -t saisen -t sea -t sen_ameli -t ses -t sor -t sub -t txt_ameli -t typrect -t typses -t typsub -t w_nivrec -s public
4
+ * This file was automatically generated by schemats v.2.10.5
5
+ * $ schemats generate -c postgres://username:password@localhost:5433/senat -t amd -t amdsen -t avicom -t avigvt -t cab -t com_ameli -t ent -t etatxt -t fbu -t grppol_ameli -t gvt -t intora -t irr -t lec_ameli -t mot -t nat -t orarol -t sai -t saisen -t sea -t sen_ameli -t ses -t sor -t sub -t txt_ameli -t typrect -t typses -t typsub -t w_nivrec -s ameli
6
6
  *
7
7
  */
8
8
  export declare namespace amdFields {
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * AUTO-GENERATED FILE - DO NOT EDIT!
3
3
  *
4
- * This file was automatically generated by schemats v.2.9.8
5
- * $ schemats generate -c postgres://username:password@localhost:5433/debats -t debats -t intdivers -t intpjl -t lecassdeb -t secdis -t secdivers -t syndeb -t typsec -s public
4
+ * This file was automatically generated by schemats v.2.10.5
5
+ * $ schemats generate -c postgres://username:password@localhost:5433/senat -t debats -t intdivers -t intpjl -t lecassdeb -t secdis -t secdivers -t syndeb -t typsec -s debats
6
6
  *
7
7
  */
8
8
  export declare namespace debatsFields {
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * AUTO-GENERATED FILE - DO NOT EDIT!
3
3
  *
4
- * This file was automatically generated by schemats v.2.9.8
5
- * $ schemats generate -c postgres://username:password@localhost:5433/dosleg -t amescr -t ass -t aud -t auteur -t ble -t catrap -t corscr -t date_seance -t deccoc -t denrap -t doc -t docatt -t docsea -t ecr -t etaloi -t evtsea -t forpub -t gen -t lecass -t lecassrap -t lecture -t lnkrap -t loi -t loithe -t natloi -t org -t orgnomhis -t orippr -t oritxt -t posvot -t qua -t rap -t raporg -t rapthe -t rolsig -t scr -t ses -t stavot -t texte -t texte_ancien -t the -t titsen -t typatt -t typaut -t typdoc -t typevtsea -t typlec -t typloi -t typorg -t typrap -t typtxt -t typurl -t votsen -s public
4
+ * This file was automatically generated by schemats v.2.10.5
5
+ * $ schemats generate -c postgres://username:password@localhost:5433/senat -t amescr -t ass -t aud -t auteur -t ble -t catrap -t corscr -t date_seance -t deccoc -t denrap -t doc -t docatt -t docsea -t ecr -t etaloi -t evtsea -t forpub -t gen -t lecass -t lecassrap -t lecture -t lnkrap -t loi -t loithe -t natloi -t org -t orgnomhis -t orippr -t oritxt -t posvot -t qua -t rap -t raporg -t rapthe -t rolsig -t scr -t ses -t stavot -t texte -t texte_ancien -t the -t titsen -t typatt -t typaut -t typdoc -t typevtsea -t typlec -t typloi -t typorg -t typrap -t typtxt -t typurl -t votsen -s dosleg
6
6
  *
7
7
  */
8
8
  export declare namespace amescrFields {
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * AUTO-GENERATED FILE - DO NOT EDIT!
3
3
  *
4
- * This file was automatically generated by schemats v.2.9.8
5
- * $ schemats generate -c postgres://username:password@localhost:5433/questions -t etatquestion -t legquestion -t naturequestion -t sortquestion -t tam_ministeres -t tam_questions -t tam_reponses -t the -s questions
4
+ * This file was automatically generated by schemats v.2.10.5
5
+ * $ schemats generate -c postgres://username:password@localhost:5433/senat -t etatquestion -t legquestion -t naturequestion -t sortquestion -t tam_ministeres -t tam_questions -t tam_reponses -t the -s questions
6
6
  *
7
7
  */
8
8
  export declare namespace etatquestionFields {
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * AUTO-GENERATED FILE - DO NOT EDIT!
3
3
  *
4
- * This file was automatically generated by schemats v.2.9.8
5
- * $ schemats generate -c postgres://username:password@localhost:5433/sens -t acr -t activite -t activite_audit -t activite_delegation -t activite_delegation_audit -t activite_loi -t activite_loi_audit -t activite_obligatoire -t activite_participant -t activite_participant_audit -t activite_senateur -t activite_senateur_audit -t activite_senateur_params -t activite_senateur_params_audit -t activites_liees -t activites_liees_audit -t actpro -t adhgrpsen -t adr -t adresse -t adrsen -t app -t assparint -t asster -t autgrpsen -t autorisation_profil -t autorisations -t avis_nomination_art13 -t basdes -t bur -t bur3r -t bur4r -t cad -t candid -t candidat -t candtodelete -t categorie_activite -t catpro -t catpro2e -t catterrit -t cible_categorie_periode -t cirdep -t com -t con -t cotgip -t csp -t cspfam -t databasechangelog -t databasechangeloglock -t delega -t derogation -t derogation_audit -t derogation_senateur -t derogation_senateur_audit -t design -t designoep -t designorg -t discou -t div -t dpt -t dpt_seuil_presence -t dptele -t dptele_files -t dptele_processing -t dptele_processing_type -t dpttypman -t droits_acces -t droits_acces_audit -t droits_type_derogation -t ele -t eleloc -t elucan -t eludep -t eludiv -t elueur -t elueur_apf -t elumet -t elureg -t elusen -t elusen2e -t elusen3r -t elusen4r -t elusencommu -t elusenpair -t eluter -t elutit -t eluvil -t etadebman -t etadebman3r -t etadebman4r -t etafinman -t etafinman3r -t etafinman4r -t etaprr -t etarpm -t etasen -t ext2e_bio -t ext2e_csp -t ext2e_mandats -t ext2e_minist -t extsencom_identite -t extsencom_mandat -t fonact_participant -t foncandid -t foncom -t fondelega -t fongrppol -t fongrpsen -t fonmemcom -t fonmemdelega -t fonmemextpar -t fonmemgrppol -t fonmemgrpsen -t fonmemorg -t fonorg -t grppol -t grppol4r -t grpsenami -t grpsenamiadh -t grpsenamiadhreq -t grpsenamiadhreqeta -t grpsenamiunadh -t grpsim -t gvt -t insee_pays2008 -t jhi_authority -t jhi_user -t jhi_user_authority -t lanetr -t libcom -t libdelega -t libgrppol -t libgrpsen -t liborg -t lisdptele -t mel -t memcom -t memcomsea -t memdelega -t memextpar -t memgrppol -t memgrpsen -t memorg -t met -t minind -t minist -t mis -t misetafin -t mismin -t misrapeta -t missen -t moddes -t mode_acces_elusenpair -t nation -t nationgrpsen -t nivlan -t org -t orgext -t orgextpres -t orgthe -t pairie_elusenpair -t parpol -t parpolglo -t participa -t pcs -t pcs24 -t pcs42 -t pcs8 -t pcscatpro -t per -t per_sen -t perapp -t periode_presence -t perpolglo -t perrol -t pj_justificatif -t pj_justificatif_audit -t plaind -t plan_table -t plsql_profiler_runs -t plsql_profiler_units -t poicon -t posvot -t presences_scrutin_surcharge -t presencesrevisionentity -t profil_applicatif -t qua -t rap_the -t reg -t reladr -t requetes_profil -t reslis -t resultat -t reu -t revchanges -t rne_mandat -t rne_mandat_diff -t rne_sen -t rne_sen_diff -t rne_type_mandat -t rol -t sal -t scr -t scrusoldelega -t sea -t sec -t sec2e -t secexe -t sen -t senbur -t senbur3r -t senbur4r -t sennom -t senpj -t sensim -t sentablenom -t senurl -t seuil_presence -t sirpas_elusen -t sirpas_fonmemcom -t sirpas_fonmemdelega -t sirpas_fonmemgrppol -t sirpas_memcom -t sirpas_memdelega -t sirpas_memgrppol -t sirpas_mvt -t sirpas_mvtcm -t sirpas_mvttri -t sirpas_sen -t sirpas_senbur -t sirpas_trf -t srv -t stajur -t stavot -t suspensiontravaux -t suspensiontravaux_audit -t sysage -t syscognos -t sysevt -t sysvar -t sysvar_sendev -t sysvar_senprod -t tapsenrevchanges -t tapsenrevisionentity -t telephone -t temval -t tenpol -t territ -t testoracle -t titele -t titelerne -t titmin -t titnob -t tmpsd -t toutes -t turelu -t typadr -t typapppol -t typbister -t typcandid -t type_activite -t type_activite_participant -t type_activite_rol -t type_activite_senateur -t type_categorie -t type_derogation -t type_droit_acces -t type_pj_justificatif -t type_rne_diff -t type_type_derogation -t typele -t typgrpsen -t typman -t typmin -t typmoddes -t typorg -t typorgext -t typparpol -t typpoicon -t typprs -t typprssta -t typscr -t typtel -t typurl -t typvoi -t uploaded_file -t uploaded_file_type -t validation -t validation_defview_profil -t validation_profil -t vercand -t verres -t votes -t zongeo -s public
4
+ * This file was automatically generated by schemats v.2.10.5
5
+ * $ schemats generate -c postgres://username:password@localhost:5433/senat -t acr -t activite -t activite_audit -t activite_delegation -t activite_delegation_audit -t activite_loi -t activite_loi_audit -t activite_obligatoire -t activite_participant -t activite_participant_audit -t activite_senateur -t activite_senateur_audit -t activite_senateur_params -t activite_senateur_params_audit -t activites_liees -t activites_liees_audit -t actpro -t adhgrpsen -t adr -t adresse -t adrsen -t app -t assparint -t asster -t autgrpsen -t autorisation_profil -t autorisations -t avis_nomination_art13 -t basdes -t bur -t bur3r -t bur4r -t cad -t candid -t candidat -t candtodelete -t categorie_activite -t catpro -t catpro2e -t catterrit -t cible_categorie_periode -t cirdep -t com -t con -t cotgip -t csp -t cspfam -t databasechangelog -t databasechangeloglock -t delega -t derogation -t derogation_audit -t derogation_senateur -t derogation_senateur_audit -t design -t designoep -t designorg -t discou -t div -t dpt -t dpt_seuil_presence -t dptele -t dptele_files -t dptele_processing -t dptele_processing_type -t dpttypman -t droits_acces -t droits_acces_audit -t droits_type_derogation -t ele -t eleloc -t elucan -t eludep -t eludiv -t elueur -t elueur_apf -t elumet -t elureg -t elusen -t elusen2e -t elusen3r -t elusen4r -t elusencommu -t elusenpair -t eluter -t elutit -t eluvil -t etadebman -t etadebman3r -t etadebman4r -t etafinman -t etafinman3r -t etafinman4r -t etaprr -t etarpm -t etasen -t ext2e_bio -t ext2e_csp -t ext2e_mandats -t ext2e_minist -t extsencom_identite -t extsencom_mandat -t fonact_participant -t foncandid -t foncom -t fondelega -t fongrppol -t fongrpsen -t fonmemcom -t fonmemdelega -t fonmemextpar -t fonmemgrppol -t fonmemgrpsen -t fonmemorg -t fonorg -t grppol -t grppol4r -t grpsenami -t grpsenamiadh -t grpsenamiadhreq -t grpsenamiadhreqeta -t grpsenamiunadh -t grpsim -t gvt -t insee_pays2008 -t jhi_authority -t jhi_user -t jhi_user_authority -t lanetr -t libcom -t libdelega -t libgrppol -t libgrpsen -t liborg -t lisdptele -t mel -t memcom -t memcomsea -t memdelega -t memextpar -t memgrppol -t memgrpsen -t memorg -t met -t minind -t minist -t mis -t misetafin -t mismin -t misrapeta -t missen -t moddes -t mode_acces_elusenpair -t nation -t nationgrpsen -t nivlan -t org -t orgext -t orgextpres -t orgthe -t pairie_elusenpair -t parpol -t parpolglo -t participa -t pcs -t pcs24 -t pcs42 -t pcs8 -t pcscatpro -t per -t per_sen -t perapp -t periode_presence -t perpolglo -t perrol -t pj_justificatif -t pj_justificatif_audit -t plaind -t plan_table -t plsql_profiler_runs -t plsql_profiler_units -t poicon -t posvot -t presences_scrutin_surcharge -t presencesrevisionentity -t profil_applicatif -t qua -t rap_the -t reg -t reladr -t requetes_profil -t reslis -t resultat -t reu -t revchanges -t rne_mandat -t rne_mandat_diff -t rne_sen -t rne_sen_diff -t rne_type_mandat -t rol -t sal -t scr -t scrusoldelega -t sea -t sec -t sec2e -t secexe -t sen -t senbur -t senbur3r -t senbur4r -t sennom -t senpj -t sensim -t sentablenom -t senurl -t seuil_presence -t sirpas_elusen -t sirpas_fonmemcom -t sirpas_fonmemdelega -t sirpas_fonmemgrppol -t sirpas_memcom -t sirpas_memdelega -t sirpas_memgrppol -t sirpas_mvt -t sirpas_mvtcm -t sirpas_mvttri -t sirpas_sen -t sirpas_senbur -t sirpas_trf -t srv -t stajur -t stavot -t suspensiontravaux -t suspensiontravaux_audit -t sysage -t syscognos -t sysevt -t sysvar -t sysvar_sendev -t sysvar_senprod -t tapsenrevchanges -t tapsenrevisionentity -t telephone -t temval -t tenpol -t territ -t testoracle -t titele -t titelerne -t titmin -t titnob -t tmpsd -t toutes -t turelu -t typadr -t typapppol -t typbister -t typcandid -t type_activite -t type_activite_participant -t type_activite_rol -t type_activite_senateur -t type_categorie -t type_derogation -t type_droit_acces -t type_pj_justificatif -t type_rne_diff -t type_type_derogation -t typele -t typgrpsen -t typman -t typmin -t typmoddes -t typorg -t typorgext -t typparpol -t typpoicon -t typprs -t typprssta -t typscr -t typtel -t typurl -t typvoi -t uploaded_file -t uploaded_file_type -t validation -t validation_defview_profil -t validation_profil -t vercand -t verres -t votes -t zongeo -s sens
6
6
  *
7
7
  */
8
8
  export declare namespace acrFields {
@@ -21,14 +21,14 @@ async function convertData() {
21
21
  const enabledDatasets = getEnabledDatasets(options["categories"]);
22
22
  console.time("data transformation time");
23
23
  if (enabledDatasets & EnabledDatasets.Ameli) {
24
- await convertDatasetAmeli(dataDir);
24
+ await convertDatasetAmeli(dataDir, options);
25
25
  }
26
26
  if (enabledDatasets & EnabledDatasets.Debats) {
27
- await convertDatasetDebats(dataDir);
27
+ await convertDatasetDebats(dataDir, options);
28
28
  }
29
29
  if (enabledDatasets & EnabledDatasets.DosLeg) {
30
- await convertDatasetDosLeg(dataDir);
31
- await convertDatasetScrutins(dataDir);
30
+ await convertDatasetDosLeg(dataDir, options);
31
+ await convertDatasetScrutins(dataDir, options);
32
32
  }
33
33
  if (enabledDatasets & EnabledDatasets.Questions) {
34
34
  await convertDatasetQuestions(dataDir);
@@ -40,27 +40,27 @@ async function convertData() {
40
40
  console.timeEnd("data transformation time");
41
41
  }
42
42
  }
43
- async function convertDatasetAmeli(dataDir) {
43
+ async function convertDatasetAmeli(dataDir, options) {
44
44
  const dataset = datasets.ameli;
45
45
  if (!options["silent"]) {
46
46
  console.log(`Converting database ${dataset.database} data into files…`);
47
47
  }
48
48
  const ameliReorganizedRootDir = path.join(dataDir, dataset.database);
49
49
  ensureAndClearDir(ameliReorganizedRootDir);
50
- for await (const amendement of findAllAmendements()) {
50
+ for await (const amendement of findAllAmendements(options["fromSession"])) {
51
51
  if (options["verbose"]) {
52
- console.log(`Converting ${amendement.numero} file…`);
52
+ console.log(`Converting ${amendement["numero"]} file…`);
53
53
  }
54
- const session = String(amendement.session) || UNDEFINED_SESSION;
55
- const signetDossierLegislatif = amendement.signet_dossier_legislatif ||
56
- `${amendement.nature_texte}-${amendement.numero_texte}`.toLowerCase();
54
+ const session = String(amendement["session"]) || UNDEFINED_SESSION;
55
+ const signetDossierLegislatif = amendement["signet_dossier_legislatif"] ||
56
+ `${amendement["nature_texte"]}-${amendement["numero_texte"]}`.toLowerCase();
57
57
  const ameliReorganizedDir = path.join(ameliReorganizedRootDir, String(session), signetDossierLegislatif);
58
58
  fs.ensureDirSync(ameliReorganizedDir);
59
- const amendementFileName = `${amendement.numero}.json`;
59
+ const amendementFileName = `${amendement["numero"]}.json`;
60
60
  fs.writeJSONSync(path.join(ameliReorganizedDir, amendementFileName), amendement, { spaces: 2 });
61
61
  }
62
62
  }
63
- async function convertDatasetDebats(dataDir) {
63
+ async function convertDatasetDebats(dataDir, options) {
64
64
  const dataset = datasets.debats;
65
65
  if (!options["silent"]) {
66
66
  console.log(`Converting database ${dataset.database} data into files…`);
@@ -74,6 +74,9 @@ async function convertDatasetDebats(dataDir) {
74
74
  }
75
75
  const enrichedDebat = await enrichDebat(debat, allAuteurs);
76
76
  const session = getSessionFromDate(enrichedDebat.date_seance);
77
+ if (options["fromSession"] && session < options["fromSession"]) {
78
+ continue;
79
+ }
77
80
  const debatsReorganizedDir = path.join(debatsReorganizedRootDir, String(session));
78
81
  fs.ensureDirSync(debatsReorganizedDir);
79
82
  const debatFileName = `${enrichedDebat.id}.json`;
@@ -84,12 +87,12 @@ async function enrichDebat(debat, auteurs) {
84
87
  const enrichedDebat = { ...debat };
85
88
  for (const section of enrichedDebat.sections) {
86
89
  for (const intervention of section.interventions) {
87
- intervention.auteur = findAuteur(intervention.auteur_code, auteurs);
90
+ intervention.auteur = findAuteur(intervention["auteur_code"], auteurs);
88
91
  }
89
92
  }
90
93
  for (const section of enrichedDebat.sections_divers) {
91
94
  for (const intervention of section.interventions) {
92
- intervention.auteur = findAuteur(intervention.auteur_code, auteurs);
95
+ intervention.auteur = findAuteur(intervention["auteur_code"], auteurs);
93
96
  }
94
97
  }
95
98
  return enrichedDebat;
@@ -97,7 +100,7 @@ async function enrichDebat(debat, auteurs) {
97
100
  function findAuteur(auteurCode, auteurs) {
98
101
  return auteurs.find(auteur => auteur.code === auteurCode);
99
102
  }
100
- async function convertDatasetDosLeg(dataDir) {
103
+ async function convertDatasetDosLeg(dataDir, options) {
101
104
  const dataset = datasets.dosleg;
102
105
  if (!options["silent"]) {
103
106
  console.log(`Converting database ${dataset.database} data into files…`);
@@ -112,6 +115,9 @@ async function convertDatasetDosLeg(dataDir) {
112
115
  }
113
116
  let loiReorganizedDir = path.join(dossiersReorganizedDir, String(UNDEFINED_SESSION));
114
117
  const session = getSessionFromSignet(loi["signet"]) || UNDEFINED_SESSION;
118
+ if (options["fromSession"] && session < options["fromSession"]) {
119
+ continue;
120
+ }
115
121
  loiReorganizedDir = path.join(dossiersReorganizedDir, String(session));
116
122
  fs.ensureDirSync(loiReorganizedDir);
117
123
  const scrutinFileName = `${loi["signet"]}.json`;
@@ -122,22 +128,22 @@ async function convertDatasetDosLeg(dataDir) {
122
128
  await convertTexteUrls(dataDir);
123
129
  await convertRapportUrls(dataDir);
124
130
  }
125
- async function convertDatasetScrutins(dataDir) {
131
+ async function convertDatasetScrutins(dataDir, options) {
126
132
  const dataset = datasets.dosleg;
127
133
  if (!options["silent"]) {
128
- console.log(`Converting database ${dataset.database} scrutins data into files…`);
134
+ console.log(`Converting database scrutins (${dataset.database}) data into files…`);
129
135
  }
130
136
  const scrutinsReorganizedDir = path.join(dataDir, SCRUTINS_FOLDER);
131
137
  ensureAndClearDir(scrutinsReorganizedDir);
132
- for await (const scrutin of findAllScrutins()) {
138
+ for await (const scrutin of findAllScrutins(options["fromSession"])) {
133
139
  if (options["verbose"]) {
134
- console.log(`Converting ${scrutin.numero} file…`);
140
+ console.log(`Converting ${scrutin["numero"]} file…`);
135
141
  }
136
142
  let scrutinReorganizedDir = path.join(scrutinsReorganizedDir, String(UNDEFINED_SESSION));
137
- const session = scrutin.session || UNDEFINED_SESSION;
143
+ const session = scrutin["session"] || UNDEFINED_SESSION;
138
144
  scrutinReorganizedDir = path.join(scrutinsReorganizedDir, String(session));
139
145
  fs.ensureDirSync(scrutinReorganizedDir);
140
- const scrutinFileName = `${scrutin.numero}.json`;
146
+ const scrutinFileName = `${scrutin["numero"]}.json`;
141
147
  fs.writeJSONSync(path.join(scrutinReorganizedDir, scrutinFileName), scrutin, {
142
148
  spaces: 2,
143
149
  });
@@ -152,12 +158,12 @@ async function convertDatasetQuestions(dataDir) {
152
158
  ensureAndClearDir(questionsReorganizedRootDir);
153
159
  for await (const question of findAllQuestions()) {
154
160
  if (options["verbose"]) {
155
- console.log(`Converting ${question.reference} file…`);
161
+ console.log(`Converting ${question["reference"]} file…`);
156
162
  }
157
- const legislature = question.legislature ? question.legislature : 0;
163
+ const legislature = question["legislature"] ? question["legislature"] : 0;
158
164
  const questionReorganizedDir = path.join(questionsReorganizedRootDir, String(legislature));
159
165
  fs.ensureDirSync(questionReorganizedDir);
160
- const questionFileName = `${question.reference}.json`;
166
+ const questionFileName = `${question["reference"]}.json`;
161
167
  fs.writeJSONSync(path.join(questionReorganizedDir, questionFileName), question, { spaces: 2 });
162
168
  }
163
169
  }
@@ -229,26 +235,26 @@ async function convertDatasetSens(dataDir) {
229
235
  ensureAndClearDir(organismesReorganizedDir);
230
236
  for await (const sen of findAllSens()) {
231
237
  if (options["verbose"]) {
232
- console.log(`Converting ${sen.matricule} file…`);
238
+ console.log(`Converting ${sen["matricule"]} file…`);
233
239
  }
234
- const senFileName = `${sen.matricule}.json`;
240
+ const senFileName = `${sen["matricule"]}.json`;
235
241
  fs.writeJSONSync(path.join(senateursReorganizedDir, senFileName), sen, {
236
242
  spaces: 2,
237
243
  });
238
244
  }
239
245
  for await (const circonscription of findAllCirconscriptions()) {
240
246
  if (options["verbose"]) {
241
- console.log(`Converting ${circonscription.identifiant} file…`);
247
+ console.log(`Converting ${circonscription["identifiant"]} file…`);
242
248
  }
243
- const circonscriptionFileName = `${circonscription.identifiant}.json`;
249
+ const circonscriptionFileName = `${circonscription["identifiant"]}.json`;
244
250
  fs.writeJSONSync(path.join(circonscriptionsReorganizedDir, circonscriptionFileName), circonscription, { spaces: 2 });
245
251
  }
246
252
  for await (const organisme of findAllOrganismes()) {
247
253
  if (options["verbose"]) {
248
- console.log(`Converting ${organisme.code} file…`);
254
+ console.log(`Converting ${organisme["code"]} file…`);
249
255
  }
250
- const organismeFileName = `${organisme.code}.json`;
251
- const organismeDir = path.join(organismesReorganizedDir, organisme.type_code);
256
+ const organismeFileName = `${organisme["code"]}.json`;
257
+ const organismeDir = path.join(organismesReorganizedDir, organisme["type_code"]);
252
258
  fs.ensureDirSync(organismeDir);
253
259
  fs.writeJSONSync(path.join(organismeDir, organismeFileName), organisme, { spaces: 2 });
254
260
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,291 @@
1
+ import fs from "fs-extra";
2
+ import assert from "assert";
3
+ import path from "path";
4
+ import * as cheerio from "cheerio";
5
+ import { COMMISSION_FOLDER, DATA_ORIGINAL_FOLDER, DATA_TRANSFORMED_FOLDER } from "../loaders";
6
+ import { createCommissionGroupIfMissing, loadCommissionAgendaForDate, parseCommissionMetadataFromHtml, } from "../utils/cr_spliting";
7
+ import { parseCommissionCRFromFile } from "../model/commission";
8
+ import commandLineArgs from "command-line-args";
9
+ import { commonOptions } from "./shared/cli_helpers";
10
+ import { sessionStartYearFromDate } from "../model/seance";
11
+ import { getSessionsFromStart } from "../types/sessions";
12
+ import { ensureAndClearDir, fetchWithRetry } from "./shared/util";
13
+ class CommissionCRDownloadError extends Error {
14
+ constructor(message, url) {
15
+ super(`An error occurred while retrieving Commission CR ${url}: ${message}`);
16
+ }
17
+ }
18
+ const optionsDefinitions = [
19
+ ...commonOptions,
20
+ { name: "concurrency", type: Number, defaultValue: 6, help: "Max parallel downloads" },
21
+ { name: "politenessMs", type: Number, defaultValue: 150, help: "Delay per worker (ms)" },
22
+ {
23
+ help: "parse and convert comptes-rendus des débats into JSON",
24
+ name: "parseDebats",
25
+ type: Boolean,
26
+ },
27
+ ];
28
+ const options = commandLineArgs(optionsDefinitions);
29
+ const COMMISSION_HUBS = {
30
+ "affaires-etrangeres": [
31
+ "https://www.senat.fr/compte-rendu-commissions/affaires-etrangeres.html",
32
+ "https://www.senat.fr/compte-rendu-commissions/affaires-etrangeres_archives.html",
33
+ ],
34
+ "affaires-economiques": [
35
+ "https://www.senat.fr/compte-rendu-commissions/affaires-economiques.html",
36
+ "https://www.senat.fr/compte-rendu-commissions/affaires-economiques_archives.html",
37
+ ],
38
+ "amenagement-developpement-durable": [
39
+ "https://www.senat.fr/compte-rendu-commissions/cadre-de-vie-et-developpement-durable.html",
40
+ "https://www.senat.fr/compte-rendu-commissions/cadre-de-vie-et-developpement-durable_archives.html",
41
+ ],
42
+ culture: [
43
+ "https://www.senat.fr/compte-rendu-commissions/culture.html",
44
+ "https://www.senat.fr/compte-rendu-commissions/culture_archives.html",
45
+ ],
46
+ finances: [
47
+ "https://www.senat.fr/compte-rendu-commissions/finances.html",
48
+ "https://www.senat.fr/compte-rendu-commissions/finances_archives.html",
49
+ ],
50
+ lois: [
51
+ "https://www.senat.fr/compte-rendu-commissions/lois.html",
52
+ "https://www.senat.fr/compte-rendu-commissions/lois_archives.html",
53
+ ],
54
+ "affaires-sociales": [
55
+ "https://www.senat.fr/compte-rendu-commissions/affaires-sociales.html",
56
+ "https://www.senat.fr/compte-rendu-commissions/affaires-sociales_archives.html",
57
+ ],
58
+ "affaires-europeennes": [
59
+ "https://www.senat.fr/compte-rendu-commissions/affaires-europeennes.html",
60
+ "https://www.senat.fr/compte-rendu-commissions/affaires-europeennes_archives.html",
61
+ ],
62
+ };
63
+ async function harvestWeeklyLinksFromHub(hubUrl) {
64
+ const res = await fetchWithRetry(hubUrl);
65
+ if (!res.ok)
66
+ return [];
67
+ const html = await res.text();
68
+ const $ = cheerio.load(html);
69
+ const out = [];
70
+ $("a[href]").each((_, a) => {
71
+ const href = ($(a).attr("href") || "").trim();
72
+ const m = href.match(/\/compte-rendu-commissions\/(\d{8})\/([a-z0-9\-]+)\.html$/i);
73
+ if (m) {
74
+ const url = href.startsWith("http") ? href : new URL(href, hubUrl).toString();
75
+ out.push(url);
76
+ }
77
+ });
78
+ return Array.from(new Set(out));
79
+ }
80
+ async function discoverCommissionWeeklyPages(fromSession) {
81
+ const results = [];
82
+ for (const [commissionKey, hubs] of Object.entries(COMMISSION_HUBS)) {
83
+ for (const hubUrl of hubs) {
84
+ try {
85
+ const links = await harvestWeeklyLinksFromHub(hubUrl);
86
+ for (const url of links) {
87
+ const m = url.match(/\/compte-rendu-commissions\/(\d{8})\/([a-z0-9\-]+)\.html$/i);
88
+ if (!m)
89
+ continue;
90
+ const yyyymmdd = m[1];
91
+ const year = Number(yyyymmdd.slice(0, 4));
92
+ const month = Number(yyyymmdd.slice(4, 6));
93
+ const session = month >= 10 ? year : year - 1;
94
+ if (session < fromSession)
95
+ continue;
96
+ results.push({ url, yyyymmdd, commissionKey });
97
+ }
98
+ }
99
+ catch (e) {
100
+ console.warn(`[COM-CR][hub-fail] ${hubUrl} → ${e?.message ?? e}`);
101
+ }
102
+ }
103
+ }
104
+ return results.sort((a, b) => a.yyyymmdd.localeCompare(b.yyyymmdd));
105
+ }
106
+ function toHourShort(hhmm) {
107
+ if (!hhmm)
108
+ return null;
109
+ const m = hhmm.match(/^(\d{2}):(\d{2})$/);
110
+ return m ? `${m[1]}${m[2]}` : null;
111
+ }
112
+ function timeToMinutes(hhmm) {
113
+ const [h, m] = hhmm.split(":").map((n) => parseInt(n, 10));
114
+ return (h || 0) * 60 + (m || 0);
115
+ }
116
+ async function tryDownload(url) {
117
+ const res = await fetch(url, { redirect: "follow" });
118
+ if (res.status === 404)
119
+ return null;
120
+ if (!res.ok)
121
+ throw new CommissionCRDownloadError(String(res.status), url);
122
+ const ab = await res.arrayBuffer();
123
+ return Buffer.from(ab);
124
+ }
125
+ async function retrieveCommissionCRs(options = {}) {
126
+ const dataDir = options["dataDir"];
127
+ const fromSession = Number(options["fromSession"]);
128
+ const concurrency = Number(options["concurrency"] ?? 6);
129
+ const politenessMs = Number(options["politenessMs"] ?? 150);
130
+ const commissionsRootDir = path.join(dataDir, COMMISSION_FOLDER);
131
+ const originalRoot = path.join(commissionsRootDir, DATA_ORIGINAL_FOLDER);
132
+ ensureAndClearDir(originalRoot);
133
+ const discovered = await discoverCommissionWeeklyPages(fromSession);
134
+ console.log(`[COM-CR][discover] ${discovered.length} links (>= session ${fromSession})`);
135
+ const jobs = discovered.map(({ url, yyyymmdd }) => {
136
+ const d = new Date(Number(yyyymmdd.slice(0, 4)), Number(yyyymmdd.slice(4, 6)) - 1, Number(yyyymmdd.slice(6, 8)));
137
+ const session = sessionStartYearFromDate(d);
138
+ const dir = path.join(originalRoot, String(session));
139
+ fs.ensureDirSync(dir);
140
+ const slug = url.replace(/^.*\/(\d{8})\/([^\/]+)\.html$/i, "$2");
141
+ const outPath = path.join(dir, `${yyyymmdd}.${slug}.html`);
142
+ return { url, outPath, yyyymmdd };
143
+ });
144
+ console.log(`[COM-CR] Downloading ${jobs.length} links → ${path.relative(process.cwd(), originalRoot)}`);
145
+ let completed = 0, saved = 0, skipped = 0, notFound = 0;
146
+ const workers = Array.from({ length: Math.max(1, concurrency) }, async () => {
147
+ while (true) {
148
+ const job = jobs.shift();
149
+ if (!job)
150
+ break;
151
+ const { url, outPath, yyyymmdd } = job;
152
+ try {
153
+ if (await fs.pathExists(outPath)) {
154
+ skipped++;
155
+ }
156
+ else {
157
+ const buf = await tryDownload(url);
158
+ if (!buf) {
159
+ notFound++;
160
+ console.warn(`[COM-CR][404] ${url} → week=${yyyymmdd}`);
161
+ }
162
+ else {
163
+ await fs.writeFile(outPath, buf);
164
+ saved++;
165
+ }
166
+ }
167
+ }
168
+ catch (e) {
169
+ console.error(`[COM-CR][err] ${url} → ${e?.message || e}`);
170
+ }
171
+ finally {
172
+ completed++;
173
+ if (politenessMs > 0)
174
+ await new Promise((r) => setTimeout(r, politenessMs));
175
+ }
176
+ }
177
+ });
178
+ await Promise.all(workers);
179
+ console.log(`[COM-CR] done: saved=${saved} | skipped=${skipped} | 404=${notFound} | total=${completed}`);
180
+ const sessions = getSessionsFromStart(options["fromSession"]);
181
+ const comRoot = path.join(dataDir, COMMISSION_FOLDER);
182
+ const transformedRoot = path.join(comRoot, DATA_TRANSFORMED_FOLDER);
183
+ if (options["parseDebats"])
184
+ ensureAndClearDir(transformedRoot);
185
+ for (const session of sessions) {
186
+ const originalSessionDir = path.join(originalRoot, String(session));
187
+ const transformedSessionDir = path.join(transformedRoot, String(session));
188
+ fs.ensureDirSync(transformedSessionDir);
189
+ if (!(await fs.pathExists(originalSessionDir)))
190
+ continue;
191
+ const htmlFiles = (await fs.readdir(originalSessionDir)).filter((f) => /\.html?$/i.test(f)).sort();
192
+ for (const f of htmlFiles) {
193
+ const htmlPath = path.join(originalSessionDir, f);
194
+ let meta;
195
+ try {
196
+ const raw = await fs.readFile(htmlPath, "utf8");
197
+ meta = parseCommissionMetadataFromHtml(raw, f);
198
+ }
199
+ catch (e) {
200
+ console.warn(`[COM-CR][PRE][${session}] Cannot read/parse ${f}:`, e);
201
+ continue;
202
+ }
203
+ const organeKeywords = (meta.organeDetected ?? meta.organeTitleRaw ?? "")
204
+ .toLowerCase()
205
+ .replace(/[’']/g, "'")
206
+ .split(/\W+/)
207
+ .filter((x) => x.length >= 3 && !["commission", "des", "de", "du", "d", "la", "le", "les", "et"].includes(x));
208
+ const MAX_TIME_DELTA_MIN = 120;
209
+ for (let i = 0; i < meta.days.length; i++) {
210
+ const day = meta.days[i];
211
+ const yyyymmdd = day.date.replace(/-/g, "");
212
+ const dt = new Date(Number(day.date.slice(0, 4)), Number(day.date.slice(5, 7)) - 1, Number(day.date.slice(8, 10)));
213
+ const daySession = sessionStartYearFromDate(dt);
214
+ const hits = await loadCommissionAgendaForDate(dataDir, yyyymmdd, daySession);
215
+ let best = null;
216
+ let reason = "fallback-none";
217
+ let deltaMin;
218
+ // a) score by title and organe keywords
219
+ if (organeKeywords.length && hits.length) {
220
+ const scored = hits
221
+ .map((h) => {
222
+ const t = (h.titre ?? "").toLowerCase();
223
+ const s = organeKeywords.reduce((acc, kw) => acc + (t.includes(kw) ? 1 : 0), 0);
224
+ return { h, s };
225
+ })
226
+ .sort((a, b) => b.s - a.s);
227
+ if (scored[0]?.s > 0) {
228
+ best = scored[0].h;
229
+ reason = "title";
230
+ }
231
+ }
232
+ // b) otherwise score by time proximity
233
+ if (!best && day.openTime && hits.length) {
234
+ const candidates = hits
235
+ .map((h) => ({ h, hhmm: h.startTime ?? null }))
236
+ .filter((x) => !!x.hhmm)
237
+ .map((x) => ({
238
+ h: x.h,
239
+ d: Math.abs(timeToMinutes(x.hhmm) - timeToMinutes(day.openTime)),
240
+ }))
241
+ .sort((a, b) => a.d - b.d);
242
+ if (candidates[0] && candidates[0].d <= MAX_TIME_DELTA_MIN) {
243
+ best = candidates[0].h;
244
+ reason = "time";
245
+ deltaMin = candidates[0].d;
246
+ }
247
+ }
248
+ if (best) {
249
+ const cr = parseCommissionCRFromFile(htmlPath, best);
250
+ if (!cr) {
251
+ console.warn(`[COM-CR][TRANSFORM] parse failed for ${f} → ${best.uid}`);
252
+ }
253
+ else {
254
+ const fileUid = cr.uid;
255
+ const outPath = path.join(transformedSessionDir, `${fileUid}.json`);
256
+ await fs.writeJSON(outPath, cr, { spaces: 2 });
257
+ const npts = Array.isArray(cr.contenu.point) ? cr.contenu.point.length : cr.contenu.point ? 1 : 0;
258
+ if (!options["silent"]) {
259
+ console.log(`[COM-CR][TRANSFORM] saved ${path.basename(outPath)} (points=${npts})`);
260
+ }
261
+ }
262
+ }
263
+ else {
264
+ const hourShort = toHourShort(day.openTime) ?? "NA";
265
+ const titreGuess = meta.organeDetected || meta.organeTitleRaw || "Commission";
266
+ const { uid, filePath } = await createCommissionGroupIfMissing(dataDir, day.date, meta.organeDetected ?? null, hourShort, titreGuess);
267
+ if (!options["silent"]) {
268
+ console.log(`[COM-CR][PRE-SPLIT][${session}] ${f} | ${day.date}` +
269
+ (day.openTime ? ` ${day.openTime}` : ``) +
270
+ ` → NO-MATCH → CREATED uid=${uid} file=${path.basename(filePath)}`);
271
+ }
272
+ }
273
+ }
274
+ }
275
+ }
276
+ }
277
+ async function main() {
278
+ const dataDir = options["dataDir"];
279
+ assert(dataDir, "Missing argument: data directory");
280
+ console.time("CRI processing time");
281
+ await retrieveCommissionCRs(options);
282
+ if (!options["silent"]) {
283
+ console.timeEnd("CRI processing time");
284
+ }
285
+ }
286
+ main()
287
+ .then(() => process.exit(0))
288
+ .catch((error) => {
289
+ console.error(error);
290
+ process.exit(1);
291
+ });
@@ -11,7 +11,7 @@ import StreamZip from "node-stream-zip";
11
11
  import * as cheerio from "cheerio";
12
12
  import { AGENDA_FOLDER, COMPTES_RENDUS_FOLDER, DATA_ORIGINAL_FOLDER, DATA_TRANSFORMED_FOLDER, } from "../loaders";
13
13
  import { commonOptions } from "./shared/cli_helpers";
14
- import { deriveTitreObjetFromSommaire, parseCompteRenduSlotFromFile, parseYYYYMMDD, sessionStartYearFromDate } from "../model/compte_rendu";
14
+ import { deriveTitreObjetFromSommaire, parseCompteRenduSlotFromFile, parseYYYYMMDD, sessionStartYearFromDate } from "../model/seance";
15
15
  import { makeGroupUid } from "../utils/reunion_grouping";
16
16
  import { getSessionsFromStart } from "../types/sessions";
17
17
  import { ensureAndClearDir, fetchWithRetry } from "./shared/util";
@@ -202,6 +202,30 @@ async function retrieveDataset(dataDir, dataset) {
202
202
  console.log(`Importing ${dataset.title}: ${sqlFilename}…`);
203
203
  }
204
204
  await copyToSenat(dataset, dataDir, options);
205
+ // Create indexes programmatically after import
206
+ if (dataset.indexes) {
207
+ for (const [table, indexes] of Object.entries(dataset.indexes)) {
208
+ for (const index of indexes) {
209
+ const indexName = index.name;
210
+ const columns = index.columns.join(", ");
211
+ const schema = dataset.database;
212
+ const sql = `CREATE INDEX IF NOT EXISTS ${indexName} ON ${schema}.${table} (${columns});`;
213
+ try {
214
+ execSync(`${options["sudo"] ? `sudo -u ${options["sudo"]} ` : ""}psql --quiet -d senat -c "${sql}"`, {
215
+ env: process.env,
216
+ encoding: "utf-8",
217
+ stdio: ["ignore", "ignore", "pipe"],
218
+ });
219
+ if (!options["silent"]) {
220
+ console.log(`Created index: ${indexName} on ${schema}.${table} (${columns})`);
221
+ }
222
+ }
223
+ catch (err) {
224
+ console.error(`Failed to create index ${indexName} on ${schema}.${table}:`, err);
225
+ }
226
+ }
227
+ }
228
+ }
205
229
  }
206
230
  if (options["schema"]) {
207
231
  let definitionsDir = path.resolve("src", "raw_types_schemats");
@@ -224,7 +248,7 @@ async function retrieveDataset(dataDir, dataset) {
224
248
  fs.writeFileSync(definitionFilePath, definitionRepaired);
225
249
  definitionsDir = path.resolve("src", "raw_types");
226
250
  definitionFilePath = path.join(definitionsDir, `${dataset.database}.ts`);
227
- execSync(`npx pg-to-ts generate -c '${dbConnectionString}' -s ${dataset.database} -o ${definitionFilePath}`, {
251
+ execSync(`npx kysely-codegen --url '${dbConnectionString}' --default-schema ${dataset.database} --include-pattern '${dataset.database}.*' --out-file ${definitionFilePath}`, {
228
252
  env: process.env,
229
253
  encoding: "utf-8",
230
254
  // stdio: ["ignore", "ignore", "pipe"],
@@ -261,6 +285,16 @@ async function retrieveOpenData() {
261
285
  for (const dataset of chosenDatasets) {
262
286
  await retrieveDataset(dataDir, dataset);
263
287
  }
288
+ if (options["schema"]) {
289
+ const dbConnectionString = `postgres://${process.env["PGUSER"]}:${process.env["PGPASSWORD"]}@${process.env["PGHOST"]}:${process.env["PGPORT"]}/senat`;
290
+ const definitionsDir = path.resolve("src", "raw_types");
291
+ const definitionFilePath = path.join(definitionsDir, `senat.ts`);
292
+ execSync(`npx kysely-codegen --url '${dbConnectionString}' --out-file ${definitionFilePath}`, {
293
+ env: process.env,
294
+ encoding: "utf-8",
295
+ // stdio: ["ignore", "ignore", "pipe"],
296
+ });
297
+ }
264
298
  if (!options["silent"]) {
265
299
  console.timeEnd("data extraction time");
266
300
  }
@@ -1,7 +1,28 @@
1
- import { TimeSlot } from "../types/agenda";
1
+ import { GroupedReunion, TimeSlot } from "../types/agenda";
2
2
  import * as cheerio from "cheerio";
3
3
  export declare function computeIntervalsBySlot($: cheerio.CheerioAPI, idx: Map<any, number>, firstSlotOfDay?: TimeSlot): {
4
4
  slot: TimeSlot;
5
5
  start: number;
6
6
  end: number;
7
7
  }[];
8
+ export declare function parseCommissionMetadataFromHtml(html: string, sourceFileName?: string): {
9
+ sourceFile: string | null;
10
+ organeTitleRaw: string | null;
11
+ organeDetected: string | null;
12
+ organeCode: string | null;
13
+ weekStart: string | null;
14
+ days: {
15
+ date: string;
16
+ openTime?: string;
17
+ h2Index: number;
18
+ }[];
19
+ };
20
+ export declare function loadCommissionAgendaForDate(dataDir: string, yyyymmdd: string, session: number): Promise<GroupedReunion[]>;
21
+ export declare function createCommissionGroupIfMissing(dataDir: string, dateISO: string, // "YYYY-MM-DD"
22
+ organeDetected: string | null, // ex. "Commission des finances"
23
+ hourShort: string | null, // "HHMM" | "NA"
24
+ titreGuess?: string | null): Promise<{
25
+ uid: string;
26
+ filePath: string;
27
+ created: boolean;
28
+ }>;