Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • leximpact/simulateur-socio-fiscal/leximpact-socio-fiscal-ui
  • dsmadja/leximpact-socio-fiscal-ui
2 results
Select Git revision
Show changes
Commits on Source (21)
...@@ -233,11 +233,26 @@ $: console.log(parameterSmicMensuel, smicValue) ...@@ -233,11 +233,26 @@ $: console.log(parameterSmicMensuel, smicValue)
### Générer les YAML de test OpenFisca à partir des cas-type LexImpact ### Générer les YAML de test OpenFisca à partir des cas-type LexImpact
```bash ```bash
npx tsx src/scripts/generate_openfisca_tests_yaml.ts -y 2025 ../../openfisca-france/tests/leximpact/ npx tsx src/scripts/generate_openfisca_tests_yaml.ts \
-y 2024 \
--openfisca_venv /path/to/openfisca-france/.venv \
--openfisca_json_model_path ../openfisca-json-model \
--leximpact_openfisca_json_path ../leximpact-socio-fiscal-openfisca-json \
../../openfisca-france/tests/leximpact/2024/
``` ```
- Le paramètre `year` générera les variables d'output pour l'année `year` Vous aurez besoin :
- L'argument par défaut `outdir` est le chemin où l'on veut exporter les YAML générés. Ceux-ci ont vocation à être pushés dans openfisca_france/tests/leximpact
- d'une install fonctionnelle d'`openfisca-france`
- de clones locaux à jour des dépôts `openfisca-json-model` et `leximpact-socio-fiscal-openfisca-json`
Paramètres attendus :
- l'argument `year` (-y) générera les cas-types avec la valeur du SMIC au 01-01-`year`, les variables d'output pour l'année `year`, et des valeurs d'input pour les années `year`, `year`-1 et `year`-2 selon les données du cas-type.
- l'argument `openfisca_venv` (-e) doit correspondre au chemin d'un venv dans lequel openfisca-france est installé. Il doit être possible d'y exécuter `openfisca test`.
- l'argument `openfisca_json_model_path` doit contenir le chemin vers un clone à jour du dépôt "openfisca-json-model"
- l'argument `leximpact_openfisca_json_path` doit contenir le chemin vers un clone à jour du dépôt "leximpact-socio-fiscal-openfisca-json"
- L'argument par défaut `outdir` est le chemin où l'on veut exporter les YAML générés. Le répertoire fourni sera créé s'il n'existe pas.
Les YAML générés contiennent, pour chaque cas-type, toutes les variables d'entrées du cas-type dans le fichier `test_cases.json`. Les YAML générés contiennent, pour chaque cas-type, toutes les variables d'entrées du cas-type dans le fichier `test_cases.json`.
Les valeurs sont mensualisées pour toutes les variables mensuelles. Les valeurs sont mensualisées pour toutes les variables mensuelles.
...@@ -245,3 +260,7 @@ Les valeurs sont mensualisées pour toutes les variables mensuelles. ...@@ -245,3 +260,7 @@ Les valeurs sont mensualisées pour toutes les variables mensuelles.
La section `output:` du YAML contient toutes les variables calculées, présentes à la fois dans les décompositions LexImpact **et** dans les variables du country-package Openfisca-France. La section `output:` du YAML contient toutes les variables calculées, présentes à la fois dans les décompositions LexImpact **et** dans les variables du country-package Openfisca-France.
Les valeurs des variables de type Enum, sont remplacées par leur index dans les valeurs possibles de l'Enum. Les valeurs des variables de type Enum, sont remplacées par leur index dans les valeurs possibles de l'Enum.
Les valeurs générées en sortie sont celles que retourne l'API d'openFisca-France **corrigées par les valeurs que retournent `openfisca test`** dans le cas où elles diffèrent.
Toutes les valeurs sont arrondies au centime, sauf la valeur d'entrée du SMIC, arrondie à l'euro.
...@@ -81,7 +81,7 @@ async function commitAndPushWithUpdatedVersions( ...@@ -81,7 +81,7 @@ async function commitAndPushWithUpdatedVersions(
await $`git add .` await $`git add .`
if ((await $`git diff --quiet --staged`.exitCode) !== 0) { if ((await $`git diff --quiet --staged`.exitCode) !== 0) {
await $`git commit -m ${nextVersion}` await $`git commit -m ${nextVersion}`
await $`git push --set-upstream` await $`git push --set-upstream origin master`
} }
await $`git tag -a ${nextVersion} -m ${nextVersion}` await $`git tag -a ${nextVersion} -m ${nextVersion}`
await $`git push --set-upstream --tags` await $`git push --set-upstream --tags`
......
{ {
"name": "leximpact-socio-fiscal-ui", "name": "leximpact-socio-fiscal-ui",
"version": "0.0.1024", "version": "0.0.1039",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "leximpact-socio-fiscal-ui", "name": "leximpact-socio-fiscal-ui",
"version": "0.0.1024", "version": "0.0.1039",
"devDependencies": { "devDependencies": {
"@auditors/core": "^0.7.0", "@auditors/core": "^0.7.0",
"@eslint/compat": "^1.2.4", "@eslint/compat": "^1.2.4",
"@fontsource/lato": "^5.0.5", "@fontsource/lato": "^5.0.5",
"@fontsource/lora": "^5.0.5", "@fontsource/lora": "^5.0.5",
"@leximpact/socio-fiscal-openfisca-json": "^0.0.333", "@leximpact/socio-fiscal-openfisca-json": "^0.0.336",
"@openfisca/json-model": "^3.1.0", "@openfisca/json-model": "^3.1.0",
"@playwright/test": "^1.28.1", "@playwright/test": "^1.28.1",
"@popperjs/core": "^2.11.6", "@popperjs/core": "^2.11.6",
...@@ -1074,13 +1074,13 @@ ...@@ -1074,13 +1074,13 @@
} }
}, },
"node_modules/@leximpact/socio-fiscal-openfisca-json": { "node_modules/@leximpact/socio-fiscal-openfisca-json": {
"version": "0.0.333", "version": "0.0.336",
"resolved": "https://registry.npmjs.org/@leximpact/socio-fiscal-openfisca-json/-/socio-fiscal-openfisca-json-0.0.333.tgz", "resolved": "https://registry.npmjs.org/@leximpact/socio-fiscal-openfisca-json/-/socio-fiscal-openfisca-json-0.0.336.tgz",
"integrity": "sha512-4Dv3wsGba3PVovrQi1EtGXAdVMakofXZk5u0WIHc4rNrToi29auza6D1wYvKoMOJBl7X/kP/3otOrcODxuck3A==", "integrity": "sha512-L7gGTI5OMFUbnLDbMk1xpyzyMTbGGyfqrCMHpe5NQmsnJyvmODKljTJP4WS2ZRVDsbX+z5wq4Hcm0oZ2WrLK3w==",
"dev": true, "dev": true,
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"peerDependencies": { "peerDependencies": {
"@openfisca/json-model": "^3.2.1" "@openfisca/json-model": "^3.3.0"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@openfisca/json-model": { "@openfisca/json-model": {
...@@ -1127,9 +1127,9 @@ ...@@ -1127,9 +1127,9 @@
} }
}, },
"node_modules/@openfisca/json-model": { "node_modules/@openfisca/json-model": {
"version": "3.2.1", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/@openfisca/json-model/-/json-model-3.2.1.tgz", "resolved": "https://registry.npmjs.org/@openfisca/json-model/-/json-model-3.3.0.tgz",
"integrity": "sha512-aLtYIt7TXd32dl+HzYb1nbhbLrpnzVwuEbzlMik/Btv8F3ZapLIvEcFdyHY7sU+obZOYde6LdBjPCSja99VCNA==", "integrity": "sha512-Ip3qLXgy5DozUVd5wpP2qEvD1W6XJVZxtkDcYa4YAcacICMw8PUH67kElJKLjcrRu5IisINpL06lGXC63BMjKg==",
"dev": true, "dev": true,
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
......
{ {
"name": "leximpact-socio-fiscal-ui", "name": "leximpact-socio-fiscal-ui",
"version": "0.0.1026", "version": "0.0.1040",
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "NODE_OPTIONS=--max_old_space_size=4096 vite build", "build": "NODE_OPTIONS=--max_old_space_size=4096 vite build",
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
"@eslint/compat": "^1.2.4", "@eslint/compat": "^1.2.4",
"@fontsource/lato": "^5.0.5", "@fontsource/lato": "^5.0.5",
"@fontsource/lora": "^5.0.5", "@fontsource/lora": "^5.0.5",
"@leximpact/socio-fiscal-openfisca-json": "^0.0.333", "@leximpact/socio-fiscal-openfisca-json": "^0.0.336",
"@openfisca/json-model": "^3.1.0", "@openfisca/json-model": "^3.1.0",
"@playwright/test": "^1.28.1", "@playwright/test": "^1.28.1",
"@popperjs/core": "^2.11.6", "@popperjs/core": "^2.11.6",
......
...@@ -752,7 +752,7 @@ ...@@ -752,7 +752,7 @@
variableCustomizations[key] = variableCustomizations[key] =
variableCustomizations[ variableCustomizations[
getVariableCustomizationName( getVariableCustomizationName(
variable.parent, variable.name,
row.calculationName, row.calculationName,
) )
] ?? ] ??
...@@ -843,7 +843,7 @@ ...@@ -843,7 +843,7 @@
variableCustomizations[key] = variableCustomizations[key] =
variableCustomizations[ variableCustomizations[
getVariableCustomizationName( getVariableCustomizationName(
variable.parent, variable.name,
row.calculationName, row.calculationName,
) )
] ?? ] ??
...@@ -1006,6 +1006,7 @@ ...@@ -1006,6 +1006,7 @@
> >
{#each getCustomizations(groupName, row.calculationName) as customization} {#each getCustomizations(groupName, row.calculationName) as customization}
<button <button
aria-label="personnaliser l'apparence"
class="h-10 w-10 rounded border-2 border-neutral-300 hover:border-blue-500" class="h-10 w-10 rounded border-2 border-neutral-300 hover:border-blue-500"
onclick={() => { onclick={() => {
variableCustomizations[ variableCustomizations[
...@@ -1061,6 +1062,7 @@ ...@@ -1061,6 +1062,7 @@
initialPlacement="top" initialPlacement="top"
> >
<button <button
aria-labelledby="hide-show-tooltip-{variable.name}"
class="group h-full pl-0.5 pr-1" class="group h-full pl-0.5 pr-1"
onclick={() => { onclick={() => {
variableCustomizations[ variableCustomizations[
...@@ -1095,7 +1097,7 @@ ...@@ -1095,7 +1097,7 @@
</button> </button>
{#snippet tooltip()} {#snippet tooltip()}
<div <div
id="hide-show-tooltip" id="hide-show-tooltip-{variable.name}"
class="overflow-hidden rounded-lg bg-black px-2 py-1 text-sm text-white" class="overflow-hidden rounded-lg bg-black px-2 py-1 text-sm text-white"
title={isSelected ? "Cacher" : "Montrer"} title={isSelected ? "Cacher" : "Montrer"}
> >
......
...@@ -79,10 +79,10 @@ export const revaluationName: string | undefined = ...@@ -79,10 +79,10 @@ export const revaluationName: string | undefined =
const today = new Date() const today = new Date()
export const year = export const year =
today.getFullYear() + (today.getMonth() > 2 /* => After March */ ? 1 : 0) today.getFullYear() + (today.getMonth() > 3 /* => After April */ ? 1 : 0)
export const yearPLF = export const yearPLF =
today.getFullYear() + today.getFullYear() +
(revaluationName !== undefined && today.getMonth() > 2 /* => After March */ (revaluationName !== undefined && today.getMonth() > 3 /* => After April */
? 1 ? 1
: 0) : 0)
export const date = `${year}-01-01` export const date = `${year}-01-01`
......
...@@ -303,9 +303,13 @@ export function setSituationVariableValue( ...@@ -303,9 +303,13 @@ export function setSituationVariableValue(
) { ) {
return false return false
} }
valueByPeriod[year - 2] = value if (variable.name === "date_naissance") {
valueByPeriod[year - 1] = value valueByPeriod[year] = value
valueByPeriod[year] = value } else {
valueByPeriod[year - 2] = value
valueByPeriod[year - 1] = value
valueByPeriod[year] = value
}
return true return true
} }
......
...@@ -3,7 +3,6 @@ import commandLineArgs from "command-line-args" ...@@ -3,7 +3,6 @@ import commandLineArgs from "command-line-args"
import fs from "fs-extra" import fs from "fs-extra"
import path from "path" import path from "path"
import YAML from "js-yaml" import YAML from "js-yaml"
import testCasesCoreUnknown from "@leximpact/socio-fiscal-openfisca-json/test_cases.json"
import { import {
summaryCalculatedVariablesName, summaryCalculatedVariablesName,
otherCalculatedVariablesName, otherCalculatedVariablesName,
...@@ -12,6 +11,7 @@ import { ...@@ -12,6 +11,7 @@ import {
import { nonVirtualVariablesName } from "$lib/decompositions" import { nonVirtualVariablesName } from "$lib/decompositions"
import type { EntityByKey, Entity, GroupEntity } from "@openfisca/json-model" import type { EntityByKey, Entity, GroupEntity } from "@openfisca/json-model"
import { entityByKey } from "$lib/entities" import { entityByKey } from "$lib/entities"
import { spawn } from "child_process"
type JsonObject = { [key: string]: any } type JsonObject = { [key: string]: any }
...@@ -34,6 +34,28 @@ const optionsDefinitions = [ ...@@ -34,6 +34,28 @@ const optionsDefinitions = [
name: "year", name: "year",
type: Number, type: Number,
}, },
{
alias: "e",
help: "Path to a venv with openfisca-france installed. You must be able to launch `openfisca test` within it.",
name: "openfisca_venv",
type: String,
},
{
help: "Path to openfisca-json-model. Be careful to clone the repo and git pull before.",
name: "openfisca_json_model_path",
type: String,
},
{
help: "Path to leximpact-socio_fiscal-openfisca-json. Be careful to clone the repo and git pull before.",
name: "leximpact_openfisca_json_path",
type: String,
},
{
alias: "t",
help: "Single Test-Case to run",
name: "testcase",
type: String,
},
{ {
defaultOption: true, defaultOption: true,
help: "Directory to write OpenFisca test YAML files", help: "Directory to write OpenFisca test YAML files",
...@@ -43,6 +65,64 @@ const optionsDefinitions = [ ...@@ -43,6 +65,64 @@ const optionsDefinitions = [
] ]
const options = commandLineArgs(optionsDefinitions) const options = commandLineArgs(optionsDefinitions)
function getSmicForYear(
smicValues: Record<string, number>,
year: number,
): number | null {
const targetDate = `${year}-01-01`
const validDates = Object.keys(smicValues)
.filter((date) => date <= targetDate)
.sort()
if (validDates.length === 0) {
return null
}
const lastValidDate = validDates[validDates.length - 1]
return smicValues[lastValidDate]
}
async function runMergeTestCases(
openFiscaJsonModelPath: string,
leximpactOpenFiscaJsonPath: string,
smicValue: number,
year: number,
): Promise<any> {
return new Promise<void>((resolve, reject) => {
const scriptPath = path.join(
openFiscaJsonModelPath,
"packages",
"tools",
"src",
"scripts",
"merge_test_cases.ts",
)
const args = [
scriptPath,
leximpactOpenFiscaJsonPath,
"--smic=" + smicValue.toString(),
"--year",
year.toString(),
]
const processMergeTestCases = spawn("npx", ["tsx", ...args], {
stdio: "inherit",
shell: true,
})
processMergeTestCases.on("close", (code) => {
if (code !== 0) {
reject(
"Unable to generate new test_cases.json. Call to script merge_test_cases.ts failed with code : " +
code,
)
} else {
resolve()
}
})
})
}
async function fetchWithRetries( async function fetchWithRetries(
url: string, url: string,
options: RequestInit, options: RequestInit,
...@@ -151,13 +231,13 @@ function replaceValuesFromReplacedKeys( ...@@ -151,13 +231,13 @@ function replaceValuesFromReplacedKeys(
return newObj return newObj
} }
function removeZeroValues(obj: any): any { function removeZeroValuesAndExcludedVariables(obj: any): any {
if (typeof obj !== "object" || obj === null) { if (typeof obj !== "object" || obj === null) {
return obj return obj
} }
if (Array.isArray(obj)) { if (Array.isArray(obj)) {
return obj.map(removeZeroValues) return obj.map(removeZeroValuesAndExcludedVariables)
} }
const newObj: any = {} const newObj: any = {}
...@@ -167,14 +247,19 @@ function removeZeroValues(obj: any): any { ...@@ -167,14 +247,19 @@ function removeZeroValues(obj: any): any {
const value = obj[key] const value = obj[key]
if (typeof value === "object" && value !== null) { if (typeof value === "object" && value !== null) {
if (Object.values(value).every((v) => v === 0 || v === null)) { if (
Object.values(value).every((v) => v === 0 || v === null) ||
key === "prelevements_sociaux_menage"
) {
continue continue
} }
const cleanedValue = removeZeroValues(value) const cleanedValue = removeZeroValuesAndExcludedVariables(value)
if (Object.keys(cleanedValue).length > 0) { if (Object.keys(cleanedValue).length > 0) {
newObj[key] = cleanedValue newObj[key] = cleanedValue
} }
} else if (typeof value === "number") {
newObj[key] = Math.round(value * 100) / 100
} else { } else {
newObj[key] = value newObj[key] = value
} }
...@@ -192,7 +277,7 @@ function cleanSimulatedJson(jsonData: any): any { ...@@ -192,7 +277,7 @@ function cleanSimulatedJson(jsonData: any): any {
jsonData[section] = Object.fromEntries( jsonData[section] = Object.fromEntries(
Object.entries(jsonData[section]).map(([key, value]) => [ Object.entries(jsonData[section]).map(([key, value]) => [
key, key,
removeZeroValues(value), removeZeroValuesAndExcludedVariables(value),
]), ]),
) )
} }
...@@ -202,14 +287,22 @@ function cleanSimulatedJson(jsonData: any): any { ...@@ -202,14 +287,22 @@ function cleanSimulatedJson(jsonData: any): any {
} }
function splitVariablesByCategory( function splitVariablesByCategory(
section: string,
entityKey: string,
entityData: JsonObject, entityData: JsonObject,
outputVariablesList: string[], initialTestCase: Situation,
) { ) {
const input: JsonObject = {} const input: JsonObject = {}
const output: JsonObject = {} const output: JsonObject = {}
for (const [variable, value] of Object.entries(entityData)) { for (const [variable, value] of Object.entries(entityData)) {
if (outputVariablesList.includes(variable)) { const replacedKeys: { [key: string]: string } = {}
initialTestCase = removeSpacesFromKeys(initialTestCase, replacedKeys)
if (
initialTestCase[section][entityKey] !== undefined &&
initialTestCase[section][entityKey][variable] === undefined
) {
if ( if (
variableSummaryByName[variable].value_type === "Enum" && variableSummaryByName[variable].value_type === "Enum" &&
variableSummaryByName[variable].possible_values !== undefined variableSummaryByName[variable].possible_values !== undefined
...@@ -233,16 +326,19 @@ function splitVariablesByCategory( ...@@ -233,16 +326,19 @@ function splitVariablesByCategory(
} }
function processSection( function processSection(
section: string,
sectionData: JsonObject, sectionData: JsonObject,
outputVariablesList: string[], initialTestCase: Situation,
) { ) {
const inputSection: JsonObject = {} const inputSection: JsonObject = {}
const outputSection: JsonObject = {} const outputSection: JsonObject = {}
for (const [entityKey, entityData] of Object.entries(sectionData)) { for (const [entityKey, entityData] of Object.entries(sectionData)) {
const { input, output } = splitVariablesByCategory( const { input, output } = splitVariablesByCategory(
section,
entityKey,
entityData, entityData,
outputVariablesList, initialTestCase,
) )
if (Object.keys(input).length > 0) { if (Object.keys(input).length > 0) {
...@@ -260,19 +356,21 @@ function buildYamlOutput( ...@@ -260,19 +356,21 @@ function buildYamlOutput(
name: string, name: string,
year: string, year: string,
simulatedTestCase: JsonObject, simulatedTestCase: JsonObject,
outputVariablesList: string[], testCase: Situation,
) { ) {
const sections = ["familles", "foyers_fiscaux", "menages", "individus"] const sections = ["familles", "foyers_fiscaux", "menages", "individus"]
const jsonForYamlOutput: { const jsonForYamlOutput: {
name: string name: string
period: string period: string
max_spiral_loops: number max_spiral_loops: number
absolute_error_margin: number
input: JsonObject input: JsonObject
output: JsonObject output: JsonObject
} = { } = {
name: name, name: name,
period: year, period: year,
max_spiral_loops: 4, max_spiral_loops: 4,
absolute_error_margin: 1,
input: {}, input: {},
output: {}, output: {},
} }
...@@ -280,8 +378,9 @@ function buildYamlOutput( ...@@ -280,8 +378,9 @@ function buildYamlOutput(
for (const section of sections) { for (const section of sections) {
if (simulatedTestCase[section]) { if (simulatedTestCase[section]) {
const { input, output } = processSection( const { input, output } = processSection(
section,
simulatedTestCase[section], simulatedTestCase[section],
outputVariablesList, testCase,
) )
if (Object.keys(input).length > 0) { if (Object.keys(input).length > 0) {
...@@ -365,14 +464,128 @@ function isKeyOrPlural(str: string): boolean { ...@@ -365,14 +464,128 @@ function isKeyOrPlural(str: string): boolean {
return false return false
} }
function replaceVariableValue(
jsonData: any,
variableName: string,
currentValue: any,
newValue: any,
period: string,
): any {
const entities = ["familles", "foyers_fiscaux", "menages", "individus"]
for (const entity of entities) {
if (!jsonData.output[entity]) continue
for (const entityKey in jsonData.output[entity]) {
const entityData = jsonData.output[entity][entityKey]
if (entityData.hasOwnProperty(variableName)) {
const variableData = entityData[variableName]
if (typeof variableData === "object") {
if (
variableData.hasOwnProperty(period) &&
Math.trunc(Number(variableData[period])) ===
Math.trunc(Number(currentValue))
) {
console.info(
`Replacing ${variableName} (${period}): ${variableData[period]}${newValue}`,
)
variableData[period] = Math.round(Number(newValue) * 100) / 100
} else {
console.log(
"Warning : rounded value",
Math.trunc(Number(currentValue)),
"not found for",
variableName,
"\nThe cause can be multiple instance of the same variable for different entities... or bug",
)
}
}
}
}
}
return
}
async function runOpenFiscaTest(
openFiscaVenvPath: string,
yamlFilePath: string,
): Promise<any> {
return new Promise<void>((resolve, reject) => {
const openfiscaExecutable = path.join(openFiscaVenvPath, "bin", "openfisca")
const args = ["test", "--country-package", "openfisca_france", yamlFilePath]
const env = {
...process.env,
VIRTUAL_ENV: openFiscaVenvPath,
PATH: `${path.join(openFiscaVenvPath, "bin")}:${process.env.PATH}`,
PYTHONPATH: path.join(
openFiscaVenvPath,
"lib",
"python3.12",
"site-packages",
),
}
let testOutput = ""
const pythonProcess = spawn(openfiscaExecutable, args, {
stdio: ["inherit", "pipe", "pipe"],
env,
})
pythonProcess.stdout.on("data", (data) => {
testOutput += data.toString()
})
pythonProcess.stderr.on("data", (data) => {
console.error(`stderr: ${data}`)
reject(data)
})
pythonProcess.on("close", (code) => {
let returned = undefined as any
if (code !== 0) {
returned = { passed: false }
const testOutputLines = testOutput.split("\n")
const regex =
/(?<variable>[a-zA-Z0-9_]+)@(?<period>\d{4}(?:-\d{2})?):\s+(?<calculated>-?\d+(?:\.\d+)?)\s+differs\s+from\s+(?<expected>-?\d+(?:\.\d+)?)/
testOutputLines.forEach((line) => {
const match = line.match(regex)
if (match && match.groups) {
returned = { ...returned, ...match.groups }
}
})
if (returned.variable !== undefined) {
resolve(returned)
} else
reject(
"Erreur de récupération des valeurs lors d'un appel à openfisca test : " +
testOutput,
)
} else {
returned = { passed: true }
resolve(returned)
}
})
})
}
async function main() { async function main() {
console.info("Initializing...") console.info("Initializing...")
const personsEntityKey = Object.entries(entityByKey)
.filter(([, entity]) => entity.is_person)
.map(([key]) => key)[0]
const year = options.year const year = options.year
const outdir = options.outdir const outdir = options.outdir
const singleTestCase = options.testcase
const openFiscaVenvPath = options.openfisca_venv
const openFiscaJsonModelPath = options.openfisca_json_model_path
const leximpactOpenFiscaJsonPath = options.leximpact_openfisca_json_path
if (!fs.existsSync(outdir)) {
fs.mkdirSync(outdir, { recursive: true })
}
const variablesFromDecompositions = [ const variablesFromDecompositions = [
...summaryCalculatedVariablesName, ...summaryCalculatedVariablesName,
...@@ -397,17 +610,64 @@ async function main() { ...@@ -397,17 +610,64 @@ async function main() {
const openFiscaVariablesList = await openFiscaVariablesListApiCall.json() const openFiscaVariablesList = await openFiscaVariablesListApiCall.json()
const smicValuesApiCall = await fetch(
"https://api.fr.openfisca.org/latest/parameter/marche_travail/salaire_minimum/smic/smic_b_mensuel",
{
method: "GET",
},
)
if (!smicValuesApiCall.ok) {
console.error(
`Error ${smicValuesApiCall.status} while calling Openfisca France API`,
)
return
}
const smicValues = (await smicValuesApiCall.json()).values
const monthlySmicValueForYear = getSmicForYear(smicValues, year)
if (monthlySmicValueForYear === null) {
throw new Error("Unable to retrieve SMIC value for year", year)
}
const smicValueForYear = Math.round(monthlySmicValueForYear * 12)
console.info(
"SMIC value retrieved from openFisca API. Will be using",
smicValueForYear,
)
console.info("Generating test cases...")
try {
await runMergeTestCases(
openFiscaJsonModelPath,
leximpactOpenFiscaJsonPath,
smicValueForYear,
year,
)
} catch (error) {
console.error("Error : ", error)
}
const testCasesCoreData = await fs.readFile(
path.join(leximpactOpenFiscaJsonPath, "test_cases.json"),
"utf-8",
)
const testCasesCore = JSON.parse(testCasesCoreData)
const testCaseCount = testCasesCore.length
const variablesToCalculate = variablesFromDecompositions.filter( const variablesToCalculate = variablesFromDecompositions.filter(
(variable: string) => openFiscaVariablesList.hasOwnProperty(variable), (variable: string) => openFiscaVariablesList.hasOwnProperty(variable),
) )
const testCasesCore = testCasesCoreUnknown as unknown as Situation[] let processedCounter = 0
for (const testCase of testCasesCore) { for (const testCase of testCasesCore) {
if (singleTestCase !== undefined && testCase.id !== singleTestCase) {
continue
}
console.info("Processing ", testCase.id) console.info("Processing ", testCase.id)
processedCounter++
let testCaseContainsMissingVariable = false let testCaseContainsMissingVariable = false
let jsonForApiCall: JsonObject = {} let jsonForApiCall: JsonObject = {}
const outputVariablesList: Array<string> = []
jsonForApiCall.input = {} jsonForApiCall.input = {}
...@@ -432,7 +692,7 @@ async function main() { ...@@ -432,7 +692,7 @@ async function main() {
!openFiscaVariablesList.hasOwnProperty(variable) && !openFiscaVariablesList.hasOwnProperty(variable) &&
!isKeyOrPlural(variable) !isKeyOrPlural(variable)
) { ) {
console.warn( console.info(
"variable", "variable",
variable, variable,
"does not exists in openFisca-France.", "does not exists in openFisca-France.",
...@@ -445,7 +705,7 @@ async function main() { ...@@ -445,7 +705,7 @@ async function main() {
} }
if (testCaseContainsMissingVariable) { if (testCaseContainsMissingVariable) {
console.warn("Ignoring test case", testCase.id) console.info("Ignoring test case", testCase.id)
continue continue
} }
...@@ -463,7 +723,6 @@ async function main() { ...@@ -463,7 +723,6 @@ async function main() {
getEntityKeyFromKeyPlural(section, entityByKey) && getEntityKeyFromKeyPlural(section, entityByKey) &&
jsonForApiCall.input[section][entityKey][variable] === undefined jsonForApiCall.input[section][entityKey][variable] === undefined
) { ) {
outputVariablesList.push(variable)
switch (variableSummaryByName[variable].definition_period) { switch (variableSummaryByName[variable].definition_period) {
case "year": { case "year": {
jsonForApiCall.input[section][entityKey][variable] = { jsonForApiCall.input[section][entityKey][variable] = {
...@@ -497,8 +756,13 @@ async function main() { ...@@ -497,8 +756,13 @@ async function main() {
} }
jsonForApiCall = replaceValuesFromReplacedKeys(jsonForApiCall, replacedKeys) jsonForApiCall = replaceValuesFromReplacedKeys(jsonForApiCall, replacedKeys)
// await fs.writeFile(
// path.join(".", testCase.id + ".json"),
// JSON.stringify(jsonForApiCall.input, null, 2),
// )
console.info("Launching simulation on OpenFisca France API...") console.info("Launching simulation on OpenFisca France API...")
const openFiscaApiUrl = "https://api.fr.openfisca.org/latest/calculate" const openFiscaApiUrl = "https://api.fr.openfisca.org/latest/calculate"
// const openFiscaApiUrl = "http://localhost:5000/calculate"
const openFiscaApiOptions: RequestInit = { const openFiscaApiOptions: RequestInit = {
body: JSON.stringify(jsonForApiCall.input), body: JSON.stringify(jsonForApiCall.input),
headers: { headers: {
...@@ -533,18 +797,93 @@ async function main() { ...@@ -533,18 +797,93 @@ async function main() {
testCase.id!, testCase.id!,
year, year,
simulatedTestCase, simulatedTestCase,
outputVariablesList, testCase,
) )
const yamlOutput = YAML.dump(jsonForYamlOutput, { let ok = false
noCompatMode: true, while (!ok) {
noRefs: true, const yamlOutput = YAML.dump(jsonForYamlOutput, {
}) noCompatMode: true,
noRefs: true,
})
const cleanedYaml = yamlOutput.replace(/'(\d{4})':/g, "$1:")
await fs.writeFile(path.join(outdir, testCase.id + ".yml"), cleanedYaml)
try {
const openFiscaTestResult = await runOpenFiscaTest(
openFiscaVenvPath,
path.join(outdir, testCase.id + ".yml"),
)
if (!openFiscaTestResult.passed) {
if (
openFiscaTestResult.variable === undefined ||
openFiscaTestResult.expected === undefined ||
openFiscaTestResult.calculated === undefined ||
openFiscaTestResult.period === undefined
) {
throw new Error("Error retrieving values to update")
}
replaceVariableValue(
jsonForYamlOutput,
openFiscaTestResult.variable,
openFiscaTestResult.expected,
openFiscaTestResult.calculated,
openFiscaTestResult.period,
)
} else {
ok = true
console.info(
"YAML file written :",
path.join(outdir, testCase.id + ".yml"),
)
}
} catch (error) {
console.error(error)
}
}
console.info("Adjusting max_spiral_loop")
while (ok && jsonForYamlOutput.max_spiral_loops > 1) {
jsonForYamlOutput.max_spiral_loops =
jsonForYamlOutput.max_spiral_loops - 1
const yamlOutput = YAML.dump(jsonForYamlOutput, {
noCompatMode: true,
noRefs: true,
})
const cleanedYaml = yamlOutput.replace(/'(\d{4})':/g, "$1:")
await fs.writeFile(path.join(outdir, testCase.id + ".yml"), cleanedYaml)
try {
const openFiscaTestResult = await runOpenFiscaTest(
openFiscaVenvPath,
path.join(outdir, testCase.id + ".yml"),
)
if (!openFiscaTestResult.passed) {
ok = false
jsonForYamlOutput.max_spiral_loops =
jsonForYamlOutput.max_spiral_loops + 1
const yamlOutput = YAML.dump(jsonForYamlOutput, {
noCompatMode: true,
noRefs: true,
})
const cleanedYaml = yamlOutput.replace(/'(\d{4})':/g, "$1:")
await fs.writeFile(
path.join(outdir, testCase.id + ".yml"),
cleanedYaml,
)
console.info("YAML file written :", outdir, testCase.id + ".yml")
} else {
ok = true
}
} catch (error) {
console.error(error)
}
}
// Remove quotes around year keys console.info("Processed test-case", processedCounter, "over", testCaseCount)
const cleanedYaml = yamlOutput.replace(/'(\d{4})':/g, "$1:")
await fs.writeFile(path.join(outdir, testCase.id + ".yml"), cleanedYaml)
console.info("YAML file written :", outdir, testCase.id + ".yml")
} }
} }
......