@tricoteuses/senat 3.1.10 → 3.1.12

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 (87) hide show
  1. package/lib/src/config.d.ts +43 -0
  2. package/lib/src/config.js +37 -0
  3. package/lib/src/conversion_textes.d.ts +11 -0
  4. package/lib/src/conversion_textes.js +320 -0
  5. package/lib/src/databases_postgres.d.ts +4 -0
  6. package/lib/src/databases_postgres.js +23 -0
  7. package/lib/src/datasets.d.ts +38 -0
  8. package/lib/src/datasets.js +247 -0
  9. package/lib/src/git.d.ts +27 -0
  10. package/lib/src/git.js +251 -0
  11. package/lib/src/loaders.d.ts +52 -0
  12. package/lib/src/loaders.js +260 -0
  13. package/lib/src/model/agenda.d.ts +6 -0
  14. package/lib/src/model/agenda.js +148 -0
  15. package/lib/src/model/ameli.d.ts +67 -0
  16. package/lib/src/model/ameli.js +150 -0
  17. package/lib/src/model/commission.d.ts +19 -0
  18. package/lib/src/model/commission.js +269 -0
  19. package/lib/src/model/debats.d.ts +39 -0
  20. package/lib/src/model/debats.js +112 -0
  21. package/lib/src/model/documents.d.ts +32 -0
  22. package/lib/src/model/documents.js +182 -0
  23. package/lib/src/model/dosleg.d.ts +144 -0
  24. package/lib/src/model/dosleg.js +468 -0
  25. package/lib/src/model/index.d.ts +7 -0
  26. package/lib/src/model/index.js +7 -0
  27. package/lib/src/model/questions.d.ts +54 -0
  28. package/lib/src/model/questions.js +91 -0
  29. package/lib/src/model/scrutins.d.ts +48 -0
  30. package/lib/src/model/scrutins.js +121 -0
  31. package/lib/src/model/seance.d.ts +3 -0
  32. package/lib/src/model/seance.js +267 -0
  33. package/lib/src/model/sens.d.ts +112 -0
  34. package/lib/src/model/sens.js +385 -0
  35. package/lib/src/model/util.d.ts +1 -0
  36. package/lib/src/model/util.js +15 -0
  37. package/lib/src/raw_types/ameli.d.ts +1762 -0
  38. package/lib/src/raw_types/ameli.js +1074 -0
  39. package/lib/src/raw_types/debats.d.ts +380 -0
  40. package/lib/src/raw_types/debats.js +266 -0
  41. package/lib/src/raw_types/dosleg.d.ts +2954 -0
  42. package/lib/src/raw_types/dosleg.js +2005 -0
  43. package/lib/src/raw_types/questions.d.ts +699 -0
  44. package/lib/src/raw_types/questions.js +493 -0
  45. package/lib/src/raw_types/sens.d.ts +7843 -0
  46. package/lib/src/raw_types/sens.js +4691 -0
  47. package/lib/src/raw_types_schemats/ameli.d.ts +541 -0
  48. package/lib/src/raw_types_schemats/ameli.js +2 -0
  49. package/lib/src/raw_types_schemats/debats.d.ts +127 -0
  50. package/lib/src/raw_types_schemats/debats.js +2 -0
  51. package/lib/src/raw_types_schemats/dosleg.d.ts +977 -0
  52. package/lib/src/raw_types_schemats/dosleg.js +2 -0
  53. package/lib/src/raw_types_schemats/questions.d.ts +237 -0
  54. package/lib/src/raw_types_schemats/questions.js +2 -0
  55. package/lib/src/raw_types_schemats/sens.d.ts +2709 -0
  56. package/lib/src/raw_types_schemats/sens.js +2 -0
  57. package/lib/src/rich_types/dosleg.d.ts +74 -0
  58. package/lib/src/rich_types/dosleg.js +34 -14
  59. package/lib/src/scripts/debug_dosleg_query.d.ts +6 -0
  60. package/lib/src/scripts/debug_dosleg_query.js +50 -0
  61. package/lib/src/scripts/retrieve_agenda.js +6 -6
  62. package/lib/src/server/dosleg.js +170 -3
  63. package/lib/src/types/agenda.d.ts +45 -0
  64. package/lib/src/types/agenda.js +1 -0
  65. package/lib/src/types/ameli.d.ts +5 -0
  66. package/lib/src/types/ameli.js +1 -0
  67. package/lib/src/types/compte_rendu.d.ts +83 -0
  68. package/lib/src/types/compte_rendu.js +1 -0
  69. package/lib/src/types/debats.d.ts +2 -0
  70. package/lib/src/types/debats.js +1 -0
  71. package/lib/src/types/dosleg.d.ts +70 -0
  72. package/lib/src/types/dosleg.js +1 -0
  73. package/lib/src/types/questions.d.ts +2 -0
  74. package/lib/src/types/questions.js +1 -0
  75. package/lib/src/types/sens.d.ts +8 -0
  76. package/lib/src/types/sens.js +1 -0
  77. package/lib/src/types/sessions.d.ts +6 -0
  78. package/lib/src/types/sessions.js +19 -0
  79. package/lib/src/types/texte.d.ts +72 -0
  80. package/lib/src/types/texte.js +15 -0
  81. package/lib/src/utils/reunion_odj_building.d.ts +7 -1
  82. package/lib/src/utils/reunion_odj_building.js +144 -21
  83. package/lib/src/utils/reunion_parsing.d.ts +3 -2
  84. package/lib/src/utils/reunion_parsing.js +4 -4
  85. package/lib/src/validators/config.d.ts +9 -0
  86. package/lib/src/validators/config.js +10 -0
  87. package/package.json +1 -1
