From a36469a994e337274e17af09dc01c8625dee5850 Mon Sep 17 00:00:00 2001 From: Emmanuel Raviart <emmanuel@raviart.com> Date: Sun, 24 Sep 2023 10:53:48 +0200 Subject: [PATCH] Add cache for test cases simulations --- example.env | 7 ++- src/lib/hash.ts | 20 ------- src/lib/server/auditors/config.ts | 1 + src/lib/server/config.ts | 2 + src/routes/+layout.svelte | 4 +- src/routes/budgets/+server.ts | 37 ++++++++++--- src/routes/test_cases/simulations/+server.ts | 57 ++++++++++++++++++++ 7 files changed, 95 insertions(+), 33 deletions(-) create mode 100644 src/routes/test_cases/simulations/+server.ts diff --git a/example.env b/example.env index 2ca2ab2be..a0c6a9b3f 100644 --- a/example.env +++ b/example.env @@ -86,11 +86,14 @@ PROXY=false # Show intro balloons. SHOW_TUTORIAL=true +# Directory containing cache of budget simulations +SIMULATIONS_BUDGET_DIR="../simulations_budget" + # Directory containing JSON of simulations saved by users SIMULATIONS_DIR="../simulations" -# Directory containing JSON of budget simulations saved by users -SIMULATIONS_BUDGET_DIR="../simulations_budget" +# Directory containing cache of test cases simulations +SIMULATIONS_TEST_CASES_DIR="../simulations_cas_types" # Key for taxable household (aka "foyer fiscal") entity TAXABLE_HOUSEHOLD_KEY="foyer_fiscal" diff --git a/src/lib/hash.ts b/src/lib/hash.ts index a7d524b77..a0f845b8e 100644 --- a/src/lib/hash.ts +++ b/src/lib/hash.ts @@ -1,8 +1,5 @@ import { XXH64 } from "xxh3-ts" -import type { ParametricReform } from "$lib/reforms" -import { budgetEditableParametersName } from "$lib/variables" - // Sometimes, the magnitude of the BigInt value generated by XXH64() // is not large enough to require 16 characters when represented in base 16. // As a result, the base 16 representation has leading zeros that are not necessary, @@ -18,20 +15,3 @@ export function hashObject(obj: object): string { .toString(16) .padStart(16, "0") } - -export function hashBudgetSimulationCache( - parametricReform: ParametricReform, - outputVariables: string[], -): string { - return hashObject({ - parametricReform: Object.entries(parametricReform) - .sort(([keyA], [keyB]) => keyA.localeCompare(keyB)) - .reduce((filtered: ParametricReform, [parameterName, value]) => { - if (budgetEditableParametersName.has(parameterName)) { - filtered[parameterName] = value - } - return filtered - }, {}), - outputVariables: outputVariables.sort().join("."), - }) -} diff --git a/src/lib/server/auditors/config.ts b/src/lib/server/auditors/config.ts index 587ff0c3d..564a457d3 100644 --- a/src/lib/server/auditors/config.ts +++ b/src/lib/server/auditors/config.ts @@ -75,6 +75,7 @@ export function auditConfig( "jwtSecret", "simulationsBudgetDir", "simulationsDir", + "simulationsTestCasesDir", "taxableHouseholdEntityKey", "title", ]) { diff --git a/src/lib/server/config.ts b/src/lib/server/config.ts index 4ba7a1801..e44521115 100644 --- a/src/lib/server/config.ts +++ b/src/lib/server/config.ts @@ -37,6 +37,7 @@ export interface Config { revaluationName?: string showTutorial?: boolean simulationsBudgetDir: string + simulationsTestCasesDir: string simulationsDir: string taxableHouseholdEntityKey: string territoiresUrl: string @@ -89,6 +90,7 @@ const [validConfig, error] = validateConfig({ showTutorial: process.env["SHOW_TUTORIAL"], simulationsBudgetDir: process.env["SIMULATIONS_BUDGET_DIR"], simulationsDir: process.env["SIMULATIONS_DIR"], + simulationsTestCasesDir: process.env["SIMULATIONS_TEST_CASES_DIR"], taxableHouseholdEntityKey: process.env["TAXABLE_HOUSEHOLD_KEY"], territoiresUrl: process.env["TERRITOIRES_URL"], title: process.env["TITLE"], diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 42d8dfc46..bafbc2ab1 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -821,9 +821,7 @@ token: string variables: string[] }) { - const apiBaseUrlIndex = Math.floor(Math.random() * apiBaseUrls.length) - const apiBaseUrl = apiBaseUrls[apiBaseUrlIndex] - const response = await fetch(new URL("simulations", apiBaseUrl), { + const response = await fetch("/test_cases/simulations", { body: JSON.stringify({ period, parametric_reform, diff --git a/src/routes/budgets/+server.ts b/src/routes/budgets/+server.ts index ad690185e..3d36204dd 100644 --- a/src/routes/budgets/+server.ts +++ b/src/routes/budgets/+server.ts @@ -3,17 +3,19 @@ import jwt from "jsonwebtoken" import path from "path" import { error, json } from "@sveltejs/kit" -import { hashBudgetSimulationCache } from "$lib/hash" +import { hashObject } from "$lib/hash" import { getParameter, rootParameter } from "$lib/parameters" import config from "$lib/server/config" +import type { ParametricReform } from "$lib/reforms" import type { CachedSimulation } from "$lib/simulations" import type { User } from "$lib/users" +import { budgetEditableParametersName } from "$lib/variables" import type { RequestHandler } from "./$types" const { simulationsBudgetDir } = config -function getPath(digest: string) { +function getCacheFilePath(digest: string) { return path.join( simulationsBudgetDir, digest.substring(0, 2), @@ -21,6 +23,25 @@ function getPath(digest: string) { ) } +function hashBudgetSimulationCache( + parametricReform: ParametricReform, + outputVariables: string[], +): string { + return hashObject({ + parametricReform: Object.entries(parametricReform) + .sort(([parameterNameA], [parameterNameB]) => + parameterNameA.localeCompare(parameterNameB), + ) + .reduce((filtered: ParametricReform, [parameterName, value]) => { + if (budgetEditableParametersName.has(parameterName)) { + filtered[parameterName] = value + } + return filtered + }, {}), + outputVariables: outputVariables.sort().join("."), + }) +} + export const POST: RequestHandler = async ({ fetch, locals, request }) => { if ( config.budgetApiUrl === undefined || @@ -62,30 +83,30 @@ export const POST: RequestHandler = async ({ fetch, locals, request }) => { {}, requestJson.output_variables, ) - const baseCachePath = getPath(baseCacheDigest) + const baseCacheFilePath = getCacheFilePath(baseCacheDigest) // Path of the current reform cache - const cachePath = getPath(cacheDigest) + const cacheFilePath = getCacheFilePath(cacheDigest) const isSimulationPublic = indexContents.find( (cachedSimulation) => cachedSimulation.hash === cacheDigest, )?.public if ( - (await fs.pathExists(cachePath)) && + (await fs.pathExists(cacheFilePath)) && (user !== undefined || isSimulationPublic) ) { return json({ errors: [], - result: (await fs.readJson(cachePath)).budgetSimulation, + result: (await fs.readJson(cacheFilePath)).budgetSimulation, hash: cacheDigest, isPublic: isSimulationPublic, }) } else if (user === undefined) { - if (await fs.pathExists(baseCachePath)) { + if (await fs.pathExists(baseCacheFilePath)) { return json({ errors: [], - result: (await fs.readJson(baseCachePath)).budgetSimulation, + result: (await fs.readJson(baseCacheFilePath)).budgetSimulation, hash: baseCacheDigest, isPublic: true, }) diff --git a/src/routes/test_cases/simulations/+server.ts b/src/routes/test_cases/simulations/+server.ts new file mode 100644 index 000000000..05099c336 --- /dev/null +++ b/src/routes/test_cases/simulations/+server.ts @@ -0,0 +1,57 @@ +import fs from "fs-extra" +import path from "path" +import { json } from "@sveltejs/kit" + +import { hashObject } from "$lib/hash" +import config from "$lib/server/config" + +import type { RequestHandler } from "./$types" + +const { apiBaseUrls, simulationsTestCasesDir } = config + +export const POST: RequestHandler = async ({ fetch, request }) => { + const input = await request.clone().json() + + const digest = hashObject({ + period: input.period, + parametric_reform: Object.entries(input.parametric_reform ?? {}).sort( + ([parameterNameA], [parameterNameB]) => + parameterNameA.localeCompare(parameterNameB), + ), + reform: input.reform, + situation: input.situation, + // Ignore title. + // Ignore token. + variables: input.variables.sort(), + }) + const cacheDir = path.join(simulationsTestCasesDir, digest.substring(0, 2)) + const cacheFilePath = path.join(cacheDir, `${digest}.json`) + + if (await fs.pathExists(cacheFilePath)) { + return json((await fs.readJson(cacheFilePath)).output) + } + + const apiBaseUrlIndex = Math.floor(Math.random() * apiBaseUrls.length) + const apiBaseUrl = apiBaseUrls[apiBaseUrlIndex] + const response = await fetch(new URL("simulations", apiBaseUrl), { + body: JSON.stringify(input), + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + method: "POST", + }) + if (!response.ok) { + return response + } + const output = await response.json() + if (!(await fs.pathExists(cacheFilePath))) { + await fs.ensureDir(cacheDir) + await fs.writeJson( + cacheFilePath, + { digest, input, output }, + { encoding: "utf-8", spaces: 2 }, + ) + } + return json(output) +} -- GitLab