diff --git a/example.env b/example.env
index 89f58881af28ad4a838b10bb4c7fb3540e38313a..312615c539623859e2099553a9dac7c023814b4e 100644
--- a/example.env
+++ b/example.env
@@ -3,9 +3,9 @@
 # Are advanced & experimental features enabled?
 # ADVANCED=false
 
-# Public HTTP(S) URL of LexImpact Socio-Fiscal API server
-API_BASE_URL="https://simulateur-socio-fiscal.leximpact.dev/api/"
-# API_BASE_URL="http://localhost:8000"
+# Public HTTP(S) URLs of LexImpact Socio-Fiscal API server
+API_BASE_URLS="https://simulateur-socio-fiscal.leximpact.dev/api/"
+# API_BASE_URLS="http://localhost:8000"
 
 # Public HTTP(S) URL of LexImpact Socio-Fiscal UI server (aka this application)
 # BASE_URL="https://simulateur-socio-fiscal.leximpact.dev/"
diff --git a/src/hooks.ts b/src/hooks.ts
index b1eb8ac8115fceec38c80bae8c95f85e264d9ce3..5b9859773c1c16ef6e8777a5b966477ddd31fa70 100644
--- a/src/hooks.ts
+++ b/src/hooks.ts
@@ -29,19 +29,21 @@ if (globalThis.fetch === undefined) {
     Headers: {
       enumerable: true,
       value: Headers,
-    }
+    },
   })
 }
 
