@tricoteuses/senat 3.1.23 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/src/index.d.ts +2 -0
- package/lib/src/other_types/plf.d.ts +21 -0
- package/lib/src/other_types/plf.js +1 -0
- package/lib/src/parsers/collaborateurs.d.ts +24 -24
- package/lib/src/parsers/collaborateurs.js +76 -76
- package/lib/src/parsers/plf/credits.d.ts +2 -0
- package/lib/src/parsers/plf/credits.js +171 -0
- package/lib/src/parsers/plf/index.d.ts +1 -0
- package/lib/src/parsers/plf/index.js +1 -0
- package/lib/src/parsers/plf/parser_utils.d.ts +18 -0
- package/lib/src/parsers/plf/parser_utils.js +172 -0
- package/lib/src/parsers/texte.d.ts +1 -3
- package/lib/src/parsers/texte.js +63 -30
- package/lib/src/rich_types/sens.d.ts +5 -0
- package/lib/src/scripts/data-download.js +1 -0
- package/lib/src/scripts/extract_plf_fixtures.d.ts +1 -0
- package/lib/src/scripts/extract_plf_fixtures.js +101 -0
- package/lib/src/scripts/retrieve_collaborateurs.js +93 -93
- package/lib/src/scripts/retrieve_documents.js +7 -46
- package/lib/src/server/parse_document.d.ts +5 -0
- package/lib/src/server/parse_document.js +47 -0
- package/lib/tests/collaborateurs.test.js +52 -52
- package/lib/tests/parsers/parseDocument.test.d.ts +1 -0
- package/lib/tests/parsers/parseDocument.test.js +73 -0
- package/lib/tests/parsers/plf/credits.integration.test.d.ts +1 -0
- package/lib/tests/parsers/plf/credits.integration.test.js +118 -0
- package/lib/tests/parsers/plf/credits.test.d.ts +1 -0
- package/lib/tests/parsers/plf/credits.test.js +467 -0
- package/lib/tests/parsers/plf/integration.test_utils.d.ts +8 -0
- package/lib/tests/parsers/plf/integration.test_utils.js +73 -0
- package/lib/tests/parsers/plf/test_utils.d.ts +20 -0
- package/lib/tests/parsers/plf/test_utils.js +85 -0
- package/lib/tests/parsers/texte.test.d.ts +1 -0
- package/lib/tests/parsers/texte.test.js +71 -0
- package/lib/tests/parsers/texte.test_utils.d.ts +15 -0
- package/lib/tests/parsers/texte.test_utils.js +73 -0
- package/lib/tests/plf/credits.test.d.ts +1 -0
- package/lib/tests/plf/credits.test.js +458 -0
- package/lib/tests/plf/test_utils.d.ts +20 -0
- package/lib/tests/plf/test_utils.js +85 -0
- package/package.json +5 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { parseDocument } from "../../src/server/parse_document.js";
|
|
6
|
+
import { article, articleAlinea, xmlDoc } from "./texte.test_utils.js";
|
|
7
|
+
import { buildArticle, etatTable3Col, missionRow, totalRow } from "./plf/test_utils.js";
|
|
8
|
+
const tmpDirs = [];
|
|
9
|
+
function makeTmpDir(prefix) {
|
|
10
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
11
|
+
tmpDirs.push(dir);
|
|
12
|
+
return dir;
|
|
13
|
+
}
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
for (const dir of tmpDirs.splice(0)) {
|
|
16
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
describe("parseDocument", () => {
|
|
20
|
+
const session = 2024;
|
|
21
|
+
const texteName = "pjl-test";
|
|
22
|
+
const body = article("art_1er", articleAlinea("al_1__art_1er", "<p>Un article de test.</p>"));
|
|
23
|
+
it("writes the parsed FlatTexte as JSON in the session/name folder and returns it", async () => {
|
|
24
|
+
const transformedDir = makeTmpDir("senat-parse-doc-");
|
|
25
|
+
const texteXml = xmlDoc(body);
|
|
26
|
+
const result = await parseDocument(session, transformedDir, texteName, texteXml);
|
|
27
|
+
const jsonPath = path.join(transformedDir, `${session}`, texteName, `${texteName}.json`);
|
|
28
|
+
expect(fs.existsSync(jsonPath)).toBe(true);
|
|
29
|
+
const written = fs.readJsonSync(jsonPath);
|
|
30
|
+
expect(written.titre).toBe("Projet de loi de finances pour 2026");
|
|
31
|
+
expect(written.divisions).toHaveLength(1);
|
|
32
|
+
expect(result?.titre).toBe(written.titre);
|
|
33
|
+
});
|
|
34
|
+
it("parses and injects the exposé des motifs when exposeHtml is provided", async () => {
|
|
35
|
+
const transformedDir = makeTmpDir("senat-parse-expose-");
|
|
36
|
+
const exposeHtml = "<section><p>EXPOSÉ DES MOTIFS</p><p>Voici les motifs du texte.</p></section>";
|
|
37
|
+
const result = await parseDocument(session, transformedDir, texteName, xmlDoc(body), exposeHtml);
|
|
38
|
+
expect(result?.expose_motifs?.text).toContain("Voici les motifs du texte.");
|
|
39
|
+
});
|
|
40
|
+
it("does not write a PLF credits file when the texte has no credits", async () => {
|
|
41
|
+
const transformedDir = makeTmpDir("senat-parse-nocredits-");
|
|
42
|
+
await parseDocument(session, transformedDir, texteName, xmlDoc(body));
|
|
43
|
+
expect(fs.existsSync(path.join(transformedDir, `${session}`, texteName, `${texteName}_plf_credits.json`))).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
it("writes a PLF credits file when the texte contains an 'etat' article", async () => {
|
|
46
|
+
const transformedDir = makeTmpDir("senat-parse-credits-");
|
|
47
|
+
const plfBody = buildArticle("etat_B", "État B", etatTable3Col("B", missionRow("Action extérieure de l'État", "3 449 452 613", "3 454 425 325") +
|
|
48
|
+
totalRow("620 249 232 626", "593 890 071 649")));
|
|
49
|
+
await parseDocument(session, transformedDir, texteName, xmlDoc(plfBody));
|
|
50
|
+
const creditsPath = path.join(transformedDir, `${session}`, texteName, `${texteName}_plf_credits.json`);
|
|
51
|
+
expect(fs.existsSync(creditsPath)).toBe(true);
|
|
52
|
+
const credits = fs.readJsonSync(creditsPath);
|
|
53
|
+
expect(credits).toHaveLength(1);
|
|
54
|
+
expect(credits[0].libelle).toBe("Budget général");
|
|
55
|
+
});
|
|
56
|
+
it("removes a stale PLF credits file when a re-parse produces no credits", async () => {
|
|
57
|
+
const transformedDir = makeTmpDir("senat-parse-stalecredits-");
|
|
58
|
+
const creditsPath = path.join(transformedDir, `${session}`, texteName, `${texteName}_plf_credits.json`);
|
|
59
|
+
const nbCreditsDir = path.join(transformedDir, `${session}`, texteName);
|
|
60
|
+
const plfBody = buildArticle("etat_B", "État B", etatTable3Col("B", missionRow("Action extérieure de l'État", "3 449 452 613", "3 454 425 325") +
|
|
61
|
+
totalRow("620 249 232 626", "593 890 071 649")));
|
|
62
|
+
await parseDocument(session, transformedDir, texteName, xmlDoc(plfBody));
|
|
63
|
+
expect(fs.existsSync(creditsPath)).toBe(true);
|
|
64
|
+
await parseDocument(session, transformedDir, texteName, xmlDoc(body));
|
|
65
|
+
expect(fs.existsSync(creditsPath)).toBe(false);
|
|
66
|
+
expect(fs.existsSync(nbCreditsDir)).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
it("works for a session-less texte using the undefined session", async () => {
|
|
69
|
+
const transformedDir = makeTmpDir("senat-parse-nosession-");
|
|
70
|
+
await parseDocument(null, transformedDir, texteName, xmlDoc(body));
|
|
71
|
+
expect(fs.existsSync(path.join(transformedDir, "0", texteName, `${texteName}.json`))).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
2
|
+
import { parsePlfCredits } from "../../../src/parsers/plf/index.js";
|
|
3
|
+
import { expectValidCreditSections, loadPlfFixture, sectionByLibelle, sectionLibelles, } from "./integration.test_utils.js";
|
|
4
|
+
// Real Sénat AKN XML, extracted from the actual senat-data dumps (only the
|
|
5
|
+
// État B, C and D articles, trimmed to a representative row sample). Regenerate/
|
|
6
|
+
// extend with:
|
|
7
|
+
// npx tsx src/scripts/extract_plf_fixtures.ts --src <senat .xml> --name <name> --only-parsed
|
|
8
|
+
const fixtures = {
|
|
9
|
+
plf2026Depot: { name: "plf2026-depot", label: "PLF 2026 (pjl25-138) — dépôt" },
|
|
10
|
+
plf2026Avance: { name: "plf2026-avance", label: "PLF 2026 (pjl25-308) — stade avancé" },
|
|
11
|
+
plf2025: { name: "plf2025", label: "PLF 2025 (pjl24-143) — dépôt" },
|
|
12
|
+
};
|
|
13
|
+
describe("parsePlfCredits — documents réels (integration)", () => {
|
|
14
|
+
let parsed;
|
|
15
|
+
beforeAll(() => {
|
|
16
|
+
parsed = Object.fromEntries(Object.values(fixtures).map(({ name }) => [
|
|
17
|
+
name,
|
|
18
|
+
parsePlfCredits(loadPlfFixture(name)),
|
|
19
|
+
]));
|
|
20
|
+
}, 30_000);
|
|
21
|
+
const loader = (name) => parsed[name];
|
|
22
|
+
describe("invariants", () => {
|
|
23
|
+
it.each(Object.values(fixtures))("$label — produit des sections cohérentes", ({ name }) => {
|
|
24
|
+
const sections = loader(name);
|
|
25
|
+
expect(sections.length).toBeGreaterThan(0);
|
|
26
|
+
expectValidCreditSections(sections);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe("section count et libellés", () => {
|
|
30
|
+
it.each([
|
|
31
|
+
{
|
|
32
|
+
fixture: fixtures.plf2026Depot,
|
|
33
|
+
expected: [
|
|
34
|
+
"Budget général",
|
|
35
|
+
"Budgets annexes",
|
|
36
|
+
"Comptes d'affectation spéciale",
|
|
37
|
+
"Comptes de concours financiers",
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
fixture: fixtures.plf2025,
|
|
42
|
+
expected: [
|
|
43
|
+
"Budget général",
|
|
44
|
+
"Budgets annexes",
|
|
45
|
+
"Comptes d'affectation spéciale",
|
|
46
|
+
"Comptes de concours financiers",
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
// L'État C est « (Conforme) » à ce stade (aucun tableau de crédits)
|
|
51
|
+
fixture: fixtures.plf2026Avance,
|
|
52
|
+
expected: [
|
|
53
|
+
"Budget général",
|
|
54
|
+
"Comptes d'affectation spéciale",
|
|
55
|
+
"Comptes de concours financiers",
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
])("$fixture.label — expose les sections attendues", ({ fixture, expected }) => {
|
|
59
|
+
expect(sectionLibelles(loader(fixture.name))).toEqual(expected);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe("totaux", () => {
|
|
63
|
+
it.each([
|
|
64
|
+
{ fixture: fixtures.plf2026Depot, section: "Budget général", total: 613_009_460_112 },
|
|
65
|
+
{ fixture: fixtures.plf2026Depot, section: "Budgets annexes", total: 2_691_230_585 },
|
|
66
|
+
{
|
|
67
|
+
fixture: fixtures.plf2026Depot,
|
|
68
|
+
section: "Comptes d'affectation spéciale",
|
|
69
|
+
total: 77_423_204_686,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
fixture: fixtures.plf2026Depot,
|
|
73
|
+
section: "Comptes de concours financiers",
|
|
74
|
+
total: 149_661_603_151,
|
|
75
|
+
},
|
|
76
|
+
])("$fixture.label / $section — total AE correct", ({ fixture, section, total }) => {
|
|
77
|
+
expect(sectionByLibelle(loader(fixture.name), section)?.total?.autorisationEngagement).toBe(total);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe("missions", () => {
|
|
81
|
+
it.each([
|
|
82
|
+
{
|
|
83
|
+
fixture: fixtures.plf2026Depot,
|
|
84
|
+
section: "Budget général",
|
|
85
|
+
mission: "Action extérieure de l’État",
|
|
86
|
+
ae: 3_452_452_613,
|
|
87
|
+
programmes: 3,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
fixture: fixtures.plf2026Avance,
|
|
91
|
+
section: "Budget général",
|
|
92
|
+
mission: "Action extérieure de l’État",
|
|
93
|
+
ae: 3_449_452_613,
|
|
94
|
+
programmes: 3,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
fixture: fixtures.plf2025,
|
|
98
|
+
section: "Budget général",
|
|
99
|
+
mission: "Action extérieure de l’État",
|
|
100
|
+
ae: 3_527_537_701,
|
|
101
|
+
programmes: 3,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
fixture: fixtures.plf2026Depot,
|
|
105
|
+
section: "Comptes d'affectation spéciale",
|
|
106
|
+
mission: "Contrôle de la circulation et du stationnement routiers",
|
|
107
|
+
ae: 1_928_700_107,
|
|
108
|
+
},
|
|
109
|
+
])("$fixture.label / $mission — première mission et son AE", ({ fixture, section, mission, ae, programmes }) => {
|
|
110
|
+
const sections = loader(fixture.name);
|
|
111
|
+
const first = sectionByLibelle(sections, section)?.missions[0];
|
|
112
|
+
expect(first?.libelle).toBe(mission);
|
|
113
|
+
expect(first?.autorisationEngagement).toBe(ae);
|
|
114
|
+
if (programmes !== undefined)
|
|
115
|
+
expect(first?.programmes).toHaveLength(programmes);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|