From d176983c2169124a741ec5d623db8d7a6533b810 Mon Sep 17 00:00:00 2001 From: centdix Date: Wed, 8 Apr 2026 12:19:10 +0200 Subject: [PATCH] refactor: simplify ai eval history Co-Authored-By: Claude Opus 4.5 --- ai_evals/.gitignore | 1 + ai_evals/cli/index.ts | 2 +- ai_evals/cli/results.ts | 2 +- ai_evals/history/README.md | 6 +- ai_evals/history/benchmark-run.schema.json | 3 +- ai_evals/history/rollups/by_model.json | 50 --------- ai_evals/history/rollups/by_surface.json | 50 --------- ai_evals/history/rollups/by_variant.json | 50 --------- ai_evals/history/rollups/latest.json | 89 --------------- ...aude-haiku-4-5-20251001__8c9603a01fa4.json | 2 +- ai_evals/history/summary.jsonl | 2 +- ai_evals/history/writer.mjs | 106 ++---------------- 12 files changed, 15 insertions(+), 348 deletions(-) create mode 100644 ai_evals/.gitignore delete mode 100644 ai_evals/history/rollups/by_model.json delete mode 100644 ai_evals/history/rollups/by_surface.json delete mode 100644 ai_evals/history/rollups/by_variant.json delete mode 100644 ai_evals/history/rollups/latest.json diff --git a/ai_evals/.gitignore b/ai_evals/.gitignore new file mode 100644 index 0000000000..2eea525d88 --- /dev/null +++ b/ai_evals/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/ai_evals/cli/index.ts b/ai_evals/cli/index.ts index 4f2e6d605d..4bf9f735ba 100644 --- a/ai_evals/cli/index.ts +++ b/ai_evals/cli/index.ts @@ -335,7 +335,7 @@ async function handleHistory(limit: number, json: boolean) { process.stdout.write(`Official runs: ${entries.length}\n`); for (const entry of entries) { process.stdout.write( - `- ${entry.timestamp} | ${formatSurfaceLabel(entry.surface)} | ${entry.variant_name} | ${formatPercent(entry.metrics.quality.pass_rate)} | flake ${formatPercent(entry.metrics.reliability.flake_rate)} | avg ${formatNumber(entry.metrics.efficiency.latency_ms_mean)} ms | ${entry.case_count} cases x ${entry.runs_per_case} runs\n` + `- ${entry.timestamp} | ${formatSurfaceLabel(entry.surface)} | ${entry.label} | ${formatPercent(entry.metrics.quality.pass_rate)} | flake ${formatPercent(entry.metrics.reliability.flake_rate)} | avg ${formatNumber(entry.metrics.efficiency.latency_ms_mean)} ms | ${entry.case_count} cases x ${entry.runs_per_case} runs\n` ); } } diff --git a/ai_evals/cli/results.ts b/ai_evals/cli/results.ts index b803049a99..95ef0014aa 100644 --- a/ai_evals/cli/results.ts +++ b/ai_evals/cli/results.ts @@ -224,7 +224,7 @@ export function buildOfficialRunFromResult( suite_version: "cli-benchmark-v1", scoring_version: "cli-deterministic-v1", surface: result.canonicalSurface, - variant_name: labelOverride ?? result.label, + label: labelOverride ?? result.label, provider: result.provider, model: result.model, judge_model: result.judgeModel, diff --git a/ai_evals/history/README.md b/ai_evals/history/README.md index a600cc12ab..c3101a9a87 100644 --- a/ai_evals/history/README.md +++ b/ai_evals/history/README.md @@ -27,7 +27,6 @@ untracked location. - `benchmark-run.schema.json`: contract for one official run snapshot - `runs/`: detailed per-run JSON snapshots - `summary.jsonl`: one compact summary row per official run -- `rollups/`: chart-ready aggregates rebuilt from `summary.jsonl` ## Summary Metrics @@ -61,7 +60,6 @@ This command will: 1. validate the input shape 2. write the detailed snapshot under `runs/` 3. upsert one summary row in `summary.jsonl` -4. rebuild the rollup JSON files -The writer is intentionally simple so frontend and CLI runners can adopt the -same format later. +The writer is intentionally simple. Official history is just the detailed run +files plus the compact summary index. diff --git a/ai_evals/history/benchmark-run.schema.json b/ai_evals/history/benchmark-run.schema.json index d973dd7ee9..2c481e07af 100644 --- a/ai_evals/history/benchmark-run.schema.json +++ b/ai_evals/history/benchmark-run.schema.json @@ -9,7 +9,6 @@ "suite_version", "scoring_version", "surface", - "variant_name", "provider", "model", "runs_per_case", @@ -42,7 +41,7 @@ "type": "string", "minLength": 1 }, - "variant_name": { + "label": { "type": "string", "minLength": 1 }, diff --git a/ai_evals/history/rollups/by_model.json b/ai_evals/history/rollups/by_model.json deleted file mode 100644 index 6d6a7f0a24..0000000000 --- a/ai_evals/history/rollups/by_model.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "generatedAt": "2026-04-03T16:13:24.774Z", - "groupKey": "provider:model", - "groups": { - "anthropic:claude-haiku-4-5-20251001": [ - { - "run_id": "2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4", - "timestamp": "2026-04-03T16:13:24.771Z", - "git_sha": "8c9603a01fa48a3e45b3380daac7814b9ed46acf", - "suite_version": "cli-benchmark-v1", - "scoring_version": "cli-deterministic-v1", - "surface": "frontend-flow", - "variant_name": "baseline", - "provider": "anthropic", - "model": "claude-haiku-4-5-20251001", - "judge_model": "claude-sonnet-4-6", - "runs_per_case": 1, - "case_count": 7, - "run_path": "runs/2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4.json", - "metrics": { - "quality": { - "pass_rate": 0.8571428571428571, - "deterministic_pass_rate": 0.8571428571428571, - "judge_score_mean": 85.42857142857143, - "judge_score_median": 88, - "judge_score_p10": 72, - "quality_score": 85.71428571428571 - }, - "reliability": { - "runs_per_case": 1, - "flake_rate": 0, - "path_consistency": 1 - }, - "efficiency": { - "latency_ms_mean": 143566.42857142858, - "latency_ms_median": 147598, - "tokens_total_mean": 0, - "tool_calls_mean": 11.714285714285714, - "iterations_mean": 12.714285714285714, - "estimated_cost_mean": 0, - "cost_per_success": 0, - "latency_per_success": 141550.33333333334, - "efficiency_score": 34.1017456040819, - "value_score": 29.230067660641627 - } - } - } - ] - } -} diff --git a/ai_evals/history/rollups/by_surface.json b/ai_evals/history/rollups/by_surface.json deleted file mode 100644 index 3c3b696d62..0000000000 --- a/ai_evals/history/rollups/by_surface.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "generatedAt": "2026-04-03T16:13:24.774Z", - "groupKey": "surface", - "groups": { - "frontend-flow": [ - { - "run_id": "2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4", - "timestamp": "2026-04-03T16:13:24.771Z", - "git_sha": "8c9603a01fa48a3e45b3380daac7814b9ed46acf", - "suite_version": "cli-benchmark-v1", - "scoring_version": "cli-deterministic-v1", - "surface": "frontend-flow", - "variant_name": "baseline", - "provider": "anthropic", - "model": "claude-haiku-4-5-20251001", - "judge_model": "claude-sonnet-4-6", - "runs_per_case": 1, - "case_count": 7, - "run_path": "runs/2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4.json", - "metrics": { - "quality": { - "pass_rate": 0.8571428571428571, - "deterministic_pass_rate": 0.8571428571428571, - "judge_score_mean": 85.42857142857143, - "judge_score_median": 88, - "judge_score_p10": 72, - "quality_score": 85.71428571428571 - }, - "reliability": { - "runs_per_case": 1, - "flake_rate": 0, - "path_consistency": 1 - }, - "efficiency": { - "latency_ms_mean": 143566.42857142858, - "latency_ms_median": 147598, - "tokens_total_mean": 0, - "tool_calls_mean": 11.714285714285714, - "iterations_mean": 12.714285714285714, - "estimated_cost_mean": 0, - "cost_per_success": 0, - "latency_per_success": 141550.33333333334, - "efficiency_score": 34.1017456040819, - "value_score": 29.230067660641627 - } - } - } - ] - } -} diff --git a/ai_evals/history/rollups/by_variant.json b/ai_evals/history/rollups/by_variant.json deleted file mode 100644 index 8997764151..0000000000 --- a/ai_evals/history/rollups/by_variant.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "generatedAt": "2026-04-03T16:13:24.774Z", - "groupKey": "surface:variant_name", - "groups": { - "frontend-flow:baseline": [ - { - "run_id": "2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4", - "timestamp": "2026-04-03T16:13:24.771Z", - "git_sha": "8c9603a01fa48a3e45b3380daac7814b9ed46acf", - "suite_version": "cli-benchmark-v1", - "scoring_version": "cli-deterministic-v1", - "surface": "frontend-flow", - "variant_name": "baseline", - "provider": "anthropic", - "model": "claude-haiku-4-5-20251001", - "judge_model": "claude-sonnet-4-6", - "runs_per_case": 1, - "case_count": 7, - "run_path": "runs/2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4.json", - "metrics": { - "quality": { - "pass_rate": 0.8571428571428571, - "deterministic_pass_rate": 0.8571428571428571, - "judge_score_mean": 85.42857142857143, - "judge_score_median": 88, - "judge_score_p10": 72, - "quality_score": 85.71428571428571 - }, - "reliability": { - "runs_per_case": 1, - "flake_rate": 0, - "path_consistency": 1 - }, - "efficiency": { - "latency_ms_mean": 143566.42857142858, - "latency_ms_median": 147598, - "tokens_total_mean": 0, - "tool_calls_mean": 11.714285714285714, - "iterations_mean": 12.714285714285714, - "estimated_cost_mean": 0, - "cost_per_success": 0, - "latency_per_success": 141550.33333333334, - "efficiency_score": 34.1017456040819, - "value_score": 29.230067660641627 - } - } - } - ] - } -} diff --git a/ai_evals/history/rollups/latest.json b/ai_evals/history/rollups/latest.json deleted file mode 100644 index 7ea1651e18..0000000000 --- a/ai_evals/history/rollups/latest.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "generatedAt": "2026-04-03T16:13:24.774Z", - "latestRun": { - "run_id": "2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4", - "timestamp": "2026-04-03T16:13:24.771Z", - "git_sha": "8c9603a01fa48a3e45b3380daac7814b9ed46acf", - "suite_version": "cli-benchmark-v1", - "scoring_version": "cli-deterministic-v1", - "surface": "frontend-flow", - "variant_name": "baseline", - "provider": "anthropic", - "model": "claude-haiku-4-5-20251001", - "judge_model": "claude-sonnet-4-6", - "runs_per_case": 1, - "case_count": 7, - "run_path": "runs/2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4.json", - "metrics": { - "quality": { - "pass_rate": 0.8571428571428571, - "deterministic_pass_rate": 0.8571428571428571, - "judge_score_mean": 85.42857142857143, - "judge_score_median": 88, - "judge_score_p10": 72, - "quality_score": 85.71428571428571 - }, - "reliability": { - "runs_per_case": 1, - "flake_rate": 0, - "path_consistency": 1 - }, - "efficiency": { - "latency_ms_mean": 143566.42857142858, - "latency_ms_median": 147598, - "tokens_total_mean": 0, - "tool_calls_mean": 11.714285714285714, - "iterations_mean": 12.714285714285714, - "estimated_cost_mean": 0, - "cost_per_success": 0, - "latency_per_success": 141550.33333333334, - "efficiency_score": 34.1017456040819, - "value_score": 29.230067660641627 - } - } - }, - "latestBySurface": { - "frontend-flow": { - "run_id": "2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4", - "timestamp": "2026-04-03T16:13:24.771Z", - "git_sha": "8c9603a01fa48a3e45b3380daac7814b9ed46acf", - "suite_version": "cli-benchmark-v1", - "scoring_version": "cli-deterministic-v1", - "surface": "frontend-flow", - "variant_name": "baseline", - "provider": "anthropic", - "model": "claude-haiku-4-5-20251001", - "judge_model": "claude-sonnet-4-6", - "runs_per_case": 1, - "case_count": 7, - "run_path": "runs/2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4.json", - "metrics": { - "quality": { - "pass_rate": 0.8571428571428571, - "deterministic_pass_rate": 0.8571428571428571, - "judge_score_mean": 85.42857142857143, - "judge_score_median": 88, - "judge_score_p10": 72, - "quality_score": 85.71428571428571 - }, - "reliability": { - "runs_per_case": 1, - "flake_rate": 0, - "path_consistency": 1 - }, - "efficiency": { - "latency_ms_mean": 143566.42857142858, - "latency_ms_median": 147598, - "tokens_total_mean": 0, - "tool_calls_mean": 11.714285714285714, - "iterations_mean": 12.714285714285714, - "estimated_cost_mean": 0, - "cost_per_success": 0, - "latency_per_success": 141550.33333333334, - "efficiency_score": 34.1017456040819, - "value_score": 29.230067660641627 - } - } - } - } -} diff --git a/ai_evals/history/runs/2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4.json b/ai_evals/history/runs/2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4.json index a8b766dd7f..6ade2c2e31 100644 --- a/ai_evals/history/runs/2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4.json +++ b/ai_evals/history/runs/2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4.json @@ -4,7 +4,7 @@ "suite_version": "cli-benchmark-v1", "scoring_version": "cli-deterministic-v1", "surface": "frontend-flow", - "variant_name": "baseline", + "label": "baseline", "provider": "anthropic", "model": "claude-haiku-4-5-20251001", "judge_model": "claude-sonnet-4-6", diff --git a/ai_evals/history/summary.jsonl b/ai_evals/history/summary.jsonl index c7172107a8..e28b4e8de6 100644 --- a/ai_evals/history/summary.jsonl +++ b/ai_evals/history/summary.jsonl @@ -1 +1 @@ -{"run_id":"2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4","timestamp":"2026-04-03T16:13:24.771Z","git_sha":"8c9603a01fa48a3e45b3380daac7814b9ed46acf","suite_version":"cli-benchmark-v1","scoring_version":"cli-deterministic-v1","surface":"frontend-flow","variant_name":"baseline","provider":"anthropic","model":"claude-haiku-4-5-20251001","judge_model":"claude-sonnet-4-6","runs_per_case":1,"case_count":7,"run_path":"runs/2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4.json","metrics":{"quality":{"pass_rate":0.8571428571428571,"deterministic_pass_rate":0.8571428571428571,"judge_score_mean":85.42857142857143,"judge_score_median":88,"judge_score_p10":72,"quality_score":85.71428571428571},"reliability":{"runs_per_case":1,"flake_rate":0,"path_consistency":1},"efficiency":{"latency_ms_mean":143566.42857142858,"latency_ms_median":147598,"tokens_total_mean":0,"tool_calls_mean":11.714285714285714,"iterations_mean":12.714285714285714,"estimated_cost_mean":0,"cost_per_success":0,"latency_per_success":141550.33333333334,"efficiency_score":34.1017456040819,"value_score":29.230067660641627}}} +{"run_id":"2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4","timestamp":"2026-04-03T16:13:24.771Z","git_sha":"8c9603a01fa48a3e45b3380daac7814b9ed46acf","suite_version":"cli-benchmark-v1","scoring_version":"cli-deterministic-v1","surface":"frontend-flow","label":"baseline","provider":"anthropic","model":"claude-haiku-4-5-20251001","judge_model":"claude-sonnet-4-6","runs_per_case":1,"case_count":7,"run_path":"runs/2026-04-03t16-13-24-771z__frontend-flow__baseline__anthropic__claude-haiku-4-5-20251001__8c9603a01fa4.json","metrics":{"quality":{"pass_rate":0.8571428571428571,"deterministic_pass_rate":0.8571428571428571,"judge_score_mean":85.42857142857143,"judge_score_median":88,"judge_score_p10":72,"quality_score":85.71428571428571},"reliability":{"runs_per_case":1,"flake_rate":0,"path_consistency":1},"efficiency":{"latency_ms_mean":143566.42857142858,"latency_ms_median":147598,"tokens_total_mean":0,"tool_calls_mean":11.714285714285714,"iterations_mean":12.714285714285714,"estimated_cost_mean":0,"cost_per_success":0,"latency_per_success":141550.33333333334,"efficiency_score":34.1017456040819,"value_score":29.230067660641627}}} diff --git a/ai_evals/history/writer.mjs b/ai_evals/history/writer.mjs index 0c9c9f6f5f..87cefeeba7 100644 --- a/ai_evals/history/writer.mjs +++ b/ai_evals/history/writer.mjs @@ -7,7 +7,6 @@ const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url)); export const DEFAULT_HISTORY_DIR = MODULE_DIR; const SUMMARY_FILENAME = "summary.jsonl"; const RUNS_DIRNAME = "runs"; -const ROLLUPS_DIRNAME = "rollups"; export async function appendOfficialRun(input, options = {}) { const absoluteHistoryDir = path.resolve(options.historyDir ?? DEFAULT_HISTORY_DIR); @@ -26,7 +25,6 @@ export async function appendOfficialRun(input, options = {}) { const nextSummaries = upsertSummaryEntry(summaries, summaryEntry); await writeSummaryEntries(summaryPath, nextSummaries); - await writeRollups(absoluteHistoryDir, nextSummaries); return { status: "ok", @@ -36,33 +34,11 @@ export async function appendOfficialRun(input, options = {}) { }; } -export async function loadLatestHistoryRollup(historyDir = DEFAULT_HISTORY_DIR) { - const rollupPath = path.join(path.resolve(historyDir), ROLLUPS_DIRNAME, "latest.json"); - return await loadJsonFile(rollupPath); -} - -export async function loadHistoryRollup( - name, - historyDir = DEFAULT_HISTORY_DIR -) { - const rollupPath = path.join(path.resolve(historyDir), ROLLUPS_DIRNAME, name); - return await loadJsonFile(rollupPath); -} - export async function loadSummaryHistory(historyDir = DEFAULT_HISTORY_DIR) { const summaryPath = path.join(path.resolve(historyDir), SUMMARY_FILENAME); return await loadSummaryEntries(summaryPath); } -async function loadJsonFile(filePath) { - const raw = await readFile(filePath, "utf8"); - try { - return JSON.parse(raw); - } catch (error) { - throw new Error(`Failed to parse JSON from ${filePath}: ${error.message}`); - } -} - function normalizeRun(input) { assertPlainObject(input, "benchmark run"); @@ -74,7 +50,7 @@ function normalizeRun(input) { "scoring_version" ); const surface = assertNonEmptyString(input.surface, "surface"); - const variantName = assertNonEmptyString(input.variant_name, "variant_name"); + const label = assertNonEmptyString(input.label, "label"); const provider = assertNonEmptyString(input.provider, "provider"); const model = assertNonEmptyString(input.model, "model"); const judgeModel = @@ -98,7 +74,7 @@ function normalizeRun(input) { : buildRunId({ timestamp, surface, - variantName, + label, provider, model, gitSha @@ -112,7 +88,7 @@ function normalizeRun(input) { suite_version: suiteVersion, scoring_version: scoringVersion, surface, - variant_name: variantName, + label, provider, model, judge_model: judgeModel, @@ -230,14 +206,14 @@ function normalizeCases(input) { }); } -function buildRunId({ timestamp, surface, variantName, provider, model, gitSha }) { +function buildRunId({ timestamp, surface, label, provider, model, gitSha }) { const timestampSlug = timestamp.replaceAll(":", "-").replaceAll(".", "-"); const shortSha = gitSha.slice(0, 12); return [ slugify(timestampSlug), slugify(surface), - slugify(variantName), + slugify(label), slugify(provider), slugify(model), shortSha @@ -254,7 +230,7 @@ function buildSummaryEntry(run, runRelativePath) { suite_version: run.suite_version, scoring_version: run.scoring_version, surface: run.surface, - variant_name: run.variant_name, + label: run.label, provider: run.provider, model: run.model, judge_model: run.judge_model, @@ -303,76 +279,8 @@ async function writeSummaryEntries(summaryPath, entries) { await writeFile(summaryPath, content, "utf8"); } -async function writeRollups(historyDir, summaries) { - const rollupsDir = path.join(historyDir, ROLLUPS_DIRNAME); - const generatedAt = new Date().toISOString(); - const latestFirst = [...summaries].sort((left, right) => - right.timestamp.localeCompare(left.timestamp) - ); - const latestBySurface = {}; - - for (const summary of latestFirst) { - if (!(summary.surface in latestBySurface)) { - latestBySurface[summary.surface] = summary; - } - } - - const latestRollup = { - generatedAt, - latestRun: latestFirst[0] ?? null, - latestBySurface - }; - const bySurfaceRollup = { - generatedAt, - groupKey: "surface", - groups: groupSummaries(summaries, (summary) => summary.surface) - }; - const byVariantRollup = { - generatedAt, - groupKey: "surface:variant_name", - groups: groupSummaries( - summaries, - (summary) => `${summary.surface}:${summary.variant_name}` - ) - }; - const byModelRollup = { - generatedAt, - groupKey: "provider:model", - groups: groupSummaries( - summaries, - (summary) => `${summary.provider}:${summary.model}` - ) - }; - - await Promise.all([ - writeJsonFile(path.join(rollupsDir, "latest.json"), latestRollup), - writeJsonFile(path.join(rollupsDir, "by_surface.json"), bySurfaceRollup), - writeJsonFile(path.join(rollupsDir, "by_variant.json"), byVariantRollup), - writeJsonFile(path.join(rollupsDir, "by_model.json"), byModelRollup) - ]); -} - -function groupSummaries(summaries, getGroupKey) { - const groups = {}; - - for (const summary of summaries) { - const key = getGroupKey(summary); - groups[key] ??= []; - groups[key].push(summary); - } - - for (const groupEntries of Object.values(groups)) { - groupEntries.sort((left, right) => left.timestamp.localeCompare(right.timestamp)); - } - - return groups; -} - async function ensureHistoryLayout(historyDir) { - await Promise.all([ - mkdir(path.join(historyDir, RUNS_DIRNAME), { recursive: true }), - mkdir(path.join(historyDir, ROLLUPS_DIRNAME), { recursive: true }) - ]); + await mkdir(path.join(historyDir, RUNS_DIRNAME), { recursive: true }); } async function writeJsonFile(filePath, value) {