diff --git a/example.env b/example.env
index 312615c539623859e2099553a9dac7c023814b4e..c5368cd77defe2d1ce34fae54367d41c23fbea3a 100644
--- a/example.env
+++ b/example.env
@@ -15,7 +15,7 @@ BASE_URL="http://localhost:3000"
 CHILDREN_KEY="enfants"
 
 # Path to file containing decompositions in JSON format
-DECOMPOSITION_PATH="../openfisca-france-json/decompositions_customized.json"
+DECOMPOSITION_PATH="../openfisca-france-json/decompositions/decompositions_customized.json"
 
 # Name of variable used as root of decomposition
 DECOMPOSITION_ROOT="revenu_disponible"
@@ -61,3 +61,6 @@ PORTAL_URL="https://leximpact.an.fr/"
 PROXY=false
 
 TITLE="Simulateur socio-fiscal"
+
+# Path to file containing description of waterfalls in JSON format
+WATERFALLS_PATH="../openfisca-france-json/custom/waterfalls.json"
diff --git a/package-lock.json b/package-lock.json
index 9a8578f1a77dfb5457558c5924bcb3297e4ba26a..56f58d5a5d374679929c8cf8141e3db7bb02008d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,7 +14,7 @@
         "@auditors/core": "^0.1.7",
         "@fontsource/lato": "^4.3.0",
         "@fontsource/lora": "^4.3.0",
-        "@openfisca/ast": "^0.12.0",
+        "@openfisca/ast": "^0.12.2",
         "@sveltejs/adapter-node": "next",
         "@sveltejs/kit": "next",
         "@tailwindcss/forms": "^0.3.2",
@@ -1907,9 +1907,9 @@
       }
     },
     "node_modules/@openfisca/ast": {
-      "version": "0.12.0",
-      "resolved": "https://registry.npmjs.org/@openfisca/ast/-/ast-0.12.0.tgz",
-      "integrity": "sha512-ExicDxD58M+Rc7gpgmWJGTug1dmwgnb+uD4adr1EAyzjzFpBZbI0AcopNnpUu/bKKFGQ/yqSeOtzjZ9byr9Atw==",
+      "version": "0.12.2",
+      "resolved": "https://registry.npmjs.org/@openfisca/ast/-/ast-0.12.2.tgz",
+      "integrity": "sha512-ZuCd8e7LkejDR8VgSp2Xwjmp2H2ypU9aF2vogt278XuxSMCs/mseMhPxSWXf+eMaaYnatDTfrDHBUcKKOYbbCA==",
       "dev": true,
       "dependencies": {
         "@auditors/core": "^0.1.11",
@@ -8235,9 +8235,9 @@
       }
     },
     "@openfisca/ast": {
-      "version": "0.12.0",
-      "resolved": "https://registry.npmjs.org/@openfisca/ast/-/ast-0.12.0.tgz",
-      "integrity": "sha512-ExicDxD58M+Rc7gpgmWJGTug1dmwgnb+uD4adr1EAyzjzFpBZbI0AcopNnpUu/bKKFGQ/yqSeOtzjZ9byr9Atw==",
+      "version": "0.12.2",
+      "resolved": "https://registry.npmjs.org/@openfisca/ast/-/ast-0.12.2.tgz",
+      "integrity": "sha512-ZuCd8e7LkejDR8VgSp2Xwjmp2H2ypU9aF2vogt278XuxSMCs/mseMhPxSWXf+eMaaYnatDTfrDHBUcKKOYbbCA==",
       "dev": true,
       "requires": {
         "@auditors/core": "^0.1.11",
diff --git a/package.json b/package.json
index 1daa3bfef989718e040bd069983ab7b97c8ddda6..08c8d1b8ccc3ecc688e96fe4543a75067d403542 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,7 @@
     "@auditors/core": "^0.1.7",
     "@fontsource/lato": "^4.3.0",
     "@fontsource/lora": "^4.3.0",
-    "@openfisca/ast": "^0.12.0",
+    "@openfisca/ast": "^0.12.2",
     "@sveltejs/adapter-node": "next",
     "@sveltejs/kit": "next",
     "@tailwindcss/forms": "^0.3.2",
diff --git a/src/hooks.ts b/src/hooks.ts
index 5b9859773c1c16ef6e8777a5b966477ddd31fa70..19aa137e1d87a6b611e0764382a7d07d02bc1455 100644
--- a/src/hooks.ts
+++ b/src/hooks.ts
@@ -2,7 +2,7 @@ import type { GetSession, Handle } from "@sveltejs/kit"
 import fetch, { Response, Request, Headers } from "node-fetch"
 
 import config from "$lib/server/config"
-import { decompositionCoreByName } from "$lib/server/decompositions"
+import { decompositionCoreByName, waterfalls } from "$lib/server/decompositions"
 import { entityByKey, personEntityKey } from "$lib/server/entities"
 import { metadata } from "$lib/server/metadata"
 import { oauth2Authenticator } from "$lib/server/oauth2"
@@ -64,10 +64,10 @@ export const getSession: GetSession<{}, Session> = async (request) => {
     },
     personEntityKey,
     portalUrl: config.portalUrl,
-    rootDecompositionName: config.rootDecompositionName,
     testCases,
     title: config.title,
     user,
+    waterfalls,
   }
 }
 