-
 const { githubPersonalAccessToken, openfiscaRepository } = config
 
 export const getSession: GetSession<{}, Session> = async (request) => {
-  const { user } = oauth2Authenticator === undefined ? { user: undefined } : await oauth2Authenticator.getSession(request)
+  const { user } =
+    oauth2Authenticator === undefined
+      ? { user: undefined }
+      : await oauth2Authenticator.getSession(request)
   return {
     advanced: config.advanced,
-    apiBaseUrl: config.apiBaseUrl,
-    apiWebSocketBaseUrl: config.apiWebSocketBaseUrl,
+    apiBaseUrls: config.apiBaseUrls,
+    apiWebSocketBaseUrls: config.apiWebSocketBaseUrls,
     authenticationEnabled: oauth2Authenticator !== undefined,
     baseUrl: config.baseUrl,
     childrenKey: config.childrenKey,
@@ -72,8 +74,8 @@ export const getSession: GetSession<{}, Session> = async (request) => {
 export const handle: Handle = async ({ request, resolve }) => {
   // TODO https://github.com/sveltejs/kit/issues/1046
   if (request.query.has("_method")) {
-    request.method = request.query.get("_method").toUpperCase();
+    request.method = request.query.get("_method").toUpperCase()
   }
 
   return await resolve(request)
-};
+}
diff --git a/src/lib/auditors/config.ts b/src/lib/auditors/config.ts
index dc0c3d7244184a12b7d476b239874bd5d0e01037..7db652ef2280e468c2cb12109c4daf5b83102ec0 100644
--- a/src/lib/auditors/config.ts
+++ b/src/lib/auditors/config.ts
@@ -1,7 +1,7 @@
 import {
   Audit,
-  auditArray,
   auditBoolean,
+  auditCleanArray,
   auditFunction,
   auditHttpUrl,
   auditInteger,
@@ -42,7 +42,19 @@ export function auditConfig(
       auditSetNullish(false),
     )
   }
-  for (const key of ["apiBaseUrl", "baseUrl", "portalUrl"]) {
+  audit.attribute(
+    data,
+    "apiBaseUrls",
+    true,
+    errors,
+    remainingKeys,
+    auditString,
+    auditFunction((keys) => keys.split(",")),
+    auditCleanArray(auditHttpUrl),
+    auditUnique,
+    auditRequire,
+  )
+  for (const key of ["baseUrl", "portalUrl"]) {
     audit.attribute(
       data,
       key,
@@ -87,17 +99,10 @@ export function auditConfig(
     remainingKeys,
     auditString,
     auditFunction((keys) => keys.split(",")),
-    auditArray(auditTrimString),
+    auditCleanArray(auditTrimString),
     auditUnique,
   )
-  audit.attribute(
-    data,
-    "matomo",
-    true,
-    errors,
-    remainingKeys,
-    auditMatomo,
-  )
+  audit.attribute(data, "matomo", true, errors, remainingKeys, auditMatomo)
   audit.attribute(
     data,
     "openfiscaRepository",
@@ -107,20 +112,12 @@ export function auditConfig(
     auditOpenFiscaRepository,
     auditRequire,
   )
-  audit.attribute(
-    data,
-    "oauth2",
-    true,
-    errors,
-    remainingKeys,
-    auditOauth2,
-  )
+  audit.attribute(data, "oauth2", true, errors, remainingKeys, auditOauth2)
 
   return audit.reduceRemaining(data, errors, remainingKeys)
 }
 
-function auditMatomo(audit: Audit, dataUnknown: unknown,
-): [unknown, unknown] {
+function auditMatomo(audit: Audit, dataUnknown: unknown): [unknown, unknown] {
   if (dataUnknown == null) {
     return [dataUnknown, null]
   }
@@ -167,15 +164,14 @@ function auditMatomo(audit: Audit, dataUnknown: unknown,
     remainingKeys,
     auditHttpUrl,
     // Ensure there is a single trailing "/" in URL.
-    auditFunction(url => url.replace(/\/$/, "") + "/"),
+    auditFunction((url) => url.replace(/\/$/, "") + "/"),
     auditRequire,
   )
 
   return audit.reduceRemaining(data, errors, remainingKeys)
 }
 
-function auditOauth2(audit: Audit, dataUnknown: unknown,
-): [unknown, unknown] {
+function auditOauth2(audit: Audit, dataUnknown: unknown): [unknown, unknown] {
   if (dataUnknown == null) {
     return [dataUnknown, null]
   }
diff --git a/src/lib/components/latchkeys/Arrow.svelte b/src/lib/components/latchkeys/Arrow.svelte
index 32310637283f4d4634688cf15882c77d15fb8e55..c110a0ca0565d86935509cb35c104b92dc038ba0 100644
--- a/src/lib/components/latchkeys/Arrow.svelte
+++ b/src/lib/components/latchkeys/Arrow.svelte
@@ -34,9 +34,9 @@
   const chevronStrokeWidth = 5
   const chevronStrokeHalfWidth = chevronStrokeWidth / 2
   // const date = new Date().toISOString().split("T")[0]
-  const decompositionByNameArray = getContext(
-    "decompositionByNameArray",
-  ) as Writable<DecompositionByName[]>
+  const decompositionByName = getContext(
+    "decompositionByName",
+  ) as Writable<DecompositionByName>
   const deltaFormatter = new Intl.NumberFormat("fr-FR", {
     currency: "EUR",
     maximumFractionDigits: 0,
@@ -99,38 +99,32 @@
       : xAggregate1
 
   function toggle() {
-    if (
-      aggregate === undefined ||
-      (aggregate !== leaf && aggregate.values.every(([x0]) => x0 === 0))
-    ) {
+    if (aggregate === undefined || (aggregate !== leaf && aggregate.trunk)) {
       return
     }
     const open = !aggregate.open
     let smallestAggregate = aggregate
     const index = $testCaseIndex
-    const decompositionByName = $decompositionByNameArray[index]
     if (open) {
       // When aggregate has just one child, we need to also open
       // its child... and so on.
       while (smallestAggregate.childrenName?.length === 1) {
         smallestAggregate =
-          decompositionByName[smallestAggregate.childrenName[0]]
+          $decompositionByName[smallestAggregate.childrenName[0]]
       }
     }
 
-    const newDecompositionByNameArray = [...$decompositionByNameArray]
-    newDecompositionByNameArray[index] = toggleToNode(
-      { ...decompositionByName },
-      decompositionByName[rootDecompositionName],
+    $decompositionByName = toggleToNode(
+      { ...$decompositionByName },
+      $decompositionByName[rootDecompositionName],
       [
         ...iterDecompositionAncestorsName(
-          decompositionByName,
+          $decompositionByName,
           smallestAggregate.name,
         ),
       ].slice(1),
       open,
     )
-    $decompositionByNameArray = newDecompositionByNameArray
   }
 
   function toggleToNode(
diff --git a/src/lib/components/latchkeys/Latchkey.svelte b/src/lib/components/latchkeys/Latchkey.svelte
index 2ba9418b5e382ebf18bc0b2c821ea07cab2fb5ce..f643eb645cb15a4d182bc2b69801d67acdc676e5 100644
--- a/src/lib/components/latchkeys/Latchkey.svelte
+++ b/src/lib/components/latchkeys/Latchkey.svelte
@@ -139,10 +139,7 @@
       latestChildDataItem.aggregate.deltaAtVectorIndex ===
         decomposition.deltaAtVectorIndex
     ) {
-      if (
-        decomposition.open ||
-        decomposition.values.every(([x0]) => x0 === 0)
-      ) {
+      if (decomposition.open || decomposition.trunk) {
         return {
           aggregate: decomposition,
           leaf: latestChildDataItem.leaf,
diff --git a/src/lib/components/scholar_waterfalls/ScholarWaterfall.svelte b/src/lib/components/scholar_waterfalls/ScholarWaterfall.svelte
index 08125318559f923aeab3464ca5ad90180f104e58..7855b428a84d82913e60680798e60bbbd957180f 100644
--- a/src/lib/components/scholar_waterfalls/ScholarWaterfall.svelte
+++ b/src/lib/components/scholar_waterfalls/ScholarWaterfall.svelte
@@ -5,13 +5,19 @@
   import { session } from "$app/stores"
   import PictoFemme from "$lib/components/pictos/PictoFemme.svelte"
   import PictoEntreprise from "$lib/components/pictos/PictoEntreprise.svelte"
-  import type { Decomposition, DecompositionByName } from "$lib/decompositions"
+  import type {
+    Decomposition,
+    DecompositionByName,
+    Evaluation,
+    EvaluationByName,
+  } from "$lib/decompositions"
   import {
     iterDecompositionChildren,
     walkDecompositions,
   } from "$lib/decompositions"
 
   export let decompositionByName: DecompositionByName
+  export let evaluationByName: EvaluationByName
 
   const adaptAmountsScale = getContext("adaptAmountsScale") as Writable<boolean>
   const advanced = $session.advanced
@@ -35,9 +41,10 @@
   const testCaseIndex = getContext("testCaseIndex") as Writable<number>
   let waterfallWidth = 100
 
-  $: nodeAndDepthCouples = [
-    ...walkVisibleDecompositions(
+  $: decompositionEvaluationAndDepthTriples = [
+    ...walkVisibleEvaluations(
       decompositionByName,
+      evaluationByName,
       rootDecompositionName,
       $showNulls,
     ),
@@ -45,6 +52,7 @@
 
   $: [valueMin, valueMax] = computeXDomain(
     decompositionByName,
+    evaluationByName,
     rootDecompositionName,
     $adaptAmountsScale,
   )
@@ -53,19 +61,24 @@
 
   function computeXDomain(
     decompositionByName: DecompositionByName,
+    evaluationByName: EvaluationByName,
     name: string,
     adaptAmountsScale: boolean,
   ): [number, number] {
     let valueMin = undefined
     let valueMax = undefined
-    for (const node of walkDecompositions(
+    for (const decomposition of walkDecompositions(
       decompositionByName,
       name,
       false,
       false,
     )) {
+      const evaluation = evaluationByName[decomposition.name]
+      if (evaluation === undefined) {
+        continue
+      }
       if (adaptAmountsScale) {
-        for (const value of node.valuesAtVectorIndex ?? []) {
+        for (const value of evaluation.valuesAtVectorIndex ?? []) {
           if (valueMin === undefined || value < valueMin) {
             valueMin = value
           }
@@ -74,7 +87,7 @@
           }
         }
       } else {
-        for (const itemValues of node.values) {
+        for (const itemValues of evaluation.values) {
           for (const value of itemValues) {
             if (valueMin === undefined || value < valueMin) {
               valueMin = value
@@ -97,38 +110,44 @@
 
   function togglableParentNode(
     decompositionByName: DecompositionByName,
-    node: Decomposition,
+    decomposition: Decomposition,
   ): boolean {
-    if (node.parentName === undefined) {
+    if (decomposition.parentName === undefined) {
       return false
     }
-    const parent = decompositionByName[node.parentName]
+    const parent = decompositionByName[decomposition.parentName]
     if (parent === undefined) {
       return false
     }
-    return parent.valuesX0Is0Array && parent.childrenName.length === 1
+    return parent.trunk && parent.childrenName.length === 1
   }
 
-  export function* walkVisibleDecompositions(
+  export function* walkVisibleEvaluations(
     decompositionByName: DecompositionByName,
+    evaluationByName: EvaluationByName,
     name: string,
     showNulls: boolean,
     depth: number = 0,
-  ): Generator<[Decomposition, number], void, unknown> {
+  ): Generator<[Decomposition, Evaluation, number], void, unknown> {
     const decomposition = decompositionByName[name]
     if (decomposition === undefined) {
       return
     }
-    const showNode = showNulls || !decomposition.deltaIs0Array
+    const evaluation = evaluationByName[name]
+    if (evaluation === undefined) {
+      return
+    }
+    const showNode = showNulls || !evaluation.deltaIs0Array
     let childrenDepth = depth
-    if (showNode && !decomposition.valuesX0Is0Array) {
-      yield [decomposition, depth]
+    if (showNode && !decomposition.trunk) {
+      yield [decomposition, evaluation, depth]
       childrenDepth = depth + 1
     }
     if (decomposition.childrenName !== undefined && decomposition.open) {
       for (const childName of decomposition.childrenName) {
-        yield* walkVisibleDecompositions(
+        yield* walkVisibleEvaluations(
           decompositionByName,
+          evaluationByName,
           childName,
           showNulls,
           childrenDepth,
@@ -137,10 +156,10 @@
     }
     if (
       showNode &&
-      decomposition.valuesX0Is0Array &&
+      decomposition.trunk &&
       (!decomposition.open || decomposition.childrenName.length > 1)
     ) {
-      yield [decomposition, depth]
+      yield [decomposition, evaluation, depth]
     }
   }
 
@@ -199,13 +218,10 @@
     }
     decomposition = { ...decomposition }
     if (decomposition.open) {
-      if (
-        decomposition.childrenName.length === 1 &&
-        decomposition.values.every(([x0]) => x0 === 0)
-      ) {
-        // When aggregate is absolute and has only one child, instead of closing node,
-        // replace its child with the children of its child,
-        // We do this, because we don't want to hide every node before this one.
+      if (decomposition.childrenName.length === 1 && decomposition.trunk) {
+        // When aggregate is absolute and has only one child, instead of closing
+        //decomposition, replace its child with the children of its child,
+        // We do this, because we don't want to hide every decomposition before this one.
         const child = decompositionByName[decomposition.childrenName[0]]
         if (child !== undefined) {
           if (child.childrenName !== undefined) {
@@ -243,22 +259,22 @@
   }
 </script>
 
-{#if nodeAndDepthCouples.length > 0}
+{#if decompositionEvaluationAndDepthTriples.length > 0}
   <div class="flex pr-2">
     <!-- partie gauche dispositifs et ticket de caisse-->
     <div class="bg-opacity-40 shadow-lg w-4/5">
       <div class="flex justify-between">
         <!-- Navigation dispositifs-->
         <div class="flex-auto pl-2 pt-2 w-3/5">
-          {#each nodeAndDepthCouples as [node, depth], index}
+          {#each decompositionEvaluationAndDepthTriples as [decomposition, _evaluation, depth], index}
             <div class="flex items-center h-8 whitespace-nowrap">
               {#each [...iterToDepth(depth)] as _level}
                 <div class="border-l-2 border-le-gris-dispositif h-8 pl-1">
                   &nbsp;
                 </div>
               {/each}
-              {#if node.open || index === 0}
-                {#if node.valuesX0Is0Array}
+              {#if decomposition.open || index === 0}
+                {#if decomposition.trunk}
                   <div
                     class="hover:bg-white border-gray-400 overflow-ellipsis overflow-x-hidden hover:overflow-x-visible text-gray-400 hover:z-20"
                     class:border-t={index !== 0}
@@ -266,24 +282,25 @@
                     {#if advanced}
                       <span
                         class="cursor-pointer text-base hover:underline"
-                        on:click={() => dispatch("selectVariable", node.name)}
-                        >{node.label}</span
+                        on:click={() =>
+                          dispatch("selectVariable", decomposition.name)}
+                        >{decomposition.label}</span
                       >
                     {:else}
-                      <span class="text-sm">{node.label}</span>
+                      <span class="text-sm">{decomposition.label}</span>
                     {/if}
                   </div>
                 {:else}
                   <div
                     class="hover:bg-white cursor-pointer hover:font-bold font-serif overflow-ellipsis overflow-x-hidden hover:overflow-x-visible text-le-gris-dispositif text-base hover:underline hover:z-20"
-                    on:click={() => zoomOut(node.name)}
+                    on:click={() => zoomOut(decomposition.name)}
                   >
-                    {node.label}
+                    {decomposition.label}
                   </div>
                 {/if}
               {:else}
                 <div>
-                  {#if node.lastReview === undefined || node.lastReview < "2020"}
+                  {#if decomposition.lastReview === undefined || decomposition.lastReview < "2020"}
                     <!-- Inspired from Material Icons name: Warning / with white symbol inside -->
                     <svg
                       aria-hidden="true"
@@ -292,10 +309,10 @@
                       xmlns="http://www.w3.org/2000/svg"
                     >
                       <title
-                        >Dernière relecture : {#if node.lastReview === undefined}<i
+                        >Dernière relecture : {#if decomposition.lastReview === undefined}<i
                             >date indéterminée</i
                           >{:else}{dateFormatter.format(
-                            new Date(node.lastReview),
+                            new Date(decomposition.lastReview),
                           )}{/if}</title
                       >
                       <path
@@ -321,7 +338,7 @@
                     >
                       <title
                         >Dernière relecture : {dateFormatter.format(
-                          new Date(node.lastReview),
+                          new Date(decomposition.lastReview),
                         )}</title
                       >
                       <path
@@ -340,19 +357,19 @@
                   class="hover:bg-white cursor-pointer font-serif overflow-ellipsis overflow-x-hidden hover:overflow-x-visible hover:text-le-gris-dispositif text-base hover:underline hover:z-20"
                   on:click={() => {
                     if (
-                      node.childrenName !== undefined &&
-                      !node.valuesX0Is0Array
+                      decomposition.childrenName !== undefined &&
+                      !decomposition.trunk
                     ) {
-                      zoomIn(node.name)
+                      zoomIn(decomposition.name)
                     }
-                    dispatch("selectVariable", node.name)
-                  }}>{node.label}</span
+                    dispatch("selectVariable", decomposition.name)
+                  }}>{decomposition.label}</span
                 >
               {/if}
-              {#if node.open && !node.valuesX0Is0Array}
+              {#if decomposition.open && !decomposition.trunk}
                 <button
                   class="text-le-gris-dispositif-dark ml-1 px-1 rounded-full  hover:border-2"
-                  on:click={() => zoomOut(node.name)}
+                  on:click={() => zoomOut(decomposition.name)}
                   ><div>
                     <!--Material Icon Expand less--><svg
                       class="fill-current"
@@ -367,10 +384,10 @@
                     >
                   </div></button
                 >
-              {:else if togglableParentNode(decompositionByName, node)}
+              {:else if togglableParentNode(decompositionByName, decomposition)}
                 <button
                   class="text-le-gris-dispositif-dark  ml-1 px-1 rounded-full  hover:border-2"
-                  on:click={() => zoomOut(node.parentName)}
+                  on:click={() => zoomOut(decomposition.parentName)}
                   ><div>
                     <!--Material Icon Expand less--><svg
                       class="fill-current"
@@ -386,10 +403,10 @@
                   </div></button
                 >
               {/if}
-              {#if (!node.open && node.childrenName !== undefined) || node.previousChildStack !== undefined}
+              {#if (!decomposition.open && decomposition.childrenName !== undefined) || decomposition.previousChildStack !== undefined}
                 <button
                   class="hover:border-2 text-le-gris-dispositif-dark  ml-1 px-1 rounded-full "
-                  on:click={() => zoomIn(node.name)}
+                  on:click={() => zoomIn(decomposition.name)}
                   ><div>
                     <svg
                       class="fill-current"
@@ -411,9 +428,9 @@
         <!-- ticket de caisse-->
         <div class="flex flex-none pt-2 px-2">
           <div class="flex-col">
-            {#each nodeAndDepthCouples as [node, depth], index}
+            {#each decompositionEvaluationAndDepthTriples as [decomposition, _evaluation, depth], index}
               <div class="border-transparent border-t h-7 my-1 px-1 text-sm">
-                {#if depth === 0 && !node.valuesX0Is0Array && index > 0}{#if node.involve === "person"}<span
+                {#if depth === 0 && !decomposition.trunk && index > 0}{#if decomposition.involve === "person"}<span
                       class="text-red-600 font-bold"
                     >
                       <PictoFemme /></span
@@ -426,43 +443,45 @@
             {/each}
           </div>
           <div class="flex-col ">
-            {#each nodeAndDepthCouples as [node, _depth], index}
+            {#each decompositionEvaluationAndDepthTriples as [decomposition, evaluation, _depth], index}
               <div
-                class="{node.open &&
-                node.valuesX0Is0Array &&
-                node.childrenName.length > 1
+                class="{decomposition.open &&
+                decomposition.trunk &&
+                decomposition.childrenName.length > 1
                   ? 'border-gray-400'
                   : 'border-transparent'} border-t h-7 my-1 px-1 text-sm"
               >
-                {#if node.open}{#if node.valuesX0Is0Array}<span
+                {#if decomposition.open}{#if decomposition.trunk}<span
                       class="text-gray-400">=</span
-                    >{/if}{:else if index > 0}{#if node.deltaAtVectorIndex < 0}<span
+                    >{/if}{:else if index > 0}{#if evaluation.deltaAtVectorIndex < 0}<span
                       class="text-red-600 font-bold">-</span
-                    >{:else if node.deltaAtVectorIndex > 0}<span
+                    >{:else if evaluation.deltaAtVectorIndex > 0}<span
                       class="text-green-600 font-bold ">+</span
                     >{/if}{/if}
               </div>
             {/each}
           </div>
           <div class="flex-col">
-            {#each nodeAndDepthCouples as [node, _depth], index}
+            {#each decompositionEvaluationAndDepthTriples as [decomposition, evaluation, _depth], index}
               <div
-                class="{node.open &&
-                node.valuesX0Is0Array &&
-                node.childrenName.length > 1
+                class="{decomposition.open &&
+                decomposition.trunk &&
+                decomposition.childrenName.length > 1
                   ? 'border-gray-400'
                   : 'border-transparent'} border-t h-7 my-1 text-right text-sm"
               >
-                {#if node.open || index === 0}
-                  {#if node.valuesX0Is0Array}
+                {#if decomposition.open || index === 0}
+                  {#if decomposition.trunk}
                     <span class="text-gray-400 text-sm"
                       >{firstDeltaFormatter.format(
-                        node.deltaAtVectorIndex,
+                        evaluation.deltaAtVectorIndex,
                       )}</span
                     >
                   {/if}
                 {:else}<span class="font-bold text-base"
-                    >{deltaFormatter.format(node.deltaAtVectorIndex)}</span
+                    >{deltaFormatter.format(
+                      evaluation.deltaAtVectorIndex,
+                    )}</span
                   >
                 {/if}
               </div>
@@ -488,21 +507,23 @@
 
     <!-- Waterfall-->
     <div class="flex flex-col pt-2 w-1/5" bind:offsetWidth={waterfallWidth}>
-      {#each nodeAndDepthCouples as [node, _depth], index}
+      {#each decompositionEvaluationAndDepthTriples as [decomposition, evaluation, _depth], index}
         <div
-          class="{node.open || index === 0
-            ? node.valuesX0Is0Array
+          class="{decomposition.open || index === 0
+            ? decomposition.trunk
               ? 'bg-gray-200'
               : 'bg-transparent'
-            : node.valuesAtVectorIndex[0] <= node.valuesAtVectorIndex[1]
+            : evaluation.valuesAtVectorIndex[0] <=
+              evaluation.valuesAtVectorIndex[1]
             ? 'bg-green-500'
             : 'bg-red-500'} border-t border-transparent h-6 my-1"
-          style="margin-left: {((Math.min(...node.valuesAtVectorIndex) -
+          style="margin-left: {((Math.min(...evaluation.valuesAtVectorIndex) -
             valueMin) /
             (valueMax - valueMin)) *
             widthMax}px; width: {Math.max(
             (Math.abs(
-              node.valuesAtVectorIndex[1] - node.valuesAtVectorIndex[0],
+              evaluation.valuesAtVectorIndex[1] -
+                evaluation.valuesAtVectorIndex[0],
             ) /
               (valueMax - valueMin)) *
               widthMax,
diff --git a/src/lib/components/test_cases/TestCaseView.svelte b/src/lib/components/test_cases/TestCaseView.svelte
index 6aded5277b20ce6d0fa568b6dfff982b48ea80f9..92470dadbc9d1f7d8a8e24878870de1ce89ca236 100644
--- a/src/lib/components/test_cases/TestCaseView.svelte
+++ b/src/lib/components/test_cases/TestCaseView.svelte
@@ -4,7 +4,10 @@
 
   import { session } from "$app/stores"
   import ScholarWaterfall from "$lib/components/scholar_waterfalls/ScholarWaterfall.svelte"
-  import type { DecompositionByName } from "$lib/decompositions"
+  import type {
+    DecompositionByName,
+    EvaluationByName,
+  } from "$lib/decompositions"
   import { walkDecompositionsCoreName } from "$lib/decompositions"
   import type { Axis, Situation } from "$lib/situations"
   import PictoArbreMetropole from "../pictos/PictoArbreMetropole.svelte"
@@ -22,6 +25,7 @@
   import { retrieveVariableSummaryByName } from "$lib/variables"
 
   export let decompositionByName: DecompositionByName
+  export let evaluationByName: EvaluationByName
   // export let index: number
   export let open: boolean
   export let situation: Situation
@@ -79,13 +83,7 @@
 
   $: adultsCount = Object.keys(personSituation).length - childrenCount
 
-  $: stateCost = Object.values(decompositionByName).reduce(
-    (sum: number, { childrenName, deltaAtVectorIndex }, index) =>
-      childrenName === undefined && index > 0
-        ? sum + (deltaAtVectorIndex ?? 0)
-        : sum,
-    0,
-  )
+  $: stateCost = computeStateCost(decompositionByName, evaluationByName)
 
   $: updateVariablesName(situation)
 
@@ -125,11 +123,25 @@
   //   dispatch("changeAxis", axis === null ? [] : [[axis]])
   // }
 
+  function computeStateCost(
+    decompositionByName: DecompositionByName,
+    evaluationByName: EvaluationByName,
+  ): number {
+    return Object.entries(decompositionByName).reduce(
+      (sum: number, [name, decomposition], index) =>
+        decomposition.childrenName === undefined && index > 0
+          ? sum + (evaluationByName[name].deltaAtVectorIndex ?? 0)
+          : sum,
+      0,
+    )
+  }
+
   function getVariableValue(
     situation: Situation,
     variableName: string,
     populationId: string,
   ): number | undefined {
+    console.log("getVariableValue", variableName, populationId)
     const variable = variableSummaryByName[variableName]
     if (variable === undefined) {
       return undefined
@@ -350,10 +362,12 @@
               {label} /an :
             </span>
             <span
-              >{#if allowSlider && isAxis(axisDescription, name, "Adulte 1")}{Math.round(
-                  vectorIndex * axisDescription.stepValue,
+              >{#if allowSlider && isAxis(axisDescription, name, "Adulte 1")}{euroAmountFormatter.format(
+                  Math.round(vectorIndex * axisDescription.stepValue),
                 )}{:else}
-                {getVariableValue(situation, name, "Adulte 1")}{/if} €</span
+                {euroAmountFormatter.format(
+                  getVariableValue(situation, name, "Adulte 1"),
+                )}{/if}</span
             >
             {#if allowSlider}
               <label class="text-xs">
@@ -406,6 +420,7 @@
     {#if open}
       <ScholarWaterfall
         {decompositionByName}
+        {evaluationByName}
         on:changeDecompositionByName
         on:selectVariable
       />
@@ -450,7 +465,7 @@
         title="⚠️ Les dispositifs n'étant pas tous à jour, ce montant est à considérer avec prudence"
       >
         {euroAmountFormatter.format(
-          decompositionByName[rootDecompositionName].deltaAtVectorIndex ?? 0,
+          evaluationByName[rootDecompositionName].deltaAtVectorIndex ?? 0,
         )}
       </p>
     </div>
@@ -467,7 +482,7 @@
         title="⚠️ Les dispositifs n'étant pas tous à jour, ce montant est à considérer avec prudence"
       >
         {euroAmountFormatter.format(
-          decompositionByName[firstDecompositionName].deltaAtVectorIndex ?? 0,
+          evaluationByName[firstDecompositionName].deltaAtVectorIndex ?? 0,
         )}
       </p>
     </div>
diff --git a/src/lib/components/test_cases/TestCasesPane.svelte b/src/lib/components/test_cases/TestCasesPane.svelte
index bc2d9e773b99d067c6925398bd98bf5d40b807d4..2d54b94db3e3abb908e97f75a1f846920bc0369e 100644
--- a/src/lib/components/test_cases/TestCasesPane.svelte
+++ b/src/lib/components/test_cases/TestCasesPane.svelte
@@ -4,7 +4,11 @@
   import type { Writable } from "svelte/store"
 
   import { session } from "$app/stores"
-  import type { DecompositionByName } from "$lib/decompositions"
+  import type {
+    CalculationName,
+    DecompositionByName,
+    EvaluationByNameArrayByCalculationName,
+  } from "$lib/decompositions"
   import type { Axis, AxisDescription, Situation } from "$lib/situations"
   import { indexOfSituationPopulationId } from "$lib/situations"
 
@@ -13,12 +17,16 @@
   export let year: number
 
   let axisBySituationIndex: { [situationIndex: string]: Axis } = {}
+  let calculationName: CalculationName = "law"
 
-  const decompositionByNameArray = getContext(
-    "decompositionByNameArray",
-  ) as Writable<DecompositionByName[]>
+  const decompositionByName = getContext(
+    "decompositionByName",
+  ) as Writable<DecompositionByName>
   const dispatch = createEventDispatcher()
   const entityByKey = $session.entityByKey as EntityByKey
+  const evaluationByNameArrayByCalculationName = getContext(
+    "evaluationByNameArrayByCalculationName",
+  ) as Writable<EvaluationByNameArrayByCalculationName>
   const testCases = getContext("testCases") as Writable<Situation[]>
   const vectorIndexes = getContext("vectorIndexes") as Writable<number[]>
 
@@ -65,11 +73,9 @@
 
   function changeDecompositionByName(
     situationIndex: number,
-    decompositionByName: DecompositionByName,
+    newDecompositionByName: DecompositionByName,
   ): void {
-    const newDecompositionByNameArray = [...$decompositionByNameArray]
-    newDecompositionByNameArray[situationIndex] = decompositionByName
-    $decompositionByNameArray = newDecompositionByNameArray
+    $decompositionByName = newDecompositionByName
   }
 
   function changeSituation(situationIndex: number, situation: Situation): void {
@@ -82,7 +88,10 @@
 <section class=" grid grid-cols-1 md:grid-cols-2 gap-10">
   {#each $testCases as situation, situationIndex}
     <TestCaseView
-      decompositionByName={$decompositionByNameArray[situationIndex]}
+      decompositionByName={$decompositionByName}
+      evaluationByName={$evaluationByNameArrayByCalculationName[
+        calculationName
+      ][situationIndex]}
       on:changeAxis={({ detail }) => changeAxis(situationIndex, detail)}
       on:changeDecompositionByName={({ detail }) =>
         changeDecompositionByName(situationIndex, detail)}
diff --git a/src/lib/components/variables/FormulaView.svelte b/src/lib/components/variables/FormulaView.svelte
index 5df37dbfd4f46cb86154eca3778134824025474a..bc68de41dc44981d055fd29abd48b6088d1fb07e 100644
--- a/src/lib/components/variables/FormulaView.svelte
+++ b/src/lib/components/variables/FormulaView.svelte
@@ -4,7 +4,6 @@
     extractFromFormulaAst,
     newFormulaRepositoryUrl,
   } from "@openfisca/ast"
-  import type Sockette from "sockette"
   import { getContext } from "svelte"
   import type { Writable } from "svelte/store"
 
@@ -13,6 +12,7 @@
   import VariableInput from "$lib/components/variables/VariableInput.svelte"
   import type { Situation } from "$lib/situations"
   import { retrieveVariableSummaryByName } from "$lib/variables"
+  import type { WebSocketByName, WebSocketOpenByName } from "$lib/websockets"
 
   export let formula: Formula
   export let situation: Situation
@@ -20,8 +20,12 @@
 
   let variableSummaryByName: { [name: string]: Variable } | undefined =
     undefined
-  const webSocket = getContext("webSocket") as Writable<Sockette | undefined>
-  const webSocketOpen = getContext("webSocketOpen") as Writable<boolean>
+  const webSocketByName = getContext("webSocketByName") as Writable<
+    WebSocketByName | undefined
+  >
+  const webSocketOpenByName = getContext(
+    "webSocketOpenByName",
+  ) as Writable<WebSocketOpenByName>
 
   $: extraction = extractFromFormulaAst(
     formula.ast,
@@ -31,7 +35,7 @@
 
   $: metadata = $session.metadata
 
-  $: if (browser && $webSocketOpen) {
+  $: if (browser && $webSocketOpenByName.law) {
     calculateVariables(extraction.openFiscaVariablesName)
   }
 
@@ -41,7 +45,7 @@
       [...variablesName],
     )
 
-    $webSocket.send(
+    $webSocketByName.law.send(
       JSON.stringify({
         calculate: true,
         variables: Object.keys(variableSummaryByName),
diff --git a/src/lib/decompositions.ts b/src/lib/decompositions.ts
index 387d2e059aa636d606e9c8bba3a06fe6ac3aa323..1a82c0504325322460a83962e481e7893086702c 100644
--- a/src/lib/decompositions.ts
+++ b/src/lib/decompositions.ts
@@ -3,15 +3,14 @@ import type {
   DecompositionReference,
 } from "@openfisca/ast"
 
+export type CalculationName = "amendment" | "bill" | "law"
+
 export interface DecompositionCore extends DecompositionJson {
   lastReview?: string
 }
 
 export interface Decomposition {
   childrenName?: string[]
-  delta?: number[]
-  deltaAtVectorIndex?: number
-  deltaIs0Array?: boolean
   index: number
   involve: "organization" | "person"
   label: string
@@ -21,26 +20,41 @@ export interface Decomposition {
   open?: boolean
   parentName?: string
   previousChildStack?: string[]
-  values?: [number, number][]
-  valuesAtVectorIndex?: [number, number]
-  valuesX0Is0Array?: boolean
+  trunk: boolean
 }
 
 export type DecompositionCoreByName = { [name: string]: DecompositionCore }
 
 export type DecompositionByName = { [name: string]: Decomposition }
 
+export interface Evaluation {
+  delta: number[]
+  deltaAtVectorIndex: number
+  deltaIs0Array: boolean
+  values: [number, number][]
+  valuesAtVectorIndex: [number, number]
+}
+
+export type EvaluationByName = { [name: string]: Evaluation }
+
+export type EvaluationByNameArrayByCalculationName = {
+  [name in CalculationName]?: EvaluationByName[]
+}
+
 export interface LatchkeyDataItem {
   aggregate?: Decomposition
   leaf: Decomposition
 }
 
+export const calculationNames: CalculationName[] = ["law", "bill", "amendment"]
+
 export function decompositionByNameFromCore(
   decompositionCoreByName: DecompositionCoreByName,
   name: string,
   parentName: string | undefined = undefined,
   index = 0,
   negate: boolean | undefined = undefined,
+  trunk = true,
   decompositionByName: DecompositionByName = {},
   visitedNames = new Set<string>(),
 ): DecompositionByName | undefined {
@@ -63,6 +77,7 @@ export function decompositionByNameFromCore(
     lastReview: decompositionCore.lastReview,
     name,
     negate,
+    trunk,
   }
   decompositionByName[name] = decomposition
   if (parentName !== undefined) {
@@ -104,6 +119,7 @@ export function decompositionByNameFromCore(
         name,
         childIndex,
         childReference.negate,
+        trunk && index === 0,
         decompositionByName,
         visitedNames,
       )
@@ -142,18 +158,18 @@ export function* iterDecompositionChildren(
   }
 }
 
-export function updateDecompositionEvaluations(
+export function updateEvaluations(
   decompositionByName: DecompositionByName,
+  evaluationByName: EvaluationByName,
   name: string,
-  deltaByName: { [name: string]: number[] },
   vectorIndex: number,
   vectorLength: number,
-  newDecompositionByName: DecompositionByName = {},
+  newEvaluationByName: EvaluationByName = {},
   valuesPrevious = undefined,
-): DecompositionByName {
+): EvaluationByName {
   const decomposition = decompositionByName[name]
   if (decomposition === undefined) {
-    return newDecompositionByName
+    return newEvaluationByName
   }
   if (valuesPrevious === undefined) {
     valuesPrevious = new Array(vectorLength).fill(0)
@@ -162,87 +178,96 @@ export function updateDecompositionEvaluations(
   if (childrenName !== undefined) {
     let childValuesPrevious = valuesPrevious
     for (const childName of childrenName) {
-      updateDecompositionEvaluations(
+      updateEvaluations(
         decompositionByName,
+        evaluationByName,
         childName,
-        deltaByName,
         vectorIndex,
         vectorLength,
-        newDecompositionByName,
+        newEvaluationByName,
         childValuesPrevious,
       )
-      const child = newDecompositionByName[childName]
-      childValuesPrevious = child.values.map((itemValue) => itemValue[1])
+      const childEvaluation = newEvaluationByName[childName]
+      childValuesPrevious = childEvaluation.values.map(
+        (itemValue) => itemValue[1],
+      )
     }
   }
-  let delta = deltaByName[name]
-  if (delta === undefined) {
+  let evaluation = evaluationByName[name]
+  let delta
+  if (evaluation === undefined) {
     if (childrenName === undefined) {
       delta = new Array(vectorLength).fill(0)
     } else {
-      const firstChildValues = newDecompositionByName[childrenName[0]].values
+      const firstChildValues = newEvaluationByName[childrenName[0]].values
       const lastChildValues =
-        newDecompositionByName[childrenName[childrenName.length - 1]].values
+        newEvaluationByName[childrenName[childrenName.length - 1]].values
       delta = lastChildValues.map(
         (lastChildValue, index) =>
           lastChildValue[1] - firstChildValues[index][0],
       )
+      if (decomposition.negate) {
+        delta = delta.map((deltaItem) => -deltaItem)
+      }
     }
+  } else {
+    delta = evaluation.delta
   }
   const values = valuesPrevious.map((previousItemValue, index) => [
     previousItemValue,
     previousItemValue + delta[index],
   ])
-  const newDecomposition = {
-    ...decomposition,
+  newEvaluationByName[name] = {
     delta,
+    deltaAtVectorIndex:
+      vectorIndex < delta.length
+        ? delta[vectorIndex]
+        : evaluation === undefined
+        ? 0
+        : evaluation.deltaAtVectorIndex,
     deltaIs0Array: delta.every((deltaItem) => deltaItem === 0),
     values,
-    valuesX0Is0Array: values.every(([x0]) => x0 === 0),
+    valuesAtVectorIndex:
+      vectorIndex < values.length
+        ? values[vectorIndex]
+        : evaluation === undefined
+        ? [0, 0]
+        : evaluation.valuesAtVectorIndex,
   }
-  if (vectorIndex < delta.length) {
-    newDecomposition.deltaAtVectorIndex = delta[vectorIndex]
-  }
-  if (vectorIndex < values.length) {
-    newDecomposition.valuesAtVectorIndex = values[vectorIndex]
-  }
-  newDecompositionByName[name] = newDecomposition
-  return newDecompositionByName
+  return newEvaluationByName
 }
 
 export function updateVectorIndex(
-  decompositionByName: DecompositionByName,
+  evaluationByName: EvaluationByName,
   vectorIndex: number,
-): DecompositionByName {
+): EvaluationByName {
   let changed = false
-  const newDecompositionByName: DecompositionByName = {}
-  for (const [name, decomposition] of Object.entries(decompositionByName)) {
-    let decompositionChanged = false
-    const newDecomposition = { ...decomposition }
+  const newEvaluationByName: EvaluationByName = {}
+  for (const [name, evaluation] of Object.entries(evaluationByName)) {
+    let evaluationChanged = false
+    const newEvaluation = { ...evaluation }
     const { delta, deltaAtVectorIndex, values, valuesAtVectorIndex } =
-      decomposition
+      evaluation
     if (vectorIndex < delta.length) {
       const newDeltaAtVectorIndex = delta[vectorIndex]
       if (newDeltaAtVectorIndex != deltaAtVectorIndex) {
-        newDecomposition.deltaAtVectorIndex = newDeltaAtVectorIndex
-        decompositionChanged = true
+        newEvaluation.deltaAtVectorIndex = newDeltaAtVectorIndex
+        evaluationChanged = true
       }
     }
     if (vectorIndex < values.length) {
       const newValuesAtVectorIndex = values[vectorIndex]
       if (newValuesAtVectorIndex != valuesAtVectorIndex) {
-        newDecomposition.valuesAtVectorIndex = newValuesAtVectorIndex
-        decompositionChanged = true
+        newEvaluation.valuesAtVectorIndex = newValuesAtVectorIndex
+        evaluationChanged = true
       }
     }
-    newDecompositionByName[name] = decompositionChanged
-      ? newDecomposition
-      : decomposition
-    if (decompositionChanged) {
+    newEvaluationByName[name] = evaluationChanged ? newEvaluation : evaluation
+    if (evaluationChanged) {
       changed = true
     }
   }
-  return changed ? newDecompositionByName : decompositionByName
+  return changed ? newEvaluationByName : evaluationByName
 }
 
 export function* walkDecompositions(
diff --git a/src/lib/server/config.ts b/src/lib/server/config.ts
index 63cf2751387b50b8b591e3a9f9dff92caaa995fb..2e25941ecc0bda657336212ee1740d4c23f459f5 100644
--- a/src/lib/server/config.ts
+++ b/src/lib/server/config.ts
@@ -4,8 +4,8 @@ import { validateConfig } from "$lib/auditors/config"
 
 export interface Config {
   advanced: boolean
-  apiBaseUrl: string
-  apiWebSocketBaseUrl: string
+  apiBaseUrls: string[]
+  apiWebSocketBaseUrls: string[]
   baseUrl: string
   childrenKey: string
   decompositionsPath: string
@@ -42,7 +42,7 @@ export interface Config {
 
 const [validConfig, error] = validateConfig({
   advanced: process.env["ADVANCED"],
-  apiBaseUrl: process.env["API_BASE_URL"],
+  apiBaseUrls: process.env["API_BASE_URLS"],
   baseUrl: process.env["BASE_URL"],
   childrenKey: process.env["CHILDREN_KEY"],
   decompositionsPath: process.env["DECOMPOSITION_PATH"],
@@ -50,21 +50,24 @@ const [validConfig, error] = validateConfig({
   githubPersonalAccessToken: process.env["GITHUB_PERSONAL_ACCESS_TOKEN"],
   hiddenEntitiesKeyPlural: process.env["HIDDEN_ENTITIES"],
   jsonDir: process.env["JSON_DIR"],
-  matomo: process.env["MATOMO_SITE_ID"] && process.env["MATOMO_URL"] ? {
-    prependDomain: process.env["MATOMO_PREPEND_DOMAIN"],
-    siteId: process.env["MATOMO_SITE_ID"],
-    subdomains: process.env["MATOMO_SUBDOMAINS"],
-    url: process.env["MATOMO_URL"],
-  } : null,
+  matomo:
+    process.env["MATOMO_SITE_ID"] && process.env["MATOMO_URL"]
+      ? {
+          prependDomain: process.env["MATOMO_PREPEND_DOMAIN"],
+          siteId: process.env["MATOMO_SITE_ID"],
+          subdomains: process.env["MATOMO_SUBDOMAINS"],
+          url: process.env["MATOMO_URL"],
+        }
+      : null,
   oauth2: process.env["OAUTH2_CLIENT_ID"]
     ? {
-      accessTokenUrl: process.env["OAUTH2_ACCESS_TOKEN_URL"],
-      authorizationUrl: process.env["OAUTH2_AUTHORIZATION_URL"],
-      clientId: process.env["OAUTH2_CLIENT_ID"],
-      clientSecret: process.env["OAUTH2_CLIENT_SECRET"],
-      jwtSecret: process.env["OAUTH2_JWT_SECRET"],
-      profileUrl: process.env["OAUTH2_PROFILE_URL"],
-    }
+        accessTokenUrl: process.env["OAUTH2_ACCESS_TOKEN_URL"],
+        authorizationUrl: process.env["OAUTH2_AUTHORIZATION_URL"],
+        clientId: process.env["OAUTH2_CLIENT_ID"],
+        clientSecret: process.env["OAUTH2_CLIENT_SECRET"],
+        jwtSecret: process.env["OAUTH2_JWT_SECRET"],
+        profileUrl: process.env["OAUTH2_PROFILE_URL"],
+      }
     : null,
   openfiscaRepository: {
     branch: process.env["OPENFISCA_BRANCH"],
@@ -89,6 +92,8 @@ if (error !== null) {
   process.exit(-1)
 }
 const config = validConfig as Config
-config.apiWebSocketBaseUrl = config.apiBaseUrl.replace(/^http/, "ws")
+config.apiWebSocketBaseUrls = config.apiBaseUrls.map((url) =>
+  url.replace(/^http/, "ws"),
+)
 
 export default config
diff --git a/src/lib/sessions.ts b/src/lib/sessions.ts
index bdc73f7f3d71cdf2b12e2cfa87d8396056dbd1ea..09d2ec09f9574932faeb9e28c820365abdaf5728 100644
--- a/src/lib/sessions.ts
+++ b/src/lib/sessions.ts
@@ -6,8 +6,8 @@ import type { User } from "$lib/users"
 
 export interface Session {
   advanced: boolean
-  apiBaseUrl: string
-  apiWebSocketBaseUrl: string
+  apiBaseUrls: string[]
+  apiWebSocketBaseUrls: string[]
   baseUrl: string
   authenticationEnabled: boolean
   childrenKey: string
diff --git a/src/lib/websockets.ts b/src/lib/websockets.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3210cdc1f78d2c458fdeafde22487f704ff713eb
--- /dev/null
+++ b/src/lib/websockets.ts
@@ -0,0 +1,11 @@
+import type Sockette from "sockette"
+
+import type { CalculationName } from "$lib/decompositions"
+
+export type WebSocketByName = {
+  [name in CalculationName]: Sockette
+}
+
+export type WebSocketOpenByName = {
+  [name in CalculationName]?: boolean
+}
diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte
index 98056d929ab7c878fa6f874fbc1232dd37a6f258..1cef106b51a5a9f2d215c16e9823f4759d6ad1bc 100644
--- a/src/routes/__layout.svelte
+++ b/src/routes/__layout.svelte
@@ -2,26 +2,25 @@
   import "../app.css"
 
   import type { EntityByKey } from "@openfisca/ast"
-  import Sockette from "sockette"
   import { setContext } from "svelte"
   import type { Writable } from "svelte/store"
   import { writable } from "svelte/store"
+  import Sockette from "sockette"
 
   import { browser } from "$app/env"
   import { page, session } from "$app/stores"
   import NavBar from "$lib/components/NavBar.svelte"
+  import type { EvaluationByName } from "$lib/decompositions"
   import {
+    calculationNames,
     decompositionByNameFromCore,
     walkDecompositionsCoreName,
-    updateDecompositionEvaluations,
+    updateEvaluations,
   } from "$lib/decompositions"
   import type { PopulationWithoutId, Situation } from "$lib/situations"
   import { getPopulationReservedKeys } from "$lib/situations"
   import type { VariableValues } from "$lib/variables"
-
-  const deltaByNameArray: Writable<Array<{ [name: string]: number[] }>> =
-    writable(new Array($session.testCases.length).fill({}))
-  setContext("deltaByNameArray", deltaByNameArray)
+  import type { WebSocketByName, WebSocketOpenByName } from "$lib/websockets"
 
   const entityByKey = $session.entityByKey as EntityByKey
 
@@ -33,21 +32,13 @@
   const vectorLength = writable(1)
   setContext("vectorLength", vectorLength)
 
-  const decompositionByNameArray = writable(
-    $deltaByNameArray.map((deltaByName, situationIndex) =>
-      updateDecompositionEvaluations(
-        decompositionByNameFromCore(
-          $session.decompositionCoreByName,
-          $session.rootDecompositionName,
-        ),
-        $session.rootDecompositionName,
-        deltaByName,
-        $vectorIndexes[situationIndex],
-        $vectorLength,
-      ),
+  const decompositionByName = writable(
+    decompositionByNameFromCore(
+      $session.decompositionCoreByName,
+      $session.rootDecompositionName,
     ),
   )
-  setContext("decompositionByNameArray", decompositionByNameArray)
+  setContext("decompositionByName", decompositionByName)
 
   // Note: Duplicates are removed from decompositionsName, because a variable name
   // may appear more than once in decomposition.
@@ -64,6 +55,29 @@
 
   let entityKeyByVariableName: { [name: string]: string } = {}
 
+  const evaluationByNameArrayByCalculationName = writable(
+    Object.fromEntries(
+      calculationNames.map((calculationName) => [
+        calculationName,
+        new Array($session.testCases.length)
+          .fill(null)
+          .map((_, situationIndex) =>
+            updateEvaluations(
+              $decompositionByName,
+              {},
+              $session.rootDecompositionName,
+              $vectorIndexes[situationIndex],
+              $vectorLength,
+            ),
+          ),
+      ]),
+    ),
+  )
+  setContext(
+    "evaluationByNameArrayByCalculationName",
+    evaluationByNameArrayByCalculationName,
+  )
+
   const inputInstantsByVariableName: Writable<{
     [name: string]: Set<string>
   }> = writable(extractInputInstantsFromTestCases($session.testCases))
@@ -90,11 +104,12 @@
 
   let variableValuesByName: { [name: string]: VariableValues } = {}
 
-  const webSocket: Writable<Sockette | undefined> = writable(undefined)
-  setContext("webSocket", webSocket)
+  const webSocketByName: Writable<WebSocketByName | undefined> =
+    writable(undefined)
+  setContext("webSocketByName", webSocketByName)
 
-  const webSocketOpen = writable(false)
-  setContext("webSocketOpen", webSocketOpen)
+  const webSocketOpenByName: Writable<WebSocketOpenByName> = writable({})
+  setContext("webSocketOpenByName", webSocketOpenByName)
 
   const year = writable(2021)
   setContext("year", year)
@@ -222,106 +237,132 @@
   }
 
   function openWebSocket() {
-    $webSocket = new Sockette(
-      new URL("simulations/calculate", $session.apiWebSocketBaseUrl).toString(),
-      {
-        // maxAttempts: 10,
-        onmessage: (event) => {
-          const result = JSON.parse(event.data)
-          if (result.errors !== undefined) {
-            console.error("API Error:", result)
-          } else if (result.token !== $calculationToken) {
-            console.log(
-              `Ignoring API response with invalid token: ${result.token} instead of ${$calculationToken}`,
-            )
-          } else {
-            const entity = entityByKey[result.entity]
-            let testCasesPopulationCount = 0
-            for (const situation of $testCases) {
-              const entitySituation = situation[entity.key_plural] ?? {}
-              const populationCount = Object.keys(entitySituation).length
-              testCasesPopulationCount += populationCount
-            }
-
-            // Update decompositions.
-            if (decompositionsName.includes(result.name)) {
-              {
-                let populationIndex = 0
-                $deltaByNameArray = $deltaByNameArray.map(
-                  (deltaByName, situationIndex) => {
-                    const situation = $testCases[situationIndex]
-                    const entitySituation = situation[entity.key_plural] ?? {}
-                    let sum = new Array($vectorLength).fill(0)
-                    for (const [populationId, population] of Object.entries(
-                      entitySituation,
-                    ).sort(([populationId1], [populationId2]) =>
-                      populationId1.localeCompare(populationId2),
-                    )) {
-                      for (
-                        let index = populationIndex, sumIndex = 0;
-                        sumIndex < $vectorLength;
-                        index += testCasesPopulationCount, sumIndex++
-                      ) {
-                        sum[sumIndex] += result.value[index]
-                      }
-                      populationIndex++
-                    }
-                    return {
-                      ...deltaByName,
-                      [result.name]: $decompositionByNameArray[situationIndex][
-                        result.name
-                      ].negate
-                        ? sum.map((sumItem) => -sumItem)
-                        : sum,
-                    }
-                  },
+    let remainingApiWebSocketBaseUrls: string[] = []
+    $webSocketByName = Object.fromEntries(
+      calculationNames.map((calculationName) => {
+        if (remainingApiWebSocketBaseUrls.length === 0) {
+          remainingApiWebSocketBaseUrls = [...$session.apiWebSocketBaseUrls]
+        }
+        const apiWebSocketBaseUrlIndex = Math.floor(
+          Math.random() * remainingApiWebSocketBaseUrls.length,
+        )
+        const apiWebSocketBaseUrl =
+          remainingApiWebSocketBaseUrls[apiWebSocketBaseUrlIndex]
+        remainingApiWebSocketBaseUrls.splice(apiWebSocketBaseUrlIndex, 1)
+        const webSocket = new Sockette(
+          new URL("simulations/calculate", apiWebSocketBaseUrl).toString(),
+          {
+            // maxAttempts: 10,
+            onmessage: (event) => {
+              const result = JSON.parse(event.data)
+              if (result.errors !== undefined) {
+                console.error("API Error:", result)
+              } else if (result.token !== $calculationToken) {
+                console.log(
+                  `Ignoring API response with invalid token: ${result.token} instead of ${$calculationToken}`,
+                )
+              } else {
+                // Count total population of test cases.
+                const entity = entityByKey[result.entity]
+                let testCasesPopulationCount = 0
+                for (const situation of $testCases) {
+                  const entitySituation = situation[entity.key_plural] ?? {}
+                  const populationCount = Object.keys(entitySituation).length
+                  testCasesPopulationCount += populationCount
+                }
+
+                // Update evaluations.
+                if (decompositionsName.includes(result.name)) {
+                  // First, update delta of evalutions.
+                  let populationIndex = 0
+                  const evaluationByNameArray =
+                    $evaluationByNameArrayByCalculationName[
+                      calculationName
+                    ].map(
+                      (evaluationByName, situationIndex): EvaluationByName => {
+                        const situation = $testCases[situationIndex]
+                        const entitySituation =
+                          situation[entity.key_plural] ?? {}
+                        let sum = new Array($vectorLength).fill(0)
+                        for (const [
+                          _populationId,
+                          _population,
+                        ] of Object.entries(entitySituation).sort(
+                          ([populationId1], [populationId2]) =>
+                            populationId1.localeCompare(populationId2),
+                        )) {
+                          for (
+                            let index = populationIndex, sumIndex = 0;
+                            sumIndex < $vectorLength;
+                            index += testCasesPopulationCount, sumIndex++
+                          ) {
+                            sum[sumIndex] += result.value[index]
+                          }
+                          populationIndex++
+                        }
+                        return {
+                          ...evaluationByName,
+                          [result.name]: {
+                            ...evaluationByName[result.name],
+                            delta: $decompositionByName[result.name].negate
+                              ? sum.map((sumItem) => -sumItem)
+                              : sum,
+                          },
+                        }
+                      },
+                    )
+
+                  // Then, update values of evaluations from their new delta.
+                  $evaluationByNameArrayByCalculationName = {
+                    ...$evaluationByNameArrayByCalculationName,
+                    [calculationName]: evaluationByNameArray.map(
+                      (evaluationByName, situationIndex) =>
+                        updateEvaluations(
+                          $decompositionByName,
+                          evaluationByName,
+                          rootDecompositionName,
+                          $vectorIndexes[situationIndex],
+                          $vectorLength,
+                        ),
+                    ),
+                  }
+                }
+
+                // Update entity key & values of variables.
+                entityKeyByVariableName = {
+                  ...entityKeyByVariableName,
+                  [result.name]: result.entity,
+                }
+                variableValuesByName = {
+                  ...variableValuesByName,
+                  [result.name]: result.value,
+                }
+
+                // Update situation.
+                $testCases = updateTestCasesVariableValues(
+                  $testCases,
+                  result.name,
+                  $vectorIndexes,
                 )
               }
-
-              $decompositionByNameArray = $decompositionByNameArray.map(
-                (decompositionByName, situationIndex) =>
-                  updateDecompositionEvaluations(
-                    decompositionByName,
-                    rootDecompositionName,
-                    $deltaByNameArray[situationIndex],
-                    $vectorIndexes[situationIndex],
-                    $vectorLength,
-                  ),
-              )
-            }
-
-            // Update entity key & values of variables.
-            entityKeyByVariableName = {
-              ...entityKeyByVariableName,
-              [result.name]: result.entity,
-            }
-            variableValuesByName = {
-              ...variableValuesByName,
-              [result.name]: result.value,
-            }
-
-            // Update situation.
-            $testCases = updateTestCasesVariableValues(
-              $testCases,
-              result.name,
-              $vectorIndexes,
-            )
-          }
-        },
-        // onopen: (event) => console.log("[WebSocket] Connected!", event),
-        onopen: () => {
-          $webSocketOpen = true
-          $simulationRequested = true
-        },
-        // onreconnect: (event) =>
-        //   console.log("[WebSocket] Reconnecting...", event),
-        // onmaximum: (event) =>
-        //   console.log("[WebSocket] Stop Attempting!", event),
-        // onclose: (event) => console.log("[WebSocket] Closed!", event),
-        // onerror: (event) => console.log("[WebSocket] Error:", event),
-        // timeout: 5e3,>
-      },
-    )
+            },
+            // onopen: (event) => console.log("[WebSocket] Connected!", event),
+            onopen(this) {
+              $webSocketOpenByName[calculationName] = true
+              $simulationRequested = true
+            },
+            // onreconnect: (event) =>
+            //   console.log("[WebSocket] Reconnecting...", event),
+            // onmaximum: (event) =>
+            //   console.log("[WebSocket] Stop Attempting!", event),
+            // onclose: (event) => console.log("[WebSocket] Closed!", event),
+            // onerror: (event) => console.log("[WebSocket] Error:", event),
+            // timeout: 5e3,
+          },
+        )
+        return [calculationName, webSocket]
+      }),
+    ) as unknown as WebSocketByName
   }
 
   function updateTestCasesFromVectorIndexes(vectorIndexes: number[]): void {
diff --git a/src/routes/index.svelte b/src/routes/index.svelte
index 5c8363b4e8fff74d6ef09bf8072328918bdd775e..d9c538cf42b4bd8d8d36b491ad31dd8d5c45eaf1 100644
--- a/src/routes/index.svelte
+++ b/src/routes/index.svelte
@@ -1,7 +1,6 @@
 <script lang="ts">
   import type { EntityByKey, GroupEntity } from "@openfisca/ast"
   import { getRolePersonsIdKey } from "@openfisca/ast"
-  import type Sockette from "sockette"
   import { getContext, setContext } from "svelte"
   import type { Writable } from "svelte/store"
   import { writable } from "svelte/store"
@@ -17,11 +16,11 @@
   import VariableReferredInputsPane from "$lib/components/variables/VariableReferredInputsPane.svelte"
   import StartTutorial from "$lib/components/tutorial/StartTutorial.svelte"
   import VariableReferredParametersPane from "$lib/components/variables/VariableReferredParametersPane.svelte"
-  import type { DecompositionByName } from "$lib/decompositions"
-  import {
-    updateDecompositionEvaluations,
-    updateVectorIndex,
+  import type {
+    DecompositionByName,
+    EvaluationByNameArrayByCalculationName,
   } from "$lib/decompositions"
+  import { updateEvaluations, updateVectorIndex } from "$lib/decompositions"
   import type { ReformChange } from "$lib/reforms"
   import type {
     Axis,
@@ -31,6 +30,7 @@
   } from "$lib/situations"
   import { getPopulationReservedKeys } from "$lib/situations"
   import type { SelfTargetAProps } from "$lib/urls"
+  import type { WebSocketByName, WebSocketOpenByName } from "$lib/websockets"
 
   type EditionMode =
     | {
@@ -56,17 +56,16 @@
   const adaptAmountsScale = writable(true)
   setContext("adaptAmountsScale", adaptAmountsScale)
   let axes: Axis[][] = []
+  const billReformName: string | null = "PLF LFI"
   const date = new Date().toISOString().split("T")[0]
-  const decompositionByNameArray = getContext(
-    "decompositionByNameArray",
-  ) as Writable<DecompositionByName[]>
+  const decompositionByName = getContext(
+    "decompositionByName",
+  ) as Writable<DecompositionByName>
   const decompositionsName = getContext("decompositionsName") as string[]
-  const deltaByNameArray = getContext("deltaByNameArray") as Writable<
-    Array<{
-      [name: string]: number[]
-    }>
-  >
   let editionMode: EditionMode = null
+  const evaluationByNameArrayByCalculationName = getContext(
+    "evaluationByNameArrayByCalculationName",
+  ) as Writable<EvaluationByNameArrayByCalculationName>
   const inputInstantsByVariableName = getContext(
     "inputInstantsByVariableName",
   ) as Writable<{
@@ -88,8 +87,12 @@
   >
   const vectorIndexes = getContext("vectorIndexes") as Writable<number[]>
   const vectorLength = getContext("vectorLength") as Writable<number>
-  const webSocket = getContext("webSocket") as Writable<Sockette | undefined>
-  const webSocketOpen = getContext("webSocketOpen") as Writable<boolean>
+  const webSocketByName = getContext("webSocketByName") as Writable<
+    WebSocketByName | undefined
+  >
+  const webSocketOpenByName = getContext(
+    "webSocketOpenByName",
+  ) as Writable<WebSocketOpenByName>
   const year = getContext("year") as Writable<number>
 
   $: query = ensureValidQuery($page.query)
@@ -121,17 +124,26 @@
     $vectorLength = newVectorLength
 
     const situationIndex = $testCaseIndex
-    const newDecompositionByNameArray = [...$decompositionByNameArray]
-    newDecompositionByNameArray[situationIndex] =
-      updateDecompositionEvaluations(
-        newDecompositionByNameArray[situationIndex],
-        rootDecompositionName,
-        $deltaByNameArray[situationIndex],
-        $vectorIndexes[situationIndex],
-        newVectorLength,
-      )
-    $decompositionByNameArray = newDecompositionByNameArray
-    if ($webSocketOpen) {
+    $evaluationByNameArrayByCalculationName = Object.fromEntries(
+      Object.entries($evaluationByNameArrayByCalculationName).map(
+        ([calculationName, evaluationByNameArray]) => {
+          evaluationByNameArray = [...evaluationByNameArray]
+          evaluationByNameArray[situationIndex] = updateEvaluations(
+            $decompositionByName,
+            evaluationByNameArray[situationIndex],
+            rootDecompositionName,
+            $vectorIndexes[situationIndex],
+            newVectorLength,
+          )
+          return [calculationName, evaluationByNameArray]
+        },
+      ),
+    )
+    if (
+      $webSocketOpenByName.amendment ||
+      $webSocketOpenByName.bill ||
+      $webSocketOpenByName.law
+    ) {
       submit()
     }
   }
@@ -140,7 +152,11 @@
     const situations = [...$testCases]
     situations[situationIndex] = situation
     $testCases = situations
-    // if ($webSocketOpen) {
+    // if (
+    //   $webSocketOpenByName.amendment ||
+    //   $webSocketOpenByName.bill ||
+    //   $webSocketOpenByName.law
+    // ) {
     //   submit()
     // }
   }
@@ -150,15 +166,27 @@
     const newVectorIndexes = [...$vectorIndexes]
     newVectorIndexes[situationIndex] = detail.vectorIndex
     $vectorIndexes = newVectorIndexes
-    const decompositionByName = $decompositionByNameArray[situationIndex]
-    const newDecompositionByName = updateVectorIndex(
-      decompositionByName,
-      detail.vectorIndex,
+    let changed = false
+    const newEvaluationByNameArrayByCalculationName = Object.fromEntries(
+      Object.entries($evaluationByNameArrayByCalculationName).map(
+        ([calculationName, evaluationByNameArray]) => {
+          const evaluationByName = evaluationByNameArray[situationIndex]
+          const newEvaluationByName = updateVectorIndex(
+            evaluationByName,
+            detail.vectorIndex,
+          )
+          if (newEvaluationByName !== evaluationByName) {
+            evaluationByNameArray = [...evaluationByNameArray]
+            evaluationByNameArray[situationIndex] = newEvaluationByName
+            changed = true
+          }
+          return [calculationName, evaluationByNameArray]
+        },
+      ),
     )
-    if (newDecompositionByName !== decompositionByName) {
-      const newDecompositionByNameArray = [...$decompositionByNameArray]
-      newDecompositionByNameArray[situationIndex] = newDecompositionByName
-      $decompositionByNameArray = newDecompositionByNameArray
+    if (changed) {
+      $evaluationByNameArrayByCalculationName =
+        newEvaluationByNameArrayByCalculationName
     }
   }
 
@@ -468,21 +496,26 @@
     }
 
     $calculationToken = uuidv4()
-    $webSocket.send(
-      JSON.stringify({
-        // reform: "PLF LFI",
-        reform: Object.keys($reform).length > 0 ? $reform : undefined,
-        situation: aggregatedSituation,
-        period: $year.toString(),
-        token: $calculationToken,
-        variables: decompositionsName,
-      }),
-    )
-    $webSocket.send(
-      JSON.stringify({
-        calculate: true,
-      }),
-    )
+    const message = {
+      calculate: true,
+      situation: aggregatedSituation,
+      period: $year.toString(),
+      token: $calculationToken,
+      variables: decompositionsName,
+    }
+    if ($webSocketOpenByName.law) {
+      $webSocketByName.law.send(JSON.stringify(message))
+    }
+    if (billReformName !== null && $webSocketOpenByName.bill) {
+      $webSocketByName.bill.send(
+        JSON.stringify({ ...message, reform: billReformName }),
+      )
+    }
+    if (Object.keys($reform).length > 0 && $webSocketOpenByName.amendment) {
+      $webSocketByName.amendment.send(
+        JSON.stringify({ ...message, reform: $reform }),
+      )
+    }
   }
 </script>