Skip to content
Snippets Groups Projects
Select Git revision
  • ebdeec274f0b35583f5a3426aa3fcd1859a9b9d8
  • 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

calculations.svelte.ts

Blame
  • calculations.svelte.ts 31.64 KiB
    import {
      getRolePersonsIdKey,
      type GroupEntity,
      type PopulationWithoutId,
    } from "@openfisca/json-model"
    import { v4 as uuidV4 } from "uuid"
    
    import {
      type Evaluation,
      type EvaluationByName,
      nonVirtualVariablesName,
      nonVirtualVariablesNameByReformName,
      updateEvaluations,
      waterfalls,
    } from "$lib/decompositions"
    import { entityByKey } from "$lib/entities"
    import { metadata } from "$lib/metadata"
    import type { ParametricReform } from "$lib/reforms"
    import {
      billActive,
      billName,
      revaluationName,
      shared,
      year,
      yearPLF,
    } from "$lib/shared.svelte"
    import {
      type Axis,
      type Calculation,
      type CalculationByName,
      getPopulationReservedKeys,
      type Situation,
      type SituationWithAxes,
      type TestCasesCalculationInput,
    } from "$lib/situations"
    import {
      budgetEditableParametersName,
      type BudgetVariable,
      budgetVariableNameByVariableName,
      budgetVariablesConfig,
      budgetVariablesName,
      otherCalculatedVariablesName,
      summaryCalculatedVariablesName,
      variableSummaryByName,
      variableSummaryByNameByReformName,
      type VariableValue,
      type VariableValueByCalculationName,
      type VariableValues,
    } from "$lib/variables"
    
    export type CalculationName = "amendment" | "bill" | "law" | "revaluation"
    
    export const calculationNames: CalculationName[] = [
      "law",
      "revaluation",
      "bill",
      "amendment",
    ]
    
    export interface RequestedCalculations {
      budgetCalculationNames?: Set<CalculationName>
      budgetVariableName?: string
      situationIndexByCalculationName: RequestedSituationIndexByCalculationName
    }
    
    export type RequestedSituationIndexByCalculationName = Partial<{
      [calculationName in CalculationName]: number | null // `null` means "every test cases"
    }>
    
    export const requestedCalculations = $state({
      situationIndexByCalculationName: {},
    }) as RequestedCalculations
    
    let budgetSimulationAbortController = new AbortController()
    
    const testCasesAbortControllers: {
      [calculationName: string]: AbortController
    } = Object.fromEntries(
      calculationNames.map((calculationName) => [
        calculationName,
        new AbortController(),
      ]),
    )
    
    export function buildBudgetDemandBody(
      variableName: string,
      outputVariables: string[],
      quantileBaseVariable: string[],
      quantileCompareVariables: string[],
      includeDisplay = true,
    ) {
      const budgetParametricReform = Object.fromEntries(
        Object.entries(shared.parametricReform).filter(([parameterName]) =>
          budgetEditableParametersName.has(parameterName),
        ),
      )
      const metadataLite = { ...metadata }
      metadataLite.distributions = {}
      return {
        amendement: budgetParametricReform,
        base: yearPLF,
        displayMode: includeDisplay ? shared.displayMode : undefined,
        metadata: metadataLite,
        output_variables: outputVariables,
        quantile_base_variable: quantileBaseVariable,
        quantile_compare_variables: quantileCompareVariables,
        winners_loosers_variable: variableName,
        quantile_nb: 10,
        plf: billName === undefined ? undefined : yearPLF,
      }
    }
    
    export async function calculateBudget(
      budgetVariableName: string,
      // budgetCalculationNames: Set<CalculationName>,
    ): Promise<void> {
      budgetSimulationAbortController.abort()
      budgetSimulationAbortController = new AbortController()
      if (!budgetVariablesName.has(budgetVariableName)) {
        console.error(
          `Budget calculation for variable ${budgetVariableName} is not available`,
        )
        shared.budgetSimulation = undefined
        return
      }
      const variableName = budgetVariableNameByVariableName[budgetVariableName]
      const variableConfig: BudgetVariable = budgetVariablesConfig[variableName]
      const body = JSON.stringify(
        buildBudgetDemandBody(
          variableName,
          variableConfig.outputVariables,
          variableConfig.quantileBaseVariable,
          variableConfig.quantileCompareVariables,
        ),
      )
      shared.budgetSimulation = undefined
      const response = await fetch("/budgets", {
        body,
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json; charset=utf-8",
        },
        method: "POST",
        signal: budgetSimulationAbortController.signal,
      })
      if (!response.ok) {
        console.error(
          `"/budgets": Error while calculating budget:\n${body}\n${response.status} ${response.statusText}`,
        )
        console.error(await response.text())
        shared.budgetSimulation = {
          errors: [
            `Une erreur inattendue (${response.status} ${response.statusText}) s'est produite et les impacts budgétaires ne sont pas disponibles pour le moment. Écrivez-nous à leximpact@assemblee-nationale.fr.`,
          ],
          isPublic: true,
        }
        shared.budgetSimulation = undefined
        return
      }
      shared.budgetSimulation = await response.json()
    }
    
    export function calculateTestCases(
      situationIndexByCalculationName: RequestedSituationIndexByCalculationName,
    ) {
      const aggregatedSituation: SituationWithAxes = {}
      const axesData: { situationIndex?: number; variables: string[] } =
        shared.axes.reduce(
          (
            result: { situationIndex?: number; variables: string[] },
            parallelAxes,
          ) => {
            for (const axis of parallelAxes) {
              result.situationIndex = axis.situationIndex
              if (result.variables.includes(axis.name)) {
                continue
              }
              result.variables.push(axis.name)
            }
            return result
          },
          { variables: [] },
        )
      const entities = Object.values(entityByKey)
      if (
        Object.values(situationIndexByCalculationName).some(
          (situationIndex) => situationIndex === null,
        )
      ) {
        let personIndex = -1
        // Some of the calculations are requested to be done for every situations.
        // Aggregate every situations into a single one without calculated variables.
        for (const [situationIndex, situation] of shared.testCases.entries()) {
          const inputInstantsByVariableName =
            shared.inputInstantsByVariableNameArray[situationIndex]
          const situationPrefix = `Cas type n°${situationIndex + 1} | `
          for (const entity of entities) {
            const entitySituation = situation[entity.key_plural as string]
            if (entitySituation === undefined) {
              continue
            }
            let aggregatedEntitySituation =
              aggregatedSituation[entity.key_plural as string]
            if (aggregatedEntitySituation === undefined) {
              aggregatedEntitySituation = aggregatedSituation[
                entity.key_plural as string
              ] = {}
            }
            const reservedKeys = getPopulationReservedKeys(entity)
            for (const [populationId, population] of Object.entries(
              entitySituation,
            ).sort(([populationId1], [populationId2]) =>
              populationId1.localeCompare(populationId2),
            )) {
              const transformedPopulation: PopulationWithoutId = {}
              if (!entity.is_person) {
                for (const role of (entity as GroupEntity).roles) {
                  const personsIdKey = getRolePersonsIdKey(role)
                  const personsId = population[personsIdKey] as string[] | undefined
                  if (personsId === undefined) {
                    continue
                  }
                  transformedPopulation[personsIdKey] = personsId.map(
                    (personId) => situationPrefix + personId,
                  )
                }
              } else {
                personIndex++
              }
              for (const [variableName, variableValueByInstant] of Object.entries(
                population,
              )) {
                if (
                  reservedKeys.has(variableName) ||
                  (entity.is_person &&
                    axesData.situationIndex === personIndex &&
                    axesData.variables.includes(variableName))
                ) {
                  continue
                }
                const inputVariableValueByInstant: {
                  [instant: string]: VariableValue | null
                } = {}
                const inputInstants =
                  inputInstantsByVariableName[variableName] ?? new Set<string>()
                for (const [instant, variableValue] of Object.entries(
                  variableValueByInstant as {
                    [instant: string]: VariableValue | null
                  },
                )) {
                  if (!inputInstants.has(instant)) {
                    // Ignore calculated value.
                    continue
                  }
                  inputVariableValueByInstant[instant] = variableValue
                }
                if (Object.keys(inputVariableValueByInstant).length > 0) {
                  transformedPopulation[variableName] = inputVariableValueByInstant
                }
              }
              aggregatedEntitySituation[situationPrefix + populationId] =
                transformedPopulation
            }
          }
        }
    
        if (shared.axes.length > 0) {
          aggregatedSituation.axes = shared.axes
        }
      }
    
      const message = {
        period: year.toString(),
      }
      const newCalculationByName: CalculationByName = {}
    
      const lawSituationIndex = situationIndexByCalculationName.law
      if (lawSituationIndex !== undefined) {
        const calculation: Calculation = (newCalculationByName.law = {
          running: true,
        })
        let situation: SituationWithAxes
        if (lawSituationIndex === null) {
          situation = aggregatedSituation
        } else {
          calculation.situationIndex = lawSituationIndex
          situation = cleanSituation(
            shared.testCases[lawSituationIndex],
            shared.axes,
          )
        }
        calculation.input = {
          ...message,
          situation,
          variables: [
            ...summaryCalculatedVariablesName,
            ...otherCalculatedVariablesName,
            ...nonVirtualVariablesName,
          ],
        }
        sendTestCasesSimulation("law", calculation.input) // Don't wait for result.
      }
    
      const revaluationSituationIndex = situationIndexByCalculationName.revaluation
      if (
        revaluationSituationIndex !== undefined &&
        revaluationName !== undefined
      ) {
        const calculation: Calculation = (newCalculationByName.revaluation = {
          running: true,
        })
        let situation: SituationWithAxes
        if (revaluationSituationIndex === null) {
          situation = aggregatedSituation
        } else {
          calculation.situationIndex = revaluationSituationIndex
          situation = cleanSituation(
            shared.testCases[revaluationSituationIndex],
            shared.axes,
          )
        }
        calculation.input = {
          ...message,
          reform: revaluationName,
          situation,
          variables: [
            ...summaryCalculatedVariablesName,
            ...otherCalculatedVariablesName,
            ...(nonVirtualVariablesNameByReformName[revaluationName] ??
              nonVirtualVariablesName),
          ],
        }
        sendTestCasesSimulation("revaluation", calculation.input) // Don't wait for result.
      }
    
      const billSituationIndex = situationIndexByCalculationName.bill
      if (billSituationIndex !== undefined && billActive) {
        const calculation: Calculation = (newCalculationByName.bill = {
          running: true,
        })
        let situation: SituationWithAxes
        if (billSituationIndex === null) {
          situation = aggregatedSituation
        } else {
          calculation.situationIndex = billSituationIndex
          situation = cleanSituation(
            shared.testCases[billSituationIndex],
            shared.axes,
          )
        }
        calculation.input = {
          ...message,
          reform: billName,
          situation,
          variables: [
            ...summaryCalculatedVariablesName,
            ...otherCalculatedVariablesName,
            ...(nonVirtualVariablesNameByReformName[billName] ??
              nonVirtualVariablesName),
          ],
        }
        sendTestCasesSimulation("bill", calculation.input) // Don't wait for result.
      }
    
      const amendmentSituationIndex = situationIndexByCalculationName.amendment
      if (amendmentSituationIndex !== undefined) {
        if (Object.keys(shared.parametricReform).length === 0) {
          // Remove amendment evaluations from decompositions.
          shared.evaluationByNameArray = shared.evaluationByNameArray.map(
            (evaluationByName, situationIndex): EvaluationByName => {
              const updatedEvaluationByName = Object.fromEntries(
                Object.entries(evaluationByName).map(
                  ([variableName, evaluation]) => {
                    const calculationEvaluationByName = {
                      ...evaluation.calculationEvaluationByName,
                    }
                    delete calculationEvaluationByName["amendment"]
                    return [
                      variableName,
                      { ...evaluation, calculationEvaluationByName },
                    ]
                  },
                ),
              )
              return updateEvaluations(
                shared.decompositionByName,
                updatedEvaluationByName,
                shared.testCases[situationIndex].slider?.vectorIndex ?? 0,
                shared.vectorLength,
                waterfalls,
              )
            },
          )
    
          // Remove amendment values.
          shared.valuesByCalculationNameByVariableNameArray =
            shared.valuesByCalculationNameByVariableNameArray.map(
              (valuesByCalculationNameByVariableName) =>
                Object.fromEntries(
                  Object.entries(valuesByCalculationNameByVariableName).map(
                    ([variableName, valuesByCalculationName]) => {
                      const updatedValuesByCalculationName = {
                        ...valuesByCalculationName,
                      }
                      delete updatedValuesByCalculationName["amendment"]
                      return [variableName, updatedValuesByCalculationName]
                    },
                  ),
                ),
            )
        } else {
          const calculation: Calculation = (newCalculationByName.amendment = {
            running: true,
          })
          let situation: SituationWithAxes
          if (amendmentSituationIndex === null) {
            situation = aggregatedSituation
          } else {
            calculation.situationIndex = amendmentSituationIndex
            situation = cleanSituation(
              shared.testCases[amendmentSituationIndex],
              shared.axes,
            )
          }
          calculation.input = {
            ...message,
            parametric_reform: shared.parametricReform,
            reform: billName ?? revaluationName, // Will be removed when billName and revaluationName are undefined.
            situation,
            variables: [
              ...summaryCalculatedVariablesName,
              ...otherCalculatedVariablesName,
              ...(nonVirtualVariablesNameByReformName[billName as string] ??
                nonVirtualVariablesNameByReformName[revaluationName as string] ??
                nonVirtualVariablesName),
            ],
          }
          sendTestCasesSimulation("amendment", calculation.input) // Don't wait for result.
        }
      }
    
      shared.calculationByName = newCalculationByName
    }
    
    export function calculateTestCasesAdditionalVariables(
      additionalVariablesName: Set<string>,
    ): void {
      const newCalculationByName = { ...shared.calculationByName }
      for (const [calculationName, calculation] of Object.entries(
        newCalculationByName,
      )) {
        if (calculation.input === undefined) {
          continue
        }
    
        let variablesAdded = false
        const variablesName = [...calculation.input.variables]
        for (const variableName of additionalVariablesName) {
          if (!variablesName.includes(variableName)) {
            variablesName.push(variableName)
            variablesAdded = true
          }
        }
    
        if (variablesAdded) {
          const input = { ...calculation.input, variables: variablesName }
          newCalculationByName[calculationName as CalculationName] = {
            ...calculation,
            input,
            running: true,
          }
          sendTestCasesSimulation(calculationName as CalculationName, input) // Don't wait for result.
        }
      }
      shared.calculationByName = newCalculationByName
    }
    
    function cleanSituation(
      situationToClean: Situation,
      axes: Axis[][],
    ): SituationWithAxes {
      const situation: SituationWithAxes = {}
      for (const entity of Object.values(entityByKey)) {
        const entitySituation = situationToClean[entity.key_plural as string]
        if (entitySituation === undefined) {
          continue
        }
        situation[entity.key_plural as string] = entitySituation
      }
      if (axes.length > 0) {
        situation.axes = axes.map((parallelAxes) =>
          parallelAxes.map((axis) => ({
            ...axis,
            index: (axis.index as number) - (axis.situationIndex as number),
            situationIndex: 0,
          })),
        )
      }
      return situation
    }
    
    export function isNullVariableValueByCalculationName(
      variableValueByCalculationName: VariableValueByCalculationName,
    ): boolean {
      return Object.values(variableValueByCalculationName).every(
        (variableValue) => variableValue === undefined || variableValue === 0,
      )
    }
    
    export function requestAllBudgetCalculations(variableName: string): void {
      if (
        variableName !== requestedCalculations.budgetVariableName ||
        requestedCalculations.budgetCalculationNames === undefined ||
        requestedCalculations.budgetCalculationNames.size < calculationNames.length
      ) {
        requestedCalculations.budgetCalculationNames = new Set(calculationNames)
        requestedCalculations.budgetVariableName = variableName
      }
    }
    
    export function requestAllTestCasesCalculations(
      requestedSituationIndex: number | null,
    ): void {
      if (
        calculationNames.some((calculationName) => {
          const situationIndex =
            requestedCalculations.situationIndexByCalculationName[calculationName]
          return (
            situationIndex !== null && situationIndex !== requestedSituationIndex
          )
        })
      ) {
        calculateTestCases(
          Object.fromEntries(
            calculationNames.map((calculationName) => {
              const situationIndex =
                requestedCalculations.situationIndexByCalculationName[
                  calculationName
                ]
              return [
                calculationName,
                situationIndex === null ? null : requestedSituationIndex,
              ]
            }),
          ),
        )
      }
    }
    
    export function requestBudgetCalculation(
      variableName: string,
      calculationName: CalculationName,
    ): void {
      const budgetCalculationNames =
        variableName === requestedCalculations.budgetVariableName
          ? (requestedCalculations.budgetCalculationNames ??
            new Set<CalculationName>())
          : new Set<CalculationName>()
      if (!budgetCalculationNames.has(calculationName)) {
        requestedCalculations.budgetCalculationNames =
          budgetCalculationNames.add(calculationName)
        requestedCalculations.budgetVariableName = variableName
      }
    }
    
    export function requestTestCasesCalculation(
      calculationName: CalculationName,
      requestedSituationIndex: number | null,
    ): void {
      const situationIndex =
        requestedCalculations.situationIndexByCalculationName[calculationName]
      if (situationIndex !== null && situationIndex !== requestedSituationIndex) {
        requestedCalculations.situationIndexByCalculationName[calculationName] =
          requestedSituationIndex
      }
    }
    
    export async function sendBudgetSimulationDemand() {
      const budgetVariableName = shared.displayMode?.parametersVariableName
      if (
        budgetVariableName === undefined ||
        !budgetVariablesName.has(budgetVariableName)
      ) {
        console.error(
          `Budget calculation for variable ${budgetVariableName} is not available`,
        )
        shared.budgetSimulation = undefined
        return
      }
      const variableName = budgetVariableNameByVariableName[budgetVariableName]
      const variableConfig: BudgetVariable = budgetVariablesConfig[variableName]
      const simulationBody = buildBudgetDemandBody(
        variableName,
        variableConfig.outputVariables,
        variableConfig.quantileBaseVariable,
        variableConfig.quantileCompareVariables,
        false,
      )
      const urlString = "/budgets/demands"
      const res = await fetch(urlString, {
        body: JSON.stringify(
          {
            displayMode: shared.displayMode,
            email: shared.requestedSimulationEmail,
            request: Object.fromEntries(
              Object.entries(shared.parametricReform).filter(([parameterName]) =>
                budgetEditableParametersName.has(parameterName),
              ),
            ),
            simulation: simulationBody,
          },
          null,
          2,
        ),
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json; charset=utf-8",
        },
        method: "POST",
      })
      if (!res.ok) {
        shared.requestedSimulationEmail = undefined
        console.error(
          `Error ${
            res.status
          } while sending a simulation request at ${urlString}\n\n${await res.text()}`,
        )
        return
      }
      shared.requestedSimulationEmail = undefined
    }
    
    async function sendTestCasesSimulation(
      calculationName: CalculationName,
      {
        period,
        parametric_reform,
        reform,
        situation,
        variables,
      }: TestCasesCalculationInput,
    ) {
      const completeVariableSummaryByName =
        billName === undefined
          ? variableSummaryByName
          : variableSummaryByNameByReformName[billName]
      try {
        // Note: crypto.randomUUID() is not supported by Safari before version 15.4.
        // const token = crypto.randomUUID()
        const token = uuidV4()
        testCasesAbortControllers[calculationName].abort()
        testCasesAbortControllers[calculationName] = new AbortController()
        const response = await fetch("/test_cases", {
          body: JSON.stringify({
            period,
            parametric_reform,
            reform,
            situation,
            title: calculationName,
            token,
            variables,
          }),
          headers: {
            Accept: "application/json",
            "Content-Type": "application/json; charset=utf-8",
          },
          method: "POST",
          signal: testCasesAbortControllers[calculationName].signal,
        })
        if (!response.ok) {
          console.error(
            `Error while submitting test case simulation:\n${response.status} ${response.statusText}`,
          )
          console.error(await response.text())
          return
        }
        const { evaluations } = (await response.json()) as {
          evaluations: [
            {
              entity: string
              name: string
              value: VariableValues
            },
          ]
          token: string
        }
    
        let calculation = shared.calculationByName[calculationName] as Calculation
    
        const updatedEvaluationByNameArray =
          calculation.situationIndex === undefined
            ? shared.evaluationByNameArray
            : [...shared.evaluationByNameArray]
    
        // Use an intermediate variable to avoir multiple updates of shared.valuesByCalculationNameByVariableNameArray
        // in following loop.
        let valuesByCalculationNameByVariableNameArray =
          shared.valuesByCalculationNameByVariableNameArray
        for (const {
          entity: entityKey,
          name: variableName,
          value,
        } of evaluations) {
          // Round returned values if unit of variable is a currency.
          const variable = completeVariableSummaryByName[variableName]
          const roundedValue =
            variable.unit === "currency" || variable.unit?.startsWith("currency-")
              ? value.map((valueAtIndex) => Math.round(valueAtIndex as number))
              : value
    
          const entity = entityByKey[entityKey]
    
          if (calculation.situationIndex === undefined) {
            // Variable has been computed for all test cases.
    
            // Count total population of test cases.
            let testCasesPopulationCount = 0
            for (const situation of shared.testCases) {
              const entitySituation = situation[entity.key_plural as string] ?? {}
              const situationPopulationCount = Object.keys(entitySituation).length
              testCasesPopulationCount += situationPopulationCount
            }
    
            // Split evaluation.value vector for each situation.
            {
              let testCasesPopulationIndex = 0
              for (const [
                situationIndex,
                valuesByCalculationNameByVariableName,
              ] of valuesByCalculationNameByVariableNameArray.entries()) {
                const situation = shared.testCases[situationIndex]
                const entitySituation = situation[entity.key_plural as string] ?? {}
                const values: VariableValues = []
                for (
                  let index = testCasesPopulationIndex, vectorIndex = 0;
                  vectorIndex < shared.vectorLength;
                  index += testCasesPopulationCount, vectorIndex++
                ) {
                  for (const situationPersonIndex of Object.keys(
                    entitySituation,
                  ).keys()) {
                    values.push(roundedValue[index + situationPersonIndex])
                  }
                }
                testCasesPopulationIndex += Object.keys(entitySituation).length
                ;(valuesByCalculationNameByVariableName[variableName] ??= {})[
                  calculationName
                ] = values
              }
            }
          } else {
            // Variable has been computed for a single test case.
    
            const updatedValuesByCalculationNameByVariableNameArray = [
              ...valuesByCalculationNameByVariableNameArray,
            ]
            const valuesByCalculationNameByVariableName = {
              ...updatedValuesByCalculationNameByVariableNameArray[
                calculation.situationIndex
              ],
            }
            const valuesByCalculationName =
              valuesByCalculationNameByVariableName[variableName] ?? {}
            valuesByCalculationNameByVariableName[variableName] = {
              ...valuesByCalculationName,
              [calculationName]: roundedValue,
            }
            updatedValuesByCalculationNameByVariableNameArray[
              calculation.situationIndex
            ] = valuesByCalculationNameByVariableName
            valuesByCalculationNameByVariableNameArray =
              updatedValuesByCalculationNameByVariableNameArray
          }
    
          // Update evaluations for decompositions.
          const calculationNonVirtualVariablesName =
            calculationName === "law"
              ? nonVirtualVariablesName
              : (nonVirtualVariablesNameByReformName[billName as string] ??
                nonVirtualVariablesName)
          if (calculationNonVirtualVariablesName.includes(variableName)) {
            if (calculation.situationIndex === undefined) {
              // Variable has been computed for all test cases.
    
              // First, update delta and values of evaluations.
              for (const [
                situationIndex,
                evaluationByName,
              ] of updatedEvaluationByNameArray.entries()) {
                const situation = shared.testCases[situationIndex]
                const values = valuesByCalculationNameByVariableNameArray[
                  situationIndex
                ][variableName][calculationName] as VariableValues
                const entitySituation = situation[entity.key_plural as string] ?? {}
                const situationPopulationCount = Object.keys(entitySituation).length
                const delta = new Array(shared.vectorLength).fill(0)
                for (const situationPersonIndex of Object.keys(
                  entitySituation,
                ).keys()) {
                  for (
                    let index = situationPersonIndex, vectorIndex = 0;
                    vectorIndex < shared.vectorLength;
                    index += situationPopulationCount, vectorIndex++
                  ) {
                    delta[vectorIndex] += values[index]
                  }
                }
    
                if (evaluationByName[variableName] === undefined) {
                  evaluationByName[variableName] = {}
                }
    
                if (
                  evaluationByName[variableName].calculationEvaluationByName ===
                  undefined
                ) {
                  evaluationByName[variableName].calculationEvaluationByName = {}
                }
    
                if (
                  evaluationByName[variableName].calculationEvaluationByName[
                    calculationName
                  ] === undefined
                ) {
                  evaluationByName[variableName].calculationEvaluationByName[
                    calculationName
                  ] = {}
                }
    
                evaluationByName[variableName].calculationEvaluationByName[
                  calculationName
                ]!.delta = delta
                evaluationByName[variableName].calculationEvaluationByName[
                  calculationName
                ]!.deltaAtVectorIndex =
                  delta[situation.slider?.vectorIndex ?? 0] ?? 0
    
                evaluationByName[variableName].fromOpenFisca = true
              }
            } else {
              // Variable has been computed for a single test case.
    
              // First, update delta and values of evaluations.
              const situation = shared.testCases[calculation.situationIndex]
              const values = valuesByCalculationNameByVariableNameArray[
                calculation.situationIndex
              ][variableName][calculationName] as VariableValues
              const entitySituation = situation[entity.key_plural as string] ?? {}
              const situationPopulationCount = Object.keys(entitySituation).length
              const delta = new Array(shared.vectorLength).fill(0)
              for (const situationPersonIndex of Object.keys(
                entitySituation,
              ).keys()) {
                for (
                  let index = situationPersonIndex, vectorIndex = 0;
                  vectorIndex < shared.vectorLength;
                  index += situationPopulationCount, vectorIndex++
                ) {
                  delta[vectorIndex] += values[index]
                }
              }
              const evaluationByName = {
                ...updatedEvaluationByNameArray[calculation.situationIndex],
              }
    
              if (evaluationByName[variableName] === undefined) {
                evaluationByName[variableName] = {}
              }
    
              if (
                evaluationByName[variableName].calculationEvaluationByName ===
                undefined
              ) {
                evaluationByName[variableName].calculationEvaluationByName = {}
              }
    
              if (
                evaluationByName[variableName].calculationEvaluationByName[
                  calculationName
                ] === undefined
              ) {
                evaluationByName[variableName].calculationEvaluationByName[
                  calculationName
                ] = {}
              }
    
              evaluationByName[variableName].calculationEvaluationByName[
                calculationName
              ]!.delta = delta
              evaluationByName[variableName].calculationEvaluationByName[
                calculationName
              ]!.deltaAtVectorIndex = delta[situation.slider?.vectorIndex ?? 0] ?? 0
    
              evaluationByName[variableName].fromOpenFisca = true
            }
          }
        }
        shared.valuesByCalculationNameByVariableNameArray =
          valuesByCalculationNameByVariableNameArray
    
        // Update deltaSums of evaluations from their new delta.
        if (calculation.situationIndex === undefined) {
          shared.evaluationByNameArray = updatedEvaluationByNameArray.map(
            (evaluationByName, situationIndex) =>
              updateEvaluations(
                shared.decompositionByName,
                evaluationByName,
                shared.testCases[situationIndex].slider?.vectorIndex ?? 0,
                shared.vectorLength,
                waterfalls,
              ),
          )
        } else {
          updatedEvaluationByNameArray[calculation.situationIndex] =
            updateEvaluations(
              shared.decompositionByName,
              updatedEvaluationByNameArray[calculation.situationIndex],
              shared.testCases[calculation.situationIndex].slider?.vectorIndex ?? 0,
              shared.vectorLength,
              waterfalls,
            )
          shared.evaluationByNameArray = updatedEvaluationByNameArray
        }
    
        calculation = { ...calculation }
        delete calculation.running
        shared.calculationByName[calculationName] = calculation
      } catch (e) {
        if (e?.name === "AbortError") {
          return
        }
      }
    }
    
    export function variableValueByCalculationNameFromEvaluation(
      evaluation: Evaluation | undefined | null,
      revaluationName: string | undefined | null,
      billName: string | undefined | null,
      parametricReform: ParametricReform,
    ): VariableValueByCalculationName {
      return {
        amendment:
          Object.keys(parametricReform).length === 0
            ? undefined
            : Math.abs(
                evaluation?.calculationEvaluationByName["amendment"]
                  ?.deltaAtVectorIndex ?? 0,
              ),
        bill:
          billName == null
            ? undefined
            : Math.abs(
                evaluation?.calculationEvaluationByName["bill"]
                  ?.deltaAtVectorIndex ?? 0,
              ),
        law: Math.abs(
          evaluation?.calculationEvaluationByName["law"]?.deltaAtVectorIndex ?? 0,
        ),
        revaluation:
          revaluationName == null
            ? undefined
            : Math.abs(
                evaluation?.calculationEvaluationByName["revaluation"]
                  ?.deltaAtVectorIndex ?? 0,
              ),
      }
    }