diff --git a/src/lib/auditors/config.ts b/src/lib/auditors/config.ts
index 7db652ef2280e468c2cb12109c4daf5b83102ec0..08b6474024fad3bff63cf10b1c68f615f3b83cae 100644
--- a/src/lib/auditors/config.ts
+++ b/src/lib/auditors/config.ts
@@ -70,8 +70,8 @@ export function auditConfig(
     "decompositionsPath",
     "familyEntityKey",
     "jsonDir",
-    "rootDecompositionName",
     "title",
+    "waterfallsPath",
   ]) {
     audit.attribute(
       data,
diff --git a/src/lib/components/latchkeys/Arrow.svelte b/src/lib/components/latchkeys/Arrow.svelte
index c110a0ca0565d86935509cb35c104b92dc038ba0..4105a56c2069eb0129cbdc58fc78637f38969976 100644
--- a/src/lib/components/latchkeys/Arrow.svelte
+++ b/src/lib/components/latchkeys/Arrow.svelte
@@ -47,10 +47,11 @@
   // const newSelfTargetAProps = getContext("newSelfTargetAProps") as (
   //   url: string,
   // ) => SelfTargetAProps
-  const rootDecompositionName = $session.rootDecompositionName
   // const showColoredRects = getContext("showColoredRects") as Writable<boolean>
   const testCaseIndex = getContext("testCaseIndex") as Writable<number>
   const verticalLineStrokeWidth = 2
+  const waterfalls = $session.waterfalls
+  const rootDecompositionName = waterfalls[0].root
 
   $: aggregate = item.aggregate
 
diff --git a/src/lib/components/latchkeys/AxisY.svelte b/src/lib/components/latchkeys/AxisY.svelte
index b2576c567b6f1d23ebacd18e2f6a492f5ab8daa0..3b49443d710a3ff051a38dbbaf083e9de8859ef0 100644
--- a/src/lib/components/latchkeys/AxisY.svelte
+++ b/src/lib/components/latchkeys/AxisY.svelte
@@ -18,8 +18,9 @@
     "decompositionByNameArray",
   ) as Writable<DecompositionByName[]>
   const dispatch = createEventDispatcher()
-  const rootDecompositionName = $session.rootDecompositionName
   const testCaseIndex = getContext("testCaseIndex") as Writable<number>
+  const waterfalls = $session.waterfalls
+  const rootDecompositionName = waterfalls[0].root
 
   $: yHalfBandwidth = $yScale.bandwidth() / 2
 
diff --git a/src/lib/components/latchkeys/Latchkey.svelte b/src/lib/components/latchkeys/Latchkey.svelte
index f643eb645cb15a4d182bc2b69801d67acdc676e5..dd3d377c86b3fc08f37558574d4e2709bc62ff79 100644
--- a/src/lib/components/latchkeys/Latchkey.svelte
+++ b/src/lib/components/latchkeys/Latchkey.svelte
@@ -19,9 +19,10 @@
   const decompositionByNameArray = getContext(
     "decompositionByNameArray",
   ) as Writable<DecompositionByName[]>
-  const rootDecompositionName = $session.rootDecompositionName
   const showNulls = getContext("showNulls") as Writable<boolean>
   const testCaseIndex = getContext("testCaseIndex") as Writable<number>
+  const waterfalls = $session.waterfalls
+  const rootDecompositionName = waterfalls[0].root
 
   // const showColoredRects = writable(false)
   // setContext("showColoredRects", showColoredRects)
diff --git a/src/lib/components/scholar_waterfalls/ScholarWaterfall.svelte b/src/lib/components/scholar_waterfalls/ScholarWaterfall.svelte
index 5599d6cb1d6c9d5ea7041adfe8a30ada2d8e8943..8bbd58717fab08e949c8cf0ee7cdb6cf6d000c37 100644
--- a/src/lib/components/scholar_waterfalls/ScholarWaterfall.svelte
+++ b/src/lib/components/scholar_waterfalls/ScholarWaterfall.svelte
@@ -36,10 +36,11 @@
     minimumFractionDigits: 0,
     style: "currency",
   })
