/** * earth-now.co * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * File: apps/api/src/scripts/refit.ts * Purpose: CLI re-fit + diff report (pnpm models:refit) — old vs new value at now, jump, deployability gate per metric */ import { counterValue, formatValue } from "@earth-now/counter"; import { assertDeployable, diffModels } from "@earth-now/models"; import { fitAllModels, loadFixtures, loadRegistry } from "@earth-now/registry"; import { fitHorizon, metricConstraints } from "../store.js"; const nowMs = Date.now(); const registry = loadRegistry(); const fixtures = loadFixtures(); const { fromMs, toMs } = fitHorizon(nowMs); const fitOpts = { validateFromMs: fromMs, validateToMs: toMs }; const horizon = { fromMs, toMs }; // The currently deployed set. TODO: read from the counter_models table once the // DB layer lands — until then the fixture fit stands in for it, so the reported // jump is 0 by construction and the report exercises the full gate anyway. const previousModels = fitAllModels(registry.metrics, fixtures, fitOpts).models; const next = fitAllModels(registry.metrics, fixtures, fitOpts); let failures = 0; console.log( `refit report @ ${new Date(nowMs).toISOString()} — ${registry.metrics.length} metrics, horizon ${new Date(fromMs).toISOString()} → ${new Date(toMs).toISOString()}\n`, ); for (const entry of registry.metrics) { const previous = previousModels.get(entry.id) ?? null; const model = next.models.get(entry.id); if (!model) { failures += 1; const issues = next.blocked.find((b) => b.metricId === entry.id)?.issues ?? []; const detail = issues.map((i) => `${i.code}: ${i.message}`).join("; ") || "no model produced"; console.log(`✗ ${entry.id}: BLOCKED — ${detail}`); continue; } const hints = model.displayHints; const newValue = counterValue(model, nowMs); const oldText = previous ? formatValue(counterValue(previous, nowMs), hints) : "(new)"; const jump = previous ? diffModels(previous, model, nowMs) : 0; const issues = assertDeployable(previous, model, metricConstraints(entry), nowMs, horizon); const ok = issues.length === 0; if (!ok) failures += 1; console.log( `${ok ? "✓" : "✗"} ${entry.id}: old=${oldText} new=${formatValue(newValue, hints)} ${hints.unit} jump=${jump.toPrecision(3)}${ok ? "" : ` — NOT DEPLOYABLE: ${issues.map((i) => i.code).join(", ")}`}`, ); } console.log( `\n${failures === 0 ? "all models deployable" : `${failures} metric(s) NOT deployable — deployment must be blocked`}`, ); if (failures > 0) process.exit(1);