Index links between texts & articles

This commit is contained in:
Emmanuel 2024-05-09 19:11:31 +02:00
parent da419fce5b
commit b846817fbb
15 changed files with 1014 additions and 849 deletions

1152
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -60,6 +60,7 @@
"@types/he": "^1.1.2",
"@types/jsdom": "^21.1.3",
"@types/morgan": "^1.9.4",
"@types/object-hash": "^3.0.6",
"@types/papaparse": "^5.3.7",
"@typescript-eslint/eslint-plugin": "^7.6.0",
"@typescript-eslint/parser": "^7.6.0",
@ -74,6 +75,7 @@
"iconify-icon": "^2.0.0",
"metslesliens": "git+https://git.en-root.org/eraviart/metslesliens.git",
"morgan": "^1.10.0",
"object-hash": "^3.0.0",
"papaparse": "^5.3.2",
"postcss": "^8.4.14",
"postcss-load-config": "^5.0.2",

View file

@ -11,9 +11,11 @@ import type {
TexteVersion,
Versions,
} from "$lib/legal"
import type { ArticleLienDb, TexteVersionLienDb } from "./legal/shared"
export interface Aggregate {
article?: { [id: string]: Article | JorfArticle | LegiArticle }
article_lien?: ArticleLienDb[]
dossier_legislatif?: { [id: string]: DossierLegislatif }
id?: string
idcc?: { [id: string]: Idcc }
@ -21,6 +23,7 @@ export interface Aggregate {
jo?: { [id: string]: Jo }
section_ta?: { [id: string]: SectionTa }
texte_version?: { [id: string]: TexteVersion }
texte_version_lien?: TexteVersionLienDb[]
textekali?: { [id: string]: Textekali }
textelr?: { [id: string]: Textelr }
versions?: { [eli: string]: Versions }

View file

@ -305,18 +305,7 @@ export interface JorfTexteVersion {
DATE_DEBUT: string
DATE_FIN: string
LIENS?: {
LIEN: Array<{
"#text"?: string
"@cidtexte"?: string
"@datesignatexte"?: string
"@id"?: string
"@naturetexte"?: JorfTexteVersionLienNature
"@num"?: string
"@nortexte"?: string
"@numtexte"?: string
"@sens": Sens
"@typelien": JorfTexteVersionLienType
}>
LIEN: Array<JorfTexteVersionLien>
}
MCS_TXT?: {
MC: string[]
@ -347,6 +336,19 @@ export interface JorfTexteVersion {
}
}
export interface JorfTexteVersionLien {
"#text"?: string
"@cidtexte"?: string
"@datesignatexte"?: string
"@id"?: string
"@naturetexte"?: JorfTexteVersionLienNature
"@nortexte"?: string
"@num"?: string
"@numtexte"?: string
"@sens": Sens
"@typelien": JorfTexteVersionLienType
}
export type JorfTexteVersionLienNature =
(typeof allJorfTexteVersionLienNatures)[number]

View file

@ -26,17 +26,7 @@ export interface LegiArticle {
}
}
LIENS?: {
LIEN: Array<{
"#text"?: string
"@cidtexte"?: string
"@datesignatexte"?: string
"@id"?: string
"@naturetexte"?: LegiArticleLienNature
"@nortexte"?: string
"@num"?: string
"@sens": Sens
"@typelien": LegiArticleLienType
}>
LIEN: Array<LegiArticleLien>
}
META: {
META_COMMUN: {
@ -76,6 +66,18 @@ export interface LegiArticle {
export type LegiArticleEtat = (typeof allLegiArticleEtats)[number]
export interface LegiArticleLien {
"#text"?: string
"@cidtexte"?: string
"@datesignatexte"?: string
"@id"?: string
"@naturetexte"?: LegiArticleLienNature
"@nortexte"?: string
"@num"?: string
"@sens": Sens
"@typelien": LegiArticleLienType
}
export type LegiArticleLienArticleOrigine =
(typeof allLegiArticleLienArticleOrigines)[number]
@ -286,18 +288,7 @@ export interface LegiTexteVersion {
DATE_FIN?: string
ETAT?: LegiTexteVersionEtat
LIENS?: {
LIEN: Array<{
"#text"?: string
"@cidtexte"?: string
"@datesignatexte"?: string
"@id"?: string
"@naturetexte"?: LegiTexteVersionLienNature
"@num"?: string
"@nortexte"?: string
"@numtexte"?: string
"@sens": Sens
"@typelien": LegiTexteVersionLienType
}>
LIEN: Array<LegiTexteVersionLien>
}
MCS_TXT?: {
MC: string[]
@ -327,6 +318,19 @@ export interface LegiTexteVersion {
export type LegiTexteVersionEtat = (typeof allLegiTexteVersionEtats)[number]
export interface LegiTexteVersionLien {
"#text"?: string
"@cidtexte"?: string
"@datesignatexte"?: string
"@id"?: string
"@naturetexte"?: LegiTexteVersionLienNature
"@num"?: string
"@nortexte"?: string
"@numtexte"?: string
"@sens": Sens
"@typelien": LegiTexteVersionLienType
}
export type LegiTexteVersionLienNature =
(typeof allLegiTexteVersionLienNatures)[number]

View file

@ -1,3 +1,17 @@
export type Sens = (typeof allSens)[number]
export const allSens = ["cible", "source"] as const
export interface ArticleLienDb {
article_id: string
cible: boolean
id: string
typelien: string
}
export interface TexteVersionLienDb {
texte_version_id: string
cible: boolean
id: string
typelien: string
}

View file

@ -1,4 +1,5 @@
import { iterArrayOrSingleton } from "@tricoteuses/explorer-tools"
import objectHash from "object-hash"
import type { Aggregate, Follow } from "$lib/aggregates"
import {
@ -12,14 +13,17 @@ import {
type Textelr,
type TexteVersion,
} from "$lib/legal"
import type { ArticleLienDb, TexteVersionLienDb } from "$lib/legal/shared"
import { db } from "$lib/server/databases"
export class Aggregator {
article: { [id: string]: Article | JorfArticle | LegiArticle } = {}
follow: Set<Follow>
article_lien: { [hash: string]: ArticleLienDb } = {}
requestedTypeAndIds: Set<TypeAndId> = new Set()
section_ta: { [id: string]: SectionTa } = {}
texte_version: { [id: string]: TexteVersion } = {}
texte_version_lien: { [hash: string]: TexteVersionLienDb } = {}
textekali: { [id: string]: Textekali } = {}
textelr: { [id: string]: Textelr } = {}
visitedTypeAndIds: Set<TypeAndId> = new Set()
@ -31,6 +35,7 @@ export class Aggregator {
addArticle(article: Article | JorfArticle | LegiArticle): void {
const id = article.META.META_COMMUN.ID
this.article[id] = article
this.visitedTypeAndIds.add(["article", id])
if (this.follow.has("LIENS.LIEN[@sens=cible,@typelien=CREATION].@id")) {
for (const lien of iterArrayOrSingleton(
@ -47,9 +52,17 @@ export class Aggregator {
}
}
addArticleLienDb(lien: ArticleLienDb): void {
this.article_lien[objectHash(lien)] = lien
this.requestTypeAndId(["article", lien.article_id])
this.requestId(lien.id)
}
addSectionTa(sectionTa: SectionTa): void {
const id = sectionTa.ID
this.section_ta[id] = sectionTa
this.visitedTypeAndIds.add(["section_ta", id])
if (this.follow.has("STRUCTURE_TA.LIEN_ART.@id")) {
for (const lien of iterArrayOrSingleton(
@ -71,6 +84,7 @@ export class Aggregator {
addTextekali(textekali: Textekali): void {
const id = textekali.META.META_COMMUN.ID
this.textekali[id] = textekali
this.visitedTypeAndIds.add(["textekali", id])
if (this.follow.has("STRUCT.LIEN_ART.@id")) {
for (const lien of iterArrayOrSingleton(textekali.STRUCT?.LIEN_ART)) {
@ -90,6 +104,7 @@ export class Aggregator {
addTextelr(textelr: Textelr): void {
const id = textelr.META.META_COMMUN.ID
this.textelr[id] = textelr
this.visitedTypeAndIds.add(["textelr", id])
if (this.follow.has("STRUCT.LIEN_ART.@id")) {
for (const lien of iterArrayOrSingleton(textelr.STRUCT?.LIEN_ART)) {
@ -109,6 +124,7 @@ export class Aggregator {
addTexteVersion(texteVersion: TexteVersion): void {
const id = texteVersion.META.META_COMMUN.ID
this.texte_version[id] = texteVersion
this.visitedTypeAndIds.add(["texte_version", id])
if (this.follow.has("TEXTEKALI")) {
this.requestTypeAndId(["textekali", id])
@ -118,10 +134,11 @@ export class Aggregator {
}
}
addToVisitedTypeAndIds(typeAndIds: TypeAndId[]) {
for (const typeAndId of typeAndIds) {
this.visitedTypeAndIds.add(typeAndId)
}
addTexteVersionLienDb(lien: TexteVersionLienDb): void {
this.texte_version_lien[objectHash(lien)] = lien
this.requestTypeAndId(["texte_version", lien.texte_version_id])
this.requestId(lien.id)
}
deleteFromRequestedTypeAndIds(typeAndIds: TypeAndId[]) {
@ -144,7 +161,6 @@ export class Aggregator {
`) {
this.addArticle(data)
}
this.addToVisitedTypeAndIds(articleTypeAndIds)
}
}
@ -160,7 +176,21 @@ export class Aggregator {
`) {
this.addSectionTa(data)
}
this.addToVisitedTypeAndIds(sectionTaTypeAndIds)
}
}
{
const texteVersionTypeAndIds = [...this.requestedTypeAndIds].filter(
([type]) => type === "texte_version",
)
if (texteVersionTypeAndIds.length > 0) {
this.deleteFromRequestedTypeAndIds(texteVersionTypeAndIds)
for (const { data } of await db<{ id: string; data: TexteVersion }[]>`
SELECT id, data FROM texte_version
WHERE id IN ${db(texteVersionTypeAndIds.map(([, id]) => id))}
`) {
this.addTexteVersion(data)
}
}
}
@ -176,7 +206,6 @@ export class Aggregator {
`) {
this.addTextekali(data)
}
this.addToVisitedTypeAndIds(textekaliTypeAndIds)
}
}
@ -192,7 +221,6 @@ export class Aggregator {
`) {
this.addTextelr(data)
}
this.addToVisitedTypeAndIds(textelrTypeAndIds)
}
}
}
@ -216,12 +244,18 @@ export class Aggregator {
if (Object.keys(this.article).length > 0) {
json.article = this.article
}
if (Object.keys(this.article_lien).length > 0) {
json.article_lien = Object.values(this.article_lien)
}
if (Object.keys(this.section_ta).length > 0) {
json.section_ta = this.section_ta
}
if (Object.keys(this.texte_version).length > 0) {
json.texte_version = this.texte_version
}
if (Object.keys(this.texte_version_lien).length > 0) {
json.texte_version_lien = Object.values(this.texte_version_lien)
}
if (Object.keys(this.textekali).length > 0) {
json.textekali = this.textekali
}

View file

@ -111,12 +111,23 @@ export async function configureDatabase() {
// await `
// CREATE TABLE IF NOT EXISTS article_autocompletions (
// autocompletion text NOT NULL,
// id bigint NOT NULL REFERENCES article(id) ON DELETE CASCADE,
// id char(20) NOT NULL REFERENCES article(id) ON DELETE CASCADE,
// weight int NOT NULL,
// PRIMARY KEY (id, autocompletion)
// )
// `
// Table: article_lien
await db`
CREATE TABLE IF NOT EXISTS article_lien (
article_id char(20) NOT NULL REFERENCES article(id) ON DELETE CASCADE,
cible boolean,
id char(20) NOT NULL,
typelien text NOT NULL,
PRIMARY KEY (article_id, cible, typelien, id)
)
`
// Table: dossier_legislatif
await db`
CREATE TABLE IF NOT EXISTS dossier_legislatif (
@ -180,6 +191,17 @@ export async function configureDatabase() {
)
`
// Table: texte_version_lien
await db`
CREATE TABLE IF NOT EXISTS texte_version_lien (
texte_version_id char(20) NOT NULL REFERENCES texte_version(id) ON DELETE CASCADE,
cible boolean,
id char(20) NOT NULL,
typelien text NOT NULL,
PRIMARY KEY (texte_version_id, cible, typelien, id)
)
`
// Table: textekali
await db`
CREATE TABLE IF NOT EXISTS textekali (
@ -274,6 +296,17 @@ export async function configureDatabase() {
// Add indexes once every table and column exists.
// await db`
// CREATE INDEX IF NOT EXISTS article_autocompletions_trigrams_idx
// ON article_autocompletions
// USING GIST (autocompletion gist_trgm_ops)
// `
await db`
CREATE INDEX IF NOT EXISTS article_lien_reverse_key
ON article_lien (id, cible, typelien)
`
await db`
CREATE INDEX IF NOT EXISTS dossier_legislatif_jorf_texte_principal_id_key
ON dossier_legislatif (jorf_texte_principal_id)
@ -289,11 +322,10 @@ export async function configureDatabase() {
ON texte_version (nature_et_num)
`
// await db`
// CREATE INDEX IF NOT EXISTS article_autocompletions_trigrams_idx
// ON article_autocompletions
// USING GIST (autocompletion gist_trgm_ops)
// `
await db`
CREATE INDEX IF NOT EXISTS texte_version_lien_reverse_key
ON texte_version_lien (id, cible, typelien)
`
// Add comments once every table and column exists.

View file

@ -23,7 +23,7 @@ export const db = postgres({
port: config.db.port,
user: config.db.user,
})
export const versionNumber = 8
export const versionNumber = 9
/// Check that assemblee database exists and is up to date.
export async function checkAssembleeDb(): Promise<void> {

View file

@ -15,6 +15,7 @@ import { Aggregator } from "$lib/server/aggregates"
import { db } from "$lib/server/databases"
import type { RequestHandler } from "./$types"
import type { ArticleLienDb, TexteVersionLienDb } from "$lib/legal/shared"
function auditQuery(audit: Audit, query: URLSearchParams): [unknown, unknown] {
if (query == null) {
@ -36,18 +37,20 @@ function auditQuery(audit: Audit, query: URLSearchParams): [unknown, unknown] {
const remainingKeys = new Set(Object.keys(data))
auditFollowQuery(audit, data, errors, remainingKeys)
audit.attribute(
data,
"latest",
true,
errors,
remainingKeys,
auditSingleton(
auditTrimString,
auditStringToBoolean,
auditSetNullish(false),
),
)
for (const key of ["latest", "liens_entrants"]) {
audit.attribute(
data,
key,
true,
errors,
remainingKeys,
auditSingleton(
auditTrimString,
auditStringToBoolean,
auditSetNullish(false),
),
)
}
return audit.reduceRemaining(data, errors, remainingKeys, auditSetNullish({}))
}
@ -58,6 +61,7 @@ export const GET: RequestHandler = async ({ params, url }) => {
{
follow: Set<Follow>
latest: boolean
liens_entrants: boolean
},
unknown,
]
@ -71,7 +75,7 @@ export const GET: RequestHandler = async ({ params, url }) => {
)
error(400, JSON.stringify(queryError, null, 2))
}
const { follow, latest } = query
const { follow, latest, liens_entrants } = query
let article = (
await db<{ data: JorfArticle | LegiArticle }[]>`
@ -106,6 +110,24 @@ export const GET: RequestHandler = async ({ params, url }) => {
const aggregator = new Aggregator(follow)
aggregator.addArticle(article)
if (liens_entrants) {
for (const lien of await db<ArticleLienDb[]>`
SELECT *
FROM article_lien
WHERE id = ${id}
`) {
aggregator.addArticleLienDb(lien)
}
for (const lien of await db<TexteVersionLienDb[]>`
SELECT *
FROM texte_version_lien
WHERE id = ${id}
`) {
aggregator.addTexteVersionLienDb(lien)
}
}
await aggregator.getAll()
return new Response(
@ -114,6 +136,7 @@ export const GET: RequestHandler = async ({ params, url }) => {
...aggregator.toJson(),
follow: [...follow],
id,
liens_entrants,
},
null,
2,

View file

@ -1,10 +1,18 @@
import { type Audit, auditSetNullish, cleanAudit } from "@auditors/core"
import {
type Audit,
auditSetNullish,
cleanAudit,
auditSingleton,
auditTrimString,
auditStringToBoolean,
} from "@auditors/core"
import { error } from "@sveltejs/kit"
import { auditFollowQuery } from "$lib/auditors/queries"
import type { Follow } from "$lib/aggregates"
import type { TexteVersion } from "$lib/legal"
import type { ArticleLienDb, TexteVersionLienDb } from "$lib/legal/shared"
import { Aggregator } from "$lib/server/aggregates"
import { db } from "$lib/server/databases"
@ -30,14 +38,28 @@ function auditQuery(audit: Audit, query: URLSearchParams): [unknown, unknown] {
const remainingKeys = new Set(Object.keys(data))
auditFollowQuery(audit, data, errors, remainingKeys)
audit.attribute(
data,
"liens_entrants",
true,
errors,
remainingKeys,
auditSingleton(
auditTrimString,
auditStringToBoolean,
auditSetNullish(false),
),
)
return audit.reduceRemaining(data, errors, remainingKeys, auditSetNullish({}))
}
export const GET: RequestHandler = async ({ params, url }) => {
const id = params.id
const [query, queryError] = auditQuery(cleanAudit, url.searchParams) as [
{
follow: Set<Follow>
liens_entrants: boolean
},
unknown,
]
@ -51,19 +73,37 @@ export const GET: RequestHandler = async ({ params, url }) => {
)
error(400, JSON.stringify(queryError, null, 2))
}
const { follow } = query
const { follow, liens_entrants } = query
const texteVersion = (
await db<{ data: TexteVersion }[]>`
SELECT data FROM texte_version
WHERE ID = ${params.id}
WHERE ID = ${id}
`
).map(({ data }) => data)[0]
if (texteVersion === undefined) {
error(404, `TEXTE_VERSION ${params.id} non trouvé`)
error(404, `TEXTE_VERSION ${id} non trouvé`)
}
const aggregator = new Aggregator(follow)
aggregator.addTexteVersion(texteVersion)
if (liens_entrants) {
for (const lien of await db<ArticleLienDb[]>`
SELECT *
FROM article_lien
WHERE id = ${id}
`) {
aggregator.addArticleLienDb(lien)
}
for (const lien of await db<TexteVersionLienDb[]>`
SELECT *
FROM texte_version_lien
WHERE id = ${id}
`) {
aggregator.addTexteVersionLienDb(lien)
}
}
await aggregator.getAll()
return new Response(
@ -71,7 +111,8 @@ export const GET: RequestHandler = async ({ params, url }) => {
{
...aggregator.toJson(),
follow: [...follow],
id: params.id,
id,
liens_entrants,
},
null,
2,

View file

@ -57,7 +57,10 @@ export const load: PageLoad = async ({ fetch, url }) => {
follow = new Set(["LIENS.LIEN[@sens=cible,@typelien=CREATION].@id"])
}
const apiUrl = urlFromUrlAndQuery(`/api${url.pathname}`, { follow })
const apiUrl = urlFromUrlAndQuery(`/api${url.pathname}`, {
follow,
liens_entrants: true,
})
const response = await fetch(apiUrl, {
headers: { Accept: "application/json" },
})

View file

@ -64,7 +64,10 @@ export const load: PageLoad = async ({ fetch, url }) => {
])
}
const apiUrl = urlFromUrlAndQuery(`/api${url.pathname}`, { follow })
const apiUrl = urlFromUrlAndQuery(`/api${url.pathname}`, {
follow,
liens_entrants: true,
})
const response = await fetch(apiUrl, {
headers: { Accept: "application/json" },
})

180
src/scripts/index_liens.ts Normal file
View file

@ -0,0 +1,180 @@
import objectHash from "object-hash"
import sade from "sade"
import { db } from "$lib/server/databases"
import type {
LegiArticleLien,
LegiArticleLienType,
LegiTexteVersionLien,
LegiTexteVersionLienType,
} from "$lib/legal/legi"
import type {
JorfTexteVersionLien,
JorfTexteVersionLienType,
} from "$lib/legal/jorf"
async function indexLiens(): Promise<void> {
console.log("Indexing articles links…")
const articlesAvecLiensCursor = db<
{
id: string
liens: Array<LegiArticleLien>
}[]
>`
SELECT
id,
data -> 'LIENS' -> 'LIEN' AS liens
FROM article
WHERE
id LIKE 'LEGIART%'
AND data -> 'LIENS' -> 'LIEN' IS NOT NULL
`.cursor(100)
for await (const rows of articlesAvecLiensCursor) {
for (const { id, liens } of rows) {
const existingLienByHash = new Map(
(
await db<
{
cible: boolean
id: string
typelien: LegiArticleLienType
}[]
>`
SELECT id, cible, typelien
FROM article_lien
WHERE article_id = ${id}
`
).map((lien) => [objectHash(lien), lien]),
)
for (const lien of liens) {
if (lien["@id"] === undefined) {
continue
}
existingLienByHash.delete(
objectHash({
cible: lien["@sens"] === "cible",
id: lien["@id"],
typelien: lien["@typelien"],
}),
)
await db`
INSERT INTO article_lien (
article_id,
cible,
id,
typelien
) VALUES (
${id},
${lien["@sens"] === "cible"},
${lien["@id"]},
${lien["@typelien"]}
)
ON CONFLICT
DO NOTHING
`
}
for (const {
cible,
id: linkedId,
typelien,
} of existingLienByHash.values()) {
await db`
DELETE FROM article_lien
WHERE
article_id = ${id}
AND cible = ${cible}
AND id = ${linkedId}
AND typelien = ${typelien}
`
}
}
}
console.log("Indexing textes versions links…")
const textesVersionsAvecLiensCursor = db<
{
id: string
liens: Array<JorfTexteVersionLien | LegiTexteVersionLien>
}[]
>`
SELECT
id,
data -> 'META' -> 'META_SPEC' -> 'META_TEXTE_VERSION' -> 'LIENS' -> 'LIEN' AS liens
FROM texte_version
WHERE
(id LIKE 'JORFTEXT%' OR id LIKE 'LEGITEXT%')
AND data -> 'META' -> 'META_SPEC' -> 'META_TEXTE_VERSION' -> 'LIENS' -> 'LIEN' IS NOT NULL
`.cursor(100)
for await (const rows of textesVersionsAvecLiensCursor) {
for (const { id, liens } of rows) {
const existingLienByHash = new Map(
(
await db<
{
cible: boolean
id: string
typelien: JorfTexteVersionLienType | LegiTexteVersionLienType
}[]
>`
SELECT id, cible, typelien
FROM texte_version_lien
WHERE texte_version_id = ${id}
`
).map((lien) => [objectHash(lien), lien]),
)
for (const lien of liens) {
if (lien["@id"] === undefined) {
continue
}
existingLienByHash.delete(
objectHash({
cible: lien["@sens"] === "cible",
id: lien["@id"],
typelien: lien["@typelien"],
}),
)
await db`
INSERT INTO texte_version_lien (
texte_version_id,
cible,
id,
typelien
) VALUES (
${id},
${lien["@sens"] === "cible"},
${lien["@id"]},
${lien["@typelien"]}
)
ON CONFLICT
DO NOTHING
`
}
for (const {
cible,
id: linkedId,
typelien,
} of existingLienByHash.values()) {
await db`
DELETE FROM texte_version_lien
WHERE
texte_version_id_id = ${id}
AND cible = ${cible}
AND id = ${linkedId}
AND typelien = ${typelien}
`
}
}
}
}
sade("index_liens", true)
.describe("Index links of LEGI articles and LEGI & JORF textes versions")
.action(async () => {
await indexLiens()
process.exit(0)
})
.parse(process.argv)

230
src/scripts/test_dole.ts Normal file
View file

@ -0,0 +1,230 @@
import assert from "assert"
import fs from "fs-extra"
import path from "path"
import sade from "sade"
import type {
DossierLegislatif,
JorfTexteVersion,
JorfTextelr,
} from "$lib/legal"
import { parseDossierLegislatif } from "$lib/parsers"
import { db } from "$lib/server/databases"
import { walkDir } from "$lib/server/file_systems"
async function testDole(dilaDir: string): Promise<void> {
const dataDir = path.join(dilaDir, "dole")
assert(await fs.pathExists(dataDir))
iterXmlFiles: for (const relativeSplitPath of walkDir(dataDir)) {
const relativePath = path.join(...relativeSplitPath)
const filePath = path.join(dataDir, relativePath)
if (!filePath.endsWith(".xml")) {
console.info(`Skipping non XML file at ${filePath}`)
continue
}
const xmlString: string = await fs.readFile(filePath, {
encoding: "utf8",
})
const dossierLegislatif = parseDossierLegislatif(filePath, xmlString)
if (dossierLegislatif === undefined) {
break iterXmlFiles
}
const metaDossierLegislatif = dossierLegislatif.META.META_DOSSIER_LEGISLATIF
const textelrs: JorfTextelr[] = []
const texteVersions: JorfTexteVersion[] = []
for (const idTexteName of [
"ID_TEXTE_1",
"ID_TEXTE_2",
"ID_TEXTE_3",
"ID_TEXTE_4",
"ID_TEXTE_5",
] as Array<keyof DossierLegislatif["META"]["META_DOSSIER_LEGISLATIF"]>) {
const idTexte = metaDossierLegislatif[idTexteName] as string | undefined
if (idTexte === undefined) {
continue
}
assert(idTexte.startsWith("JORFTEXT"))
const textelr = (
await db<{ data: JorfTextelr }[]>`
SELECT data
FROM textelr
WHERE id = ${idTexte}
`
).map(({ data }) => data)[0]
if (textelr === undefined) {
console.warn(
`In dossier législatif ${dossierLegislatif.META.META_COMMUN.ID}, field META.META_DOSSIER_LEGISLATIF.${idTexteName} has value ${idTexte} that is not a valid JORF reference of a textelr`,
)
continue
}
const texteVersion = (
await db<{ data: JorfTexteVersion }[]>`
SELECT data
FROM texte_version
WHERE id = ${idTexte}
`
).map(({ data }) => data)[0]
if (texteVersion === undefined) {
console.warn(
`In dossier législatif ${dossierLegislatif.META.META_COMMUN.ID}, field META.META_DOSSIER_LEGISLATIF.${idTexteName} has value ${idTexte} that is not a valid JORF reference of a texte_version`,
)
continue
}
textelrs.push(textelr)
texteVersions.push(texteVersion)
}
const natures = textelrs.map((textelr) => textelr.META.META_COMMUN.NATURE)
for (const expectedNatures of [
[],
["DECISION", "DECISION"],
["DIRECTIVE_EURO"],
["LOI"],
[
"LOI",
undefined, // Décision
],
[
"LOI",
undefined, // Décision
"LOI",
],
["LOI", "AVIS"],
["LOI", "DECISION"],
["LOI", "DECISION", "ACCORD"],
["LOI", "DECISION", "AVIS", "LOI"],
["LOI", "DECISION", "DECISION"],
["LOI", "DECISION", "DECISION", "LOI"],
["LOI", "DECISION", "DIRECTIVE_EURO"],
["LOI", "DECISION", "LOI"],
["LOI", "DECISION", "LOI", "DECISION"],
["LOI", "DECISION", "LOI", "LOI"],
["LOI", "DECISION", "RAPPORT", "ORDONNANCE"],
["LOI", "DECRET"],
["LOI", "DIRECTIVE_EURO"],
["LOI", "DIRECTIVE_EURO", "DIRECTIVE_EURO"],
["LOI", "LOI"],
["LOI", "LOI", "DECISION"],
["LOI", "LOI", "DECISION", "DECISION"],
["LOI", "LOI", "DECISION", "DIRECTIVE_EURO"],
["LOI", "LOI", "LOI"],
["LOI", "RAPPORT", "LOI"],
["LOI_CONSTIT"],
["LOI_ORGANIQUE"],
["LOI_ORGANIQUE", "DECISION"],
["LOI_ORGANIQUE", "LOI_ORGANIQUE"],
["LOI_ORGANIQUE", "LOI_ORGANIQUE", "DECISION"],
["ORDONNANCE"],
// [
// "ORDONNANCE",
// undefined, // Rapport au Président de la République relatif à l'ordonnance
// ],
// [
// "ORDONNANCE",
// undefined, // Rapport au Président de la République relatif à l'ordonnance
// "LOI",
// ],
// // ["ORDONNANCE", "LOI"],
// ["ORDONNANCE", "LOI", "DECISION"],
// ["ORDONNANCE", "ORDONNANCE", "LOI"],
// ["ORDONNANCE", "ORDONNANCE", "ORDONNANCE", "LOI"],
// ["ORDONNANCE", "ORDONNANCE", "ORDONNANCE", "RAPPORT", "LOI"],
// ["ORDONNANCE", "ORDONNANCE", "RAPPORT", "LOI"],
["ORDONNANCE", "ORDONNANCE", "RAPPORT"],
// ["ORDONNANCE", "ORDONNANCE", "RAPPORT", "LOI"],
["ORDONNANCE", "RAPPORT"],
["ORDONNANCE", "RAPPORT", "DECISION"],
// ["ORDONNANCE", "RAPPORT", "DECISION", "LOI"],
["ORDONNANCE", "RAPPORT", "DECRET"],
// ["ORDONNANCE", "RAPPORT", "DECRET", "LOI"],
["ORDONNANCE", "RAPPORT", "DECRET", "ORDONNANCE"],
["ORDONNANCE", "RAPPORT", "DIRECTIVE_EURO"],
["ORDONNANCE", "RAPPORT", "DIRECTIVE_EURO", "DIRECTIVE_EURO"],
[
"ORDONNANCE",
"RAPPORT",
"DIRECTIVE_EURO",
"DIRECTIVE_EURO",
"DIRECTIVE_EURO",
],
// ["ORDONNANCE", "RAPPORT", "DIRECTIVE_EURO", "DIRECTIVE_EURO", "LOI"],
// ["ORDONNANCE", "RAPPORT", "DIRECTIVE_EURO", "LOI"],
// ["ORDONNANCE", "RAPPORT", "DIRECTIVE_EURO", "LOI", "DIRECTIVE_EURO"],
// ["ORDONNANCE", "RAPPORT", "DIRECTIVE_EURO", "LOI", "LOI"],
["ORDONNANCE", "RAPPORT", "DIRECTIVE_EURO", "ORDONNANCE"],
// ["ORDONNANCE", "RAPPORT", "LOI"],
// ["ORDONNANCE", "RAPPORT", "LOI", "DECISION"],
// ["ORDONNANCE", "RAPPORT", "LOI", "DIRECTIVE_EURO"],
// ["ORDONNANCE", "RAPPORT", "LOI", "DIRECTIVE_EURO", "DIRECTIVE_EURO"],
// ["ORDONNANCE", "RAPPORT", "LOI", "LOI"],
// ["ORDONNANCE", "RAPPORT", "LOI", "LOI", "LOI"],
["ORDONNANCE", "RAPPORT", "ORDONNANCE"],
[
"ORDONNANCE",
"RAPPORT",
"ORDONNANCE",
undefined, // Rapport au Président de la République relatif à l'ordonnance
],
// [
// "ORDONNANCE",
// "RAPPORT",
// "ORDONNANCE",
// undefined, // Rapport au Président de la République relatif à l'ordonnance
// "LOI",
// ],
[
"ORDONNANCE",
"RAPPORT",
"ORDONNANCE",
"DIRECTIVE_EURO",
"DIRECTIVE_EURO",
],
// ["ORDONNANCE", "RAPPORT", "ORDONNANCE", "LOI"],
["ORDONNANCE", "RAPPORT", "ORDONNANCE", "ORDONNANCE"],
// ["ORDONNANCE", "RAPPORT", "ORDONNANCE", "ORDONNANCE", "LOI"],
["ORDONNANCE", "RAPPORT", "ORDONNANCE", "ORDONNANCE", "ORDONNANCE"],
["ORDONNANCE", "RAPPORT", "RAPPORT"],
// ["ORDONNANCE", "RAPPORT", "RAPPORT", "LOI"],
["RAPPORT"],
// ["RAPPORT", "ORDONNANCE", "LOI"],
]) {
if (
natures.length === expectedNatures.length &&
natures.every((nature, index) => nature === expectedNatures[index])
) {
continue iterXmlFiles
}
}
// console.log(
// "Dossier",
// dossierLegislatif.META.META_COMMUN.ID,
// dossierLegislatif.META.META_DOSSIER_LEGISLATIF.TITRE,
// )
for (const [index, textelr] of textelrs.entries()) {
const texteVersion = texteVersions[index]
if (textelr.META.META_COMMUN.NATURE === "LOI") {
console.log(
"Dossier",
dossierLegislatif.META.META_COMMUN.ID,
dossierLegislatif.META.META_DOSSIER_LEGISLATIF.TITRE,
)
console.log(
" ",
textelr.META.META_COMMUN.ID,
textelr.META.META_COMMUN.NATURE,
texteVersion.META.META_SPEC.META_TEXTE_VERSION.TITREFULL,
)
}
}
}
}
sade("test_dole <dilaDir>", true)
.describe("Test Dila's DOLE database")
.action(async (dilaDir) => {
await testDole(dilaDir)
process.exit(0)
})
.parse(process.argv)