Skip to content
Snippets Groups Projects

chore(perimetres): corriger les périmètres qui ne sont pas valides

Merged SAFINE LAGET Anis requested to merge verifier-perimetres into master
1 file
+ 79
122
Compare changes
  • Side-by-side
  • Inline
@@ -5,16 +5,10 @@ import { Effect } from 'effect'
import { knex } from '../knex'
import { EtapeId } from 'camino-common/src/etape'
import { CaminoError } from 'camino-common/src/zod-tools'
import { getPerimetreInfosInternal, PerimetreInfos } from '../api/rest/etapes'
import { titreEtapeUpdationValidate } from '../business/validations/titre-etape-updation-validate'
import { titreDemarcheGet } from '../database/queries/titres-demarches'
import { isNullOrUndefined } from 'camino-common/src/typescript-tools'
import { userSuper } from '../database/user-super'
import { titreEtapeGet } from '../database/queries/titres-etapes'
import { iTitreEtapeToFlattenEtape } from '../api/_format/titres-etapes'
import { FlattenEtape } from 'camino-common/src/etape-form'
import TitresDemarches from '../database/models/titres-demarches'
import { getPerimetreInfosInternal } from '../api/rest/etapes'
import { TitreTypeId } from 'camino-common/src/static/titresTypes'
import { FeatureCollectionForages, FeatureCollectionPoints, FeatureMultiPolygon, MultiPolygon } from 'camino-common/src/perimetre'
import { GeoSystemeId } from 'camino-common/src/static/geoSystemes'
// Le pool ne doit être qu'aux entrypoints : le daily, le monthly, et l'application.
const pool = new pg.Pool({
@@ -25,26 +19,47 @@ const pool = new pg.Pool({
})
type QueryError = "Échec de récupération des ids d'étapes en BDD"
type PerimetreError = 'Certaines étapes ont des périmètres invalides'
type EtapeValidationError = "Échec de validation d'une étape"
type EtapeFetchError = "Échec de récupération d'une étape complète"
type DemarcheFetchError = "Échec de récupération d'une démarche"
type PipelineError = CaminoError<QueryError | PerimetreError | EtapeValidationError | EtapeFetchError | DemarcheFetchError>
function getAllEtapeIds(): Effect.Effect<{ id: EtapeId; titreTypeId: TitreTypeId }[], CaminoError<QueryError>, never> {
type EtapeError = 'Des étapes ont des périmètres invalides'
type PerimetreValidationError = "Échec de validation d'un périmètre"
type PipelineError = CaminoError<QueryError | EtapeError | PerimetreValidationError>
type Perimetre = {
titreTypeId: TitreTypeId
etapeId: EtapeId
geojson4326Perimetre: MultiPolygon
geojsonOriginePerimetre: FeatureMultiPolygon | null
geojsonOriginePoints: FeatureCollectionPoints | null
geojsonOrigineGeoSystemeId: GeoSystemeId | null
geojsonOrigineForages: FeatureCollectionForages | null
}
function getAllPerimetres(): Effect.Effect<Perimetre[], CaminoError<QueryError>, never> {
return Effect.gen(function* () {
const { rows } = yield* Effect.tryPromise({
try: () =>
knex.raw<{ rows: { id: EtapeId; titreTypeId: TitreTypeId }[] }>(`
knex.raw<{ rows: Perimetre[] }>(`
SELECT
te.id,
t.type_id AS "titreTypeId"
FROM titres_etapes te
JOIN titres_demarches td ON td.id = te.titre_demarche_id
JOIN titres t ON t.id = td.titre_id
WHERE te.geojson4326_perimetre IS NOT NULL
LIMIT 100
`), // FIXME: retirer le LIMIT 100
"titreTypeId",
"etapeId",
ST_AsGeoJSON("geojson4326_perimetre", 40)::json AS "geojson4326Perimetre",
"geojsonOriginePerimetre",
"geojsonOriginePoints",
"geojsonOrigineGeoSystemeId",
"geojsonOrigineForages"
FROM (
SELECT DISTINCT
t.type_id AS "titreTypeId",
te.id AS "etapeId",
te.geojson4326_perimetre,
te.geojson_origine_perimetre AS "geojsonOriginePerimetre",
te.geojson_origine_points AS "geojsonOriginePoints",
te.geojson_origine_geo_systeme_id AS "geojsonOrigineGeoSystemeId",
te.geojson_origine_forages AS "geojsonOrigineForages"
FROM titres_etapes te
JOIN titres_demarches td ON td.id = te.titre_demarche_id
JOIN titres t ON t.id = td.titre_id
WHERE geojson4326_perimetre IS NOT NULL
) t
`),
catch: error => ({
message: "Échec de récupération des ids d'étapes en BDD" as const,
extra: { error },
@@ -56,15 +71,15 @@ function getAllEtapeIds(): Effect.Effect<{ id: EtapeId; titreTypeId: TitreTypeId
}
type InvalidEtape = { id: EtapeId; errors: string[] }
function filterInvalidEtapes(rows: { id: EtapeId; titreTypeId: TitreTypeId }[]): Effect.Effect<InvalidEtape[], CaminoError<GetEtapeErrorsErrors>, never> {
function getInvalidEtapes(rows: Perimetre[]): Effect.Effect<InvalidEtape[], CaminoError<PerimetreValidationError>, never> {
return Effect.gen(function* (_) {
const invalidEtapes: InvalidEtape[] = []
for (let i = 0; i < rows.length; i += 1) {
const errors = yield* _(getEtapeErrors(rows[i]))
const errors = yield* _(getPerimetreErrors(rows[i]))
if (errors.length > 0) {
invalidEtapes.push({ id: rows[i].id, errors })
console.error(`${rows[i].id} : ${errors.join(', ')}\n`)
invalidEtapes.push({ id: rows[i].etapeId, errors })
console.error(`(${Math.round(i / rows.length * 10000) / 100}%) ${rows[i].etapeId} : ${errors.join(', ')}\n`)
}
}
@@ -72,109 +87,51 @@ function filterInvalidEtapes(rows: { id: EtapeId; titreTypeId: TitreTypeId }[]):
})
}
type FetchFullEtapeErrors = EtapeFetchError | DemarcheFetchError
function fetchFullEtape(etapeId: EtapeId): Effect.Effect<{ etape: FlattenEtape; demarche: TitresDemarches }, CaminoError<FetchFullEtapeErrors>, never> {
return Effect.gen(function* () {
const etape = yield* Effect.tryPromise({
try: async () => {
const titreEtape = await titreEtapeGet(etapeId, { fields: { demarche: { titre: { pointsEtape: { id: {} } } } }, fetchHeritage: true }, userSuper)
return iTitreEtapeToFlattenEtape(titreEtape)
},
catch: error => ({
message: "Échec de récupération d'une étape complète" as const,
extra: {
etapeId,
error: error instanceof Error ? error.message : error,
},
}),
})
const demarche = yield* Effect.tryPromise({
try: () => titreDemarcheGet(etape.titreDemarcheId, { fields: {} }, userSuper),
catch: error => ({
message: "Échec de récupération d'une démarche" as const,
error: error instanceof Error ? error.message : error,
}),
})
if (isNullOrUndefined(demarche) || isNullOrUndefined(demarche.titre)) {
return yield* Effect.fail({
message: "Échec de récupération d'une démarche" as const,
error: 'La démarche (ou son titre) est undefined',
})
}
return { etape, demarche }
})
}
type GetEtapeErrorsErrors = FetchFullEtapeErrors | EtapeValidationError
function getEtapeErrors(row: { id: EtapeId; titreTypeId: TitreTypeId }): Effect.Effect<string[], CaminoError<GetEtapeErrorsErrors>, never> {
return Effect.gen(function* () {
const { etape, demarche } = yield* fetchFullEtape(row.id)
return yield* Effect.tryPromise({
try: async () => {
if (isNullOrUndefined(etape.perimetre.value?.geojson4326Perimetre)) {
console.error(etape.perimetre.value)
throw new Error('Périmètre manquant')
}
if (isNullOrUndefined(demarche.titre)) {
throw new Error('Titre de la démarche manquant')
}
let perimetreInfos: PerimetreInfos
try {
perimetreInfos = await getPerimetreInfosInternal(
pool,
etape.perimetre.value.geojson4326Perimetre,
etape.perimetre.value.geojsonOriginePerimetre,
etape.perimetre.value.geojsonOriginePoints,
row.titreTypeId,
etape.perimetre.value.geojsonOrigineGeoSystemeId,
etape.perimetre.value.geojsonOrigineForages
)
} catch (error) {
if (error instanceof Error) {
return [error.message]
} else if (typeof error === 'string') {
return [error]
} else {
return ['Périmètre invalide (raison inconnue)']
}
function getPerimetreErrors(perimetre: Perimetre): Effect.Effect<string[], CaminoError<PerimetreValidationError>, never> {
return Effect.tryPromise({
try: async () => {
try {
await getPerimetreInfosInternal(
pool,
{ type: 'Feature', geometry: perimetre.geojson4326Perimetre, properties: {} },
perimetre.geojsonOriginePerimetre,
perimetre.geojsonOriginePoints,
perimetre.titreTypeId,
perimetre.geojsonOrigineGeoSystemeId,
perimetre.geojsonOrigineForages
)
} catch (error) {
if (error instanceof Error) {
return [error.message]
} else if (typeof error === 'string') {
return [error]
} else {
return ['Périmètre invalide (raison inconnue)']
}
}
return titreEtapeUpdationValidate(
etape,
demarche,
demarche.titre,
[],
[],
[],
perimetreInfos.sdomZones,
perimetreInfos.communes.map(({ id }) => id),
userSuper,
null,
null
)
return []
},
catch: error => ({
message: "Échec de validation d'un périmètre" as const,
extra: {
etapeId: perimetre.etapeId,
error: error instanceof Error ? error.message : error,
},
catch: error => ({
message: "Échec de validation d'une étape" as const,
extra: {
etapeId: row.id,
error: error instanceof Error ? error.message : error,
},
}),
})
}),
})
}
const pipeline: Effect.Effect<void, PipelineError, never> = Effect.gen(function* () {
const rows = yield* getAllEtapeIds()
const invalidEtapes = yield* filterInvalidEtapes(rows)
console.time('PIPELINE')
const perimetres = yield* getAllPerimetres()
const invalidEtapes = yield* getInvalidEtapes(perimetres)
console.timeEnd('PIPELINE')
if (invalidEtapes.length > 0) {
console.log(`${invalidEtapes.length} ont des périmètres invalides : ${invalidEtapes.map(({ id }) => id).join(',')}`)
yield* Effect.fail({
message: 'Certaines étapes ont des périmètres invalides' as const,
message: `Des étapes ont des périmètres invalides` as const,
extra: invalidEtapes.map(({ id, errors }) => `${id} : ${errors.join(', ')}`).join('\n'),
})
}
Loading