diff --git a/packages/api/package.json b/packages/api/package.json
index e38eef6ed3c05411bdbb327f2e33cb78ada6f2f4..75cb20513e70c32a288fde86df5a6c020744c5b2 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -7,7 +7,7 @@
   "type": "module",
   "scripts": {
     "build": "tsc --incremental",
-    "check-perimetres": "node --enable-source-maps --loader ts-node/esm/transpile-only ./src/scripts/check-perimetres.ts",
+    "check-prolongations": "node --enable-source-maps --loader ts-node/esm/transpile-only ./src/scripts/check-prolongations.ts",
     "daily": "node --enable-source-maps --loader ts-node/esm/transpile-only ./src/scripts/daily.ts",
     "monthly": "node --enable-source-maps --loader ts-node/esm/transpile-only ./src/scripts/monthly.ts",
     "db:dump": "rm -rf ./backups/* && pg_dump --host=localhost --username=postgres --clean --if-exists --format=d --no-owner --no-privileges --dbname=camino --file=./backups/",
diff --git a/packages/api/src/business/check-prolongations.ts b/packages/api/src/business/check-prolongations.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8f378675dcc3fd1b5da6e529837e824d795da4f0
--- /dev/null
+++ b/packages/api/src/business/check-prolongations.ts
@@ -0,0 +1,303 @@
+import { Pool } from 'pg'
+import { getDemarcheByIdOrSlug, GetDemarcheByIdOrSlugErrors, getDemarches, getEtapesByDemarcheId } from '../api/rest/demarches.queries'
+import { Effect, pipe } from 'effect'
+import { CaminoError } from 'camino-common/src/zod-tools'
+import { DemarcheId } from 'camino-common/src/demarche'
+import { isEtapeComplete } from 'camino-common/src/permissions/titres-etapes'
+import { userSuper } from '../database/user-super'
+import { firstEtapeDateValidator } from 'camino-common/src/date'
+import { titreEtapeGet, titreEtapeUpsert } from '../database/queries/titres-etapes'
+import { isNotNullNorUndefined, isNotNullNorUndefinedNorEmpty, isNullOrUndefined, isNullOrUndefinedOrEmpty, memoize, toSorted } from 'camino-common/src/typescript-tools'
+import { iTitreEtapeToFlattenEtape, TitreEtapeToFlattenEtapeErrors } from '../api/_format/titres-etapes'
+import { ITitreEtape } from '../types'
+import {
+  getDocumentsByEtapeId,
+  getEntrepriseDocumentIdsByEtapeId,
+  getEtapeAvisLargeObjectIdsByEtapeId,
+  GetEtapeAvisLargeObjectIdsByEtapeIdErrors,
+  GetEtapeDocumentLargeObjectIdsByEtapeIdErrors,
+} from '../database/queries/titres-etapes.queries'
+import { getAdministrationsLocales } from 'camino-common/src/administrations'
+import { ZodUnparseable } from '../tools/fp-tools'
+import { TitreId, TitreSlug } from 'camino-common/src/validators/titres'
+import { EtapeId } from 'camino-common/src/etape'
+import { isDemarcheTypeProlongations } from 'camino-common/src/static/demarchesTypes'
+import { getMostRecentValuePropFromEtapeFondamentaleValide, TitreGetDemarche } from 'camino-common/src/titres'
+import { isDemarcheStatutNonStatue, isDemarcheStatutNonValide } from 'camino-common/src/static/demarchesStatuts'
+import { getTitre } from '../api/rest/titres.queries'
+
+type CheckedProlongation = {
+  titre_slug: TitreSlug | null
+  demarche_id: DemarcheId
+  etape_id: EtapeId | null
+  titre_id: TitreId | null
+  errors: string[]
+}
+
+const demarchePasProlongationError = 'pas une prolongation' as const
+const demarcheSansEtapesError = "La démarche ne contient pas d'étapes" as const
+const titreEtapeGetError = 'Le fetch de la firstEtape a échoué' as const
+type CheckOnePrologationErrors =
+  | GetDemarcheByIdOrSlugErrors
+  | TitreEtapeToFlattenEtapeErrors
+  | GetEtapeDocumentLargeObjectIdsByEtapeIdErrors
+  | ZodUnparseable
+  | GetEtapeAvisLargeObjectIdsByEtapeIdErrors
+  | typeof titreEtapeGetError
+  | typeof demarcheSansEtapesError
+  | typeof demarchePasProlongationError
+const checkOneProlongation = (pool: Pool, demarcheId: DemarcheId): Effect.Effect<CheckedProlongation, CaminoError<CheckOnePrologationErrors>> => {
+  return Effect.Do.pipe(
+    Effect.bind('demarche', () => getDemarcheByIdOrSlug(pool, demarcheId)),
+    Effect.filterOrFail(
+      ({ demarche }) => isDemarcheTypeProlongations(demarche.demarche_type_id),
+      () => ({ message: demarchePasProlongationError })
+    ),
+    Effect.bind('etapes', () => getEtapesByDemarcheId(pool, demarcheId)),
+    Effect.filterOrFail(
+      ({ etapes }) => isNotNullNorUndefinedNorEmpty(etapes),
+      () => ({ message: demarcheSansEtapesError, extra: {} })
+    ),
+    Effect.bind('firstEtape', ({ etapes }) =>
+      pipe(
+        Effect.tryPromise({
+          try: () => titreEtapeGet(toSorted(etapes, (a, b) => a.ordre - b.ordre)[0].id, { fields: { id: {} }, fetchHeritage: true }, userSuper),
+          catch: error => ({ message: titreEtapeGetError, extra: error }),
+        }),
+        Effect.filterOrFail(
+          (firstEtape): firstEtape is ITitreEtape => isNotNullNorUndefined(firstEtape),
+          () => ({ message: titreEtapeGetError, extra: 'La first étape est null ou undefined' })
+        )
+      )
+    ),
+    Effect.let('titreProps', ({ demarche, firstEtape }) => ({
+      titreTypeId: memoize(() => Promise.resolve(demarche.titre_type_id)),
+      administrationsLocales: memoize(() =>
+        Promise.resolve(getAdministrationsLocales(isNotNullNorUndefined(firstEtape.communes) ? firstEtape.communes.map(({ id }) => id) : [], firstEtape.secteursMaritime))
+      ),
+      entreprisesTitulairesOuAmodiataires: memoize(() => Promise.resolve([...(firstEtape.titulaireIds ?? []), ...(firstEtape.amodiataireIds ?? [])])),
+    })),
+    Effect.bind('etapeDocuments', ({ firstEtape, demarche, titreProps }) => {
+      return getDocumentsByEtapeId(firstEtape.id, pool, userSuper, titreProps.titreTypeId, titreProps.administrationsLocales, titreProps.entreprisesTitulairesOuAmodiataires, firstEtape.typeId, {
+        demarche_type_id: demarche.demarche_type_id,
+        entreprises_lecture: demarche.entreprises_lecture,
+        public_lecture: demarche.public_lecture,
+        titre_public_lecture: false,
+      })
+    }),
+    Effect.bind('entrepriseDocuments', ({ firstEtape }) => getEntrepriseDocumentIdsByEtapeId({ titre_etape_id: firstEtape.id }, pool, userSuper)),
+    Effect.bind('etapeAvis', ({ firstEtape, demarche, titreProps }) =>
+      getEtapeAvisLargeObjectIdsByEtapeId(
+        firstEtape.id,
+        pool,
+        userSuper,
+        titreProps.titreTypeId,
+        titreProps.administrationsLocales,
+        titreProps.entreprisesTitulairesOuAmodiataires,
+        firstEtape.typeId,
+        {
+          demarche_type_id: demarche.demarche_type_id,
+          entreprises_lecture: demarche.entreprises_lecture,
+          public_lecture: demarche.public_lecture,
+          titre_public_lecture: false,
+        }
+      )
+    ),
+    Effect.bind('flattenFirstEtape', ({ firstEtape }) => iTitreEtapeToFlattenEtape(firstEtape)),
+    Effect.map(({ demarche, firstEtape, flattenFirstEtape, etapeDocuments, entrepriseDocuments, etapeAvis }) => {
+      const isFirstEtapeComplete = isEtapeComplete(
+        flattenFirstEtape,
+        demarche.titre_type_id,
+        demarcheId,
+        demarche.demarche_type_id,
+        etapeDocuments,
+        entrepriseDocuments,
+        firstEtape.sdomZones,
+        isNotNullNorUndefined(firstEtape.communes) ? firstEtape.communes.map(({ id }) => id) : [],
+        etapeAvis,
+        userSuper,
+        firstEtapeDateValidator.parse(flattenFirstEtape.date)
+      )
+
+      return {
+        titre_slug: demarche.titre_slug,
+        demarche_id: demarche.demarche_id,
+        etape_id: firstEtape.id,
+        titre_id: demarche.titre_id,
+        errors: isFirstEtapeComplete.valid ? [] : isFirstEtapeComplete.errors,
+      }
+    }),
+    Effect.catchAll(caminoError => {
+      return Effect.succeed({
+        titre_slug: null,
+        demarche_id: demarcheId,
+        etape_id: null,
+        titre_id: null,
+        errors: [caminoError.message],
+      })
+    })
+  )
+}
+
+const titreIdVideError = "Pas d'id de titre" as const
+const upsertFailError = "Échec de mise à jour de l'étape" as const
+const titreGetError = 'Impossible de fetch le titre' as const
+const titreGetIsNullError = 'Titre introuvable' as const
+const etapeIntrouvableError = 'Étape introuvable dans le titre' as const
+const demarcheListIsEmptyError = 'Liste des démarches héritables vide' as const
+type FixProlongationErrors = typeof titreIdVideError | typeof upsertFailError | typeof titreGetError | typeof titreGetIsNullError | typeof demarcheListIsEmptyError | typeof etapeIntrouvableError
+const fixProlongation = (pool: Pool, checkedProlongation: CheckedProlongation): Effect.Effect<null, CaminoError<FixProlongationErrors>> => {
+  return Effect.Do.pipe(
+    Effect.bind('titre', () =>
+      pipe(
+        Effect.succeed(checkedProlongation.titre_id),
+        Effect.filterOrFail(
+          titreId => isNotNullNorUndefined(titreId),
+          () => ({ message: titreIdVideError })
+        ),
+        Effect.flatMap(titreId =>
+          Effect.tryPromise({
+            try: () => getTitre(pool, userSuper, titreId),
+            catch: error => ({ message: titreGetError, extra: error }),
+          })
+        ),
+        Effect.filterOrFail(
+          (titre): titre is NonNullable<typeof titre> => isNotNullNorUndefined(titre),
+          error => ({ message: titreGetIsNullError, extra: error })
+        )
+      )
+    ),
+    Effect.bind('etape', ({ titre }) =>
+      pipe(
+        Effect.succeed(titre.demarches.find(({ id }) => id === checkedProlongation.demarche_id)),
+        Effect.filterOrFail(
+          (demarche): demarche is NonNullable<typeof demarche> => isNotNullNorUndefined(checkedProlongation.demarche_id) && isNotNullNorUndefined(demarche),
+          () => ({ message: etapeIntrouvableError, extra: 'Démarche introuvable' })
+        ),
+        Effect.map(demarche => demarche.etapes.find(({ id }) => id === checkedProlongation.etape_id)),
+        Effect.filterOrFail(
+          (etape): etape is NonNullable<typeof etape> => isNotNullNorUndefined(checkedProlongation.etape_id) && isNotNullNorUndefined(etape),
+          () => ({ message: etapeIntrouvableError, extra: 'Étape introuvable' })
+        )
+      )
+    ),
+    Effect.bind('propsHeritees', ({ titre }) =>
+      Effect.Do.pipe(
+        Effect.bind('demarches', () => {
+          const demarches: TitreGetDemarche[] = []
+          for (const demarche of toSorted([...titre.demarches], (a, b) => a.ordre - b.ordre)) {
+            if (demarche.id === checkedProlongation.demarche_id) {
+              return Effect.succeed([...demarches, demarche])
+            } else if (!isDemarcheStatutNonStatue(demarche.demarche_statut_id) && !isDemarcheStatutNonValide(demarche.demarche_statut_id)) {
+              demarches.push(demarche)
+            }
+          }
+
+          return Effect.fail({ message: demarcheListIsEmptyError })
+        }),
+        Effect.map(({ demarches }) => {
+          return {
+            substances: getMostRecentValuePropFromEtapeFondamentaleValide('substances', demarches),
+            titulaireIds: getMostRecentValuePropFromEtapeFondamentaleValide('titulaireIds', demarches),
+            perimetre: getMostRecentValuePropFromEtapeFondamentaleValide('perimetre', demarches),
+          }
+        })
+      )
+    ),
+    Effect.flatMap(({ titre, etape, propsHeritees }) =>
+      Effect.tryPromise({
+        try: () => {
+          console.info(`Trying to fix http://localhost:4180/etapes/${etape.id}`)
+          return titreEtapeUpsert(
+            {
+              id: etape.id,
+              titreDemarcheId: checkedProlongation.demarche_id,
+              date: etape.date,
+              substances: 'fondamentale' in etape && isNotNullNorUndefinedNorEmpty(etape.fondamentale.substances) ? etape.fondamentale.substances : propsHeritees.substances,
+              titulaireIds: 'fondamentale' in etape && isNotNullNorUndefinedNorEmpty(etape.fondamentale.titulaireIds) ? etape.fondamentale.titulaireIds : propsHeritees.titulaireIds,
+              surface: 'fondamentale' in etape && isNotNullNorUndefined(etape.fondamentale.perimetre?.surface) ? etape.fondamentale.perimetre.surface : propsHeritees.perimetre?.surface,
+              geojson4326Perimetre:
+                'fondamentale' in etape && isNotNullNorUndefined(etape.fondamentale.perimetre?.geojson4326_perimetre)
+                  ? etape.fondamentale.perimetre.geojson4326_perimetre
+                  : propsHeritees.perimetre?.geojson4326_perimetre,
+              geojsonOriginePerimetre:
+                'fondamentale' in etape && isNotNullNorUndefined(etape.fondamentale.perimetre?.geojson_origine_perimetre)
+                  ? etape.fondamentale.perimetre.geojson_origine_perimetre
+                  : propsHeritees.perimetre?.geojson_origine_perimetre,
+              geojsonOrigineGeoSystemeId:
+                'fondamentale' in etape && isNotNullNorUndefined(etape.fondamentale.perimetre?.geojson_origine_geo_systeme_id)
+                  ? etape.fondamentale.perimetre.geojson_origine_geo_systeme_id
+                  : propsHeritees.perimetre?.geojson_origine_geo_systeme_id,
+            },
+            userSuper,
+            titre.id
+          )
+        },
+        catch: error => ({ message: upsertFailError, extra: error }),
+      })
+    ),
+    Effect.map(() => null),
+    Effect.catchAll(error => {
+      console.info(error)
+      return Effect.succeed(null)
+    })
+  )
+}
+
+const toPercentage = (percentage: number): string => {
+  return `${Math.round(percentage * 10000) / 100} %`
+}
+
+export const checkProlongations = (pool: Pool): Effect.Effect<CheckedProlongation[], CaminoError<CheckOnePrologationErrors | FixProlongationErrors>> => {
+  return pipe(
+    getDemarches(pool),
+    Effect.flatMap(demarches => {
+      const initialAcc: CheckedProlongation[] = []
+      return Effect.reduce(
+        demarches.map(({ id }) => id),
+        initialAcc,
+        (acc, demarcheId) => {
+          return pipe(
+            checkOneProlongation(pool, demarcheId),
+            Effect.tap(checkedProlongation => {
+              if (isNullOrUndefinedOrEmpty(checkedProlongation.errors) || checkedProlongation.errors.includes(demarchePasProlongationError)) {
+                return Effect.succeed(checkedProlongation)
+              }
+
+              return fixProlongation(pool, checkedProlongation)
+            }),
+            Effect.map(checkedProlongation => {
+              // if (isNotNullNorUndefinedNorEmpty(checkedProlongation.errors) && !checkedProlongation.errors.includes(demarchePasProlongationError)) {
+              //   const link = isNotNullNorUndefined(checkedProlongation.titre_slug)
+              //     ? `http://localhost:4180/titres/${checkedProlongation.titre_slug}`
+              //     : `http://localhost:4180/demarches/${checkedProlongation.demarche_id}`
+              //   console.info(`${link} : ${checkedProlongation.errors.join(' | ')}`)
+              // }
+
+              return [...acc, checkedProlongation]
+            })
+          )
+        }
+      )
+    }),
+    Effect.tap(checkedProlongation => {
+      const prolongations = checkedProlongation.filter(({ errors }) => !errors.includes(demarchePasProlongationError))
+      const prolongationsEnVrac = prolongations.filter(({ errors }) => errors.length > 0)
+      const prolongationsAvecDureeManquante = prolongations.filter(({ errors }) => errors.some(error => error.includes('la durée est obligatoire')))
+
+      const prolongationsParType = prolongationsEnVrac.reduce<{ [key: string]: number }>((acc, prolongation) => {
+        const domaine = isNotNullNorUndefined(prolongation.titre_slug) ? `${prolongation.titre_slug.slice(2, 4)}${prolongation.titre_slug.slice(0, 1)}` : 'inconnu'
+        if (isNullOrUndefined(acc[domaine])) {
+          acc[domaine] = 1
+        } else {
+          acc[domaine] += 1
+        }
+
+        return acc
+      }, {})
+      console.info(`Prolongations en vrac : ${prolongationsEnVrac.length} (${toPercentage(prolongationsEnVrac.length / prolongations.length)})`)
+      console.info(JSON.stringify(prolongationsParType, null, 2))
+      console.info(`Prolongations sans durée : ${prolongationsAvecDureeManquante.length} (${toPercentage(prolongationsAvecDureeManquante.length / prolongationsEnVrac.length)})`)
+    })
+  )
+}
diff --git a/packages/api/src/scripts/check-prolongations.ts b/packages/api/src/scripts/check-prolongations.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fa142e25b82e0e085841bfe3c489f6de28e62418
--- /dev/null
+++ b/packages/api/src/scripts/check-prolongations.ts
@@ -0,0 +1,14 @@
+import '../init'
+import pg from 'pg'
+import { config } from '../config'
+import { checkProlongations } from '../business/check-prolongations'
+import { Effect } from 'effect'
+
+const pool = new pg.Pool({
+  host: config().PGHOST,
+  user: config().PGUSER,
+  password: config().PGPASSWORD,
+  database: config().PGDATABASE,
+})
+
+Effect.runPromiseExit(checkProlongations(pool)).catch(console.error)