-  const rootDecompositionName = $session.rootDecompositionName
   const showNulls = getContext("showNulls") as Writable<boolean>
   const testCaseIndex = getContext("testCaseIndex") as Writable<number>
   let waterfallWidth = 100
+  const waterfalls = $session.waterfalls
+  const rootDecompositionName = waterfalls[0].root
 
   $: decompositionEvaluationAndDepthTriples = [
     ...walkVisibleEvaluations(
diff --git a/src/lib/components/test_cases/TestCaseView.svelte b/src/lib/components/test_cases/TestCaseView.svelte
index c519be7950e2154ab038d9e924e8681ec5d980c4..cfce09960ed1e261d469029325f26c65b08b84f6 100644
--- a/src/lib/components/test_cases/TestCaseView.svelte
+++ b/src/lib/components/test_cases/TestCaseView.svelte
@@ -58,12 +58,6 @@
   let inited = false
   const personEntity = entityByKey[$session.personEntityKey]
   const reform = getContext("reform") as Writable<ReformChange>
-  const rootDecompositionName = $session.rootDecompositionName
-  const firstDecompositionName = walkDecompositionsCoreName(
-    $session.decompositionCoreByName,
-    rootDecompositionName,
-    true,
-  ).next().value as string
   const variableDefinitionByName = {
     age: {
       allowSlider: true,
@@ -85,6 +79,13 @@
   )
   let variableSummaryByName: { [name: string]: Variable } | undefined =
     undefined
+  const waterfalls = $session.waterfalls
+  const rootDecompositionName = waterfalls[0].root
+  const firstDecompositionName = walkDecompositionsCoreName(
+    $session.decompositionCoreByName,
+    rootDecompositionName,
+    true,
+  ).next().value as string
 
   $: familySituation = situation[familyEntity.key_plural]
 
diff --git a/src/lib/components/waterfalls/Waterfall.svelte b/src/lib/components/waterfalls/Waterfall.svelte
index 16a87866f1b20daeeda5112347452a9c009d5897..a144d6ead1e556d2f54366567752c266681017dd 100644
--- a/src/lib/components/waterfalls/Waterfall.svelte
+++ b/src/lib/components/waterfalls/Waterfall.svelte
@@ -16,9 +16,10 @@
   const decompositionByNameArray = getContext(
     "decompositionByNameArray",
   ) as Writable<DecompositionByName[]>
-  const rootDecompositionName = $session.rootDecompositionName
   const showNulls = getContext("showNulls") as Writable<boolean>
   const testCaseIndex = getContext("testCaseIndex") as Writable<number>
+  const waterfalls = $session.waterfalls
+  const rootDecompositionName = waterfalls[0].root
 
   $: data = [
     ...walkDecompositions(
diff --git a/src/lib/server/config.ts b/src/lib/server/config.ts
index 2e25941ecc0bda657336212ee1740d4c23f459f5..cbc0a482f94242697ce3a748b4d1ffecf2f00e5b 100644
--- a/src/lib/server/config.ts
+++ b/src/lib/server/config.ts
@@ -36,8 +36,8 @@ export interface Config {
   }
   portalUrl: string
   proxy: boolean
-  rootDecompositionName: string
   title: string
+  waterfallsPath: string
 }
 
 const [validConfig, error] = validateConfig({
@@ -78,7 +78,7 @@ const [validConfig, error] = validateConfig({
   },
   portalUrl: process.env["PORTAL_URL"],
   proxy: process.env["PROXY"],
-  rootDecompositionName: process.env["DECOMPOSITION_ROOT"],
+  waterfallsPath: process.env["WATERFALLS_PATH"],
   title: process.env["TITLE"],
 })
 if (error !== null) {
diff --git a/src/lib/server/decompositions.ts b/src/lib/server/decompositions.ts
index 8757eda44786c7da90ec928647567d377ad8b677..414f03ac848ee61f7cc78fdd21e7eb355939f063 100644
--- a/src/lib/server/decompositions.ts
+++ b/src/lib/server/decompositions.ts
@@ -1,22 +1,51 @@
+import {
+  auditChain,
+  auditCleanArray,
+  auditFunction,
+  auditRequire,
+  strictAudit,
+} from "@auditors/core"
+import type { Waterfall } from "@openfisca/ast"
+import { auditDecompositionByName, auditWaterfall } from "@openfisca/ast"
 import assert from "assert"
 import fs from "fs-extra"
 
 import type { DecompositionCoreByName } from "$lib/decompositions"
-import {
-  decompositionByNameFromCore,
-  walkDecompositionsCore,
-} from "$lib/decompositions"
-import type { Variable, VariableByName } from "@openfisca/ast"
+import { decompositionByNameFromCore } from "$lib/decompositions"
 import config from "$lib/server/config"
-import { variableSummaryByName } from "$lib/server/variables"
 
-const { decompositionsPath, rootDecompositionName } = config
+const { decompositionsPath, waterfallsPath } = config
+
+const [decompositionByName, decompositionByNameError] = auditChain(
+  auditDecompositionByName,
+  auditRequire,
+)(strictAudit, fs.readJsonSync(decompositionsPath)) as [
+  DecompositionCoreByName,
+  unknown,
+]
+if (decompositionByNameError !== null) {
+  console.error(decompositionsPath)
+  console.error(JSON.stringify(decompositionByName, null, 2))
+  console.error(JSON.stringify(decompositionByNameError, null, 2))
+  process.exit(1)
+}
+export const decompositionCoreByName = decompositionByName
 
-export const decompositionCoreByName = fs.readJsonSync(
-  decompositionsPath,
-) as DecompositionCoreByName
+const [validWaterfalls, waterfallsError] = auditChain(
+  auditCleanArray(auditWaterfall),
+  auditRequire,
+)(strictAudit, fs.readJsonSync(waterfallsPath)) as [Waterfall[], unknown]
+if (waterfallsError !== null) {
+  console.error(waterfallsPath)
+  console.error(JSON.stringify(validWaterfalls, null, 2))
+  console.error(JSON.stringify(waterfallsError, null, 2))
+  process.exit(1)
+}
+export const waterfalls = validWaterfalls
 
-assert.notStrictEqual(
-  decompositionByNameFromCore(decompositionCoreByName, rootDecompositionName),
-  undefined,
-)
+for (const waterfall of waterfalls) {
+  assert.notStrictEqual(
+    decompositionByNameFromCore(decompositionCoreByName, waterfall.root),
+    undefined,
+  )
+}
diff --git a/src/lib/sessions.ts b/src/lib/sessions.ts
index 09d2ec09f9574932faeb9e28c820365abdaf5728..25bf1b1567a57fa95ed5e13996a26d42946571d2 100644
--- a/src/lib/sessions.ts
+++ b/src/lib/sessions.ts
@@ -1,4 +1,4 @@
-import type { EntityByKey, Metadata } from "@openfisca/ast"
+import type { EntityByKey, Metadata, Waterfall } from "@openfisca/ast"
 
 import type { DecompositionCoreByName } from "$lib/decompositions"
 import type { Situation } from "$lib/situations"
@@ -27,10 +27,10 @@ export interface Session {
   openfiscaRepository: SessionOpenFiscaRepository
   personEntityKey: string
   portalUrl: string
-  rootDecompositionName: string
   testCases: Situation[]
   title: string
   user?: User
+  waterfalls: Waterfall[]
 }
 
 export interface SessionOpenFiscaRepository {
diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte
index 0b70dd0c1b756a500f95c870941bb5ec68e4120c..bbcf6be1398ba77d6136bf51b4217ac00c53b6b1 100644
--- a/src/routes/__layout.svelte
+++ b/src/routes/__layout.svelte
@@ -38,10 +38,12 @@
   const vectorLength = writable(1)
   setContext("vectorLength", vectorLength)
 
+  const waterfalls = $session.waterfalls
+  const rootDecompositionName = waterfalls[0].root
   const decompositionByName = writable(
     decompositionByNameFromCore(
       $session.decompositionCoreByName,
-      $session.rootDecompositionName,
+      rootDecompositionName,
     ),
   )
   setContext("decompositionByName", decompositionByName)
@@ -58,7 +60,7 @@
             updateEvaluations(
               $decompositionByName,
               {},
-              $session.rootDecompositionName,
+              rootDecompositionName,
               $vectorIndexes[situationIndex],
               $vectorLength,
             ),
@@ -85,7 +87,7 @@
       [
         ...walkDecompositionsCore(
           $session.decompositionCoreByName,
-          $session.rootDecompositionName,
+          rootDecompositionName,
           true,
         ),
       ]
@@ -112,8 +114,6 @@
   > = writable({})
   setContext("calculationTokenByName", calculationTokenByName)
 
-  const rootDecompositionName = $session.rootDecompositionName
-
   let variableValuesByName: { [name: string]: VariableValues } = {}
 
   const webSocketByName: Writable<WebSocketByName | undefined> =
diff --git a/src/routes/index.svelte b/src/routes/index.svelte
index 5035eb596ca12f02fccbeb7e3fdbc97b8b3162eb..664b8c67393aa32b94203de2586e2f20fe0a0c1b 100644
--- a/src/routes/index.svelte
+++ b/src/routes/index.svelte
@@ -84,7 +84,6 @@
   }>
   setContext("newSelfTargetAProps", newSelfTargetAProps)
   const reform = getContext("reform") as Writable<ReformChange>
-  const rootDecompositionName = $session.rootDecompositionName
   const showNulls = writable(false)
   setContext("showNulls", showNulls)
   let showTutorial = true
@@ -98,6 +97,8 @@
   ) as Writable<{ [name in CalculationName]?: string }>
   const vectorIndexes = getContext("vectorIndexes") as Writable<number[]>
   const vectorLength = getContext("vectorLength") as Writable<number>
+  const waterfalls = $session.waterfalls
+  const rootDecompositionName = waterfalls[0].root
   const webSocketByName = getContext("webSocketByName") as Writable<
     WebSocketByName | undefined
   >
diff --git a/src/routes/variables/[variable]/inputs/[date].json.ts b/src/routes/variables/[variable]/inputs/[date].json.ts
index 2f8bb5a2798aa5893c9f7527755c140bc779d7e5..1a9330fce140ed4938ca9ad94c6d20e0d2fac940 100644
--- a/src/routes/variables/[variable]/inputs/[date].json.ts
+++ b/src/routes/variables/[variable]/inputs/[date].json.ts
@@ -15,10 +15,11 @@ import sanitizeFilename from "sanitize-filename"
 
 import { walkDecompositionsCoreName } from "$lib/decompositions"
 import config from "$lib/server/config"
-import { decompositionCoreByName } from "$lib/server/decompositions"
+import { decompositionCoreByName, waterfalls } from "$lib/server/decompositions"
 import { iterVariableInputVariables } from "$lib/server/variables"
 
 const { jsonDir } = config
+const rootDecompositionName = waterfalls[0].root
 
 function auditVariableInputsQuery(
   audit: Audit,
@@ -105,7 +106,7 @@ export const get: RequestHandler = async ({
     ignoreVariablesName = new Set(
       walkDecompositionsCoreName(
         decompositionCoreByName,
-        config.rootDecompositionName,
+        rootDecompositionName,
         false,
       ),
     )