@@ -0,0 +1,83 @@
1
+ export interface CompteRendu {
2
+ uid: string;
3
+ seanceRef: string;
4
+ sessionRef: string;
5
+ metadonnees: Metadonnees;
6
+ contenu: Contenu;
7
+ }
8
+ export interface Metadonnees {
9
+ dateSeance: string;
10
+ dateSeanceJour: string;
11
+ numSeanceJour: string;
12
+ numSeance: string;
13
+ typeAssemblee: "AN" | "SN";
14
+ legislature: string;
15
+ session: string;
16
+ nomFichierJo: string;
17
+ validite: string;
18
+ etat: string;
19
+ diffusion: string;
20
+ version: string;
21
+ environnement: string;
22
+ heureGeneration: Date;
23
+ sommaire?: Sommaire;
24
+ }
25
+ export interface Contenu {
26
+ quantiemes: Quantiemes;
27
+ ouvertureSeance?: Point[] | Point;
28
+ point: Point[] | Point;
29
+ finSeance?: FinSeance;
30
+ paragraphe?: Point[];
31
+ }
32
+ export interface FinSeance {
33
+ point: Point;
34
+ }
35
+ export interface Quantiemes {
36
+ journee: string;
37
+ session: string;
38
+ }
39
+ export interface Point {
40
+ ordre_absolu_seance: string;
41
+ code_grammaire: string;
42
+ roledebat?: string;
43
+ orateurs?: {
44
+ orateur: {
45
+ nom: string;
46
+ id: string;
47
+ qualite: string;
48
+ };
49
+ };
50
+ texte: {
51
+ _: string;
52
+ };
53
+ code_style?: string;
54
+ }
55
+ export interface Texte {
56
+ _?: string;
57
+ id_syceron?: string;
58
+ stime?: string;
59
+ sup?: string;
60
+ lienAdt?: Texte[] | Texte;
61
+ }
62
+ export interface Sommaire {
63
+ presidentSeance: Texte;
64
+ sommaire1: SommaireElement[] | SommaireElement;
65
+ sommaire3?: SommaireElement[] | SommaireElement;
66
+ sommaire2?: SommaireElement[] | SommaireElement;
67
+ para?: Texte[] | Texte;
68
+ }
69
+ export interface SommaireElement {
70
+ valeur_pts_odj: string | undefined;
71
+ titreStruct: TitreStruct;
72
+ para?: Array<Texte | string> | Texte;
73
+ sommaire2?: SommaireElement[] | SommaireElement;
74
+ sommaire3?: SommaireElement[] | SommaireElement;
75
+ presidentSeance?: Texte[] | Texte;
76
+ type_debat?: string;
77
+ }
78
+ export interface TitreStruct {
79
+ id_syceron: string;
80
+ intitule?: string;
81
+ sousIntitule?: string;
82
+ type_debat?: string;
83
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { debats as Debat, lecassdeb as LecAssDeb } from "../raw_types_schemats/debats";
2
+ export type { Debat, LecAssDeb };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,70 @@
1
+ import { Ass, Aud as AudRaw, Auteur as AuteurRaw, DateSeance as DateSeanceRaw, Deccoc, Denrap, Docatt as DocattRaw, Ecr as EcrRaw, Etaloi, Lecass as LecassRaw, Lecassrap as LecassrapRaw, Lecture as LectureRaw, Loi as LoiRaw, Org, Oritxt, Qua, Rap as RapRaw, Raporg, Scr, Texte as TexteRaw, Typatt, Typlec, Typloi, Typtxt, Typurl } from "../raw_types/dosleg.js";
2
+ import { TxtAmeli } from "../raw_types/ameli.js";
3
+ import { Debats } from "../raw_types/debats.js";
4
+ import { TxtAmeliCustom } from "./ameli.js";
5
+ export type { Deccoc as DecCoc, Denrap as DenRap, Etaloi as EtaLoi, Oritxt as OriTxt, Raporg as RapOrg, Typatt as TypAtt, Typlec as TypLec, Typloi as TypLoi, Typtxt as TypTxt, Typurl as TypUrl, };
6
+ export interface Aud extends AudRaw {
7
+ org?: Org;
8
+ }
9
+ export interface Auteur extends AuteurRaw {
10
+ qua?: Qua;
11
+ }
12
+ export interface DateSeance extends DateSeanceRaw {
13
+ debat?: Debats;
14
+ scrids?: string[];
15
+ scrs?: Scr[];
16
+ }
17
+ export interface DocAtt extends DocattRaw {
18
+ rap?: RapRaw;
19
+ typatt?: Typatt;
20
+ }
21
+ export interface Ecr extends EcrRaw {
22
+ aut?: Auteur;
23
+ }
24
+ export interface LecAss extends LecassRaw {
25
+ ass?: Ass;
26
+ auds?: Aud[];
27
+ audcles?: AudRaw["audcle"][];
28
+ datesSeances?: DateSeance[];
29
+ datesSeancesCodes?: DateSeanceRaw["code"][];
30
+ debatdatseas: Date[];
31
+ lecassraps?: LecAssRap[];
32
+ lecassrapids?: string[];
33
+ org?: Org;
34
+ texcods: TexteRaw["texcod"][];
35
+ textes?: Texte[];
36
+ }
37
+ export interface LecAssRap extends LecassrapRaw {
38
+ rap?: RapRaw;
39
+ }
40
+ export interface Lecture extends LectureRaw {
41
+ typlec?: Typlec;
42
+ lecassidts?: LecassRaw["lecassidt"][];
43
+ lecasss?: LecAss[];
44
+ }
45
+ export interface Loi extends LoiRaw {
46
+ deccoc?: Deccoc;
47
+ etaloi?: Etaloi;
48
+ lecidts?: LectureRaw["lecidt"][];
49
+ lectures?: Lecture[];
50
+ typloi?: Typloi;
51
+ }
52
+ export interface Rap extends RapRaw {
53
+ denrap?: Denrap;
54
+ docattcles?: DocattRaw["docattcle"][];
55
+ docatts?: DocAtt[];
56
+ ecrnums?: EcrRaw["ecrnum"][];
57
+ ecrs?: Ecr[];
58
+ orgcods?: Raporg["orgcod"][];
59
+ orgs?: Org[];
60
+ }
61
+ export interface Texte extends TexteRaw {
62
+ ecrs?: Ecr[];
63
+ ecrnums?: EcrRaw["ecrnum"][];
64
+ libtypurl?: Typurl["libtypurl"];
65
+ org?: Org;
66
+ oritxt: Oritxt;
67
+ typtxt?: Typtxt;
68
+ txtAmeli?: TxtAmeliCustom;
69
+ txtAmeliId: TxtAmeli["id"];
70
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { TamQuestions } from "../raw_types/questions.js";
2
+ export type { TamQuestions as Question };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ export interface Photo {
2
+ chemin: string;
3
+ cheminMosaique: string;
4
+ hauteur: number;
5
+ largeur: number;
6
+ xMosaique: number;
7
+ yMosaique: number;
8
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ export declare const UNDEFINED_SESSION = 0;
2
+ export declare const sessionOptions: readonly [1958, 1959, 1960, 1961, 1962, 1963, 1964, 1965, 1966, 1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026];
3
+ export declare const sessionOptionsOrAll: readonly [0, 1958, 1959, 1960, 1961, 1962, 1963, 1964, 1965, 1966, 1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026];
4
+ export type Session = (typeof sessionOptions)[number];
5
+ export type SessionOrAll = (typeof sessionOptionsOrAll)[number];
6
+ export declare function getSessionsFromStart(startSession: SessionOrAll): (1958 | 1959 | 1960 | 1961 | 1962 | 1963 | 1964 | 1965 | 1966 | 1967 | 1968 | 1969 | 1970 | 1971 | 1972 | 1973 | 1974 | 1975 | 1976 | 1977 | 1978 | 1979 | 1980 | 1981 | 1982 | 1983 | 1984 | 1985 | 1986 | 1987 | 1988 | 1989 | 1990 | 1991 | 1992 | 1993 | 1994 | 1995 | 1996 | 1997 | 1998 | 1999 | 2000 | 2001 | 2002 | 2003 | 2004 | 2005 | 2006 | 2007 | 2008 | 2009 | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | 2022 | 2023 | 2024 | 2025 | 2026)[];
@@ -0,0 +1,19 @@
1
+ export const UNDEFINED_SESSION = 0;
2
+ export const sessionOptions = [
3
+ 1958, 1959, 1960, 1961, 1962, 1963, 1964, 1965, 1966, 1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, 1976,
4
+ 1977, 1978, 1979, 1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995,
5
+ 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014,
6
+ 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026,
7
+ // TO COMPLETE EVERY YEAR :)
8
+ ];
9
+ export const sessionOptionsOrAll = [UNDEFINED_SESSION, ...sessionOptions];
10
+ export function getSessionsFromStart(startSession) {
11
+ if (startSession === UNDEFINED_SESSION) {
12
+ return Array.from(sessionOptions);
13
+ }
14
+ const sessionIndex = sessionOptions.findIndex((session) => startSession === session);
15
+ if (sessionIndex >= 0) {
16
+ return sessionOptions.slice(sessionIndex);
17
+ }
18
+ return [];
19
+ }
@@ -0,0 +1,72 @@
1
+ import { DocumentResult } from "../loaders.js";
2
+ export declare enum DivisionType {
3
+ tome = 1,
4
+ part = 2,
5
+ book = 3,
6
+ title = 4,
7
+ subtitle = 5,
8
+ chapter = 6,
9
+ section = 7,
10
+ subsection = 8,
11
+ paragraph = 9,
12
+ article = 10,
13
+ alinea = 11,
14
+ division = 12
15
+ }
16
+ export type DivisionTag = keyof typeof DivisionType;
17
+ export interface DocumentMetadata {
18
+ name: string;
19
+ session: number | null | undefined;
20
+ date?: string | null;
21
+ url_expose_des_motifs?: URL;
22
+ url_xml?: URL;
23
+ url_html: URL;
24
+ url_pdf: URL;
25
+ }
26
+ export interface FlatTexte extends Partial<DocumentResult> {
27
+ titre: string | null;
28
+ titre_court: string | null;
29
+ url_dossier_senat: string | null;
30
+ url_dossier_assemblee: string | null;
31
+ date_presentation: Date | null;
32
+ date_depot: Date | null;
33
+ date_publication_xml: Date | null;
34
+ version: Version | null;
35
+ divisions: Division[];
36
+ expose_motifs?: ExposeDesMotifs | null;
37
+ }
38
+ export type Version = "RECT" | "RECT_BIS" | "RECT_TER" | "RECT_QUATER" | "RECT_QUINQUIES";
39
+ export interface Step {
40
+ eId: string;
41
+ date: Date | null;
42
+ type: string | null;
43
+ session: string | null;
44
+ numero: string | null;
45
+ version: Version | null;
46
+ outcome: string | null;
47
+ }
48
+ export interface Division {
49
+ index: number;
50
+ eId: string;
51
+ tag: DivisionTag;
52
+ level: number;
53
+ headings: DivisionContent[];
54
+ }
55
+ export interface Article extends Division {
56
+ alineas: Alinea[];
57
+ }
58
+ export interface DivisionContent {
59
+ text: string | null;
60
+ html?: string | null;
61
+ }
62
+ export interface Alinea extends DivisionContent {
63
+ eId: string;
64
+ heading: DivisionContent;
65
+ pastille: string | null;
66
+ text_avec_liens?: string | null;
67
+ html_avec_liens?: string | null;
68
+ }
69
+ export interface ExposeDesMotifs {
70
+ text: string | null;
71
+ html: string | null;
72
+ }
@@ -0,0 +1,15 @@
1
+ export var DivisionType;
2
+ (function (DivisionType) {
3
+ DivisionType[DivisionType["tome"] = 1] = "tome";
4
+ DivisionType[DivisionType["part"] = 2] = "part";
5
+ DivisionType[DivisionType["book"] = 3] = "book";
6
+ DivisionType[DivisionType["title"] = 4] = "title";
7
+ DivisionType[DivisionType["subtitle"] = 5] = "subtitle";
8
+ DivisionType[DivisionType["chapter"] = 6] = "chapter";
9
+ DivisionType[DivisionType["section"] = 7] = "section";
10
+ DivisionType[DivisionType["subsection"] = 8] = "subsection";
11
+ DivisionType[DivisionType["paragraph"] = 9] = "paragraph";
12
+ DivisionType[DivisionType["article"] = 10] = "article";
13
+ DivisionType[DivisionType["alinea"] = 11] = "alinea";
14
+ DivisionType[DivisionType["division"] = 12] = "division";
15
+ })(DivisionType || (DivisionType = {}));
@@ -4,6 +4,12 @@ import { AgendaEvent, ReunionOdj } from "../other_types/agenda.js";
4
4
  type DossierWithActes = DossierLegislatifResult & {
5
5
  actes_legislatifs?: ActeLegislatif[] | null;
6
6
  };
7
- export declare function buildOdj(events: AgendaEvent[], dossierBySenatUrl: Record<string, DossierWithActes>): ReunionOdj | undefined;
7
+ export type DoslegReunionIndex = Map<string, {
8
+ signet: string;
9
+ dossier: DossierWithActes;
10
+ reunionTitre?: string;
11
+ }[]>;
12
+ export declare function buildOdj(events: AgendaEvent[], dossierBySenatUrl: Record<string, DossierWithActes>, doslegReunionIndex?: DoslegReunionIndex): ReunionOdj | undefined;
8
13
  export declare function buildSenatDossierIndex(options: commandLineArgs.CommandLineOptions): Record<string, DossierWithActes>;
14
+ export declare function buildDoslegReunionIndex(options: commandLineArgs.CommandLineOptions): DoslegReunionIndex;
9
15
  export {};
@@ -1,34 +1,111 @@
1
1
  import { getSessionsFromStart } from "../other_types/sessions.js";
2
2
  import { iterLoadSenatDossiersLegislatifs } from "../server/loaders.js";
3
- export function buildOdj(events, dossierBySenatUrl) {
4
- const byObjet = new Map(); // objet -> set de dossier uids
5
- let codeEtape = null;
6
- let dossier = null;
3
+ function normalizeOrganeForMatch(text) {
4
+ return (text || "")
5
+ .toLowerCase()
6
+ .normalize("NFD")
7
+ .replace(/[\u0300-\u036f]/g, "")
8
+ .replace(/[^a-z0-9\s]/g, " ")
9
+ .replace(/\s+/g, " ")
10
+ .trim();
11
+ }
12
+ function buildReunionLookupKey(date, organe) {
13
+ const d = (date || "").split("T")[0];
14
+ const o = normalizeOrganeForMatch(organe || "");
15
+ return `${d}|${o}`;
16
+ }
17
+ export function buildOdj(events, dossierBySenatUrl, doslegReunionIndex) {
18
+ const byObjet = new Map();
19
+ let lastCodeEtape = null;
20
+ let lastFlatActes;
7
21
  for (const ev of events) {
8
22
  const objetKey = (ev.objet ?? "").trim();
9
23
  const url = normalizeSenatUrl(ev.urlDossierSenat) ?? undefined;
10
- dossier = url ? dossierBySenatUrl[url] : null;
24
+ let dossier = url ? (dossierBySenatUrl[url] ?? null) : null;
25
+ if (!dossier && doslegReunionIndex) {
26
+ dossier = lookupDossierFromDoslegReunion(ev, doslegReunionIndex);
27
+ }
11
28
  const dossierUid = dossier ? pickDossierUid(dossier) : undefined;
12
- codeEtape = dossier ? computeCodeEtape(ev, dossier) : null;
13
- // si on n’a ni objet ni dossier, ça ne sert à rien de créer un point
29
+ if (dossier) {
30
+ lastFlatActes = buildFlatActes(dossier);
31
+ lastCodeEtape = computeCodeEtape(ev, dossier, lastFlatActes);
32
+ }
33
+ else {
34
+ lastFlatActes = undefined;
35
+ lastCodeEtape = null;
36
+ }
14
37
  if (!objetKey && !dossierUid)
15
38
  continue;
16
- if (!byObjet.has(objetKey) && dossierUid) {
17
- byObjet.set(objetKey, dossierUid);
39
+ if (!byObjet.has(objetKey) && dossierUid && dossier) {
40
+ byObjet.set(objetKey, { dossierUid, dossier, flatActes: lastFlatActes });
18
41
  }
19
42
  }
20
43
  if (byObjet.size === 0)
21
44
  return undefined;
22
45
  const pointsOdj = [];
23
- for (const [objetKey, dossierUid] of byObjet) {
46
+ for (const [objetKey, { dossierUid, dossier: matchedDossier, flatActes }] of byObjet) {
47
+ const matchedCodeEtape = lastCodeEtape ?? (matchedDossier ? computeCodeEtapeFromFirstAct(matchedDossier, flatActes) : null);
24
48
  pointsOdj.push({
25
49
  objet: objetKey || null,
26
50
  dossierLegislatifRef: dossierUid || null,
27
- codeEtape,
51
+ codeEtape: matchedCodeEtape,
28
52
  });
29
53
  }
30
54
  return { pointsOdj };
31
55
  }
56
+ function scoreCandidate(c, evText) {
57
+ let score = 0;
58
+ const dosSignet = c.signet.toLowerCase();
59
+ if (evText.includes(dosSignet))
60
+ score += 10;
61
+ if (c.reunionTitre) {
62
+ const reunionWords = normalizeOrganeForMatch(c.reunionTitre).split(/\s+/).filter((w) => w.length >= 5);
63
+ score += reunionWords.filter((w) => evText.includes(w)).length;
64
+ }
65
+ const dossierTitre = normalizeOrganeForMatch(c.dossier.titre ?? "");
66
+ if (dossierTitre && evText.includes(dossierTitre))
67
+ score += 5;
68
+ if (dossierTitre) {
69
+ const words = dossierTitre.split(/\s+/).filter((w) => w.length >= 5);
70
+ score += words.filter((w) => evText.includes(w)).length;
71
+ }
72
+ return score;
73
+ }
74
+ function bestMatch(candidates, evText) {
75
+ if (candidates.length === 0)
76
+ return null;
77
+ const scored = candidates.map((c) => ({ c, score: scoreCandidate(c, evText) }));
78
+ scored.sort((a, b) => b.score - a.score);
79
+ return scored[0].score >= 3 ? scored[0].c.dossier : null;
80
+ }
81
+ function lookupDossierFromDoslegReunion(ev, index) {
82
+ const evDate = (ev.date || "").split("T")[0];
83
+ const evOrgane = normalizeOrganeForMatch(ev.organe ?? "");
84
+ const key = buildReunionLookupKey(evDate, ev.organe);
85
+ const evText = normalizeOrganeForMatch((ev.titre ?? "") + " " + (ev.objet ?? ""));
86
+ const candidates = index.get(key);
87
+ if (candidates && candidates.length > 0) {
88
+ return bestMatch(candidates, evText);
89
+ }
90
+ const dateOnlyCandidates = [];
91
+ for (const [k, v] of index) {
92
+ if (k.startsWith(evDate + "|") && k !== key) {
93
+ const kOrgane = k.split("|")[1] || "";
94
+ if (evOrgane && kOrgane && (evOrgane.includes(kOrgane) || kOrgane.includes(evOrgane))) {
95
+ dateOnlyCandidates.push(...v);
96
+ }
97
+ }
98
+ }
99
+ return bestMatch(dateOnlyCandidates, evText);
100
+ }
101
+ function computeCodeEtapeFromFirstAct(dossier, flatActes) {
102
+ const flat = flatActes ?? buildFlatActes(dossier);
103
+ if (flat.length === 0)
104
+ return null;
105
+ const cleanCode = (code) => code.replace(/-SEANCE$/, "");
106
+ flat.sort((a, b) => a.date.localeCompare(b.date) || a.codeActe.length - b.codeActe.length);
107
+ return cleanCode(flat[0].codeActe);
108
+ }
32
109
  function pickDossierUid(d) {
33
110
  if (d["signet"] && d["signet"].trim())
34
111
  return d["signet"].trim();
@@ -62,6 +139,44 @@ export function buildSenatDossierIndex(options) {
62
139
  }
63
140
  return index;
64
141
  }
142
+ export function buildDoslegReunionIndex(options) {
143
+ const index = new Map();
144
+ const sessions = getSessionsFromStart(2015);
145
+ for (const session of sessions) {
146
+ for (const item of iterLoadSenatDossiersLegislatifs(options["dataDir"], session)) {
147
+ const dossier = item.item;
148
+ const signet = dossier.signet?.trim();
149
+ if (!signet)
150
+ continue;
151
+ const lectures = dossier.lectures ?? [];
152
+ for (const lecture of lectures) {
153
+ const lecturesAssemblee = lecture.lectures_assemblee ?? [];
154
+ for (const lecAss of lecturesAssemblee) {
155
+ if (lecAss.assemblee !== "Sénat")
156
+ continue;
157
+ const commissions = lecAss.commissions_saisies ?? [];
158
+ for (const commission of commissions) {
159
+ const reunions = commission.reunions ?? [];
160
+ for (const reunion of reunions) {
161
+ if (!reunion.date)
162
+ continue;
163
+ const key = buildReunionLookupKey(reunion.date, commission.libelle_organisme);
164
+ let entries = index.get(key);
165
+ if (!entries) {
166
+ entries = [];
167
+ index.set(key, entries);
168
+ }
169
+ if (!entries.some((e) => e.signet === signet)) {
170
+ entries.push({ signet, dossier, reunionTitre: reunion.titre ?? undefined });
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+ return index;
179
+ }
65
180
  function detectLecture(objet) {
66
181
  objet = objet.toLowerCase();
67
182
  if (objet.includes("première lecture"))
@@ -72,18 +187,17 @@ function detectLecture(objet) {
72
187
  return 3;
73
188
  return undefined;
74
189
  }
75
- function computeCodeEtape(ev, dossier) {
76
- // In order to match with stage, we need to remove the '-SEANCE' suffix from the codeActe
190
+ function computeCodeEtape(ev, dossier, flatActes) {
77
191
  const cleanCode = (code) => code.replace(/-SEANCE$/, "");
78
192
  const lecture = detectLecture(ev.objet ?? "");
79
- const organe = ev.organe ?? "";
80
- const nature = organe.toLowerCase().includes("commission")
193
+ const organeLower = (ev.organe ?? "").toLowerCase();
194
+ const nature = organeLower.includes("commission")
81
195
  ? "COM"
82
- : organe.toLowerCase().includes("séance publique")
196
+ : organeLower.includes("séance publique")
83
197
  ? "DEBATS"
84
198
  : "";
85
199
  const evDate = ev.date.split("T")[0];
86
- const flat = buildFlatActes(dossier);
200
+ const flat = flatActes ?? buildFlatActes(dossier);
87
201
  // 1) Strict matching: same date + same nature
88
202
  let candidates = flat.filter((a) => {
89
203
  if (a.date !== evDate)
@@ -105,17 +219,26 @@ function computeCodeEtape(ev, dossier) {
105
219
  return cleanCode(candidates[0].codeActe);
106
220
  }
107
221
  // 2) Fallback COM: If no exact date match for a commission event,
108
- // take the latest commission act for this lecture on or before the event date.
222
+ // find the nearest commission act for this lecture (before or after the event date).
109
223
  if (nature === "COM") {
110
- let comActs = flat.filter((a) => a.codeActe.includes("COM") && a.date <= evDate);
224
+ let comActs = flat.filter((a) => a.codeActe.includes("COM"));
111
225
  if (lecture !== undefined) {
112
226
  const byLecture = comActs.filter((a) => a.ordreLecture === lecture);
113
227
  if (byLecture.length > 0)
114
228
  comActs = byLecture;
115
229
  }
116
230
  if (comActs.length > 0) {
117
- comActs.sort((a, b) => b.date.localeCompare(a.date) || b.codeActe.length - a.codeActe.length);
118
- return cleanCode(comActs[0].codeActe);
231
+ // Prefer acts on or before the event date (latest first), then closest after
232
+ const before = comActs.filter((a) => a.date <= evDate);
233
+ const after = comActs.filter((a) => a.date > evDate);
234
+ if (before.length > 0) {
235
+ before.sort((a, b) => b.date.localeCompare(a.date) || b.codeActe.length - a.codeActe.length);
236
+ return cleanCode(before[0].codeActe);
237
+ }
238
+ if (after.length > 0) {
239
+ after.sort((a, b) => a.date.localeCompare(b.date) || a.codeActe.length - b.codeActe.length);
240
+ return cleanCode(after[0].codeActe);
241
+ }
119
242
  }
120
243
  }
121
244
  // 3) Fallback general lecture: if nothing else worked but a lecture is identified,
@@ -2,12 +2,13 @@ import { DateTime } from "luxon";
2
2
  import type { AnyNode } from "domhandler";
3
3
  import { AgendaEvent, Reunion } from "../other_types/agenda.js";
4
4
  import { DossierLegislatifResult } from "../rich_types/dosleg.js";
5
+ import { DoslegReunionIndex } from "./reunion_odj_building.js";
5
6
  import * as cheerio from "cheerio";
6
7
  type KnownType = "SP" | "COM" | "MC" | "OD" | "ID";
7
- type DossierBySenatUrl = Record<string, DossierLegislatifResult>;
8
8
  type ReunionBucket = "IDS" | "IDC" | "IDM" | "IDO" | "IDI";
9
9
  type ReunionsByBucket = Record<ReunionBucket, Reunion[]>;
10
- export declare function buildReunionsByBucket(events: AgendaEvent[], dossierBySenatUrl: DossierBySenatUrl): ReunionsByBucket;
10
+ type DossierBySenatUrl = Record<string, DossierLegislatifResult>;
11
+ export declare function buildReunionsByBucket(events: AgendaEvent[], dossierBySenatUrl: DossierBySenatUrl, doslegReunionIndex?: DoslegReunionIndex): ReunionsByBucket;
11
12
  export declare function makeReunionUid(dateISO: string, kind: KnownType, agendaEventId: string, organe?: string | null): string;
12
13
  export declare function formatYYYYMMDD(dateYYYYMMDD: string): string;
13
14
  export declare function deriveTimesForEvent(ev: AgendaEvent): {
@@ -22,7 +22,7 @@ const STOPWORDS = new Set([
22
22
  "a",
23
23
  "aux",
24
24
  ]);
25
- function toReunion(e, dossierBySenatUrl, uid) {
25
+ function toReunion(e, dossierBySenatUrl, uid, doslegReunionIndex) {
26
26
  const date = norm(e.date) ?? e.date;
27
27
  const { startISO, endISO } = deriveTimesForEvent(e);
28
28
  const startTime = startISO ?? e.startTime ?? null;
@@ -39,11 +39,11 @@ function toReunion(e, dossierBySenatUrl, uid) {
39
39
  titre: e.titre,
40
40
  objet: e.objet || "",
41
41
  events: [e], // TODO remove
42
- odj: buildOdj([e], dossierBySenatUrl),
42
+ odj: buildOdj([e], dossierBySenatUrl, doslegReunionIndex),
43
43
  lieu: e.lieu || undefined,
44
44
  };
45
45
  }
46
- export function buildReunionsByBucket(events, dossierBySenatUrl) {
46
+ export function buildReunionsByBucket(events, dossierBySenatUrl, doslegReunionIndex) {
47
47
  const out = { IDS: [], IDC: [], IDM: [], IDO: [], IDI: [] };
48
48
  if (!events?.length)
49
49
  return out;
@@ -55,7 +55,7 @@ export function buildReunionsByBucket(events, dossierBySenatUrl) {
55
55
  }
56
56
  const bucket = typeToSuffixStrict(kind);
57
57
  const uid = makeReunionUid(e.date, kind, e.id, e.organe ?? null);
58
- out[bucket].push(toReunion(e, dossierBySenatUrl, uid));
58
+ out[bucket].push(toReunion(e, dossierBySenatUrl, uid, doslegReunionIndex));
59
59
  }
60
60
  // Tri stable par bucket (date + heure, inconnus à la fin)
61
61
  for (const k of Object.keys(out)) {
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ export declare const configSchema: z.ZodObject<{
3
+ db: z.ZodObject<{
4
+ host: z.ZodString;
5
+ password: z.ZodString;
6
+ user: z.ZodString;
7
+ port: z.ZodCoercedNumber<unknown>;
8
+ }, z.core.$strip>;
9
+ }, z.core.$strip>;
@@ -0,0 +1,10 @@
1
+ import { z } from "zod";
2
+ const dbSchema = z.object({
3
+ host: z.string().trim().min(1, "Must not be empty"),
4
+ password: z.string().trim().min(1, "Must not be empty"),
5
+ user: z.string().trim().min(1, "Must not be empty"),
6
+ port: z.coerce.number().int().min(0).max(65535),
7
+ });
8
+ export const configSchema = z.object({
9
+ db: dbSchema,
10
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tricoteuses/senat",
3
- "version": "3.1.10",
3
+ "version": "3.1.12",
4
4
  "description": "Handle French Sénat's open data",
5
5
  "keywords": [
6
6
  "France",