Skip to content
Snippets Groups Projects
Select Git revision
  • f3185f319b1a4567a0de61433b312bd8acc3e773
  • master default protected
  • maj-readme
  • update-budget-msg-2022
  • share-with-metadata
  • explications-resultats-macros-PLF2022
  • plf_fix
  • benoit-cty-master-patch-87290
  • activate-plf-2021
  • stats
  • switch-plf
  • carto
  • adapt-matomo
  • js-to-ts
  • nbre-part-logic
  • add-nbre-part
  • 1.0.0
17 results

jest.config.js

Blame
  • decompositions.ts 58.67 KiB
    import decompositionCoreByNameUnknown from "@leximpact/socio-fiscal-openfisca-json/decompositions.json"
    import waterfallsUnknown from "@leximpact/socio-fiscal-openfisca-json/waterfalls.json"
    import type {
      Decomposition as DecompositionCore,
      DecompositionByName as DecompositionCoreByName,
      DecompositionReference,
      EntityByKey,
      Variable,
      VariableByName,
      Waterfall,
      WaterfallOptions,
    } from "@openfisca/json-model"
    import deepEqual from "deep-equal"
    
    import type { CalculationName } from "$lib/calculations"
    import { reformChangesByName } from "$lib/reforms"
    import type { Situation } from "$lib/situations"
    import {
      variableSummaryByName,
      variableSummaryByNameByReformName,
    } from "$lib/variables"
    
    export type {
      Decomposition as DecompositionCore,
      DecompositionByName as DecompositionCoreByName,
    } from "@openfisca/json-model"
    
    export interface CalculationEvaluation {
      delta: number[]
      deltaAtVectorIndex: number
    }
    
    export type CalculationEvaluationByName = Partial<{
      [name in CalculationName]: CalculationEvaluation
    }>
    
    export interface Decomposition extends DecompositionCore {
      name: string
      open?: boolean
    }
    
    export type DecompositionByName = { [name: string]: Decomposition }
    
    export interface Evaluation {
      calculationEvaluationByName: CalculationEvaluationByName
      fromOpenFisca?: boolean
    }
    
    export type EvaluationByName = { [name: string]: Evaluation }
    
    export interface LatchkeyDataItem {
      aggregate?: Decomposition
      leaf: Decomposition
    }
    
    export interface VisibleDecomposition extends VisibleDecompositionBase {
      rows: VisibleRow[]
      visibleEvaluationByCalculationName: VisibleEvaluationByCalculationName
    }
    
    export interface VisibleDecompositionBase {
      decomposition: Decomposition
      depth: number
      trunk: boolean
      variable?: Variable
      /// These children are not always the same as the one from the decomposition,
      /// because, the visible children of a decomposition, are not always the same
      /// as the children used to calculate the evaluation of the decomposition
      /// from OpenFisca variables.
      visibleChildren?: DecompositionReference[]
    }
    
    export interface VisibleDecompositionForComparison
      extends VisibleDecompositionBase {
      rows: VisibleRowForComparison[]
      visibleEvaluationByCalculationNameArray: VisibleEvaluationByCalculationName[]
    }
    
    export interface VisibleDecompositionForGraph extends VisibleDecompositionBase {
      parent: string | undefined
      rows: VisibleRowForGraph[]
      visibleEvaluationsByCalculationName: VisibleEvaluationsByCalculationName
    }
    
    export interface VisibleEvaluation {
      delta: number[]
      deltaAtVectorIndex: number
      deltaSums: [number, number][]
      deltaSumsAtVectorIndex: [number, number]
    }
    
    export type VisibleEvaluationByCalculationName = Partial<{
      [name in CalculationName]: VisibleEvaluation
    }>
    
    export interface VisibleEvaluations {
      delta: number[]
      deltaAtVectorIndex: number
      isNegative: boolean
    }
    
    export type VisibleEvaluationsByCalculationName = Partial<{
      [name in CalculationName]: VisibleEvaluations
    }>
    
    export interface VisibleRow {
      calculationName: CalculationName
      deltaAtVectorIndex: number
      deltaSumsAtVectorIndex: [number, number]
    }
    
    export interface VisibleRowForComparison {
      calculationName: CalculationName
      deltaAtVectorIndexArray: number[]
      deltaSumsAtVectorIndexArray: [number, number][]
    }
    
    export interface VisibleRowForGraph {
      calculationName: CalculationName
      delta: number[]
      deltaAtVectorIndex: number
      isNegative: boolean
    }
    
    export const decompositionCoreByName: DecompositionCoreByName =
      decompositionCoreByNameUnknown
    
    export const decompositionCoreByNameByReformName: {
      [name: string]: DecompositionCoreByName
    } = Object.fromEntries(
      Object.entries(reformChangesByName).map(([reformName, reformChanges]) => [
        reformName,
        patchDecompositionCoreByName(
          decompositionCoreByName,
          reformChanges.decompositions,
        ),
      ]),
    )
    
    export const waterfalls: Waterfall[] = waterfallsUnknown
    
    export const withLinkedVariableNames = extractWithLinkedVariableNames(
      decompositionCoreByName,
      variableSummaryByName,
      waterfalls,
    )
    
    export const nonVirtualVariablesName = extractNonVirtualVariablesName(
      decompositionCoreByName,
      variableSummaryByName,
      waterfalls,
    )
    export const nonVirtualVariablesNameByReformName: {
      [name: string]: string[]
    } = Object.fromEntries(
      Object.entries(decompositionCoreByNameByReformName).map(
        ([reformName, reformDecompositionCoreByName]) => [
          reformName,
          extractNonVirtualVariablesName(
            reformDecompositionCoreByName,
            variableSummaryByNameByReformName[reformName],
            waterfalls,
          ),
        ],
      ),
    )
    
    export const decompositionsOptionsVariablesName = new Set<string>()
    for (const decompositionCore of Object.values(decompositionCoreByName)) {
      if (decompositionCore.options === undefined) {
        continue
      }
      for (const options of decompositionCore.options) {
        if (options.waterfall !== undefined) {
          continue
        }
        for (const variableName of Object.keys(options)) {
          if (["else", "then"].includes(variableName)) {
            continue
          }
          decompositionsOptionsVariablesName.add(variableName)
        }
      }
    }
    
    export function buildDecompositionByNameFromCore(
      decompositionCoreByName: DecompositionCoreByName,
    ): DecompositionByName | undefined {
      return Object.fromEntries(
        Object.entries(decompositionCoreByName).map(([name, decompositionCore]) => [
          name,
          {
            ...decompositionCore,
            name,
          },
        ]),
      )
    }
    
    export function buildVisibleDecompositions(
      decompositionByName: DecompositionByName,
      entityByKey: EntityByKey,
      evaluationByName: EvaluationByName,
      situation: Situation,
      variableSummaryByName: VariableByName,
      waterfall: Waterfall,
      showNulls: boolean,
      useRevaluationInsteadOfLaw: boolean,
      vectorLength: number,
      year: number,
    ): VisibleDecomposition[] {
      const visibleDecompositions: VisibleDecomposition[] = []
      buildVisibleDecompositions1(
        decompositionByName,
        entityByKey,
        evaluationByName,
        situation,
        variableSummaryByName,
        waterfall.name,
        waterfall.root,
        showNulls,
        useRevaluationInsteadOfLaw,
        vectorLength,
        0,
        false,
        true,
        {},
        visibleDecompositions,
        year,
      )
      return visibleDecompositions
    }
    
    function buildVisibleDecompositions1(
      decompositionByName: DecompositionByName,
      entityByKey: EntityByKey,
      evaluationByName: EvaluationByName,
      situation: Situation,
      variableSummaryByName: VariableByName,
      waterfallName: string,
      name: string,
      showNulls: boolean,
      useRevaluationInsteadOfLaw: boolean,
      vectorLength: number,
      depth: number,
      negate: boolean,
      trunk = true,
      deltaSumsPreviousByCalculationName: Partial<{
        [calculationName in CalculationName]: number[]
      }>,
      visibleDecompositions: VisibleDecomposition[],
      year: number,
    ): number {
      const decomposition = decompositionByName[name]
      if (decomposition === undefined) {
        return -1
      }
      const evaluation = evaluationByName[name]
      if (evaluation === undefined) {
        return -1
      }
    
      let hidden = decomposition.hidden
      // Handle waterfall options for "hidden" attribute.
      for (const options of decomposition.options ?? []) {
        if (options.waterfall !== undefined) {
          if (
            options.then?.hidden !== undefined &&
            (options as WaterfallOptions).waterfall.includes(waterfallName)
          ) {
            hidden = options.then.hidden ?? undefined
          } else if (
            options.else?.hidden !== undefined &&
            !(options as WaterfallOptions).waterfall.includes(waterfallName)
          ) {
            hidden = options.else.hidden ?? undefined
          }
        }
      }
      // Handle variables options for "hidden" attribute.
      for (const options of decomposition.options ?? []) {
        if (options.waterfall === undefined) {
          if (
            options.then?.hidden !== undefined ||
            options.else?.hidden !== undefined
          ) {
            let allVariablesMatch = true
            for (const [variableName, variableValues] of Object.entries(options)) {
              if (["else", "then"].includes(variableName)) {
                continue
              }
              const variable = variableSummaryByName[variableName]
              const entity = entityByKey[variable.entity]
              const entitySituation = situation[entity.key_plural]
              if (entitySituation === undefined) {
                allVariablesMatch = false
                break
              }
              let variableMatch = false
              for (const population of Object.values(entitySituation)) {
                if (
                  variableValues.includes(
                    population[variableName]?.[year] ?? variable.default_value,
                  )
                ) {
                  variableMatch = true
                }
              }
              if (!variableMatch) {
                allVariablesMatch = false
                break
              }
            }
            if (options.then?.hidden !== undefined && allVariablesMatch) {
              hidden = options.then.hidden ?? undefined
            } else if (options.else?.hidden !== undefined && !allVariablesMatch) {
              hidden = options.else.hidden ?? undefined
            }
          }
        }
      }
    
      let visibleDecompositionIndex = -1
      const showNode =
        (showNulls && !hidden) ||
        Object.values(evaluation.calculationEvaluationByName).some(
          (deltaEvaluation) =>
            deltaEvaluation.delta.some((deltaItem) => deltaItem !== 0),
        )
      if (showNode) {
        const visibleDecomposition = {
          decomposition,
          depth,
          trunk,
          visibleEvaluationByCalculationName: Object.fromEntries(
            Object.entries(evaluation.calculationEvaluationByName).map(
              ([calculationName, { delta, deltaAtVectorIndex }]) => {
                if (negate) {
                  delta = delta.map((deltaItem) => -deltaItem)
                  deltaAtVectorIndex = -deltaAtVectorIndex
                }
                return [
                  calculationName,
                  {
                    delta,
                    deltaAtVectorIndex: deltaAtVectorIndex,
                  } as VisibleEvaluation,
                ]
              },
            ),
          ),
        } as VisibleDecomposition
    
        let visibleChildren = decomposition.children
        for (const options of decomposition.options ?? []) {
          if (options.waterfall !== undefined) {
            if (
              (options as WaterfallOptions).then?.children !== undefined &&
              (options as WaterfallOptions).waterfall.includes(waterfallName)
            ) {
              visibleChildren =
                (options as WaterfallOptions).then.children ?? undefined
            } else if (
              (options as WaterfallOptions).else?.children !== undefined &&
              !(options as WaterfallOptions).waterfall.includes(waterfallName)
            ) {
              visibleChildren =
                (options as WaterfallOptions).else.children ?? undefined
            }
          }
        }
        if (visibleChildren !== undefined) {
          visibleDecomposition.visibleChildren = visibleChildren
          // if ((trunk || depth < 1) && !decomposition.open) {
          if (trunk && !decomposition.open) {
            decomposition.open = true
          }
        } else if (decomposition.open) {
          delete decomposition.open
        }
    
        let childrenDepth = depth
        if (!trunk) {
          visibleDecompositionIndex = visibleDecompositions.length
          visibleDecompositions.push(visibleDecomposition)
          childrenDepth = depth + 1
        }
        if (decomposition.open && visibleChildren !== undefined) {
          const beforeChildrenVisibleDecompositionLength =
            visibleDecompositions.length
          let childDeltaSumsPreviousByCalculationName =
            deltaSumsPreviousByCalculationName
          for (const childReference of visibleChildren) {
            const childVisibleDecompositionIndex = buildVisibleDecompositions1(
              decompositionByName,
              entityByKey,
              evaluationByName,
              situation,
              variableSummaryByName,
              waterfallName,
              childReference.name,
              showNulls,
              useRevaluationInsteadOfLaw,
              vectorLength,
              childrenDepth,
              Boolean(negate ? !childReference.negate : childReference.negate),
              trunk &&
                visibleDecompositions.length ===
                  beforeChildrenVisibleDecompositionLength,
              childDeltaSumsPreviousByCalculationName,
              visibleDecompositions,
              year,
            )
            if (childVisibleDecompositionIndex < 0) {
              continue
            }
            const childVisibleDecomposition =
              visibleDecompositions[childVisibleDecompositionIndex]
            childDeltaSumsPreviousByCalculationName = Object.fromEntries(
              Object.entries(
                childVisibleDecomposition.visibleEvaluationByCalculationName,
              ).map(([calculationName, childVisibleEvaluation]) => [
                calculationName,
                childVisibleEvaluation.deltaSums.map((itemValue) => itemValue[1]),
              ]),
            )
          }
        }
        if (trunk) {
          visibleDecompositionIndex = visibleDecompositions.length
          visibleDecompositions.push(visibleDecomposition)
        }
    
        const vectorIndex = situation.slider?.vectorIndex ?? 0
        for (const [calculationName, visibleEvaluation] of Object.entries(
          visibleDecomposition.visibleEvaluationByCalculationName,
        )) {
          const deltaSumsPrevious =
            deltaSumsPreviousByCalculationName[
              calculationName as CalculationName
            ] ?? new Array(vectorLength).fill(0)
          const deltaSums = deltaSumsPrevious.map((previousItemValue, index) => [
            previousItemValue,
            previousItemValue + visibleEvaluation.delta[index],
          ]) as [number, number][]
          visibleEvaluation.deltaSums = deltaSums
          visibleEvaluation.deltaSumsAtVectorIndex =
            vectorIndex < deltaSums.length ? deltaSums[vectorIndex] : [0, 0]
        }
    
        const variable = variableSummaryByName[name]
        if (variable !== undefined) {
          visibleDecomposition.variable = variable
        }
    
        const visibleEvaluationByCalculationName =
          visibleDecomposition.visibleEvaluationByCalculationName
        const rows: VisibleRow[] = (visibleDecomposition.rows = [])
    
        const firstVisibleEvaluation = useRevaluationInsteadOfLaw
          ? visibleEvaluationByCalculationName.revaluation
          : visibleEvaluationByCalculationName.law
        if (firstVisibleEvaluation !== undefined) {
          const firstRow: VisibleRow = {
            calculationName: useRevaluationInsteadOfLaw ? "revaluation" : "law",
            deltaAtVectorIndex: firstVisibleEvaluation.deltaAtVectorIndex,
            deltaSumsAtVectorIndex: firstVisibleEvaluation.deltaSumsAtVectorIndex,
          }
          rows.push(firstRow)
          let previousRow = firstRow
          let previousVisibleEvaluation = firstVisibleEvaluation
    
          const billVisibleEvaluation = visibleEvaluationByCalculationName.bill
          if (billVisibleEvaluation !== undefined) {
            if (
              billVisibleEvaluation.deltaAtVectorIndex ===
              previousVisibleEvaluation.deltaAtVectorIndex
            ) {
              previousRow.deltaSumsAtVectorIndex =
                billVisibleEvaluation.deltaSumsAtVectorIndex
            } else {
              previousRow.deltaSumsAtVectorIndex = [
                billVisibleEvaluation.deltaSumsAtVectorIndex[0],
                billVisibleEvaluation.deltaSumsAtVectorIndex[0] +
                  previousVisibleEvaluation.deltaAtVectorIndex,
              ]
              const billRow: VisibleRow = {
                calculationName: "bill",
                deltaAtVectorIndex: billVisibleEvaluation.deltaAtVectorIndex,
                deltaSumsAtVectorIndex:
                  billVisibleEvaluation.deltaSumsAtVectorIndex,
              }
              rows.push(billRow)
              previousRow = billRow
            }
            previousVisibleEvaluation = billVisibleEvaluation
          }
    
          const amendmentVisibleEvaluation =
            visibleEvaluationByCalculationName.amendment
          if (amendmentVisibleEvaluation !== undefined) {
            // (law or revaluation) + bill + amendement
    
            if (
              amendmentVisibleEvaluation.deltaAtVectorIndex ===
              previousVisibleEvaluation.deltaAtVectorIndex
            ) {
              previousRow.deltaSumsAtVectorIndex =
                amendmentVisibleEvaluation.deltaSumsAtVectorIndex
            } else {
              previousRow.deltaSumsAtVectorIndex = [
                amendmentVisibleEvaluation.deltaSumsAtVectorIndex[0],
                amendmentVisibleEvaluation.deltaSumsAtVectorIndex[0] +
                  previousVisibleEvaluation.deltaAtVectorIndex,
              ]
              const amendmentRow: VisibleRow = {
                calculationName: "amendment",
                deltaAtVectorIndex: amendmentVisibleEvaluation.deltaAtVectorIndex,
                deltaSumsAtVectorIndex:
                  amendmentVisibleEvaluation.deltaSumsAtVectorIndex,
              }
              rows.push(amendmentRow)
            }
          }
        }
      }
    
      return visibleDecompositionIndex
    }
    
    export function buildVisibleDecompositionsForComparison(
      decompositionByName: DecompositionByName,
      entityByKey: EntityByKey,
      evaluationByNameArray: EvaluationByName[],
      situations: Situation[],
      variableSummaryByName: VariableByName,
      waterfall: Waterfall,
      showNulls: boolean,
      useRevaluationInsteadOfLaw: boolean,
      vectorLength: number,
      year: number,
    ): VisibleDecompositionForComparison[] {
      const visibleDecompositions: VisibleDecompositionForComparison[] = []
      buildVisibleDecompositionsForComparison1(
        decompositionByName,
        entityByKey,
        evaluationByNameArray,
        situations,
        variableSummaryByName,
        waterfall.name,
        waterfall.root,
        showNulls,
        useRevaluationInsteadOfLaw,
        vectorLength,
        0,
        false,
        true,
        situations.map(() => ({})),
        visibleDecompositions,
        year,
      )
      return visibleDecompositions
    }
    
    function buildVisibleDecompositionsForComparison1(
      decompositionByName: DecompositionByName,
      entityByKey: EntityByKey,
      evaluationByNameArray: EvaluationByName[],
      situations: Situation[],
      variableSummaryByName: VariableByName,
      waterfallName: string,
      name: string,
      showNulls: boolean,
      useRevaluationInsteadOfLaw: boolean,
      vectorLength: number,
      depth: number,
      negate: boolean,
      trunk = true,
      deltaSumsPreviousByCalculationNameArray: Array<
        Partial<{
          [calculationName in CalculationName]: number[]
        }>
      >,
      visibleDecompositions: VisibleDecompositionForComparison[],
      year: number,
    ): number {
      const decomposition = decompositionByName[name]
      if (decomposition === undefined) {
        return -1
      }
      const evaluations = evaluationByNameArray.map(
        (evaluationByName) => evaluationByName[name],
      )
      if (evaluations.some((evaluation) => evaluation === undefined)) {
        return -1
      }
    
      let hidden = decomposition.hidden
      // Handle waterfall options for "hidden" attribute.
      for (const options of decomposition.options ?? []) {
        if (options.waterfall !== undefined) {
          if (
            options.then?.hidden !== undefined &&
            (options as WaterfallOptions).waterfall.includes(waterfallName)
          ) {
            hidden = options.then.hidden ?? undefined
          } else if (
            options.else?.hidden !== undefined &&
            !(options as WaterfallOptions).waterfall.includes(waterfallName)
          ) {
            hidden = options.else.hidden ?? undefined
          }
        }
      }
      // Handle variables options for "hidden" attribute.
      for (const options of decomposition.options ?? []) {
        if (options.waterfall === undefined) {
          if (
            options.then?.hidden !== undefined ||
            options.else?.hidden !== undefined
          ) {
            const allVariablesMatchArray = situations.map(() => true)
            for (const [variableName, variableValues] of Object.entries(options)) {
              if (["else", "then"].includes(variableName)) {
                continue
              }
              const variable = variableSummaryByName[variableName]
              const entity = entityByKey[variable.entity]
    
              for (const [situationIndex, situation] of situations.entries()) {
                const allVariablesMatch = allVariablesMatchArray[situationIndex]
                if (allVariablesMatch) {
                  const entitySituation0 = situation[entity.key_plural]
                  if (entitySituation0 === undefined) {
                    allVariablesMatchArray[situationIndex] = false
                    break
                  }
                  let variableMatch = false
                  for (const population of Object.values(entitySituation0)) {
                    if (
                      variableValues.includes(
                        population[variableName]?.[year] ?? variable.default_value,
                      )
                    ) {
                      variableMatch = true
                    }
                  }
                  if (!variableMatch) {
                    allVariablesMatchArray[situationIndex] = false
                  }
                }
              }
            }
    
            const thenHidden = options.then?.hidden
            if (
              thenHidden === true &&
              allVariablesMatchArray.every((allVariablesMatch) => allVariablesMatch)
            ) {
              hidden = true
            } else if (
              thenHidden === false &&
              allVariablesMatchArray.some((allVariablesMatch) => allVariablesMatch)
            ) {
              hidden = false
            } else {
              const elseHidden = options.else?.hidden
              if (
                elseHidden === true &&
                !allVariablesMatchArray.every(
                  (allVariablesMatch) => allVariablesMatch,
                )
              ) {
                hidden = true
              } else if (
                elseHidden === false &&
                !allVariablesMatchArray.some(
                  (allVariablesMatch) => allVariablesMatch,
                )
              ) {
                hidden = false
              }
            }
          }
        }
      }
    
      let visibleDecompositionIndex = -1
      const showNode =
        (showNulls && !hidden) ||
        evaluations.some((evaluation) =>
          Object.values(evaluation.calculationEvaluationByName).some(
            (deltaEvaluation) =>
              deltaEvaluation.delta.some((deltaItem) => deltaItem !== 0),
          ),
        )
      if (showNode) {
        const visibleDecomposition = {
          decomposition,
          depth,
          trunk,
          visibleEvaluationByCalculationNameArray: evaluations.map((evaluation) =>
            Object.fromEntries(
              Object.entries(evaluation.calculationEvaluationByName).map(
                ([calculationName, { delta, deltaAtVectorIndex }]) => {
                  if (negate) {
                    delta = delta.map((deltaItem) => -deltaItem)
                  }
                  return [
                    calculationName,
                    {
                      delta,
                      deltaAtVectorIndex,
                    } as VisibleEvaluation,
                  ]
                },
              ),
            ),
          ),
        } as VisibleDecompositionForComparison
    
        let visibleChildren = decomposition.children
        for (const options of decomposition.options ?? []) {
          if (options.waterfall !== undefined) {
            if (
              (options as WaterfallOptions).then?.children !== undefined &&
              (options as WaterfallOptions).waterfall.includes(waterfallName)
            ) {
              visibleChildren =
                (options as WaterfallOptions).then.children ?? undefined
            } else if (
              (options as WaterfallOptions).else?.children !== undefined &&
              !(options as WaterfallOptions).waterfall.includes(waterfallName)
            ) {
              visibleChildren =
                (options as WaterfallOptions).else.children ?? undefined
            }
          }
        }
        if (visibleChildren !== undefined) {
          visibleDecomposition.visibleChildren = visibleChildren
          // if ((trunk || depth < 1) && !decomposition.open) {
          if (trunk && !decomposition.open) {
            decomposition.open = true
          }
        } else if (decomposition.open) {
          delete decomposition.open
        }
    
        let childrenDepth = depth
        if (!trunk) {
          visibleDecompositionIndex = visibleDecompositions.length
          visibleDecompositions.push(visibleDecomposition)
          childrenDepth = depth + 1
        }
        if (decomposition.open && visibleChildren !== undefined) {
          const beforeChildrenVisibleDecompositionLength =
            visibleDecompositions.length
          let childDeltaSumsPreviousByCalculationNameArray =
            deltaSumsPreviousByCalculationNameArray
          for (const childReference of visibleChildren) {
            const childVisibleDecompositionIndex =
              buildVisibleDecompositionsForComparison1(
                decompositionByName,
                entityByKey,
                evaluationByNameArray,
                situations,
                variableSummaryByName,
                waterfallName,
                childReference.name,
                showNulls,
                useRevaluationInsteadOfLaw,
                vectorLength,
                childrenDepth,
                Boolean(negate ? !childReference.negate : childReference.negate),
                trunk &&
                  visibleDecompositions.length ===
                    beforeChildrenVisibleDecompositionLength,
                childDeltaSumsPreviousByCalculationNameArray,
                visibleDecompositions,
                year,
              )
            if (childVisibleDecompositionIndex < 0) {
              continue
            }
            const childVisibleDecomposition =
              visibleDecompositions[childVisibleDecompositionIndex]
            childDeltaSumsPreviousByCalculationNameArray =
              childVisibleDecomposition.visibleEvaluationByCalculationNameArray.map(
                (childVisibleEvaluationByCalculationName) =>
                  Object.fromEntries(
                    Object.entries(childVisibleEvaluationByCalculationName).map(
                      ([calculationName, childVisibleEvaluation]) => [
                        calculationName,
                        childVisibleEvaluation.deltaSums.map(
                          (itemValue) => itemValue[1],
                        ),
                      ],
                    ),
                  ),
              )
          }
        }
        if (trunk) {
          visibleDecompositionIndex = visibleDecompositions.length
          visibleDecompositions.push(visibleDecomposition)
        }
    
        const vectorsIndex = situations.map(
          (situation) => situation.slider?.vectorIndex ?? 0,
        )
        for (const [
          situationIndex,
          visibleEvaluationByCalculationName,
        ] of visibleDecomposition.visibleEvaluationByCalculationNameArray.entries()) {
          for (const [calculationName, visibleEvaluation] of Object.entries(
            visibleEvaluationByCalculationName,
          )) {
            const deltaSumsPrevious =
              deltaSumsPreviousByCalculationNameArray[situationIndex][
                calculationName as CalculationName
              ] ?? new Array(vectorLength).fill(0)
            const deltaSums = deltaSumsPrevious.map((previousItemValue, index) => [
              previousItemValue,
              previousItemValue + visibleEvaluation.delta[index],
            ]) as [number, number][]
            visibleEvaluation.deltaSums = deltaSums
    
            const vectorIndex = vectorsIndex[situationIndex]
            visibleEvaluation.deltaSumsAtVectorIndex =
              vectorIndex < deltaSums.length ? deltaSums[vectorIndex] : [0, 0]
          }
        }
    
        const variable = variableSummaryByName[name]
        if (variable !== undefined) {
          visibleDecomposition.variable = variable
        }
    
        const visibleEvaluationByCalculationNameArray =
          visibleDecomposition.visibleEvaluationByCalculationNameArray
        const rows: VisibleRowForComparison[] = (visibleDecomposition.rows = [])
    
        const firstVisibleEvaluations = visibleEvaluationByCalculationNameArray.map(
          (visibleEvaluationByCalculationName) =>
            useRevaluationInsteadOfLaw
              ? visibleEvaluationByCalculationName.revaluation
              : visibleEvaluationByCalculationName.law,
        ) as VisibleEvaluation[]
        if (
          firstVisibleEvaluations.every(
            (firstVisibleEvaluation) => firstVisibleEvaluation !== undefined,
          )
        ) {
          const firstRow: VisibleRowForComparison = {
            calculationName: useRevaluationInsteadOfLaw ? "revaluation" : "law",
            deltaAtVectorIndexArray: firstVisibleEvaluations.map(
              (firstVisibleEvaluation) =>
                (firstVisibleEvaluation as VisibleEvaluation).deltaAtVectorIndex,
            ),
            deltaSumsAtVectorIndexArray: firstVisibleEvaluations.map(
              (firstVisibleEvaluation) =>
                (firstVisibleEvaluation as VisibleEvaluation)
                  .deltaSumsAtVectorIndex,
            ),
          }
          rows.push(firstRow)
    
          let previousRow = firstRow
          let previousVisibleEvaluations: VisibleEvaluation[]
    
          const billVisibleEvaluations =
            visibleEvaluationByCalculationNameArray.map(
              (visibleEvaluationByCalculationName) =>
                visibleEvaluationByCalculationName.bill,
            ) as VisibleEvaluation[]
          if (
            billVisibleEvaluations.some(
              (billVisibleEvaluation) => billVisibleEvaluation === undefined,
            )
          ) {
            previousVisibleEvaluations = firstVisibleEvaluations
          } else {
            const billRow: VisibleRowForComparison = {
              calculationName: "bill",
              deltaAtVectorIndexArray: [],
              deltaSumsAtVectorIndexArray: [],
            }
            const useBillRow = situations.map(() => false)
            for (const [
              situationIndex,
              billVisibleEvaluation,
            ] of billVisibleEvaluations.entries()) {
              if (
                billVisibleEvaluation.deltaAtVectorIndex ===
                firstVisibleEvaluations[situationIndex].deltaAtVectorIndex
              ) {
                firstRow.deltaSumsAtVectorIndexArray[situationIndex] =
                  billVisibleEvaluation.deltaSumsAtVectorIndex
                billRow.deltaAtVectorIndexArray.push(
                  firstRow.deltaAtVectorIndexArray[situationIndex],
                )
                billRow.deltaSumsAtVectorIndexArray.push([
                  ...firstRow.deltaSumsAtVectorIndexArray[situationIndex],
                ])
              } else {
                firstRow.deltaSumsAtVectorIndexArray[situationIndex] = [
                  billVisibleEvaluation.deltaSumsAtVectorIndex[0],
                  billVisibleEvaluation.deltaSumsAtVectorIndex[0] +
                    firstVisibleEvaluations[situationIndex].deltaAtVectorIndex,
                ]
                billRow.deltaAtVectorIndexArray.push(
                  billVisibleEvaluation.deltaAtVectorIndex,
                )
                billRow.deltaSumsAtVectorIndexArray.push(
                  billVisibleEvaluation.deltaSumsAtVectorIndex,
                )
                useBillRow[situationIndex] = true
              }
            }
            if (useBillRow.some((useBillRow) => useBillRow)) {
              rows.push(billRow)
              previousRow = billRow
            }
            previousVisibleEvaluations = billVisibleEvaluations
          }
    
          const amendmentVisibleEvaluations =
            visibleEvaluationByCalculationNameArray.map(
              (visibleEvaluationByCalculationName) =>
                visibleEvaluationByCalculationName.amendment,
            ) as VisibleEvaluation[]
          if (
            amendmentVisibleEvaluations.every(
              (amendmentVisibleEvaluation) =>
                amendmentVisibleEvaluation !== undefined,
            )
          ) {
            // (law or revaluation) + bill + amendement
            const amendmentRow: VisibleRowForComparison = {
              calculationName: "amendment",
              deltaAtVectorIndexArray: [],
              deltaSumsAtVectorIndexArray: [],
            }
            const useAmendmentRow = situations.map(() => false)
            for (const [
              situationIndex,
              amendmentVisibleEvaluation,
            ] of amendmentVisibleEvaluations.entries()) {
              if (
                amendmentVisibleEvaluation.deltaAtVectorIndex ===
                previousVisibleEvaluations[situationIndex].deltaAtVectorIndex
              ) {
                previousRow.deltaSumsAtVectorIndexArray[situationIndex] =
                  amendmentVisibleEvaluation.deltaSumsAtVectorIndex
                amendmentRow.deltaAtVectorIndexArray.push(
                  previousRow.deltaAtVectorIndexArray[situationIndex],
                )
                amendmentRow.deltaSumsAtVectorIndexArray.push([
                  ...previousRow.deltaSumsAtVectorIndexArray[situationIndex],
                ])
              } else {
                previousRow.deltaSumsAtVectorIndexArray[situationIndex] = [
                  amendmentVisibleEvaluation.deltaSumsAtVectorIndex[0],
                  amendmentVisibleEvaluation.deltaSumsAtVectorIndex[0] +
                    previousVisibleEvaluations[situationIndex].deltaAtVectorIndex,
                ]
                amendmentRow.deltaAtVectorIndexArray.push(
                  amendmentVisibleEvaluation.deltaAtVectorIndex,
                )
                amendmentRow.deltaSumsAtVectorIndexArray.push(
                  amendmentVisibleEvaluation.deltaSumsAtVectorIndex,
                )
                useAmendmentRow[situationIndex] = true
              }
            }
            if (useAmendmentRow.some((use) => use)) {
              rows.push(amendmentRow)
            }
          }
        }
      }
    
      return visibleDecompositionIndex
    }
    
    export function buildVisibleDecompositionsForGraph(
      decompositionByName: DecompositionByName,
      entityByKey: EntityByKey,
      evaluationByName: EvaluationByName,
      situation: Situation,
      variableSummaryByName: VariableByName,
      waterfall: Waterfall,
      showNulls: boolean,
      useRevaluationInsteadOfLaw: boolean,
      vectorLength: number,
      year: number,
    ): VisibleDecompositionForGraph[] {
      const visibleDecompositions: VisibleDecompositionForGraph[] = []
      buildVisibleDecompositionsForGraph1(
        decompositionByName,
        entityByKey,
        evaluationByName, // contains law bill revaluation with each { delta: [] and deltaAtVectorIndex: number }
        situation, // contains slider with min, max and vectorIndex
        variableSummaryByName,
        waterfall.name,
        waterfall.root,
        undefined,
        showNulls,
        useRevaluationInsteadOfLaw,
        vectorLength,
        0,
        false,
        true,
        visibleDecompositions,
        year,
      )
      return visibleDecompositions
    }
    
    function buildVisibleDecompositionsForGraph1(
      decompositionByName: DecompositionByName,
      entityByKey: EntityByKey,
      evaluationByName: EvaluationByName,
      situation: Situation,
      variableSummaryByName: VariableByName,
      waterfallName: string,
      name: string,
      parent: string | undefined,
      showNulls: boolean,
      useRevaluationInsteadOfLaw: boolean,
      vectorLength: number,
      depth: number,
      negate: boolean,
      trunk = true,
      visibleDecompositions: VisibleDecompositionForGraph[],
      year: number,
    ): number {
      const decomposition = decompositionByName[name]
      if (decomposition === undefined) {
        return -1
      }
      const evaluation = evaluationByName[name]
      if (evaluation === undefined) {
        return -1
      }
    
      let hidden = decomposition.hidden
      // Handle waterfall options for "hidden" attribute.
      for (const options of decomposition.options ?? []) {
        if (options.waterfall !== undefined) {
          if (
            options.then?.hidden !== undefined &&
            (options as WaterfallOptions).waterfall.includes(waterfallName)
          ) {
            hidden = options.then.hidden ?? undefined
          } else if (
            options.else?.hidden !== undefined &&
            !(options as WaterfallOptions).waterfall.includes(waterfallName)
          ) {
            hidden = options.else.hidden ?? undefined
          }
        }
      }
      // Handle variables options for "hidden" attribute.
      for (const options of decomposition.options ?? []) {
        if (options.waterfall === undefined) {
          if (
            options.then?.hidden !== undefined ||
            options.else?.hidden !== undefined
          ) {
            let allVariablesMatch = true
            for (const [variableName, variableValues] of Object.entries(options)) {
              if (["else", "then"].includes(variableName)) {
                continue
              }
              const variable = variableSummaryByName[variableName]
              const entity = entityByKey[variable.entity]
              const entitySituation = situation[entity.key_plural]
              if (entitySituation === undefined) {
                allVariablesMatch = false
                break
              }
              let variableMatch = false
              for (const population of Object.values(entitySituation)) {
                if (
                  variableValues.includes(
                    population[variableName]?.[year] ?? variable.default_value,
                  )
                ) {
                  variableMatch = true
                }
              }
              if (!variableMatch) {
                allVariablesMatch = false
                break
              }
            }
            if (options.then?.hidden !== undefined && allVariablesMatch) {
              hidden = options.then.hidden ?? undefined
            } else if (options.else?.hidden !== undefined && !allVariablesMatch) {
              hidden = options.else.hidden ?? undefined
            }
          }
        }
      }
    
      let visibleDecompositionIndex = -1
      const showNode =
        (showNulls && !hidden) ||
        Object.values(evaluation.calculationEvaluationByName).some(({ delta }) =>
          delta.some((deltaItem) => deltaItem !== 0),
        )
      if (showNode) {
        const visibleDecomposition = {
          decomposition,
          depth,
          parent,
          trunk,
          visibleEvaluationsByCalculationName: Object.fromEntries(
            Object.entries(evaluation.calculationEvaluationByName).map(
              ([calculationName, { delta, deltaAtVectorIndex }]) => {
                if (negate) {
                  delta = delta.map((deltaItem) => -deltaItem)
                  deltaAtVectorIndex = -deltaAtVectorIndex
                }
                return [
                  calculationName,
                  {
                    delta,
                    deltaAtVectorIndex: deltaAtVectorIndex,
                    isNegative: delta.some((val) => (isNaN(val) ? 0 : val) < 0),
                  } as VisibleEvaluations,
                ]
              },
            ),
          ),
        } as VisibleDecompositionForGraph
    
        let visibleChildren = decomposition.children
        for (const options of decomposition.options ?? []) {
          if (options.waterfall !== undefined) {
            if (
              (options as WaterfallOptions).then?.children !== undefined &&
              (options as WaterfallOptions).waterfall.includes(waterfallName)
            ) {
              visibleChildren =
                (options as WaterfallOptions).then.children ?? undefined
            } else if (
              (options as WaterfallOptions).else?.children !== undefined &&
              !(options as WaterfallOptions).waterfall.includes(waterfallName)
            ) {
              visibleChildren =
                (options as WaterfallOptions).else.children ?? undefined
            }
          }
        }
        if (visibleChildren !== undefined) {
          visibleDecomposition.visibleChildren = visibleChildren
          // if ((trunk || depth < 1) && !decomposition.open) {
          if (trunk && !decomposition.open) {
            decomposition.open = true
          }
        } else if (decomposition.open) {
          delete decomposition.open
        }
    
        let childrenDepth = depth
        if (!trunk) {
          visibleDecompositionIndex = visibleDecompositions.length
          visibleDecompositions.push(visibleDecomposition)
          childrenDepth = depth + 1
        }
        if (decomposition.open && visibleChildren !== undefined) {
          const beforeChildrenVisibleDecompositionLength =
            visibleDecompositions.length
          for (const childReference of visibleChildren) {
            buildVisibleDecompositionsForGraph1(
              decompositionByName,
              entityByKey,
              evaluationByName,
              situation,
              variableSummaryByName,
              waterfallName,
              childReference.name,
              childrenDepth > 0 ? name : undefined,
              showNulls,
              useRevaluationInsteadOfLaw,
              vectorLength,
              childrenDepth,
              Boolean(negate ? !childReference.negate : childReference.negate),
              trunk &&
                visibleDecompositions.length ===
                  beforeChildrenVisibleDecompositionLength,
              visibleDecompositions,
              year,
            )
          }
        }
        if (trunk) {
          visibleDecompositionIndex = visibleDecompositions.length
          visibleDecompositions.push(visibleDecomposition)
        }
    
        const variable = variableSummaryByName[name]
        if (variable !== undefined) {
          visibleDecomposition.variable = variable
        }
    
        const visibleEvaluationByCalculationName =
          visibleDecomposition.visibleEvaluationsByCalculationName
        const rows: VisibleRowForGraph[] = (visibleDecomposition.rows = [])
    
        const firstVisibleEvaluation = useRevaluationInsteadOfLaw
          ? visibleEvaluationByCalculationName.revaluation
          : visibleEvaluationByCalculationName.law
        if (firstVisibleEvaluation !== undefined) {
          const firstRow: VisibleRowForGraph = {
            calculationName: useRevaluationInsteadOfLaw ? "revaluation" : "law",
            delta: firstVisibleEvaluation.delta,
            deltaAtVectorIndex: firstVisibleEvaluation.deltaAtVectorIndex,
            isNegative: firstVisibleEvaluation.isNegative,
          }
          rows.push(firstRow)
          let previousVisibleEvaluation = firstVisibleEvaluation
    
          const billVisibleEvaluation = visibleEvaluationByCalculationName.bill
          if (billVisibleEvaluation !== undefined) {
            if (
              !deepEqual(
                billVisibleEvaluation.delta,
                previousVisibleEvaluation.delta,
              )
            ) {
              const billRow: VisibleRowForGraph = {
                calculationName: "bill",
                delta: billVisibleEvaluation.delta,
                deltaAtVectorIndex: billVisibleEvaluation.deltaAtVectorIndex,
                isNegative: billVisibleEvaluation.isNegative,
              }
              rows.push(billRow)
            }
            previousVisibleEvaluation = billVisibleEvaluation
          }
    
          const amendmentVisibleEvaluation =
            visibleEvaluationByCalculationName.amendment
          if (amendmentVisibleEvaluation !== undefined) {
            // (law or revaluation) + bill + amendement
            if (
              !deepEqual(
                amendmentVisibleEvaluation.delta,
                previousVisibleEvaluation.delta,
              )
            ) {
              const amendmentRow: VisibleRowForGraph = {
                calculationName: "amendment",
                delta: amendmentVisibleEvaluation.delta,
                deltaAtVectorIndex: amendmentVisibleEvaluation.deltaAtVectorIndex,
                isNegative: amendmentVisibleEvaluation.isNegative,
              }
              rows.push(amendmentRow)
            }
          }
        }
      }
    
      return visibleDecompositionIndex
    }
    
    export function buildVisibleDecompositionsWithoutValues(
      decompositionByName: DecompositionByName,
      variableSummaryByName: VariableByName,
      waterfall: Waterfall,
      year: number,
    ): VisibleDecomposition[] {
      const visibleDecompositions: VisibleDecomposition[] = []
      buildVisibleDecompositionsWithoutValues1(
        decompositionByName,
        variableSummaryByName,
        waterfall.name,
        waterfall.root,
        0,
        false,
        true,
        {},
        visibleDecompositions,
        year,
      )
      return visibleDecompositions
    }
    
    function buildVisibleDecompositionsWithoutValues1(
      decompositionByName: DecompositionByName,
      variableSummaryByName: VariableByName,
      waterfallName: string,
      name: string,
      depth: number,
      negate: boolean,
      trunk = true,
      deltaSumsPreviousByCalculationName: Partial<{
        [calculationName in CalculationName]: number[]
      }>,
      visibleDecompositions: VisibleDecomposition[],
      year: number,
    ): number {
      const decomposition = decompositionByName[name]
      if (decomposition === undefined) {
        return -1
      }
    
      let visibleDecompositionIndex = -1
      // If we need to show less variables, then we should pass
      // a situation and handle it here
      if (!decomposition.hidden) {
        const visibleDecomposition = {
          decomposition,
          depth,
          trunk,
        } as VisibleDecomposition
    
        let visibleChildren = decomposition.children
        for (const options of decomposition.options ?? []) {
          if (options.waterfall !== undefined) {
            if (
              (options as WaterfallOptions).then?.children !== undefined &&
              (options as WaterfallOptions).waterfall.includes(waterfallName)
            ) {
              visibleChildren =
                (options as WaterfallOptions).then.children ?? undefined
            } else if (
              (options as WaterfallOptions).else?.children !== undefined &&
              !(options as WaterfallOptions).waterfall.includes(waterfallName)
            ) {
              visibleChildren =
                (options as WaterfallOptions).else.children ?? undefined
            }
          }
        }
        if (visibleChildren !== undefined) {
          visibleDecomposition.visibleChildren = visibleChildren
          // if ((trunk || depth < 1) && !decomposition.open) {
          if (trunk && !decomposition.open) {
            decomposition.open = true
          }
        } else if (decomposition.open) {
          delete decomposition.open
        }
    
        let childrenDepth = depth
        if (!trunk) {
          visibleDecompositionIndex = visibleDecompositions.length
          visibleDecompositions.push(visibleDecomposition)
          childrenDepth = depth + 1
        }
        if (decomposition.open && visibleChildren !== undefined) {
          const beforeChildrenVisibleDecompositionLength =
            visibleDecompositions.length
          for (const childReference of visibleChildren) {
            buildVisibleDecompositionsWithoutValues1(
              decompositionByName,
              variableSummaryByName,
              waterfallName,
              childReference.name,
              childrenDepth,
              Boolean(negate ? !childReference.negate : childReference.negate),
              trunk &&
                visibleDecompositions.length ===
                  beforeChildrenVisibleDecompositionLength,
              deltaSumsPreviousByCalculationName,
              visibleDecompositions,
              year,
            )
          }
        }
        if (trunk) {
          visibleDecompositionIndex = visibleDecompositions.length
          visibleDecompositions.push(visibleDecomposition)
        }
    
        const variable = variableSummaryByName[name]
        if (variable !== undefined) {
          visibleDecomposition.variable = variable
        }
      }
    
      return visibleDecompositionIndex
    }
    
    function extractLinkedVariablesName(
      linkedVariablesName: Set<string>,
      name: string,
      variableSummaryByName: VariableByName,
    ): void {
      const variableSummary = variableSummaryByName[name]
      if (variableSummary === undefined) {
        console.warn("Unknown variable in extractLinkedVariablesName():", name)
      }
      for (const linkedVariableName of variableSummary?.linked_added_variables ??
        []) {
        if (!linkedVariablesName.has(linkedVariableName)) {
          linkedVariablesName.add(linkedVariableName)
          extractLinkedVariablesName(
            linkedVariablesName,
            linkedVariableName,
            variableSummaryByName,
          )
        }
      }
      for (const linkedVariableName of variableSummary?.linked_other_variables ??
        []) {
        if (!linkedVariablesName.has(linkedVariableName)) {
          linkedVariablesName.add(linkedVariableName)
          extractLinkedVariablesName(
            linkedVariablesName,
            linkedVariableName,
            variableSummaryByName,
          )
        }
      }
      for (const linkedVariableName of variableSummary?.linked_output_variables ??
        []) {
        if (!linkedVariablesName.has(linkedVariableName)) {
          linkedVariablesName.add(linkedVariableName)
          extractLinkedVariablesName(
            linkedVariablesName,
            linkedVariableName,
            variableSummaryByName,
          )
        }
      }
    }
    
    function extractWithLinkedVariableNames(
      decompositionCoreByName: DecompositionCoreByName,
      variableSummaryByName: VariableByName,
      waterfalls: Waterfall[],
    ) {
      const linkedVariableNames = new Set<string>()
      const variableNames: string[] = []
      for (const { name: waterfallName, root } of waterfalls) {
        for (const [name] of walkDecompositionsCore(
          decompositionCoreByName,
          waterfallName,
          root,
          true,
        )) {
          // Note: Duplicates are removed from variableNames, because a
          // variable name may appear more than once in a decomposition.
          if (!variableNames.includes(name)) {
            variableNames.push(name)
            const variableSummary = variableSummaryByName[name]
            for (const linkedVariableName of variableSummary?.linked_added_variables ??
              []) {
              linkedVariableNames.add(linkedVariableName)
            }
            for (const linkedVariableName of variableSummary?.linked_other_variables ??
              []) {
              linkedVariableNames.add(linkedVariableName)
            }
            for (const linkedVariableName of variableSummary?.linked_output_variables ??
              []) {
              linkedVariableNames.add(linkedVariableName)
            }
          }
        }
      }
    
      // Add linked variables.
      for (const linkedVariableName of linkedVariableNames) {
        extractLinkedVariablesName(
          linkedVariableNames,
          linkedVariableName,
          variableSummaryByName,
        )
      }
      for (const linkedVariableName of linkedVariableNames) {
        if (!variableNames.includes(linkedVariableName)) {
          variableNames.push(linkedVariableName)
        }
      }
    
      return variableNames
    }
    
    function extractNonVirtualVariablesName(
      decompositionCoreByName: DecompositionCoreByName,
      variableSummaryByName: VariableByName,
      waterfalls: Waterfall[],
    ): string[] {
      const linkedVariablesName = new Set<string>()
      const nonVirtualVariablesName: string[] = []
      for (const { name: waterfallName, root } of waterfalls) {
        for (const [name, decomposition] of walkDecompositionsCore(
          decompositionCoreByName,
          waterfallName,
          root,
          true,
        )) {
          // Note: Duplicates are removed from nonVirtualVariablesName, because a variable name
          // may appear more than once in decomposition.
          if (!decomposition.virtual && !nonVirtualVariablesName.includes(name)) {
            nonVirtualVariablesName.push(name)
            const variableSummary = variableSummaryByName[name]
            for (const linkedVariableName of variableSummary.linked_added_variables ??
              []) {
              linkedVariablesName.add(linkedVariableName)
            }
            for (const linkedVariableName of variableSummary.linked_other_variables ??
              []) {
              linkedVariablesName.add(linkedVariableName)
            }
            for (const linkedVariableName of variableSummary.linked_output_variables ??
              []) {
              linkedVariablesName.add(linkedVariableName)
            }
          }
        }
      }
    
      // Add linked variables.
      for (const linkedVariableName of linkedVariablesName) {
        extractLinkedVariablesName(
          linkedVariablesName,
          linkedVariableName,
          variableSummaryByName,
        )
      }
      for (const linkedVariableName of linkedVariablesName) {
        if (!nonVirtualVariablesName.includes(linkedVariableName)) {
          nonVirtualVariablesName.push(linkedVariableName)
        }
      }
    
      return nonVirtualVariablesName
    }
    
    // export function* iterDecompositionAncestorsName(
    //   decompositionByName: DecompositionByName,
    //   name: string | undefined | null,
    // ): Generator<string, void, unknown> {
    //   if (name == null) {
    //     return
    //   }
    //   const decomposition = decompositionByName[name]
    //   if (decomposition === undefined) {
    //     return
    //   }
    //   yield* iterDecompositionAncestorsName(
    //     decompositionByName,
    //     decomposition.parentName,
    //   )
    //   yield name
    // }
    
    // export function* iterDecompositionChildren(
    //   decompositionByName: DecompositionByName,
    //   decomposition: Decomposition,
    // ): Generator<Decomposition, void, unknown> {
    //   for (const childReference of decomposition.children ?? []) {
    //     const child = decompositionByName[childReference.name]
    //     if (child !== undefined) {
    //       yield child
    //     }
    //   }
    // }
    
    function patchDecompositionCoreByName(
      decompositionCoreByName: DecompositionCoreByName,
      patch: { [name: string]: DecompositionCore | null },
    ): DecompositionCoreByName {
      if (Object.keys(patch).length === 0) {
        return decompositionCoreByName
      }
      const patchedDecompositionCoreByName = { ...decompositionCoreByName }
      for (const [name, decompositionCorePatch] of Object.entries(patch)) {
        if (decompositionCorePatch === null) {
          // This case should not occur, because a reform should always
          // have all the decompositions of the original tax-benefit system.
          delete patchedDecompositionCoreByName[name]
        } else {
          patchedDecompositionCoreByName[name] = decompositionCorePatch
        }
      }
      return patchedDecompositionCoreByName
    }
    
    export function updateEvaluations(
      decompositionByName: DecompositionByName,
      evaluationByName: EvaluationByName,
      vectorIndex: number,
      vectorLength: number,
      waterfalls: Waterfall[],
    ): EvaluationByName {
      const newEvaluationByName = { ...evaluationByName }
      const updatedNames = new Set<string>()
      for (const { root } of waterfalls) {
        updateEvaluations1(
          decompositionByName,
          evaluationByName,
          root,
          newEvaluationByName,
          updatedNames,
          vectorIndex,
          vectorLength,
        )
      }
      return newEvaluationByName
    }
    
    function updateEvaluations1(
      decompositionByName: DecompositionByName,
      evaluationByName: EvaluationByName,
      name: string,
      newEvaluationByName: EvaluationByName,
      updatedNames: Set<string>,
      vectorIndex: number,
      vectorLength: number,
    ): Evaluation | undefined {
      if (updatedNames.has(name)) {
        return newEvaluationByName[name]
      }
      updatedNames.add(name)
    
      const decomposition = decompositionByName[name]
      if (decomposition === undefined) {
        return undefined
      }
    
      // Note: Don't use children defined in decompositions options here,
      // because those children are used for rendering, not to calculate
      // from OpenFisca variables.
      const children = decomposition.children
      const evaluation = evaluationByName[name]
      const newCalculationEvaluationByName: Partial<{
        [calculationName in CalculationName]: Partial<CalculationEvaluation>
      }> = {}
      if (children !== undefined) {
        for (const childReference of children) {
          const childEvaluation = updateEvaluations1(
            decompositionByName,
            evaluationByName,
            childReference.name,
            newEvaluationByName,
            updatedNames,
            vectorIndex,
            vectorLength,
          )
          // When variable is not (yet?) computed by OpenFisca, compute its delta
          // from the delta of its children.
          if (!evaluation?.fromOpenFisca && childEvaluation !== undefined) {
            for (const [
              calculationName,
              childCalculationEvaluation,
            ] of Object.entries(childEvaluation.calculationEvaluationByName)) {
              let newCalculationEvaluation =
                newCalculationEvaluationByName[calculationName]
              if (newCalculationEvaluation === undefined) {
                newCalculationEvaluation = newCalculationEvaluationByName[
                  calculationName
                ] = {
                  delta: new Array(vectorLength).fill(0),
                }
              }
              const delta = newCalculationEvaluation.delta
              if (childReference.negate) {
                delta.map(
                  (_deltaAtIndex, index) =>
                    (delta[index] -= childCalculationEvaluation.delta[index]),
                )
              } else {
                delta.map(
                  (_deltaAtIndex, index) =>
                    (delta[index] += childCalculationEvaluation.delta[index]),
                )
              }
            }
          }
        }
      }
    
      // If variable is computed by OpenFisca, recopy (and rescale if needed) its
      // computed delta
      if (evaluation?.fromOpenFisca) {
        for (const [calculationName, deltaEvaluation] of Object.entries(
          evaluation.calculationEvaluationByName,
        )) {
          newCalculationEvaluationByName[calculationName] = {
            delta:
              deltaEvaluation.delta.length === vectorLength
                ? deltaEvaluation.delta
                : new Array(vectorLength).fill(deltaEvaluation.deltaAtVectorIndex),
          }
        }
      }
    
      // Extract deltaAtIndex from delta.
      for (const [calculationName, newCalculationEvaluation] of Object.entries(
        newCalculationEvaluationByName,
      )) {
        const oldCalculationEvaluation =
          evaluation?.calculationEvaluationByName[calculationName]
        newCalculationEvaluation.deltaAtVectorIndex =
          vectorIndex < newCalculationEvaluation.delta.length
            ? newCalculationEvaluation.delta[vectorIndex]
            : oldCalculationEvaluation === undefined
              ? 0
              : oldCalculationEvaluation.deltaAtVectorIndex
      }
    
      const newEvaluation = {
        ...(evaluation ?? {}),
        calculationEvaluationByName:
          newCalculationEvaluationByName as CalculationEvaluationByName,
      }
      newEvaluationByName[name] = newEvaluation
      return newEvaluation
    }
    
    export function updateEvaluationsVectorIndex(
      evaluationByName: EvaluationByName,
      vectorIndex: number,
    ): EvaluationByName {
      let changed = false
      const newEvaluationByName: EvaluationByName = {}
      for (const [name, evaluation] of Object.entries(evaluationByName)) {
        let evaluationChanged = false
        const newCalculationEvaluationByName = {
          ...evaluation.calculationEvaluationByName,
        }
        for (const [
          calculationName,
          { delta, deltaAtVectorIndex },
        ] of Object.entries(newCalculationEvaluationByName)) {
          if (vectorIndex < delta.length) {
            const newDeltaAtVectorIndex = delta[vectorIndex]
            if (newDeltaAtVectorIndex != deltaAtVectorIndex) {
              newCalculationEvaluationByName[calculationName] = {
                delta,
                deltaAtVectorIndex: newDeltaAtVectorIndex,
              }
              evaluationChanged = true
            }
          }
        }
        newEvaluationByName[name] = evaluationChanged
          ? {
              ...evaluation,
              calculationEvaluationByName: newCalculationEvaluationByName,
            }
          : evaluation
        if (evaluationChanged) {
          changed = true
        }
      }
      return changed ? newEvaluationByName : evaluationByName
    }
    
    // export function* walkDecompositions(
    //   decompositionByName: DecompositionByName,
    //   name: string,
    //   depthFirst: boolean,
    //   openOnly: boolean,
    // ): Generator<Decomposition, void, unknown> {
    //   const decomposition = decompositionByName[name]
    //   if (decomposition === undefined) {
    //     return
    //   }
    //   if (!depthFirst) {
    //     yield decomposition
    //   }
    //   if (
    //     decomposition.children !== undefined &&
    //     (decomposition.open || !openOnly)
    //   ) {
    //     for (const childReference of decomposition.children) {
    //       yield* walkDecompositions(
    //         decompositionByName,
    //         childReference.name,
    //         depthFirst,
    //         openOnly,
    //       )
    //     }
    //   }
    //   if (depthFirst) {
    //     yield decomposition
    //   }
    // }
    
    export function* walkDecompositionsCore(
      decompositionCoreByName: DecompositionCoreByName,
      waterfallName: string,
      name: string,
      depthFirst: boolean,
    ): Generator<[string, DecompositionCore], void, unknown> {
      const decompositionCore = decompositionCoreByName[name]
      if (decompositionCore === undefined) {
        return
      }
      if (!depthFirst) {
        yield [name, decompositionCore]
      }
    
      let children = decompositionCore.children
      for (const options of decompositionCore.options ?? []) {
        if (options.waterfall !== undefined) {
          if (
            (options as WaterfallOptions).then?.children !== undefined &&
            (options as WaterfallOptions).waterfall.includes(waterfallName)
          ) {
            children = (options as WaterfallOptions).then.children ?? undefined
          } else if (
            (options as WaterfallOptions).else?.children !== undefined &&
            !(options as WaterfallOptions).waterfall.includes(waterfallName)
          ) {
            children = (options as WaterfallOptions).else.children ?? undefined
          }
        }
      }
      if (children !== undefined) {
        for (const childReference of children) {
          yield* walkDecompositionsCore(
            decompositionCoreByName,
            waterfallName,
            childReference.name,
            depthFirst,
          )
        }
      }
    
      if (depthFirst) {
        yield [name, decompositionCore]
      }
    }
    
    export function* walkDecompositionsCoreName(
      decompositionCoreByName: DecompositionCoreByName,
      waterfallName: string,
      name: string,
      depthFirst: boolean,
    ): Generator<string, void, unknown> {
      for (const [decompositionName] of walkDecompositionsCore(
        decompositionCoreByName,
        waterfallName,
        name,
        depthFirst,
      )) {
        yield decompositionName
      }
    }