Skip to content
Snippets Groups Projects
Select Git revision
  • 95f505cdc0dbbba28c9295b411bca86e22dcadf7
  • master default protected
  • 366-signe-a-cote-du-droit-en-vigueur-sur-l-ui-pour-indiquer-que-la-reforme-a-eu-lieu-mais-qu-elle-n
  • revalo_retraites
  • 381-pb-affichage-labels-des-parametres-sur-plus-de-3-lignes
  • ajoute-duplicate-aide-logement
  • poc_castype_ia
  • parametres-editables-budget
  • ui-parametres
  • 355-les-dispositifs-prestations-sociales-du-graphique-se-cachent-montrent-en-meme-temps-2
  • 358-les-variables-dont-le-montant-est-nul-apparaissent-en-bleu-et-non-cliquables
  • 356-ajuster-la-largeur-sur-les-graphiques-budgetaires
  • incoherence_cas_type_0
  • fix-ui-suppression-tranches-baremes
  • ajout-agregat-cehr-version-plf
  • impact_carbone
  • xlsx
  • header_revamp
  • 270-concevoir-la-page-d-accueil-leximpact
  • 219-conversion-des-montants-min-et-max-de-l-axe-des-x-en-smic
  • 294-afficher-le-salaire-des-cas-types-en-nombre-de-smic
  • 0.0.1174
  • 0.0.1173
  • 0.0.1172
  • 0.0.1171
  • 0.0.1170
  • 0.0.1169
  • 0.0.1168
  • 0.0.1167
  • 0.0.1166
  • 0.0.1165
  • 0.0.1164
  • 0.0.1163
  • 0.0.1162
  • 0.0.1161
  • 0.0.1160
  • 0.0.1159
  • 0.0.1158
  • 0.0.1157
  • 0.0.1156
  • 0.0.1155
41 results

decompositions.ts

Blame
  • decompositions.ts 24.88 KiB
    import decompositionCoreByNameUnknown from "@openfisca/france-json/decompositions.json"
    import waterfallsUnknown from "@openfisca/france-json/waterfalls.json"
    export type {
      Decomposition as DecompositionCore,
      DecompositionByName as DecompositionCoreByName,
    } from "@openfisca/json-model"
    import type {
      Decomposition as DecompositionCore,
      DecompositionByName as DecompositionCoreByName,
      DecompositionReference,
      EntityByKey,
      Variable,
      VariableByName,
      Waterfall,
      WaterfallOptions,
    } from "@openfisca/json-model"
    
    import type { CalculationName } from "$lib/calculations"
    import { reformChangesByName } from "$lib/reforms"
    import type { Situation } from "$lib/situations"
    
    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 {
      decomposition: Decomposition
      depth: number
      rows: VisibleRow[]
      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[]
      visibleEvaluationByCalculationName: VisibleEvaluationByCalculationName
    }
    
    export interface VisibleEvaluation {
      delta: number[]
      deltaAtVectorIndex: number
      deltaSums: [number, number][]
      deltaSumsAtVectorIndex: [number, number]
    }
    
    export type VisibleEvaluationByCalculationName = Partial<{
      [name in CalculationName]: VisibleEvaluation
    }>
    
    export interface VisibleRow {
      calculationName: CalculationName
      deltaAtVectorIndex: number
      deltaSumsAtVectorIndex: [number, number]
    }
    
    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
    
    // Note: Duplicates are removed from nonVirtualDecompositionsName, because a variable name
    // may appear more than once in decomposition.
    export const nonVirtualDecompositionsName = extractNonVirtualDecompositionsName(
      decompositionCoreByName,
      waterfalls,
    )
    export const nonVirtualDecompositionsNameByReformName: {
      [name: string]: string[]
    } = Object.fromEntries(
      Object.entries(decompositionCoreByNameByReformName).map(
        ([reformName, reformDecompositionCoreByName]) => [
          reformName,
          extractNonVirtualDecompositionsName(
            reformDecompositionCoreByName,
            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,
      vectorLength: number,
      year: number,
    ): VisibleDecomposition[] {
      const visibleDecompositions: VisibleDecomposition[] = []
      buildVisibleDecompositions1(
        decompositionByName,
        entityByKey,
        evaluationByName,
        situation,
        variableSummaryByName,
        waterfall.name,
        waterfall.root,
        showNulls,
        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,
      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) {
          let beforeChildrenVisibleDecompositionLength =
            visibleDecompositions.length
          let childDeltaSumsPreviousByCalculationName =
            deltaSumsPreviousByCalculationName
          for (const [childIndex, childReference] of visibleChildren.entries()) {
            const childVisibleDecompositionIndex = buildVisibleDecompositions1(
              decompositionByName,
              entityByKey,
              evaluationByName,
              situation,
              variableSummaryByName,
              waterfallName,
              childReference.name,
              showNulls,
              vectorLength,
              childrenDepth,
              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] ??
            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 = (visibleDecomposition.rows = [])
    
        const lawVisibleEvaluation = visibleEvaluationByCalculationName.law
        if (lawVisibleEvaluation !== undefined) {
          const lawRow: VisibleRow = {
            calculationName: "law",
            deltaAtVectorIndex: lawVisibleEvaluation.deltaAtVectorIndex,
            deltaSumsAtVectorIndex: lawVisibleEvaluation.deltaSumsAtVectorIndex,
          }
          rows.push(lawRow)
    
          const billVisibleEvaluation = visibleEvaluationByCalculationName.bill
          let previousRow: VisibleRow
          let previousVisibleEvaluation: VisibleEvaluation
          if (billVisibleEvaluation === undefined) {
            previousRow = lawRow
            previousVisibleEvaluation = lawVisibleEvaluation
          } else {
            if (
              billVisibleEvaluation.deltaAtVectorIndex ===
              lawVisibleEvaluation.deltaAtVectorIndex
            ) {
              lawRow.deltaSumsAtVectorIndex =
                billVisibleEvaluation.deltaSumsAtVectorIndex
              previousRow = lawRow
            } else {
              lawRow.deltaSumsAtVectorIndex = [
                billVisibleEvaluation.deltaSumsAtVectorIndex[0],
                billVisibleEvaluation.deltaSumsAtVectorIndex[0] +
                  lawVisibleEvaluation.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 + 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
    }
    
    function extractNonVirtualDecompositionsName(
      decompositionCoreByName: DecompositionCoreByName,
      waterfalls: Waterfall[],
    ): string[] {
      const nonVirtualDecompositionsName: string[] = []
      for (const { name: waterfallName, root } of waterfalls) {
        for (const [name, decomposition] of walkDecompositionsCore(
          decompositionCoreByName,
          waterfallName,
          root,
          true,
        )) {
          if (
            !decomposition.virtual &&
            !nonVirtualDecompositionsName.includes(name)
          ) {
            nonVirtualDecompositionsName.push(name)
          }
        }
      }
      return nonVirtualDecompositionsName
    }
    
    // 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
      }
    }