diff --git a/AGENTS.md b/AGENTS.md index a414e40d..de787654 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,4 +7,4 @@ - Before providing a Staging preview URL, request `/api/microcosm/staging/runs` for the selected country and confirm that the JSON response reports the repository as available. Checking only the HTTP status is insufficient because the route can return HTTP 200 with `available: false`. Treat 401, 403, the 404 response Hugging Face may return for an unauthorized private repository, and any `available: false` response as a failed launch. - When investigating a discrepant Microcosm calibration target, follow [the shared investigation workflow](docs/ai/workflows/investigate-microcosm-target.md) and its linked role-specific reviews. - Before starting the application for Cross-dataset work, configure exactly one DIR or URL artifact location for each country you will use, as described in [the Cross-dataset application configuration](docs/cross-dataset-api.md#configure-the-application). -- Adding a Microcosm country is one entry in `frontend/lib/microcosm/countries.ts`; pages are gated by the registration's capabilities and the release artifact's `country` block, never by country code (see [the spec-driven countries note](docs/spec-driven-countries.md)). +- Adding a Microcosm country is one entry in `frontend/packages/calibration-tree/src/countries.ts`; pages are gated by the registration's capabilities and the release artifact's `country` block, never by country code (see [the spec-driven countries note](docs/spec-driven-countries.md)). diff --git a/CLAUDE.md b/CLAUDE.md index e8b7c29a..d2891d09 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,4 +2,4 @@ - When investigating a discrepant Microcosm calibration target, follow [the shared investigation workflow](docs/ai/workflows/investigate-microcosm-target.md) and its linked role-specific reviews. - Before starting the application for Cross-dataset work, configure exactly one DIR or URL artifact location for each country you will use, as described in [the Cross-dataset application configuration](docs/cross-dataset-api.md#configure-the-application). -- Adding a Microcosm country is one entry in `frontend/lib/microcosm/countries.ts`; pages are gated by the registration's capabilities and the release artifact's `country` block, never by country code (see [the spec-driven countries note](docs/spec-driven-countries.md)). +- Adding a Microcosm country is one entry in `frontend/packages/calibration-tree/src/countries.ts`; pages are gated by the registration's capabilities and the release artifact's `country` block, never by country code (see [the spec-driven countries note](docs/spec-driven-countries.md)). diff --git a/README.md b/README.md index bbdf607c..c6fe1fe0 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ with `POPULACE_UK_STAGING_HF_REPO` or `POPULACE_UK_STAGING_HF_REVISION`. Never use a `NEXT_PUBLIC_` variable for a private-repository credential. The staging contract fixtures under -`frontend/lib/microcosm/fixtures/staging-contract/` are byte-identical copies +`frontend/packages/calibration-tree/src/fixtures/staging-contract/` are byte-identical copies of Microcosm's canonical producer fixtures. The consumer tests pin each version's `SHA256SUMS` digest and verify every listed file. A contract change must update the canonical Microcosm fixture, its digest manifest, this copy, diff --git a/docs/calibration-tree-artifacts.md b/docs/calibration-tree-artifacts.md index 390f2a59..240245dc 100644 --- a/docs/calibration-tree-artifacts.md +++ b/docs/calibration-tree-artifacts.md @@ -4,6 +4,23 @@ The calibration explorer reads precomputed schema-6 JSON from private Vercel Blob storage. The browser does not download calibration diagnostics or rebuild the hierarchy. Schemas 1 through 5 are unsupported. +## Code + +The builder, the schema-6 types, parsers and serializers, the Blob I/O, the +release and staging readers the builder reads from, and the publisher +(`src/publish-calibration-tree.ts`, run by `bun run publish:calibration-tree`) +live in the `@policyengine/calibration-tree` workspace package at +`frontend/packages/calibration-tree/`. The dashboard imports it through its +subpath exports, such as `@policyengine/calibration-tree/calibration-tree-bundle`. +The package imports only its own modules, Node built-ins and `@vercel/blob`, +and its boundary test enforces that. + +`src/fixtures/calibration-tree-golden.json` records the exact bytes the builder +writes for representative US and UK release and staging inputs, the +comparisons between them, and the manifest. The golden tests require every +change to reproduce it. Regenerate it only for an intended output change, as +`src/test-support/calibration-tree-golden-cases.ts` describes. + ## Required Blob folder schema ```text @@ -355,7 +372,8 @@ fails publication and therefore fails the GitHub Actions job. ## Verification -Run from `frontend/`: +Run from `frontend/`; `bun test` includes the package's tests and +`bun run lint` also typechecks the package: ```sh bun test @@ -363,6 +381,9 @@ bun run lint bun run build ``` +To check the package on its own, run `bun test` and `bun run typecheck` from +`frontend/packages/calibration-tree/`. + Then verify that: 1. The manifest and all parts declare schema 6. diff --git a/docs/spec-driven-countries.md b/docs/spec-driven-countries.md index 7680c713..b285d73c 100644 --- a/docs/spec-driven-countries.md +++ b/docs/spec-driven-countries.md @@ -24,7 +24,7 @@ contract: they are served only by `/microcosm/datasets` from ## Code-owned metadata that remains -`COUNTRY_REGISTRY` in `frontend/lib/microcosm/countries.ts` is the only required +`COUNTRY_REGISTRY` in `frontend/packages/calibration-tree/src/countries.ts` is the only required registration point: country unions and parsers, selector lists, dataset and national-geography labels, public/private link behavior, and page gating read the registration or the artifact `country` block. Overview and target-browser @@ -73,10 +73,10 @@ production tables. ### Country `release_manifest.country` is read by `releaseCountry` in -`frontend/lib/microcosm/latest-artifact.ts` and served as `country` on the +`frontend/packages/calibration-tree/src/latest-artifact.ts` and served as `country` on the overview summary and the target-diagnostics page (client type `MicrocosmArtifactCountry`). Registration lives in -`frontend/lib/microcosm/countries.ts`; adding a country is one entry there. +`frontend/packages/calibration-tree/src/countries.ts`; adding a country is one entry there. ```json { diff --git a/frontend/app/api/microcosm/builds/route.ts b/frontend/app/api/microcosm/builds/route.ts index 5dfbd34d..0e06f6b9 100644 --- a/frontend/app/api/microcosm/builds/route.ts +++ b/frontend/app/api/microcosm/builds/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server"; import { parseBuildSource } from "@/lib/microcosm/build-source"; import { loadBuildRuns } from "@/lib/microcosm/build-runs"; -import { parseCountry, scrub } from "@/lib/microcosm/latest-artifact"; +import { parseCountry, scrub } from "@policyengine/calibration-tree/latest-artifact"; export const dynamic = "force-dynamic"; export const revalidate = 0; diff --git a/frontend/app/api/microcosm/builds/run/route.ts b/frontend/app/api/microcosm/builds/run/route.ts index d6c1250e..cf80ae7a 100644 --- a/frontend/app/api/microcosm/builds/run/route.ts +++ b/frontend/app/api/microcosm/builds/run/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server"; import { parseBuildSource } from "@/lib/microcosm/build-source"; import { loadBuildRun } from "@/lib/microcosm/build-runs"; -import { parseCountry, scrub } from "@/lib/microcosm/latest-artifact"; +import { parseCountry, scrub } from "@policyengine/calibration-tree/latest-artifact"; export const dynamic = "force-dynamic"; export const revalidate = 0; diff --git a/frontend/app/api/microcosm/compare/route.ts b/frontend/app/api/microcosm/compare/route.ts index bc6fa558..5083fb65 100644 --- a/frontend/app/api/microcosm/compare/route.ts +++ b/frontend/app/api/microcosm/compare/route.ts @@ -6,7 +6,7 @@ import { loadPointerReleaseId, parseCountry, scrub, -} from "@/lib/microcosm/latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; export const revalidate = 21_600; export const runtime = "nodejs"; diff --git a/frontend/app/api/microcosm/comparison-tree/route.test.ts b/frontend/app/api/microcosm/comparison-tree/route.test.ts index 4cf1fe9c..c0658c2d 100644 --- a/frontend/app/api/microcosm/comparison-tree/route.test.ts +++ b/frontend/app/api/microcosm/comparison-tree/route.test.ts @@ -4,7 +4,7 @@ import { gzipSync } from "node:zlib"; import { calibrationComparisonBuildArtifactId, calibrationComparisonPairArtifactId, -} from "@/lib/microcosm/calibration-comparison-bundle"; +} from "@policyengine/calibration-tree/calibration-comparison-bundle"; import { createCalibrationComparisonTreeHandler, type CalibrationComparisonTreeRouteDependencies, diff --git a/frontend/app/api/microcosm/comparison-tree/route.ts b/frontend/app/api/microcosm/comparison-tree/route.ts index d1692fe1..2001f26d 100644 --- a/frontend/app/api/microcosm/comparison-tree/route.ts +++ b/frontend/app/api/microcosm/comparison-tree/route.ts @@ -1,24 +1,24 @@ import { NextResponse } from "next/server"; import { withBasePath } from "@/lib/base-path"; -import { ensureCalibrationComparison } from "@/lib/microcosm/calibration-comparison-blob"; +import { ensureCalibrationComparison } from "@policyengine/calibration-tree/calibration-comparison-blob"; import { calibrationComparisonBuildArtifactId, calibrationComparisonPairArtifactId, -} from "@/lib/microcosm/calibration-comparison-bundle"; -import { getCalibrationTreeBlob } from "@/lib/microcosm/calibration-tree-blob"; +} from "@policyengine/calibration-tree/calibration-comparison-bundle"; +import { getCalibrationTreeBlob } from "@policyengine/calibration-tree/calibration-tree-blob"; import { immutableCalibrationTreePartResponse } from "@/lib/microcosm/calibration-tree-http"; import { isCalibrationTreePart, isSha256, type CalibrationTreePart, -} from "@/lib/microcosm/calibration-tree-artifact"; +} from "@policyengine/calibration-tree/calibration-tree-artifact"; import { isCountry, selectableCountries, type MicrocosmCountry, -} from "@/lib/microcosm/countries"; -import type { TargetChangeMode } from "@/lib/microcosm/target-change"; +} from "@policyengine/calibration-tree/countries"; +import type { TargetChangeMode } from "@policyengine/calibration-tree/target-change"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/frontend/app/api/microcosm/comparison-tree/target-detail/route.test.ts b/frontend/app/api/microcosm/comparison-tree/target-detail/route.test.ts index 465c6184..37c1e541 100644 --- a/frontend/app/api/microcosm/comparison-tree/target-detail/route.test.ts +++ b/frontend/app/api/microcosm/comparison-tree/target-detail/route.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; -import type { CalibrationTreeComparisonTargetDetailResponse } from "@/lib/microcosm/calibration-tree-artifact"; +import type { CalibrationTreeComparisonTargetDetailResponse } from "@policyengine/calibration-tree/calibration-tree-artifact"; import { createCalibrationComparisonTargetDetailHandler, type CalibrationComparisonTargetDetailRouteDependencies, diff --git a/frontend/app/api/microcosm/comparison-tree/target-detail/route.ts b/frontend/app/api/microcosm/comparison-tree/target-detail/route.ts index a078c0dc..e42b3d11 100644 --- a/frontend/app/api/microcosm/comparison-tree/target-detail/route.ts +++ b/frontend/app/api/microcosm/comparison-tree/target-detail/route.ts @@ -1,12 +1,12 @@ import { NextResponse } from "next/server"; -import { loadCalibrationComparisonTargetDetail } from "@/lib/microcosm/calibration-comparison-blob"; -import { isSha256 } from "@/lib/microcosm/calibration-tree-artifact"; +import { loadCalibrationComparisonTargetDetail } from "@policyengine/calibration-tree/calibration-comparison-blob"; +import { isSha256 } from "@policyengine/calibration-tree/calibration-tree-artifact"; import { isCountry, selectableCountries, type MicrocosmCountry, -} from "@/lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/frontend/app/api/microcosm/cross-dataset/route.ts b/frontend/app/api/microcosm/cross-dataset/route.ts index 10ec163f..924f10d3 100644 --- a/frontend/app/api/microcosm/cross-dataset/route.ts +++ b/frontend/app/api/microcosm/cross-dataset/route.ts @@ -3,7 +3,7 @@ import { NextResponse } from "next/server"; import { ArtifactError } from "@/lib/cross-dataset/artifact"; import { crossDatasetApiResponse } from "@/lib/cross-dataset/query"; import { configuredCrossDatasetReader } from "@/lib/cross-dataset/source"; -import { parseCountry } from "@/lib/microcosm/latest-artifact"; +import { parseCountry } from "@policyengine/calibration-tree/latest-artifact"; export const revalidate = 300; export const runtime = "nodejs"; diff --git a/frontend/app/api/microcosm/releases/route.ts b/frontend/app/api/microcosm/releases/route.ts index 53336b66..1a1b1c05 100644 --- a/frontend/app/api/microcosm/releases/route.ts +++ b/frontend/app/api/microcosm/releases/route.ts @@ -5,7 +5,7 @@ import { loadReleases, parseCountry, scrub, -} from "@/lib/microcosm/latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; export const revalidate = 60; diff --git a/frontend/app/api/microcosm/route.ts b/frontend/app/api/microcosm/route.ts index e734c9e8..e9401726 100644 --- a/frontend/app/api/microcosm/route.ts +++ b/frontend/app/api/microcosm/route.ts @@ -11,7 +11,7 @@ import { parseCountry, microcosmRepo, scrub, -} from "@/lib/microcosm/latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; export const revalidate = 21_600; export const runtime = "nodejs"; diff --git a/frontend/app/api/microcosm/staging/compare/route.ts b/frontend/app/api/microcosm/staging/compare/route.ts index df8ef2d9..f5ba57a3 100644 --- a/frontend/app/api/microcosm/staging/compare/route.ts +++ b/frontend/app/api/microcosm/staging/compare/route.ts @@ -1,12 +1,12 @@ import { NextResponse } from "next/server"; -import { hasCapability } from "@/lib/microcosm/countries"; +import { hasCapability } from "@policyengine/calibration-tree/countries"; import { loadPointerReleaseId, parseCountry, scrub, -} from "@/lib/microcosm/latest-artifact"; -import { loadStagingComparison } from "@/lib/microcosm/staging-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; +import { loadStagingComparison } from "@policyengine/calibration-tree/staging-artifact"; export const dynamic = "force-dynamic"; export const revalidate = 0; diff --git a/frontend/app/api/microcosm/staging/run/route.ts b/frontend/app/api/microcosm/staging/run/route.ts index 586e2827..0c01d4cf 100644 --- a/frontend/app/api/microcosm/staging/run/route.ts +++ b/frontend/app/api/microcosm/staging/run/route.ts @@ -1,8 +1,8 @@ import { NextResponse } from "next/server"; -import { hasCapability } from "@/lib/microcosm/countries"; -import { parseCountry, scrub } from "@/lib/microcosm/latest-artifact"; -import { loadStagingRun } from "@/lib/microcosm/staging-artifact"; +import { hasCapability } from "@policyengine/calibration-tree/countries"; +import { parseCountry, scrub } from "@policyengine/calibration-tree/latest-artifact"; +import { loadStagingRun } from "@policyengine/calibration-tree/staging-artifact"; export const dynamic = "force-dynamic"; export const revalidate = 0; diff --git a/frontend/app/api/microcosm/staging/runs/route.ts b/frontend/app/api/microcosm/staging/runs/route.ts index 2a1def49..418fc4db 100644 --- a/frontend/app/api/microcosm/staging/runs/route.ts +++ b/frontend/app/api/microcosm/staging/runs/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from "next/server"; -import { loadStagingRuns } from "@/lib/microcosm/staging-artifact"; -import { parseCountry, scrub } from "@/lib/microcosm/latest-artifact"; +import { loadStagingRuns } from "@policyengine/calibration-tree/staging-artifact"; +import { parseCountry, scrub } from "@policyengine/calibration-tree/latest-artifact"; export const dynamic = "force-dynamic"; export const revalidate = 0; diff --git a/frontend/app/api/microcosm/staging/target-change-tree/route.ts b/frontend/app/api/microcosm/staging/target-change-tree/route.ts index 5dd51949..b4ab3afe 100644 --- a/frontend/app/api/microcosm/staging/target-change-tree/route.ts +++ b/frontend/app/api/microcosm/staging/target-change-tree/route.ts @@ -5,13 +5,13 @@ import { classifyApiError, parseCountry, scrub, -} from "@/lib/microcosm/latest-artifact"; -import { isSha256 } from "@/lib/microcosm/calibration-tree-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; +import { isSha256 } from "@policyengine/calibration-tree/calibration-tree-artifact"; import { loadStagingTargetChangeDataset, loadStagingTargetChangeDatasetFromBuild, -} from "@/lib/microcosm/staging-artifact"; -import type { TargetChangeMode } from "@/lib/microcosm/target-change"; +} from "@policyengine/calibration-tree/staging-artifact"; +import type { TargetChangeMode } from "@policyengine/calibration-tree/target-change"; import { buildTargetChangeTree } from "@/lib/microcosm/target-change-tree"; export const dynamic = "force-dynamic"; diff --git a/frontend/app/api/microcosm/staging/target-diagnostics/route.ts b/frontend/app/api/microcosm/staging/target-diagnostics/route.ts index 8da6e8b2..80254929 100644 --- a/frontend/app/api/microcosm/staging/target-diagnostics/route.ts +++ b/frontend/app/api/microcosm/staging/target-diagnostics/route.ts @@ -1,8 +1,8 @@ import { NextResponse } from "next/server"; -import { hasCapability } from "@/lib/microcosm/countries"; -import { parseCountry, scrub } from "@/lib/microcosm/latest-artifact"; -import { loadStagingTargetDiagnostics } from "@/lib/microcosm/staging-artifact"; +import { hasCapability } from "@policyengine/calibration-tree/countries"; +import { parseCountry, scrub } from "@policyengine/calibration-tree/latest-artifact"; +import { loadStagingTargetDiagnostics } from "@policyengine/calibration-tree/staging-artifact"; export const dynamic = "force-dynamic"; export const revalidate = 0; diff --git a/frontend/app/api/microcosm/staging/target-tree/route.ts b/frontend/app/api/microcosm/staging/target-tree/route.ts index 4dae4471..748a113c 100644 --- a/frontend/app/api/microcosm/staging/target-tree/route.ts +++ b/frontend/app/api/microcosm/staging/target-tree/route.ts @@ -1,9 +1,9 @@ import { NextResponse } from "next/server"; -import { buildCalibrationTree, type CalibrationTreeTarget } from "@/lib/microcosm/calibration-tree"; +import { buildCalibrationTree, type CalibrationTreeTarget } from "@policyengine/calibration-tree/calibration-tree"; import { calibrationTreeRequestState } from "@/lib/microcosm/calibration-tree-request"; -import { classifyApiError, parseCountry, scrub } from "@/lib/microcosm/latest-artifact"; -import { loadStagingCalibration } from "@/lib/microcosm/staging-artifact"; +import { classifyApiError, parseCountry, scrub } from "@policyengine/calibration-tree/latest-artifact"; +import { loadStagingCalibration } from "@policyengine/calibration-tree/staging-artifact"; export const dynamic = "force-dynamic"; export const revalidate = 0; diff --git a/frontend/app/api/microcosm/target-diagnostics/route.ts b/frontend/app/api/microcosm/target-diagnostics/route.ts index 45b49265..aef1ebda 100644 --- a/frontend/app/api/microcosm/target-diagnostics/route.ts +++ b/frontend/app/api/microcosm/target-diagnostics/route.ts @@ -6,7 +6,7 @@ import { loadRelease, parseCountry, scrub, -} from "@/lib/microcosm/latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; export const revalidate = 21_600; export const runtime = "nodejs"; diff --git a/frontend/app/api/microcosm/target-investigation/route.ts b/frontend/app/api/microcosm/target-investigation/route.ts index f6446f82..3690a728 100644 --- a/frontend/app/api/microcosm/target-investigation/route.ts +++ b/frontend/app/api/microcosm/target-investigation/route.ts @@ -6,7 +6,7 @@ import { loadRelease, parseCountry, scrub, -} from "@/lib/microcosm/latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; export const revalidate = 21_600; diff --git a/frontend/app/api/microcosm/target-treemap/route.ts b/frontend/app/api/microcosm/target-treemap/route.ts index db910387..3e4e0d94 100644 --- a/frontend/app/api/microcosm/target-treemap/route.ts +++ b/frontend/app/api/microcosm/target-treemap/route.ts @@ -6,7 +6,7 @@ import { parseCountry, microcosmTargetTreemap, scrub, -} from "@/lib/microcosm/latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; export const revalidate = 21_600; export const runtime = "nodejs"; diff --git a/frontend/app/api/microcosm/tree-manifest/route.test.ts b/frontend/app/api/microcosm/tree-manifest/route.test.ts index f27ed8aa..99f096d3 100644 --- a/frontend/app/api/microcosm/tree-manifest/route.test.ts +++ b/frontend/app/api/microcosm/tree-manifest/route.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; -import { emptyCalibrationTreeManifest, withCalibrationTreeManifestEntry } from "@/lib/microcosm/calibration-tree-manifest"; +import { emptyCalibrationTreeManifest, withCalibrationTreeManifestEntry } from "@policyengine/calibration-tree/calibration-tree-manifest"; import { createCalibrationTreeManifestHandler } from "./route"; test("manifest API returns one country's current immutable release identity", async () => { diff --git a/frontend/app/api/microcosm/tree-manifest/route.ts b/frontend/app/api/microcosm/tree-manifest/route.ts index bfc79e68..efeb6ff9 100644 --- a/frontend/app/api/microcosm/tree-manifest/route.ts +++ b/frontend/app/api/microcosm/tree-manifest/route.ts @@ -1,11 +1,11 @@ import { NextResponse } from "next/server"; -import { readCalibrationTreeManifest } from "@/lib/microcosm/calibration-tree-blob"; -import { calibrationTreeCountryManifest } from "@/lib/microcosm/calibration-tree-manifest"; +import { readCalibrationTreeManifest } from "@policyengine/calibration-tree/calibration-tree-blob"; +import { calibrationTreeCountryManifest } from "@policyengine/calibration-tree/calibration-tree-manifest"; import { isCountry, selectableCountries, -} from "@/lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/frontend/app/api/microcosm/tree/route.ts b/frontend/app/api/microcosm/tree/route.ts index 8a6e64a6..02e1d0fc 100644 --- a/frontend/app/api/microcosm/tree/route.ts +++ b/frontend/app/api/microcosm/tree/route.ts @@ -1,19 +1,19 @@ import { NextResponse } from "next/server"; import { withBasePath } from "@/lib/base-path"; -import { getCalibrationTreeBlob } from "@/lib/microcosm/calibration-tree-blob"; +import { getCalibrationTreeBlob } from "@policyengine/calibration-tree/calibration-tree-blob"; import { immutableCalibrationTreePartResponse } from "@/lib/microcosm/calibration-tree-http"; import { isCalibrationTreePart, isSha256, type CalibrationTreePart, -} from "@/lib/microcosm/calibration-tree-artifact"; -import { resolveCalibrationRelease } from "@/lib/microcosm/calibration-release-locator"; +} from "@policyengine/calibration-tree/calibration-tree-artifact"; +import { resolveCalibrationRelease } from "@policyengine/calibration-tree/calibration-release-locator"; import { isCountry, selectableCountries, type MicrocosmCountry, -} from "@/lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/frontend/app/api/microcosm/variable/route.ts b/frontend/app/api/microcosm/variable/route.ts index b32a43bf..4e10e052 100644 --- a/frontend/app/api/microcosm/variable/route.ts +++ b/frontend/app/api/microcosm/variable/route.ts @@ -4,7 +4,7 @@ import { promisify } from "node:util"; import { NextResponse } from "next/server"; -import { microcosmRepo, scrub } from "@/lib/microcosm/latest-artifact"; +import { microcosmRepo, scrub } from "@policyengine/calibration-tree/latest-artifact"; import { HOSTED_US_RELEASE, reviewedVariableRelease, diff --git a/frontend/app/microcosm/page.tsx b/frontend/app/microcosm/page.tsx index 9314538d..1e8e6245 100644 --- a/frontend/app/microcosm/page.tsx +++ b/frontend/app/microcosm/page.tsx @@ -1,7 +1,7 @@ import { AppShell } from "@/components/layout/app-shell"; import { MicrocosmOverviewView } from "@/components/microcosm/microcosm-overview-view"; import { publishedReleaseSelection } from "@/lib/microcosm/calibration-selection"; -import { parseCountry } from "@/lib/microcosm/countries"; +import { parseCountry } from "@policyengine/calibration-tree/countries"; interface MicrocosmOverviewPageProps { searchParams?: Promise>; diff --git a/frontend/app/microcosm/targets/page.tsx b/frontend/app/microcosm/targets/page.tsx index a3d2e99c..5d46b1a6 100644 --- a/frontend/app/microcosm/targets/page.tsx +++ b/frontend/app/microcosm/targets/page.tsx @@ -1,7 +1,7 @@ import { AppShell } from "@/components/layout/app-shell"; import { MicrocosmTargetsView } from "@/components/microcosm/microcosm-targets-view"; import { publishedReleaseSelection } from "@/lib/microcosm/calibration-selection"; -import { parseCountry } from "@/lib/microcosm/countries"; +import { parseCountry } from "@policyengine/calibration-tree/countries"; interface MicrocosmTargetsPageProps { searchParams?: Promise>; diff --git a/frontend/bun.lock b/frontend/bun.lock index a664d676..b1329105 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -5,6 +5,7 @@ "": { "name": "calibration-diagnostics-frontend", "dependencies": { + "@policyengine/calibration-tree": "workspace:*", "@policyengine/ui-kit": "^0.9.0", "@tanstack/react-query": "^5.84.0", "@tanstack/react-query-devtools": "^5.83.0", @@ -31,6 +32,17 @@ "typescript": "^5.8.0", }, }, + "packages/calibration-tree": { + "name": "@policyengine/calibration-tree", + "version": "0.1.0", + "dependencies": { + "@vercel/blob": "^2.6.1", + }, + "devDependencies": { + "@types/bun": "^1.3.14", + "typescript": "^5.8.0", + }, + }, }, "packages": { "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -125,6 +137,8 @@ "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.6", "", { "os": "win32", "cpu": "x64" }, "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA=="], + "@policyengine/calibration-tree": ["@policyengine/calibration-tree@workspace:packages/calibration-tree"], + "@policyengine/ui-kit": ["@policyengine/ui-kit@0.9.1", "", { "dependencies": { "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "d3-geo": "^3.1.0", "dayjs": "^1.11.13", "lucide-react": "^0.577.0", "radix-ui": "^1.4.3", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.4.0" }, "peerDependencies": { "react": ">=19", "react-dom": ">=19", "recharts": ">=2.12" } }, "sha512-wigj/WodytMGqW8n02IKda2uYVxzBE3iDqzRweDJn+4VZQqb02rCf1TBqsM9WGslkPAetavjAoQrBCVnGZQEKQ=="], "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], diff --git a/frontend/bunfig.toml b/frontend/bunfig.toml index 8755352e..27f7bdaa 100644 --- a/frontend/bunfig.toml +++ b/frontend/bunfig.toml @@ -1,2 +1,7 @@ +[install] +# Keep the hoisted node_modules layout that Next.js and Vercel build against. +# Bun otherwise installs a project with workspaces using its isolated linker. +linker = "hoisted" + [test] preload = ["./test/setup.ts"] diff --git a/frontend/components/layout/country-context.tsx b/frontend/components/layout/country-context.tsx index 4e8d4a4b..868f4fde 100644 --- a/frontend/components/layout/country-context.tsx +++ b/frontend/components/layout/country-context.tsx @@ -13,7 +13,7 @@ import { DEFAULT_COUNTRY, isCountry, type MicrocosmCountry, -} from "@/lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; export type Country = MicrocosmCountry; diff --git a/frontend/components/layout/mobile-nav.tsx b/frontend/components/layout/mobile-nav.tsx index fb432aa7..09feab9d 100644 --- a/frontend/components/layout/mobile-nav.tsx +++ b/frontend/components/layout/mobile-nav.tsx @@ -5,7 +5,7 @@ import { useEffect, useId, useRef, useState } from "react"; import { useCountry } from "@/components/layout/country-context"; import { activeNavItem, navGroupsForCountry } from "@/components/layout/nav-items"; -import { countryRegistration } from "@/lib/microcosm/countries"; +import { countryRegistration } from "@policyengine/calibration-tree/countries"; import { NavSidebar } from "./nav-sidebar"; diff --git a/frontend/components/layout/nav-items.test.ts b/frontend/components/layout/nav-items.test.ts index 57bcc391..e1aef082 100644 --- a/frontend/components/layout/nav-items.test.ts +++ b/frontend/components/layout/nav-items.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; -import { hasCapability, selectableCountries } from "@/lib/microcosm/countries"; +import { hasCapability, selectableCountries } from "@policyengine/calibration-tree/countries"; import { activeNavItem, diff --git a/frontend/components/layout/nav-items.ts b/frontend/components/layout/nav-items.ts index 34b97396..67117a34 100644 --- a/frontend/components/layout/nav-items.ts +++ b/frontend/components/layout/nav-items.ts @@ -2,7 +2,7 @@ import type { Country } from "@/components/layout/country-context"; import { countryCapabilities, type CountryCapability, -} from "@/lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; // A page is shown when the country (or the release artifact, which can narrow // a registration) serves its capability. Pages that run on country-specific diff --git a/frontend/components/layout/nav-sidebar.tsx b/frontend/components/layout/nav-sidebar.tsx index 1e15d76b..acb1b6b0 100644 --- a/frontend/components/layout/nav-sidebar.tsx +++ b/frontend/components/layout/nav-sidebar.tsx @@ -13,7 +13,7 @@ import { import { countryRegistration, selectableCountries, -} from "@/lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; export function NavSidebar({ onNavigate }: { onNavigate?: () => void } = {}) { const pathname = usePathname(); diff --git a/frontend/components/microcosm/calibration-explorer-filter-menu.tsx b/frontend/components/microcosm/calibration-explorer-filter-menu.tsx index 9c381faf..d77f0016 100644 --- a/frontend/components/microcosm/calibration-explorer-filter-menu.tsx +++ b/frontend/components/microcosm/calibration-explorer-filter-menu.tsx @@ -7,8 +7,8 @@ import { type ComparisonFit, type ExplorerFilters, type ExplorerState, -} from "@/lib/microcosm/calibration-explorer"; -import type { CalibrationTreeResponse } from "@/lib/microcosm/calibration-tree"; +} from "@policyengine/calibration-tree/calibration-explorer"; +import type { CalibrationTreeResponse } from "@policyengine/calibration-tree/calibration-tree"; const FIT_BAND_LABELS: Record = { "0_5": "0–5%", diff --git a/frontend/components/microcosm/calibration-explorer-map.tsx b/frontend/components/microcosm/calibration-explorer-map.tsx index 5c50e14b..485251f2 100644 --- a/frontend/components/microcosm/calibration-explorer-map.tsx +++ b/frontend/components/microcosm/calibration-explorer-map.tsx @@ -43,14 +43,14 @@ import { explorerReducer, type ExplorerBreakdown, type ExplorerState, -} from "@/lib/microcosm/calibration-explorer"; +} from "@policyengine/calibration-tree/calibration-explorer"; import { prefetchCalibrationDescendants } from "@/lib/microcosm/calibration-prefetch"; import { type CalibrationTreeGroup, type CalibrationTreeNode, type CalibrationTreeResponse, type CalibrationTreeSizeMode, -} from "@/lib/microcosm/calibration-tree"; +} from "@policyengine/calibration-tree/calibration-tree"; import { condenseCalibrationTreemap, expandedGroupsForNode, diff --git a/frontend/components/microcosm/calibration-explorer-view.test.ts b/frontend/components/microcosm/calibration-explorer-view.test.ts index 8baaecf5..00467077 100644 --- a/frontend/components/microcosm/calibration-explorer-view.test.ts +++ b/frontend/components/microcosm/calibration-explorer-view.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; -import type { ExplorerState } from "../../lib/microcosm/calibration-explorer"; +import type { ExplorerState } from "@policyengine/calibration-tree/calibration-explorer"; import { EXPLORER_MAP_VERTICAL_PADDING, explorerBreadcrumbs, diff --git a/frontend/components/microcosm/calibration-explorer-view.ts b/frontend/components/microcosm/calibration-explorer-view.ts index f45dd8bd..6dc4dfde 100644 --- a/frontend/components/microcosm/calibration-explorer-view.ts +++ b/frontend/components/microcosm/calibration-explorer-view.ts @@ -1,14 +1,14 @@ import type { ExplorerPath, ExplorerState, -} from "@/lib/microcosm/calibration-explorer"; +} from "@policyengine/calibration-tree/calibration-explorer"; import { type CalibrationTreeMetrics, type CalibrationTreeNode, type CalibrationTreeSizeMode, -} from "@/lib/microcosm/calibration-tree"; -import { canonicalLabel, programLabel } from "@/lib/microcosm/program-label"; -import { sourceLabel } from "@/lib/microcosm/source-label"; +} from "@policyengine/calibration-tree/calibration-tree"; +import { canonicalLabel, programLabel } from "@policyengine/calibration-tree/program-label"; +import { sourceLabel } from "@policyengine/calibration-tree/source-label"; export const EXPLORER_MAP_VERTICAL_PADDING = 10; diff --git a/frontend/components/microcosm/microcosm-staging-view.tsx b/frontend/components/microcosm/microcosm-staging-view.tsx index d48a502a..17879880 100644 --- a/frontend/components/microcosm/microcosm-staging-view.tsx +++ b/frontend/components/microcosm/microcosm-staging-view.tsx @@ -39,7 +39,7 @@ import { type MicrocosmStagingRunResponse, type MicrocosmStagingRunSummary, } from "@/lib/api/hooks/use-microcosm"; -import { countryRegistration, hasCapability } from "@/lib/microcosm/countries"; +import { countryRegistration, hasCapability } from "@policyengine/calibration-tree/countries"; import { formatStagingCurrentStatus, formatStagingStatus, diff --git a/frontend/components/microcosm/microcosm-target-detail.tsx b/frontend/components/microcosm/microcosm-target-detail.tsx index 4e2c0f81..06d5d428 100644 --- a/frontend/components/microcosm/microcosm-target-detail.tsx +++ b/frontend/components/microcosm/microcosm-target-detail.tsx @@ -9,8 +9,8 @@ import { type MicrocosmTargetRow, } from "@/lib/api/hooks/use-microcosm"; import { chronicleSourceEntryUrl } from "@/lib/microcosm/chronicle-entry-url"; -import { canonicalLabel } from "@/lib/microcosm/program-label"; -import { sourceLabel } from "@/lib/microcosm/source-label"; +import { canonicalLabel } from "@policyengine/calibration-tree/program-label"; +import { sourceLabel } from "@policyengine/calibration-tree/source-label"; import { usStateName } from "@/lib/microcosm/us-state-names"; interface DisplayDimension { diff --git a/frontend/components/microcosm/microcosm-targets-view.tsx b/frontend/components/microcosm/microcosm-targets-view.tsx index 2f5c353d..cc21740d 100644 --- a/frontend/components/microcosm/microcosm-targets-view.tsx +++ b/frontend/components/microcosm/microcosm-targets-view.tsx @@ -17,7 +17,7 @@ import { PageHeader } from "@/components/shared/page-header"; import { SectionCard } from "@/components/shared/section-card"; import { ToolbarSelect } from "@/components/shared/toolbar-select"; import { MicrocosmTargetDetail } from "@/components/microcosm/microcosm-target-detail"; -import { sourceLabel } from "@/lib/microcosm/source-label"; +import { sourceLabel } from "@policyengine/calibration-tree/source-label"; import { releaseSelectOptions, useMicrocosmReleases, diff --git a/frontend/components/microcosm/staging-target-change-map.tsx b/frontend/components/microcosm/staging-target-change-map.tsx index 942f947e..2e0f5173 100644 --- a/frontend/components/microcosm/staging-target-change-map.tsx +++ b/frontend/components/microcosm/staging-target-change-map.tsx @@ -29,18 +29,18 @@ import { createExplorerFilters, createExplorerState, explorerReducer, -} from "@/lib/microcosm/calibration-explorer"; +} from "@policyengine/calibration-tree/calibration-explorer"; import type { CalibrationTreeGroup, CalibrationTreeMetrics, -} from "@/lib/microcosm/calibration-tree"; +} from "@policyengine/calibration-tree/calibration-tree"; import { condenseCalibrationTreemapByMetric, expandedGroupsForNode, type CalibrationTreemapGroup, type CalibrationTreemapNode, } from "@/lib/microcosm/calibration-treemap-layout"; -import type { TargetChangeMode } from "@/lib/microcosm/target-change"; +import type { TargetChangeMode } from "@policyengine/calibration-tree/target-change"; import type { TargetChangeTreeResponse } from "@/lib/microcosm/target-change-tree"; import { formatTargetChange, diff --git a/frontend/components/microcosm/staging-target-detail-presentation.test.ts b/frontend/components/microcosm/staging-target-detail-presentation.test.ts index c7561ca3..d0a4a1e1 100644 --- a/frontend/components/microcosm/staging-target-detail-presentation.test.ts +++ b/frontend/components/microcosm/staging-target-detail-presentation.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; -import type { TargetChangeRow } from "@/lib/microcosm/target-change"; +import type { TargetChangeRow } from "@policyengine/calibration-tree/target-change"; import { stagingTargetDetailPresentation } from "./staging-target-detail-presentation"; const TARGET_CHANGE: TargetChangeRow = { diff --git a/frontend/components/microcosm/staging-target-detail-presentation.ts b/frontend/components/microcosm/staging-target-detail-presentation.ts index bc1ed857..27a06a37 100644 --- a/frontend/components/microcosm/staging-target-detail-presentation.ts +++ b/frontend/components/microcosm/staging-target-detail-presentation.ts @@ -6,7 +6,7 @@ import { type MicrocosmTargetErrorSummary, } from "@/components/microcosm/microcosm-target-detail"; import type { MicrocosmTargetRow } from "@/lib/api/hooks/use-microcosm"; -import type { TargetChangeRow } from "@/lib/microcosm/target-change"; +import type { TargetChangeRow } from "@policyengine/calibration-tree/target-change"; import { formatTargetChange } from "@/lib/microcosm/target-change-visualization"; export interface StagingTargetDetailPresentation { diff --git a/frontend/lib/api/hooks/use-microcosm.test.ts b/frontend/lib/api/hooks/use-microcosm.test.ts index 69f67702..2b0928a3 100644 --- a/frontend/lib/api/hooks/use-microcosm.test.ts +++ b/frontend/lib/api/hooks/use-microcosm.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; -import { createExplorerState } from "@/lib/microcosm/calibration-explorer"; +import { createExplorerState } from "@policyengine/calibration-tree/calibration-explorer"; import { fetchCalibrationTreePartsConcurrently, diff --git a/frontend/lib/api/hooks/use-microcosm.ts b/frontend/lib/api/hooks/use-microcosm.ts index 46df32fd..675c1227 100644 --- a/frontend/lib/api/hooks/use-microcosm.ts +++ b/frontend/lib/api/hooks/use-microcosm.ts @@ -17,8 +17,8 @@ import { import { withBasePath } from "@/lib/base-path"; import type { BuildRunSource } from "@/lib/microcosm/build-monitor"; import type { BuildRunResponse, BuildRunsResponse } from "@/lib/microcosm/build-runs"; -import type { ExplorerState } from "@/lib/microcosm/calibration-explorer"; -import type { CalibrationTreeResponse } from "@/lib/microcosm/calibration-tree"; +import type { ExplorerState } from "@policyengine/calibration-tree/calibration-explorer"; +import type { CalibrationTreeResponse } from "@policyengine/calibration-tree/calibration-tree"; import { calibrationTreeResponseFromBundle, calibrationTreeTargetDetailSelection, @@ -34,12 +34,12 @@ import { type CalibrationTreeTargetDetailsArtifact, type CalibrationTreeTargetSummaryArtifact, type CalibrationTreeTierArtifact, -} from "@/lib/microcosm/calibration-tree-artifact"; -import type { CalibrationProvenance } from "@/lib/microcosm/target-loss-attribution"; +} from "@policyengine/calibration-tree/calibration-tree-artifact"; +import type { CalibrationProvenance } from "@policyengine/calibration-tree/target-loss-attribution"; import type { TargetChangeMode, TargetChangeRow, -} from "@/lib/microcosm/target-change"; +} from "@policyengine/calibration-tree/target-change"; import type { TargetChangeTreeApiResponse, TargetChangeTreeResponse, @@ -49,7 +49,7 @@ import { hasCapability, type CountryCapability, type RepositoryVisibility, -} from "@/lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; import { apiGet } from "../client"; export interface MicrocosmGates { diff --git a/frontend/lib/cross-dataset/fact-presentation.ts b/frontend/lib/cross-dataset/fact-presentation.ts index 09a1f0b4..cfb07b57 100644 --- a/frontend/lib/cross-dataset/fact-presentation.ts +++ b/frontend/lib/cross-dataset/fact-presentation.ts @@ -4,9 +4,9 @@ import type { SourceSummary, } from "./artifact"; import type { Country } from "@/components/layout/country-context"; -import { DEFAULT_COUNTRY, isCountry } from "@/lib/microcosm/countries"; +import { DEFAULT_COUNTRY, isCountry } from "@policyengine/calibration-tree/countries"; import { sourceDisplayLabel } from "./presentation"; -import { sourceAuthorityLabel } from "../source-labels"; +import { sourceAuthorityLabel } from "@policyengine/calibration-tree/source-labels"; export interface FactCatalogParams { country: Country; diff --git a/frontend/lib/cross-dataset/source.ts b/frontend/lib/cross-dataset/source.ts index a027c738..0c849958 100644 --- a/frontend/lib/cross-dataset/source.ts +++ b/frontend/lib/cross-dataset/source.ts @@ -6,7 +6,7 @@ import { DEFAULT_COUNTRY, countryRegistration, type MicrocosmCountry, -} from "../microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; interface CountryArtifactConfig { countryCode: string; diff --git a/frontend/lib/microcosm/build-monitor.test.ts b/frontend/lib/microcosm/build-monitor.test.ts index f9db4e9c..828c241c 100644 --- a/frontend/lib/microcosm/build-monitor.test.ts +++ b/frontend/lib/microcosm/build-monitor.test.ts @@ -27,7 +27,7 @@ import { parseStagingEvents, parseStagingManifest, parseStagingProgress, -} from "./staging-contract"; +} from "@policyengine/calibration-tree/staging-contract"; const T0 = Date.parse("2026-09-26T10:00:00Z"); const min = (n: number) => n * 60_000; @@ -1001,7 +1001,19 @@ describe("solver passes inside calibration", () => { }); describe("version 3 outcomes", () => { - const FIXTURES = join(import.meta.dir, "fixtures", "staging-contract", "v3"); + // The staging contract fixtures live beside the contract parser in the + // calibration tree package. + const FIXTURES = join( + import.meta.dir, + "..", + "..", + "packages", + "calibration-tree", + "src", + "fixtures", + "staging-contract", + "v3", + ); const fixtureText = (path: string) => readFileSync(join(FIXTURES, path)).toString(); const fixtureJson = (path: string) => JSON.parse(fixtureText(path)) as Record; diff --git a/frontend/lib/microcosm/build-runs.ts b/frontend/lib/microcosm/build-runs.ts index 79d2fd1c..b8812efb 100644 --- a/frontend/lib/microcosm/build-runs.ts +++ b/frontend/lib/microcosm/build-runs.ts @@ -23,7 +23,7 @@ import { import { hasCapability, type MicrocosmCountry, -} from "@/lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; import { discoverLocalRuns, loadLocalRunDocuments, @@ -33,7 +33,7 @@ import { import { loadStagingRuns, loadStagingRunTelemetry, -} from "@/lib/microcosm/staging-artifact"; +} from "@policyengine/calibration-tree/staging-artifact"; import { collectorConfigured, loadCollectorRun, diff --git a/frontend/lib/microcosm/calibration-prefetch.test.ts b/frontend/lib/microcosm/calibration-prefetch.test.ts index 2ac80b17..f9da57b2 100644 --- a/frontend/lib/microcosm/calibration-prefetch.test.ts +++ b/frontend/lib/microcosm/calibration-prefetch.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; -import { createExplorerState, type ExplorerState } from "./calibration-explorer"; +import { createExplorerState, type ExplorerState } from "@policyengine/calibration-tree/calibration-explorer"; import { prefetchCalibrationDescendants, type CalibrationTreeFetcher, @@ -8,7 +8,7 @@ import { import type { CalibrationTreeNode, CalibrationTreeResponse, -} from "./calibration-tree"; +} from "@policyengine/calibration-tree/calibration-tree"; const EMPTY_METRICS = { nTargets: 0, diff --git a/frontend/lib/microcosm/calibration-prefetch.ts b/frontend/lib/microcosm/calibration-prefetch.ts index 2a25d8e2..a52a432b 100644 --- a/frontend/lib/microcosm/calibration-prefetch.ts +++ b/frontend/lib/microcosm/calibration-prefetch.ts @@ -1,8 +1,8 @@ import { nextLevelExplorerStates, type ExplorerState, -} from "./calibration-explorer"; -import type { CalibrationTreeResponse } from "./calibration-tree"; +} from "@policyengine/calibration-tree/calibration-explorer"; +import type { CalibrationTreeResponse } from "@policyengine/calibration-tree/calibration-tree"; export type CalibrationTreeFetcher = ( state: ExplorerState, diff --git a/frontend/lib/microcosm/calibration-tree-http.ts b/frontend/lib/microcosm/calibration-tree-http.ts index ddd36e43..23b31f54 100644 --- a/frontend/lib/microcosm/calibration-tree-http.ts +++ b/frontend/lib/microcosm/calibration-tree-http.ts @@ -1,7 +1,7 @@ import type { GetBlobResult } from "@vercel/blob"; import { NextResponse } from "next/server"; -import { calibrationTreeBlobText } from "./calibration-tree-blob"; +import { calibrationTreeBlobText } from "@policyengine/calibration-tree/calibration-tree-blob"; function acceptsGzip(request: Request): boolean { return (request.headers.get("accept-encoding") ?? "") diff --git a/frontend/lib/microcosm/calibration-tree-request.ts b/frontend/lib/microcosm/calibration-tree-request.ts index fa9c3a99..50dcad86 100644 --- a/frontend/lib/microcosm/calibration-tree-request.ts +++ b/frontend/lib/microcosm/calibration-tree-request.ts @@ -5,7 +5,7 @@ import { type CalibrationStatus, type ComparisonFit, type FitBand, -} from "./calibration-explorer"; +} from "@policyengine/calibration-tree/calibration-explorer"; const CALIBRATION_STATUSES = new Set([ "included", diff --git a/frontend/lib/microcosm/calibration-treemap-layout.test.ts b/frontend/lib/microcosm/calibration-treemap-layout.test.ts index 480d6868..005981be 100644 --- a/frontend/lib/microcosm/calibration-treemap-layout.test.ts +++ b/frontend/lib/microcosm/calibration-treemap-layout.test.ts @@ -4,7 +4,7 @@ import type { CalibrationTreeGroup, CalibrationTreeMetrics, CalibrationTreeNode, -} from "./calibration-tree"; +} from "@policyengine/calibration-tree/calibration-tree"; import { condenseCalibrationTreemap, expandedGroupsForNode, diff --git a/frontend/lib/microcosm/calibration-treemap-layout.ts b/frontend/lib/microcosm/calibration-treemap-layout.ts index c97a8081..b7ef0dd5 100644 --- a/frontend/lib/microcosm/calibration-treemap-layout.ts +++ b/frontend/lib/microcosm/calibration-treemap-layout.ts @@ -1,11 +1,11 @@ -import type { ExplorerNodeSelection } from "./calibration-explorer"; +import type { ExplorerNodeSelection } from "@policyengine/calibration-tree/calibration-explorer"; import type { CalibrationTreeGroup, CalibrationTreeMetrics, CalibrationTreeNode, CalibrationTreeSizeMode, CalibrationTreeTarget, -} from "./calibration-tree"; +} from "@policyengine/calibration-tree/calibration-tree"; // Keep these aligned with the original calibration map. They represent roughly // a 51px square for groups and a 28px square for nodes. diff --git a/frontend/lib/microcosm/countries.ts b/frontend/lib/microcosm/countries.ts index a57253d5..1d8d5144 100644 --- a/frontend/lib/microcosm/countries.ts +++ b/frontend/lib/microcosm/countries.ts @@ -1,241 +1,5 @@ -// Country registry for the Microcosm dashboard: the one place a country is -// registered. Adding a country is one entry here; everything else (selectors, -// navigation, repository resolution, national geography, link behaviour) reads -// the registration or the release artifact's typed `country` block. -// -// Isomorphic: imported by client components and API routes alike, so it must -// not import server-only modules or read `process.env`. Repository resolvers -// accept an explicit environment object from server-side callers. - -export const COUNTRY_CAPABILITIES = [ - "calibration", - "targets", - "compare", - "cross_dataset", - "staging", - "model_coverage", - "pipeline", - "variables", - "external_checks", -] as const; -export type CountryCapability = (typeof COUNTRY_CAPABILITIES)[number]; - -export type RepositoryVisibility = "public" | "private"; - -export type RepositoryEnvironment = Readonly< - Record ->; - -export interface ResolvedRepository { - repo: string; - revision: string; -} - -export interface CountryRegistration { - // Default HF dataset repository and revision (the server may override both - // through the deployment variables named in repo_env / revision_env). - repo: string; - revision: string; - repo_env?: string; - revision_env?: string; - // Country display name ("United States"). - label: string; - // Sidebar dataset line ("Microcosm US"). - dataset_label: string; - // National geography label and id (null when the id is unknown). - geography: string; - geography_id: string | null; - visibility: RepositoryVisibility; - capabilities: readonly CountryCapability[]; - // Optional staging telemetry repository. A staging-capable country must - // declare this instead of inheriting another country's repository. - staging?: { - repo: string; - revision: string; - repo_env?: string; - revision_env?: string; - token_env?: string; - }; - // Other jurisdiction codes a cross-dataset bundle may use for this country. - jurisdiction_aliases?: readonly string[]; - // Conformance-only registration: a valid country that is never listed in - // selectors or alert allowlists. - fixture?: true; -} - -const ALL_CAPABILITIES: readonly CountryCapability[] = COUNTRY_CAPABILITIES; - -const CALIBRATION_CAPABILITIES: readonly CountryCapability[] = [ - "calibration", - "targets", - "compare", - "cross_dataset", -]; - -// Deprecated upstream identifiers: Microcosm's published HF repositories and -// deployment variables still use the former Populace names. -export const COUNTRY_REGISTRY = { - us: { - repo: "policyengine/populace-us", - revision: "main", - repo_env: "POPULACE_HF_REPO", - label: "United States", - dataset_label: "Microcosm US", - geography: "United States", - geography_id: "0100000US", - visibility: "public", - capabilities: ALL_CAPABILITIES, - staging: { - // Deprecated upstream identifier: the US staging publisher still uses - // the former Populace repository and deployment-variable names. - repo: "policyengine/populace-us-staging", - revision: "main", - repo_env: "POPULACE_STAGING_HF_REPO", - revision_env: "POPULACE_STAGING_HF_REVISION", - }, - }, - uk: { - repo: "policyengine/populace-uk-private", - revision: "main", - repo_env: "POPULACE_UK_HF_REPO", - revision_env: "POPULACE_UK_HF_REVISION", - label: "United Kingdom", - dataset_label: "Microcosm UK", - geography: "United Kingdom", - geography_id: null, - visibility: "private", - capabilities: [...CALIBRATION_CAPABILITIES, "staging"], - staging: { - repo: "policyengine/populace-uk-staging", - revision: "main", - repo_env: "POPULACE_UK_STAGING_HF_REPO", - revision_env: "POPULACE_UK_STAGING_HF_REVISION", - token_env: "POPULACE_UK_STAGING_HF_TOKEN", - }, - jurisdiction_aliases: ["GB"], - }, - be: { - repo: "policyengine/populace-be-private", - revision: "main", - repo_env: "POPULACE_BE_HF_REPO", - revision_env: "POPULACE_BE_HF_REVISION", - label: "Belgium", - dataset_label: "Microcosm Belgium", - geography: "Belgium", - geography_id: null, - visibility: "private", - capabilities: CALIBRATION_CAPABILITIES, - }, - // Synthetic repository used only by the third-country conformance fixture. - zz: { - repo: "policyengine/microcosm-zz-fixture", - revision: "main", - label: "Zedland", - dataset_label: "Microcosm ZZ", - geography: "Zedland", - geography_id: null, - visibility: "private", - capabilities: ["calibration", "targets", "compare"], - fixture: true, - }, - // Synthetic non-US staging registration. It verifies that staging artifact - // resolution is country-scoped without exposing an unfinished country in - // selectors or treating its fixture repository as a published dataset. - am: { - repo: "policyengine/microcosm-am-fixture", - revision: "main", - label: "Armenia", - dataset_label: "Microcosm Armenia", - geography: "Armenia", - geography_id: null, - visibility: "private", - capabilities: ["calibration", "targets", "compare", "staging"], - staging: { - repo: "policyengine/microcosm-am-staging-fixture", - revision: "main", - }, - fixture: true, - }, -} satisfies Record; - -export type MicrocosmCountry = keyof typeof COUNTRY_REGISTRY; - -export const DEFAULT_COUNTRY: MicrocosmCountry = "us"; - -const COUNTRY_CODES = Object.keys(COUNTRY_REGISTRY) as MicrocosmCountry[]; - -export function isCountry(value: string | null | undefined): value is MicrocosmCountry { - return value != null && Object.hasOwn(COUNTRY_REGISTRY, value); -} - -export function parseCountry(value: string | null | undefined): MicrocosmCountry { - return isCountry(value) ? value : DEFAULT_COUNTRY; -} - -export function countryRegistration(country: MicrocosmCountry): CountryRegistration { - return COUNTRY_REGISTRY[country]; -} - -// Countries offered in selectors, in registry order; fixtures are excluded. -export function selectableCountries(): MicrocosmCountry[] { - return COUNTRY_CODES.filter((country) => !countryRegistration(country).fixture); -} - -export function countryCapabilities(country: MicrocosmCountry): readonly CountryCapability[] { - return COUNTRY_REGISTRY[country].capabilities; -} - -export function hasCapability(country: MicrocosmCountry, capability: CountryCapability): boolean { - return countryCapabilities(country).includes(capability); -} - -function configuredValue( - environment: RepositoryEnvironment, - variableName: string | undefined, - fallback: string, -): string { - return (variableName ? environment[variableName] : undefined) ?? fallback; -} - -export function resolveRegisteredRepository( - country: MicrocosmCountry, - environment: RepositoryEnvironment, -): ResolvedRepository { - const registration = countryRegistration(country); - return { - repo: configuredValue( - environment, - registration.repo_env, - registration.repo, - ), - revision: configuredValue( - environment, - registration.revision_env, - registration.revision, - ), - }; -} - -export function resolveRegisteredStagingRepository( - country: MicrocosmCountry, - environment: RepositoryEnvironment, -): ResolvedRepository | null { - const registration = countryRegistration(country); - if (!hasCapability(country, "staging") || !registration.staging) return null; - return { - repo: configuredValue( - environment, - registration.staging.repo_env, - registration.staging.repo, - ), - revision: configuredValue( - environment, - registration.staging.revision_env, - registration.staging.revision, - ), - }; -} - -export function isCountryCapability(value: unknown): value is CountryCapability { - return typeof value === "string" && (COUNTRY_CAPABILITIES as readonly string[]).includes(value); -} +// Former location of the country registry, kept as a compatibility +// re-export for code that has not yet moved to the package path. The +// registry lives in @policyengine/calibration-tree/countries; import it from +// there. +export * from "@policyengine/calibration-tree/countries"; diff --git a/frontend/lib/microcosm/hf-webhook-config.ts b/frontend/lib/microcosm/hf-webhook-config.ts index 1a359b0e..60fe8000 100644 --- a/frontend/lib/microcosm/hf-webhook-config.ts +++ b/frontend/lib/microcosm/hf-webhook-config.ts @@ -4,7 +4,7 @@ import { selectableCountries, type MicrocosmCountry, type RepositoryEnvironment, -} from "./countries"; +} from "@policyengine/calibration-tree/countries"; export interface WebhookRepositoryRegistration { country: MicrocosmCountry; diff --git a/frontend/lib/microcosm/hf-webhook-handler.ts b/frontend/lib/microcosm/hf-webhook-handler.ts index d6b552ab..156cbfc7 100644 --- a/frontend/lib/microcosm/hf-webhook-handler.ts +++ b/frontend/lib/microcosm/hf-webhook-handler.ts @@ -4,7 +4,7 @@ import { NextResponse } from "next/server"; import { postReleaseAlert } from "@/lib/slack"; -import type { MicrocosmCountry } from "./countries"; +import type { MicrocosmCountry } from "@policyengine/calibration-tree/countries"; import type { HfWebhookConfig } from "./hf-webhook-config"; interface UpdatedRef { diff --git a/frontend/lib/microcosm/local-build-runs.ts b/frontend/lib/microcosm/local-build-runs.ts index 2a8b412f..86963cf6 100644 --- a/frontend/lib/microcosm/local-build-runs.ts +++ b/frontend/lib/microcosm/local-build-runs.ts @@ -8,7 +8,7 @@ import { parseStagingManifest, parseStagingProgress, validateStagingRunConsistency, -} from "@/lib/microcosm/staging-contract"; +} from "@policyengine/calibration-tree/staging-contract"; // Local build runs: the `runs//` telemetry folders a Microcosm build // writes on the machine it runs on (the US release writes them under diff --git a/frontend/lib/microcosm/presentation.ts b/frontend/lib/microcosm/presentation.ts index d74c78bf..7bdf5e07 100644 --- a/frontend/lib/microcosm/presentation.ts +++ b/frontend/lib/microcosm/presentation.ts @@ -1,4 +1,4 @@ -import type { MicrocosmCountry } from "./countries"; +import type { MicrocosmCountry } from "@policyengine/calibration-tree/countries"; export interface MicrocosmPresentationSlots { overview_intro?: string; diff --git a/frontend/lib/microcosm/production-release.test.ts b/frontend/lib/microcosm/production-release.test.ts index 875617a6..1a59f04c 100644 --- a/frontend/lib/microcosm/production-release.test.ts +++ b/frontend/lib/microcosm/production-release.test.ts @@ -7,7 +7,7 @@ import { import { hfResolveUrl, microcosmRevision, -} from "./latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; test("dashboard discovery follows the repository branch while variable calculation stays pinned", () => { expect(microcosmRevision("us")).toBe("main"); diff --git a/frontend/lib/microcosm/reviewed-repository-import.fixture.ts b/frontend/lib/microcosm/reviewed-repository-import.fixture.ts index f97a8c99..c62cb82b 100644 --- a/frontend/lib/microcosm/reviewed-repository-import.fixture.ts +++ b/frontend/lib/microcosm/reviewed-repository-import.fixture.ts @@ -9,7 +9,7 @@ import { microcosmCountryGeography, microcosmRepo, microcosmRevision, -} from "./latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; import { IncompatibleProductionReleaseError } from "./production-release"; // Collecting page data imports every route, so one country's refused selection diff --git a/frontend/lib/microcosm/reviewed-repository-import.test.ts b/frontend/lib/microcosm/reviewed-repository-import.test.ts index 3148fa89..cac9d6f7 100644 --- a/frontend/lib/microcosm/reviewed-repository-import.test.ts +++ b/frontend/lib/microcosm/reviewed-repository-import.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; -import { COUNTRY_REGISTRY } from "./countries"; +import { COUNTRY_REGISTRY } from "@policyengine/calibration-tree/countries"; // A US-only data-selection conflict must fail US reads only. Asserting it while // building the repository table would throw during module import instead, taking diff --git a/frontend/lib/microcosm/source-attribution.ts b/frontend/lib/microcosm/source-attribution.ts index 2130a80c..d8d9761b 100644 --- a/frontend/lib/microcosm/source-attribution.ts +++ b/frontend/lib/microcosm/source-attribution.ts @@ -2,7 +2,7 @@ import { countryRegistration, type MicrocosmCountry, type RepositoryVisibility, -} from "./countries"; +} from "@policyengine/calibration-tree/countries"; export interface MicrocosmSourceAttribution { label: string; diff --git a/frontend/lib/microcosm/target-change-tree.test.ts b/frontend/lib/microcosm/target-change-tree.test.ts index 1189c9b6..f3cd529f 100644 --- a/frontend/lib/microcosm/target-change-tree.test.ts +++ b/frontend/lib/microcosm/target-change-tree.test.ts @@ -3,9 +3,9 @@ import { describe, expect, test } from "bun:test"; import { createExplorerState, type ExplorerState, -} from "./calibration-explorer"; -import type { Calibration } from "./latest-artifact"; -import { buildTargetChangeDataset } from "./target-change"; +} from "@policyengine/calibration-tree/calibration-explorer"; +import type { Calibration } from "@policyengine/calibration-tree/latest-artifact"; +import { buildTargetChangeDataset } from "@policyengine/calibration-tree/target-change"; import { buildTargetChangeTree } from "./target-change-tree"; interface RowInput { diff --git a/frontend/lib/microcosm/target-change-tree.ts b/frontend/lib/microcosm/target-change-tree.ts index 32c2f437..e8b0c8ef 100644 --- a/frontend/lib/microcosm/target-change-tree.ts +++ b/frontend/lib/microcosm/target-change-tree.ts @@ -1,9 +1,9 @@ -import type { ExplorerState } from "./calibration-explorer"; +import type { ExplorerState } from "@policyengine/calibration-tree/calibration-explorer"; import { buildCalibrationTree, type CalibrationTreeResponse, type CalibrationTreeTarget, -} from "./calibration-tree"; +} from "@policyengine/calibration-tree/calibration-tree"; import { targetChangeForMode, type TargetChangeAttributionSide, @@ -12,8 +12,8 @@ import { type TargetChangeMode, type TargetChangeRow, type TargetChangeSummary, -} from "./target-change"; -import type { TargetMatchingSummary } from "./target-surface-matcher"; +} from "@policyengine/calibration-tree/target-change"; +import type { TargetMatchingSummary } from "@policyengine/calibration-tree/target-surface-matcher"; export interface TargetChangeTreeResponse extends CalibrationTreeResponse { available: boolean; diff --git a/frontend/lib/microcosm/target-change-visualization.test.ts b/frontend/lib/microcosm/target-change-visualization.test.ts index 4033d962..e8bb44ba 100644 --- a/frontend/lib/microcosm/target-change-visualization.test.ts +++ b/frontend/lib/microcosm/target-change-visualization.test.ts @@ -3,8 +3,8 @@ import { describe, expect, test } from "bun:test"; import type { CalibrationTreeGroup, CalibrationTreeMetrics, -} from "./calibration-tree"; -import type { TargetChangeRow } from "./target-change"; +} from "@policyengine/calibration-tree/calibration-tree"; +import type { TargetChangeRow } from "@policyengine/calibration-tree/target-change"; import { formatTargetChange, targetChangeDetailValues, diff --git a/frontend/lib/microcosm/target-change-visualization.ts b/frontend/lib/microcosm/target-change-visualization.ts index 99bd2e20..9899038c 100644 --- a/frontend/lib/microcosm/target-change-visualization.ts +++ b/frontend/lib/microcosm/target-change-visualization.ts @@ -2,12 +2,12 @@ import type { Placed } from "@/lib/treemap/squarify"; import type { CalibrationTreeChangeMetrics, CalibrationTreeGroup, -} from "./calibration-tree"; +} from "@policyengine/calibration-tree/calibration-tree"; import { aggregateCalibrationTreeMetrics } from "./calibration-treemap-layout"; import type { TargetChangeMode, TargetChangeRow, -} from "./target-change"; +} from "@policyengine/calibration-tree/target-change"; export type TargetChangeDirection = "increase" | "reduction"; diff --git a/frontend/lib/microcosm/target-matching-presentation.ts b/frontend/lib/microcosm/target-matching-presentation.ts index 614e3b8e..c0ead2b2 100644 --- a/frontend/lib/microcosm/target-matching-presentation.ts +++ b/frontend/lib/microcosm/target-matching-presentation.ts @@ -1,8 +1,8 @@ -import type { TargetMatchKind } from "./target-surface-matcher"; +import type { TargetMatchKind } from "@policyengine/calibration-tree/target-surface-matcher"; import type { TargetRepresentation, TargetRowRepresentation, -} from "./target-representation"; +} from "@policyengine/calibration-tree/target-representation"; export function targetRepresentationLabel( representation: TargetRepresentation | TargetRowRepresentation, diff --git a/frontend/lib/microcosm/third-country-conformance.test.ts b/frontend/lib/microcosm/third-country-conformance.test.ts index f99a8532..19274d23 100644 --- a/frontend/lib/microcosm/third-country-conformance.test.ts +++ b/frontend/lib/microcosm/third-country-conformance.test.ts @@ -2,8 +2,8 @@ import { describe, expect, test } from "bun:test"; import { isCountry as isClientCountry } from "@/components/layout/country-context"; import { navGroupsForCountry } from "@/components/layout/nav-items"; -import { countryCapabilities, selectableCountries } from "@/lib/microcosm/countries"; -import { sourceAuthorityLabel } from "@/lib/source-labels"; +import { countryCapabilities, selectableCountries } from "@policyengine/calibration-tree/countries"; +import { sourceAuthorityLabel } from "@policyengine/calibration-tree/source-labels"; import diagnosticsFixture from "./fixtures/zz-release/calibration_diagnostics.json"; import releaseManifestFixture from "./fixtures/zz-release/release_manifest.json"; @@ -17,7 +17,7 @@ import { microcosmRevision, microcosmTargetTreemap, parseCountry, -} from "./latest-artifact"; +} from "@policyengine/calibration-tree/latest-artifact"; const COUNTRY = "zz" as const; const RELEASE_ID = "microcosm-zz-2026-conformance"; diff --git a/frontend/lib/slack.ts b/frontend/lib/slack.ts index f0d97e2c..156bef2b 100644 --- a/frontend/lib/slack.ts +++ b/frontend/lib/slack.ts @@ -1,4 +1,4 @@ -import type { MicrocosmCountry } from "@/lib/microcosm/latest-artifact"; +import type { MicrocosmCountry } from "@policyengine/calibration-tree/latest-artifact"; const DASHBOARD_URL = "https://microcosm.institute/calibration/dashboard/microcosm"; diff --git a/frontend/package.json b/frontend/package.json index c76942ef..faabd39d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,16 +3,20 @@ "version": "0.1.0", "private": true, "type": "module", + "workspaces": [ + "packages/*" + ], "scripts": { "dev": "node scripts/dev.mjs", "build": "next build", "start": "next start", - "lint": "tsc --noEmit", - "publish:calibration-tree": "bun run scripts/publish-calibration-tree.ts", + "lint": "tsc --noEmit && tsc --noEmit -p packages/calibration-tree", + "publish:calibration-tree": "bun run packages/calibration-tree/src/publish-calibration-tree.ts", "test:dev-port": "node --test scripts/available-port.node-test.mjs", "replay:build-run": "bun run scripts/replay-build-run.ts" }, "dependencies": { + "@policyengine/calibration-tree": "workspace:*", "@policyengine/ui-kit": "^0.9.0", "@tanstack/react-query": "^5.84.0", "@tanstack/react-query-devtools": "^5.83.0", diff --git a/frontend/packages/calibration-tree/package.json b/frontend/packages/calibration-tree/package.json new file mode 100644 index 00000000..76a6b871 --- /dev/null +++ b/frontend/packages/calibration-tree/package.json @@ -0,0 +1,51 @@ +{ + "name": "@policyengine/calibration-tree", + "version": "0.1.0", + "description": "Builds, validates, reads and publishes Microcosm calibration tree artifacts (tree schema 6), with the release and staging readers they are built from.", + "private": true, + "type": "module", + "exports": { + "./calibration-build-comparison": "./src/calibration-build-comparison.ts", + "./calibration-comparison-blob": "./src/calibration-comparison-blob.ts", + "./calibration-comparison-bundle": "./src/calibration-comparison-bundle.ts", + "./calibration-explorer": "./src/calibration-explorer.ts", + "./calibration-release-locator": "./src/calibration-release-locator.ts", + "./calibration-tree": "./src/calibration-tree.ts", + "./calibration-tree-artifact": "./src/calibration-tree-artifact.ts", + "./calibration-tree-blob": "./src/calibration-tree-blob.ts", + "./calibration-tree-bundle": "./src/calibration-tree-bundle.ts", + "./calibration-tree-manifest": "./src/calibration-tree-manifest.ts", + "./chronicle-metadata": "./src/chronicle-metadata.ts", + "./countries": "./src/countries.ts", + "./hierarchy-target-reader": "./src/hierarchy-target-reader.ts", + "./historical-weight-summary": "./src/historical-weight-summary.ts", + "./latest-artifact": "./src/latest-artifact.ts", + "./legacy-target-reader": "./src/legacy-target-reader.ts", + "./program-label": "./src/program-label.ts", + "./publish-calibration-tree": "./src/publish-calibration-tree.ts", + "./reforms": "./src/reforms.ts", + "./source-label": "./src/source-label.ts", + "./source-labels": "./src/source-labels.ts", + "./staging-artifact": "./src/staging-artifact.ts", + "./staging-contract": "./src/staging-contract.ts", + "./structured-dimension-reader": "./src/structured-dimension-reader.ts", + "./structured-target-reader": "./src/structured-target-reader.ts", + "./target-change": "./src/target-change.ts", + "./target-loss-attribution": "./src/target-loss-attribution.ts", + "./target-loss-attribution-manifest": "./src/target-loss-attribution-manifest.ts", + "./target-representation": "./src/target-representation.ts", + "./target-surface-matcher": "./src/target-surface-matcher.ts" + }, + "scripts": { + "publish:calibration-tree": "bun run src/publish-calibration-tree.ts", + "test": "bun test", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@vercel/blob": "^2.6.1" + }, + "devDependencies": { + "@types/bun": "^1.3.14", + "typescript": "^5.8.0" + } +} diff --git a/frontend/lib/microcosm/calibration-build-comparison.test.ts b/frontend/packages/calibration-tree/src/calibration-build-comparison.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-build-comparison.test.ts rename to frontend/packages/calibration-tree/src/calibration-build-comparison.test.ts diff --git a/frontend/lib/microcosm/calibration-build-comparison.ts b/frontend/packages/calibration-tree/src/calibration-build-comparison.ts similarity index 100% rename from frontend/lib/microcosm/calibration-build-comparison.ts rename to frontend/packages/calibration-tree/src/calibration-build-comparison.ts diff --git a/frontend/lib/microcosm/calibration-comparison-blob.test.ts b/frontend/packages/calibration-tree/src/calibration-comparison-blob.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-comparison-blob.test.ts rename to frontend/packages/calibration-tree/src/calibration-comparison-blob.test.ts diff --git a/frontend/lib/microcosm/calibration-comparison-blob.ts b/frontend/packages/calibration-tree/src/calibration-comparison-blob.ts similarity index 100% rename from frontend/lib/microcosm/calibration-comparison-blob.ts rename to frontend/packages/calibration-tree/src/calibration-comparison-blob.ts diff --git a/frontend/lib/microcosm/calibration-comparison-bundle.test.ts b/frontend/packages/calibration-tree/src/calibration-comparison-bundle.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-comparison-bundle.test.ts rename to frontend/packages/calibration-tree/src/calibration-comparison-bundle.test.ts diff --git a/frontend/lib/microcosm/calibration-comparison-bundle.ts b/frontend/packages/calibration-tree/src/calibration-comparison-bundle.ts similarity index 100% rename from frontend/lib/microcosm/calibration-comparison-bundle.ts rename to frontend/packages/calibration-tree/src/calibration-comparison-bundle.ts diff --git a/frontend/lib/microcosm/calibration-explorer.test.ts b/frontend/packages/calibration-tree/src/calibration-explorer.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-explorer.test.ts rename to frontend/packages/calibration-tree/src/calibration-explorer.test.ts diff --git a/frontend/lib/microcosm/calibration-explorer.ts b/frontend/packages/calibration-tree/src/calibration-explorer.ts similarity index 100% rename from frontend/lib/microcosm/calibration-explorer.ts rename to frontend/packages/calibration-tree/src/calibration-explorer.ts diff --git a/frontend/lib/microcosm/calibration-release-locator.test.ts b/frontend/packages/calibration-tree/src/calibration-release-locator.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-release-locator.test.ts rename to frontend/packages/calibration-tree/src/calibration-release-locator.test.ts diff --git a/frontend/lib/microcosm/calibration-release-locator.ts b/frontend/packages/calibration-tree/src/calibration-release-locator.ts similarity index 100% rename from frontend/lib/microcosm/calibration-release-locator.ts rename to frontend/packages/calibration-tree/src/calibration-release-locator.ts diff --git a/frontend/lib/microcosm/calibration-tree-artifact.test.ts b/frontend/packages/calibration-tree/src/calibration-tree-artifact.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree-artifact.test.ts rename to frontend/packages/calibration-tree/src/calibration-tree-artifact.test.ts diff --git a/frontend/lib/microcosm/calibration-tree-artifact.ts b/frontend/packages/calibration-tree/src/calibration-tree-artifact.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree-artifact.ts rename to frontend/packages/calibration-tree/src/calibration-tree-artifact.ts diff --git a/frontend/lib/microcosm/calibration-tree-blob.test.ts b/frontend/packages/calibration-tree/src/calibration-tree-blob.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree-blob.test.ts rename to frontend/packages/calibration-tree/src/calibration-tree-blob.test.ts diff --git a/frontend/lib/microcosm/calibration-tree-blob.ts b/frontend/packages/calibration-tree/src/calibration-tree-blob.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree-blob.ts rename to frontend/packages/calibration-tree/src/calibration-tree-blob.ts diff --git a/frontend/lib/microcosm/calibration-tree-bundle.ts b/frontend/packages/calibration-tree/src/calibration-tree-bundle.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree-bundle.ts rename to frontend/packages/calibration-tree/src/calibration-tree-bundle.ts diff --git a/frontend/packages/calibration-tree/src/calibration-tree-golden.test.ts b/frontend/packages/calibration-tree/src/calibration-tree-golden.test.ts new file mode 100644 index 00000000..5f53f011 --- /dev/null +++ b/frontend/packages/calibration-tree/src/calibration-tree-golden.test.ts @@ -0,0 +1,91 @@ +import { expect, test } from "bun:test"; + +import recordedGoldens from "./fixtures/calibration-tree-golden.json"; +import { + GOLDEN_COMPARISON_CASES, + GOLDEN_SOURCE_CASES, + GZIP_HEADER_WITHOUT_OS, + goldenComparisonRecord, + goldenManifestRecord, + goldenSourceBuild, + goldenSourceRecord, + type CalibrationTreeGoldenRecord, + type GoldenComparisonCaseName, + type GoldenSourceCaseName, +} from "./test-support/calibration-tree-golden-cases"; + +// The record was produced by the builder before it moved into its own +// package. Every case must reproduce it exactly: paths, raw JSON digests and +// sizes, gzip sizes, deflate streams, build ids, and reader responses. +const recorded = recordedGoldens as CalibrationTreeGoldenRecord; + +// The US release has 5,659 targets; building and validating its bundle +// gzips about 40 MB three times. The limit is generous so the suite also +// passes on a heavily loaded machine. +const BUILD_TIMEOUT_MS = 300_000; + +const SOURCE_DESCRIPTIONS: Record = { + "us-release": "a US legacy release", + "uk-release": "a UK schema 8 hierarchy release", + "uk-release-loss-attribution": "a UK release with target loss attribution", + "be-release": "a Belgium release", + "us-staging": "a US version 1 staging run", + "uk-staging": "a UK version 2 staging run", + "uk-staging-staged-bundle": "a UK staging run read from its staged dataset bundle", +}; + +const COMPARISON_DESCRIPTIONS: Record = { + "us-comparison": "a US release-to-staging comparison", + "uk-comparison": "a UK release-to-staging comparison", +}; + +for (const name of GOLDEN_SOURCE_CASES) { + test( + `${SOURCE_DESCRIPTIONS[name]} reproduces its recorded tree bundle and reader responses`, + async () => { + expect(await goldenSourceRecord(name)).toEqual(recorded.sources[name]); + }, + BUILD_TIMEOUT_MS, + ); +} + +for (const name of GOLDEN_COMPARISON_CASES) { + test( + `${COMPARISON_DESCRIPTIONS[name]} reproduces its recorded bundles and target detail`, + async () => { + expect(await goldenComparisonRecord(name)).toEqual(recorded.comparisons[name]); + }, + BUILD_TIMEOUT_MS, + ); +} + +test( + "the tree manifest for every recorded build reproduces its recorded bytes", + async () => { + expect(await goldenManifestRecord()).toEqual(recorded.manifest); + }, + BUILD_TIMEOUT_MS, +); + +test( + "every recorded part is gzip with the fixed header ahead of its recorded deflate stream", + async () => { + for (const name of GOLDEN_SOURCE_CASES) { + const { bundle } = await goldenSourceBuild(name); + for (const file of bundle.files) { + expect([...file.compressed.subarray(0, GZIP_HEADER_WITHOUT_OS.length)]) + .toEqual(GZIP_HEADER_WITHOUT_OS); + } + } + }, + BUILD_TIMEOUT_MS, +); + +test("the recorded US release spans several target-summary and target-detail shards", () => { + const parts = recorded.sources["us-release"].files.map((file) => file.part); + expect(parts.filter((part) => part.startsWith("target-summary-")).length) + .toBeGreaterThan(1); + expect(parts.filter((part) => part.startsWith("target-details-")).length) + .toBeGreaterThan(1); + expect(parts.filter((part) => part.startsWith("tier-")).length).toBeGreaterThan(1); +}); diff --git a/frontend/lib/microcosm/calibration-tree-manifest.test.ts b/frontend/packages/calibration-tree/src/calibration-tree-manifest.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree-manifest.test.ts rename to frontend/packages/calibration-tree/src/calibration-tree-manifest.test.ts diff --git a/frontend/lib/microcosm/calibration-tree-manifest.ts b/frontend/packages/calibration-tree/src/calibration-tree-manifest.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree-manifest.ts rename to frontend/packages/calibration-tree/src/calibration-tree-manifest.ts diff --git a/frontend/lib/microcosm/calibration-tree.test.ts b/frontend/packages/calibration-tree/src/calibration-tree.test.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree.test.ts rename to frontend/packages/calibration-tree/src/calibration-tree.test.ts diff --git a/frontend/lib/microcosm/calibration-tree.ts b/frontend/packages/calibration-tree/src/calibration-tree.ts similarity index 100% rename from frontend/lib/microcosm/calibration-tree.ts rename to frontend/packages/calibration-tree/src/calibration-tree.ts diff --git a/frontend/lib/microcosm/chronicle-metadata.test.ts b/frontend/packages/calibration-tree/src/chronicle-metadata.test.ts similarity index 100% rename from frontend/lib/microcosm/chronicle-metadata.test.ts rename to frontend/packages/calibration-tree/src/chronicle-metadata.test.ts diff --git a/frontend/lib/microcosm/chronicle-metadata.ts b/frontend/packages/calibration-tree/src/chronicle-metadata.ts similarity index 100% rename from frontend/lib/microcosm/chronicle-metadata.ts rename to frontend/packages/calibration-tree/src/chronicle-metadata.ts diff --git a/frontend/lib/microcosm/countries.test.ts b/frontend/packages/calibration-tree/src/countries.test.ts similarity index 100% rename from frontend/lib/microcosm/countries.test.ts rename to frontend/packages/calibration-tree/src/countries.test.ts diff --git a/frontend/packages/calibration-tree/src/countries.ts b/frontend/packages/calibration-tree/src/countries.ts new file mode 100644 index 00000000..a57253d5 --- /dev/null +++ b/frontend/packages/calibration-tree/src/countries.ts @@ -0,0 +1,241 @@ +// Country registry for the Microcosm dashboard: the one place a country is +// registered. Adding a country is one entry here; everything else (selectors, +// navigation, repository resolution, national geography, link behaviour) reads +// the registration or the release artifact's typed `country` block. +// +// Isomorphic: imported by client components and API routes alike, so it must +// not import server-only modules or read `process.env`. Repository resolvers +// accept an explicit environment object from server-side callers. + +export const COUNTRY_CAPABILITIES = [ + "calibration", + "targets", + "compare", + "cross_dataset", + "staging", + "model_coverage", + "pipeline", + "variables", + "external_checks", +] as const; +export type CountryCapability = (typeof COUNTRY_CAPABILITIES)[number]; + +export type RepositoryVisibility = "public" | "private"; + +export type RepositoryEnvironment = Readonly< + Record +>; + +export interface ResolvedRepository { + repo: string; + revision: string; +} + +export interface CountryRegistration { + // Default HF dataset repository and revision (the server may override both + // through the deployment variables named in repo_env / revision_env). + repo: string; + revision: string; + repo_env?: string; + revision_env?: string; + // Country display name ("United States"). + label: string; + // Sidebar dataset line ("Microcosm US"). + dataset_label: string; + // National geography label and id (null when the id is unknown). + geography: string; + geography_id: string | null; + visibility: RepositoryVisibility; + capabilities: readonly CountryCapability[]; + // Optional staging telemetry repository. A staging-capable country must + // declare this instead of inheriting another country's repository. + staging?: { + repo: string; + revision: string; + repo_env?: string; + revision_env?: string; + token_env?: string; + }; + // Other jurisdiction codes a cross-dataset bundle may use for this country. + jurisdiction_aliases?: readonly string[]; + // Conformance-only registration: a valid country that is never listed in + // selectors or alert allowlists. + fixture?: true; +} + +const ALL_CAPABILITIES: readonly CountryCapability[] = COUNTRY_CAPABILITIES; + +const CALIBRATION_CAPABILITIES: readonly CountryCapability[] = [ + "calibration", + "targets", + "compare", + "cross_dataset", +]; + +// Deprecated upstream identifiers: Microcosm's published HF repositories and +// deployment variables still use the former Populace names. +export const COUNTRY_REGISTRY = { + us: { + repo: "policyengine/populace-us", + revision: "main", + repo_env: "POPULACE_HF_REPO", + label: "United States", + dataset_label: "Microcosm US", + geography: "United States", + geography_id: "0100000US", + visibility: "public", + capabilities: ALL_CAPABILITIES, + staging: { + // Deprecated upstream identifier: the US staging publisher still uses + // the former Populace repository and deployment-variable names. + repo: "policyengine/populace-us-staging", + revision: "main", + repo_env: "POPULACE_STAGING_HF_REPO", + revision_env: "POPULACE_STAGING_HF_REVISION", + }, + }, + uk: { + repo: "policyengine/populace-uk-private", + revision: "main", + repo_env: "POPULACE_UK_HF_REPO", + revision_env: "POPULACE_UK_HF_REVISION", + label: "United Kingdom", + dataset_label: "Microcosm UK", + geography: "United Kingdom", + geography_id: null, + visibility: "private", + capabilities: [...CALIBRATION_CAPABILITIES, "staging"], + staging: { + repo: "policyengine/populace-uk-staging", + revision: "main", + repo_env: "POPULACE_UK_STAGING_HF_REPO", + revision_env: "POPULACE_UK_STAGING_HF_REVISION", + token_env: "POPULACE_UK_STAGING_HF_TOKEN", + }, + jurisdiction_aliases: ["GB"], + }, + be: { + repo: "policyengine/populace-be-private", + revision: "main", + repo_env: "POPULACE_BE_HF_REPO", + revision_env: "POPULACE_BE_HF_REVISION", + label: "Belgium", + dataset_label: "Microcosm Belgium", + geography: "Belgium", + geography_id: null, + visibility: "private", + capabilities: CALIBRATION_CAPABILITIES, + }, + // Synthetic repository used only by the third-country conformance fixture. + zz: { + repo: "policyengine/microcosm-zz-fixture", + revision: "main", + label: "Zedland", + dataset_label: "Microcosm ZZ", + geography: "Zedland", + geography_id: null, + visibility: "private", + capabilities: ["calibration", "targets", "compare"], + fixture: true, + }, + // Synthetic non-US staging registration. It verifies that staging artifact + // resolution is country-scoped without exposing an unfinished country in + // selectors or treating its fixture repository as a published dataset. + am: { + repo: "policyengine/microcosm-am-fixture", + revision: "main", + label: "Armenia", + dataset_label: "Microcosm Armenia", + geography: "Armenia", + geography_id: null, + visibility: "private", + capabilities: ["calibration", "targets", "compare", "staging"], + staging: { + repo: "policyengine/microcosm-am-staging-fixture", + revision: "main", + }, + fixture: true, + }, +} satisfies Record; + +export type MicrocosmCountry = keyof typeof COUNTRY_REGISTRY; + +export const DEFAULT_COUNTRY: MicrocosmCountry = "us"; + +const COUNTRY_CODES = Object.keys(COUNTRY_REGISTRY) as MicrocosmCountry[]; + +export function isCountry(value: string | null | undefined): value is MicrocosmCountry { + return value != null && Object.hasOwn(COUNTRY_REGISTRY, value); +} + +export function parseCountry(value: string | null | undefined): MicrocosmCountry { + return isCountry(value) ? value : DEFAULT_COUNTRY; +} + +export function countryRegistration(country: MicrocosmCountry): CountryRegistration { + return COUNTRY_REGISTRY[country]; +} + +// Countries offered in selectors, in registry order; fixtures are excluded. +export function selectableCountries(): MicrocosmCountry[] { + return COUNTRY_CODES.filter((country) => !countryRegistration(country).fixture); +} + +export function countryCapabilities(country: MicrocosmCountry): readonly CountryCapability[] { + return COUNTRY_REGISTRY[country].capabilities; +} + +export function hasCapability(country: MicrocosmCountry, capability: CountryCapability): boolean { + return countryCapabilities(country).includes(capability); +} + +function configuredValue( + environment: RepositoryEnvironment, + variableName: string | undefined, + fallback: string, +): string { + return (variableName ? environment[variableName] : undefined) ?? fallback; +} + +export function resolveRegisteredRepository( + country: MicrocosmCountry, + environment: RepositoryEnvironment, +): ResolvedRepository { + const registration = countryRegistration(country); + return { + repo: configuredValue( + environment, + registration.repo_env, + registration.repo, + ), + revision: configuredValue( + environment, + registration.revision_env, + registration.revision, + ), + }; +} + +export function resolveRegisteredStagingRepository( + country: MicrocosmCountry, + environment: RepositoryEnvironment, +): ResolvedRepository | null { + const registration = countryRegistration(country); + if (!hasCapability(country, "staging") || !registration.staging) return null; + return { + repo: configuredValue( + environment, + registration.staging.repo_env, + registration.staging.repo, + ), + revision: configuredValue( + environment, + registration.staging.revision_env, + registration.staging.revision, + ), + }; +} + +export function isCountryCapability(value: unknown): value is CountryCapability { + return typeof value === "string" && (COUNTRY_CAPABILITIES as readonly string[]).includes(value); +} diff --git a/frontend/lib/microcosm/fixtures/be-release/calibration_diagnostics.json b/frontend/packages/calibration-tree/src/fixtures/be-release/calibration_diagnostics.json similarity index 100% rename from frontend/lib/microcosm/fixtures/be-release/calibration_diagnostics.json rename to frontend/packages/calibration-tree/src/fixtures/be-release/calibration_diagnostics.json diff --git a/frontend/lib/microcosm/fixtures/be-release/release_manifest.json b/frontend/packages/calibration-tree/src/fixtures/be-release/release_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/be-release/release_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/be-release/release_manifest.json diff --git a/frontend/packages/calibration-tree/src/fixtures/calibration-tree-golden.json b/frontend/packages/calibration-tree/src/fixtures/calibration-tree-golden.json new file mode 100644 index 00000000..a4d719e4 --- /dev/null +++ b/frontend/packages/calibration-tree/src/fixtures/calibration-tree-golden.json @@ -0,0 +1,742 @@ +{ + "schemaVersion": 6, + "sources": { + "us-release": { + "buildArtifactId": "a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/filter-index.json.gz", + "sha256": "6a741042b02bddfbfa1c481f9d4a20e8a8cdd4d2cbe80bf2c9fe8d9f977ed51a", + "rawBytes": 138229, + "gzipBytes": 33527, + "deflateSha256": "1ca1078eb8f427e9abb1322cb06e956c8d9f81980c467b223ccb89610f2b4055" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/target-summary-0001.json.gz", + "sha256": "9cfc348ae34a3f8b140e938d55701ad5eb3e853611c74869fdf90477f4a5164a", + "rawBytes": 7998998, + "gzipBytes": 291909, + "deflateSha256": "9334f9fa5c1ae6058acb88fbd70b73c17074db82104ad2251a89a24b5a4a8eff" + }, + { + "part": "target-summary-0002", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/target-summary-0002.json.gz", + "sha256": "0b354b4876b07d310ea0034bf9287bd579ffebd93ed4c35944d4380d9a0ac124", + "rawBytes": 5419957, + "gzipBytes": 187997, + "deflateSha256": "f1866f4d6f4a73e9810cd3c6b4081728d9fc3810d9190be5b946e61d65c3ff5d" + }, + { + "part": "target-details-0001", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/target-details-0001.json.gz", + "sha256": "167a6a57c2ecca93b6a4e72a0ce20aaf1bde92899dc5745678094acbb49bd68c", + "rawBytes": 3998564, + "gzipBytes": 155413, + "deflateSha256": "4771b858ee5f58e95c6118a1a12827484776dee08b95ebb3e75e7fa46aae1510" + }, + { + "part": "target-details-0002", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/target-details-0002.json.gz", + "sha256": "4b12c96cd24ccec2bc2048cb19abd3947a7432d1d59d2ae47174530f56bf81fb", + "rawBytes": 3996828, + "gzipBytes": 144405, + "deflateSha256": "3dd197235316a10a9eac3ac164485cab2ee54d5ab4fc376df0419af3b47f458f" + }, + { + "part": "target-details-0003", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/target-details-0003.json.gz", + "sha256": "94a835e63c3c260ad965c86e4db81a2e120fc9e4be9ae77fa9578c2fe82e21de", + "rawBytes": 3996936, + "gzipBytes": 147792, + "deflateSha256": "f30fa7894d25957aa5b1fd2cb8b72945312af6dd0edef64c459b658e16ddd64d" + }, + { + "part": "target-details-0004", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/target-details-0004.json.gz", + "sha256": "c1c4d31d954121b632befb1f7677ae40112bce1c0ca5f2a66e19cf7fedd90ef1", + "rawBytes": 3997158, + "gzipBytes": 146814, + "deflateSha256": "ab1300872d9994cf8eac7bd8c3e22911200cf094c85b92eefe20c1e382744422" + }, + { + "part": "target-details-0005", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/target-details-0005.json.gz", + "sha256": "54f53ac5cd93367f662f485fb5a29fbf4c0adb5c97ee8448372bd2883443709c", + "rawBytes": 3692745, + "gzipBytes": 134739, + "deflateSha256": "67238ec4a79fbdcaf025178a8db8f2886671003ecb9a304b485f1ec0c056911b" + }, + { + "part": "tier-1", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/tier-1.json.gz", + "sha256": "e047b9322a142a12ee7fb372825d38dce92df84187d09d51055741b16bab5f34", + "rawBytes": 1925251, + "gzipBytes": 136491, + "deflateSha256": "552a0d63fa2693ebeaf85803265f0887f99539534a83b807e18afbb19732d6f4" + }, + { + "part": "tier-2", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/tier-2.json.gz", + "sha256": "4df69b52a5f37874a794fe626f49d7553980780168af19fa115fcb4e6b716721", + "rawBytes": 3977571, + "gzipBytes": 184597, + "deflateSha256": "5ff15b7e5b0e92dc518451f987d564e668dbf8adebc0ab723a655b750feb27ee" + }, + { + "part": "tier-3", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/tier-3.json.gz", + "sha256": "8284ec366d164f2d9fd07d12e63b252e5273835357941371fca4df45aa0fc41c", + "rawBytes": 2787021, + "gzipBytes": 118869, + "deflateSha256": "095e7fdf1ce7d9b7166b9cbcbb3bf57f02f5645d70e4d78aaa504365e8284165" + }, + { + "part": "tier-4", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/tier-4.json.gz", + "sha256": "da5436b8e262840545c335684c0ba3ef910d6bd6ab68b30587d42dfa0ca8f3ab", + "rawBytes": 235484, + "gzipBytes": 12101, + "deflateSha256": "d12f1e6b1303dfbb43ec00174d98f75db08409b4b1a9b7d9974a8aff08fed11b" + }, + { + "part": "index", + "path": "calibration-trees/us/a66bd48af3da1089ab48225245a8549c3adbf2f2a13d0c922f75eabace014887/index.json.gz", + "sha256": "95b80c3363c2a60d12162f1affd32c4f9baca3424950bfec119892336bf18c3b", + "rawBytes": 277887, + "gzipBytes": 71381, + "deflateSha256": "dcf78f6cd4ab5c59de0c15aba0cca6c4243c522d4adf43fb55222bd3cf9270d0" + } + ], + "readers": { + "programRootSha256": "d779b63b324b1f7780f5de5acbeec16ec60aec692c3d7e75cb11f912fb80c1f7", + "geographyRootSha256": "aceb71e38199712740e1265ab7860f9e515c3310bd01523ea6a145b2802100e1" + } + }, + "uk-release": { + "buildArtifactId": "cf259cfc2b491934f6730d39f2af26980ad9cd712259215476924c626f96f269", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/uk/cf259cfc2b491934f6730d39f2af26980ad9cd712259215476924c626f96f269/filter-index.json.gz", + "sha256": "d072056bb788ca8264cd7fb35e7fab4e36799577404083c811b9d9d2ca8aad12", + "rawBytes": 712, + "gzipBytes": 335, + "deflateSha256": "8738777f02867e8e946e4196623767f0425585b69d824da049ac7d747c275e47" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/uk/cf259cfc2b491934f6730d39f2af26980ad9cd712259215476924c626f96f269/target-summary-0001.json.gz", + "sha256": "1189c92b969109fb85982dbab23674ac4f24f2056f9c0b64f782061742c665e2", + "rawBytes": 26240, + "gzipBytes": 1963, + "deflateSha256": "1750e9150a9aee244ae8b8cdcdc3684c59451bab65eaac2c31909afe3dd9e53b" + }, + { + "part": "target-details-0001", + "path": "calibration-trees/uk/cf259cfc2b491934f6730d39f2af26980ad9cd712259215476924c626f96f269/target-details-0001.json.gz", + "sha256": "c8f764a32739e60f9d2c2f7183f09c4fbc21329a5158175df53414ce7afc5682", + "rawBytes": 35154, + "gzipBytes": 2514, + "deflateSha256": "81625364304d5bc5dd61c077e7344133f9ef2078b37378f72d735e97e2040088" + }, + { + "part": "tier-1", + "path": "calibration-trees/uk/cf259cfc2b491934f6730d39f2af26980ad9cd712259215476924c626f96f269/tier-1.json.gz", + "sha256": "3e0762bc4706f535db0a54e4f08857fd3c42230f6490eb5a97607bf6e4b4f63d", + "rawBytes": 12122, + "gzipBytes": 1294, + "deflateSha256": "1b69edc96be45b5e3cab99bcfd862815e2efe2764a699811ddc7f9abc58d7080" + }, + { + "part": "tier-2", + "path": "calibration-trees/uk/cf259cfc2b491934f6730d39f2af26980ad9cd712259215476924c626f96f269/tier-2.json.gz", + "sha256": "c7e8153154fe8aca532d47e60e6f0c30542056b5a12d40521050d824f8ffba7c", + "rawBytes": 12422, + "gzipBytes": 1233, + "deflateSha256": "d144798c42b127e51cb0b2f1a4ec4a5fc525191e4166e9d6ba4a30d1a826880d" + }, + { + "part": "tier-3", + "path": "calibration-trees/uk/cf259cfc2b491934f6730d39f2af26980ad9cd712259215476924c626f96f269/tier-3.json.gz", + "sha256": "98274a322bb7aba17caf61f5d45cf094181b2bdb4711dff6c59bd3d6287f77f1", + "rawBytes": 10299, + "gzipBytes": 981, + "deflateSha256": "c2783702053f63f85e8b9b32b183ed1e3883879c1203c10efd69342b0b306aa7" + }, + { + "part": "index", + "path": "calibration-trees/uk/cf259cfc2b491934f6730d39f2af26980ad9cd712259215476924c626f96f269/index.json.gz", + "sha256": "4eb54beae26438fc9ca829416a785d89eb2d06042d178e877acb8dc0ddc921df", + "rawBytes": 8691, + "gzipBytes": 2171, + "deflateSha256": "73794c40e3b60ea42a41ded5bbf922b10f2c2d90f5e4cd1ee1472ae4205f00f1" + } + ], + "readers": { + "programRootSha256": "96538e6c83d7d8a099e858ce0d400a1d6a24fcd6113f61882b7cc18af4f695b7", + "geographyRootSha256": "5f12106506775ee68ec3d33669dc3f3cf45985770ca15a9e69f72bfe60393598" + } + }, + "uk-release-loss-attribution": { + "buildArtifactId": "b3d7b849dc1a5b5e7a58f10ef5ec165c78c83254ec559c2f3c8fee40dc124702", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/uk/b3d7b849dc1a5b5e7a58f10ef5ec165c78c83254ec559c2f3c8fee40dc124702/filter-index.json.gz", + "sha256": "af1d80bffc34c54334509323196afa03a6153cd984ff2af5c2339d5e48e8ed51", + "rawBytes": 516, + "gzipBytes": 294, + "deflateSha256": "69d7435a503c2b355afe205fc8258196ca13d0839249f17905583d2ecfb4b33b" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/uk/b3d7b849dc1a5b5e7a58f10ef5ec165c78c83254ec559c2f3c8fee40dc124702/target-summary-0001.json.gz", + "sha256": "ec03a849ff444317331ab8a3b6999b61559642a06e3c7cabf51d61b953601b92", + "rawBytes": 3394, + "gzipBytes": 861, + "deflateSha256": "08860c52164991d0da4794285a66e771e62ad3ebd3ba0031e2a778d1407d684d" + }, + { + "part": "target-details-0001", + "path": "calibration-trees/uk/b3d7b849dc1a5b5e7a58f10ef5ec165c78c83254ec559c2f3c8fee40dc124702/target-details-0001.json.gz", + "sha256": "f31be395376be010badd5c6c71a815629b5e181a67a8b0529a4d18b2704cf516", + "rawBytes": 4135, + "gzipBytes": 940, + "deflateSha256": "5f7da89297cc8b8632da90b8ac4500106c5c6d6baa90b30d7346e75629d8caa0" + }, + { + "part": "tier-1", + "path": "calibration-trees/uk/b3d7b849dc1a5b5e7a58f10ef5ec165c78c83254ec559c2f3c8fee40dc124702/tier-1.json.gz", + "sha256": "d8d20da0ff764fbadf701243b9b93e257be63fa1a9fd55cd346fa4f27a0bcb88", + "rawBytes": 3695, + "gzipBytes": 614, + "deflateSha256": "7fee5206ccb86351a94f733a2b61a8d621acf430f22d8c6921e7097317e6fd1a" + }, + { + "part": "tier-2", + "path": "calibration-trees/uk/b3d7b849dc1a5b5e7a58f10ef5ec165c78c83254ec559c2f3c8fee40dc124702/tier-2.json.gz", + "sha256": "288f688d1ee5c977f71c2afc48cd7fe9431f297ae07203971f2df14281ca197b", + "rawBytes": 2042, + "gzipBytes": 488, + "deflateSha256": "f85a3fba480e654d1b8d40c1c66980f61d2789eb842ea87647dfb78d99f21a78" + }, + { + "part": "index", + "path": "calibration-trees/uk/b3d7b849dc1a5b5e7a58f10ef5ec165c78c83254ec559c2f3c8fee40dc124702/index.json.gz", + "sha256": "4c4a8659c29663ada1668f126382151be0da548512fa6515b0deafb8a3d51209", + "rawBytes": 6385, + "gzipBytes": 1642, + "deflateSha256": "9ce173c5baf4527e126c0f437a4733563577330b5777d838acac199d94e98f5e" + } + ], + "readers": { + "programRootSha256": "bed713ac6393d42bebfd2d045c994cffce38e7dbdc32f85a0eacbcc0012f28cb", + "geographyRootSha256": "d540f8f9bde0a0f5d95799b7c32a2d9eb30922c34597a9f5f214592f2c06e858" + } + }, + "be-release": { + "buildArtifactId": "3f1dd7eed7c2424998507bbd7c05cb5090febbf1b6af5762dff52bdd56e49b92", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/be/3f1dd7eed7c2424998507bbd7c05cb5090febbf1b6af5762dff52bdd56e49b92/filter-index.json.gz", + "sha256": "ffa4ddc71fd3a12973639095d03b71f796a10a751eb9a9f6a874610e03d0cb5d", + "rawBytes": 559, + "gzipBytes": 292, + "deflateSha256": "5f02a32cf57111faee1bd28147907b958078dec3c8272fc8213fd3f8b17e7c21" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/be/3f1dd7eed7c2424998507bbd7c05cb5090febbf1b6af5762dff52bdd56e49b92/target-summary-0001.json.gz", + "sha256": "942b442f47aeeaac57f815dd4686779f09f3a9b211d22c6f4f2701fc4c204149", + "rawBytes": 5493, + "gzipBytes": 1226, + "deflateSha256": "aabb61222ebf9051c3cddf150d03807e96da2b7fbcdb298c8d1d39af51c8ceb1" + }, + { + "part": "target-details-0001", + "path": "calibration-trees/be/3f1dd7eed7c2424998507bbd7c05cb5090febbf1b6af5762dff52bdd56e49b92/target-details-0001.json.gz", + "sha256": "251dc89a046ccc3a87edeb1f821adebe17d2759acf63c08ca942c7c835590400", + "rawBytes": 9943, + "gzipBytes": 2175, + "deflateSha256": "dd5442a83f52c3ac45ed6a5cd7e3fd2b1ebe4ef76b3d2c17901a6f279d24fec3" + }, + { + "part": "tier-1", + "path": "calibration-trees/be/3f1dd7eed7c2424998507bbd7c05cb5090febbf1b6af5762dff52bdd56e49b92/tier-1.json.gz", + "sha256": "eb6a49f6287348cc0fb0f2fb3e9484a8f05002d54b98551ff6c4cb378e063160", + "rawBytes": 6205, + "gzipBytes": 813, + "deflateSha256": "0bd6f1d57380b9fa37c4792072d0891bbffe018c6d519163d9c13f2db4a739bc" + }, + { + "part": "tier-2", + "path": "calibration-trees/be/3f1dd7eed7c2424998507bbd7c05cb5090febbf1b6af5762dff52bdd56e49b92/tier-2.json.gz", + "sha256": "3778c614299c9198db70563b8b6643e5a0be385b28f6c9786ceb33a4cdc97c1c", + "rawBytes": 3575, + "gzipBytes": 741, + "deflateSha256": "1edd02e58cb975731431beca88371f582e02b8f455a30ec314b2ac8b667da25d" + }, + { + "part": "index", + "path": "calibration-trees/be/3f1dd7eed7c2424998507bbd7c05cb5090febbf1b6af5762dff52bdd56e49b92/index.json.gz", + "sha256": "50aec5e9efc2c23dbc3c53c4f87e30e0a759de43de492c7a5305b1b95929a10a", + "rawBytes": 7588, + "gzipBytes": 1847, + "deflateSha256": "549baf405496cd7bdda4610b1144e75d6de550b75fcc39babe1d29248816c96c" + } + ], + "readers": { + "programRootSha256": "0e7cf0a69fb8231677e484e80c1394ed3ebec6949355eb49b31c86e4d57a8072", + "geographyRootSha256": "939d5bf5d74c528b5dfd9fb40e30f4a463456acf85acbbdcb72b6a4f71db33a9" + } + }, + "us-staging": { + "buildArtifactId": "b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/us/b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2/filter-index.json.gz", + "sha256": "ec362ae4d656d32ab8c93e615861881abaa8a5999bf839976f17b4b58db85623", + "rawBytes": 34254, + "gzipBytes": 5441, + "deflateSha256": "65444bf6458ad47ef3024dd1577ade516e8d9faad68653ff057cd5c85351542f" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/us/b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2/target-summary-0001.json.gz", + "sha256": "05af411f3a512cc66b0fd30f611085a1c54c9afc20db3ee4336fd7c7d01a00cd", + "rawBytes": 3481970, + "gzipBytes": 145130, + "deflateSha256": "550c15c88f1fb4bada0b15e786bdc8b642ef89e678016ee60da6dbc76d0ac03e" + }, + { + "part": "target-details-0001", + "path": "calibration-trees/us/b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2/target-details-0001.json.gz", + "sha256": "3c586bca8cfc1df8efb3986551bfbb84b58b7c74783a2642ca3beee8c7a610ca", + "rawBytes": 3997974, + "gzipBytes": 175885, + "deflateSha256": "77a9c4c9e16e2c136d8c8622065eda1c95145573c73eef11493c80010eb09ed8" + }, + { + "part": "target-details-0002", + "path": "calibration-trees/us/b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2/target-details-0002.json.gz", + "sha256": "a34b20714ae4a3d08e1ec363d3ec56f6956e26b9fe12b01adb81743eafaf9e15", + "rawBytes": 1347045, + "gzipBytes": 57128, + "deflateSha256": "87055b67b1c390a522e90f1088faa1ff692247ed88c2ede7afa67e5dec7a3f72" + }, + { + "part": "tier-1", + "path": "calibration-trees/us/b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2/tier-1.json.gz", + "sha256": "27566511b431c6100155e11a177a90f8adab792a72c98875c92e41094d2be637", + "rawBytes": 412455, + "gzipBytes": 37843, + "deflateSha256": "87b3e9a0ebe59e6c4cf3f51b2fbc7d84f6ab8247690468a5f11eeed0a3498723" + }, + { + "part": "tier-2", + "path": "calibration-trees/us/b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2/tier-2.json.gz", + "sha256": "bd7a122f9e685bec93f4f094da3b4faf5e8fee2889b31efae92acc0bdbd18864", + "rawBytes": 906593, + "gzipBytes": 57400, + "deflateSha256": "d7233caf7e2f259426394ee9acf8e5c9942bc1ea390ce80ad0ee3327381dae40" + }, + { + "part": "tier-3", + "path": "calibration-trees/us/b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2/tier-3.json.gz", + "sha256": "e85cf5dc46e34f585fa078aa58d001794d08fed1b8b85ea2a68d979433d19c82", + "rawBytes": 1579869, + "gzipBytes": 72062, + "deflateSha256": "6905fb5238046fad7b2eb0d82249d80ca3d6ba967e4b602f567ce925a03fe6fd" + }, + { + "part": "index", + "path": "calibration-trees/us/b185a3ee9a2f79006b919073f302c10355a7bb66119bc393f3a2466b93a0c8a2/index.json.gz", + "sha256": "0d99c1d5527f8ca99030011bd74f31dd5ca1f5a70f606ed28333935ce7790240", + "rawBytes": 108144, + "gzipBytes": 17624, + "deflateSha256": "c947115c296eaf9dbbc44b618aca7478e9969d7d8403812a97b82842b23abc32" + } + ], + "readers": { + "programRootSha256": "258a9ea648962e6ac21d8c0cd43d6e1aa21957c1744cce35ec8ad5476773bc59", + "geographyRootSha256": "f6661f55b7bff607e0c2f93d5da5ce24fc319be67a1194d209d165ac0e201573" + } + }, + "uk-staging": { + "buildArtifactId": "76b4e84866cfe1e91c38eb8028210147c2b5107d5cbbb08d5e251a7f1752ee82", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/uk/76b4e84866cfe1e91c38eb8028210147c2b5107d5cbbb08d5e251a7f1752ee82/filter-index.json.gz", + "sha256": "646c22a59fe577a5c7ffee5a5b7d0c9cd993b44c6b88d8736e512b487cd16ffc", + "rawBytes": 749, + "gzipBytes": 346, + "deflateSha256": "3ac0dee3c91c6ed48b7c749169e8bb259dd732cb13fefe4c142a6541a43221ec" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/uk/76b4e84866cfe1e91c38eb8028210147c2b5107d5cbbb08d5e251a7f1752ee82/target-summary-0001.json.gz", + "sha256": "f657c7e06d08f97bfb98041a20fe3847b43452f25293b0a0f683f253972988f7", + "rawBytes": 26859, + "gzipBytes": 2170, + "deflateSha256": "c08450dcf07c6376f2149628dc0c3c2dade7d8a70d480d4bc42711f82edf1e66" + }, + { + "part": "target-details-0001", + "path": "calibration-trees/uk/76b4e84866cfe1e91c38eb8028210147c2b5107d5cbbb08d5e251a7f1752ee82/target-details-0001.json.gz", + "sha256": "78cb2561ce61822e4291387122cc8930b9b9b403ad4b1eab6639b8f594590aab", + "rawBytes": 36359, + "gzipBytes": 2884, + "deflateSha256": "0e09769db8fafe490e55f4af3afb57c71925e91f68b4a69f71c3c1747d24fd81" + }, + { + "part": "tier-1", + "path": "calibration-trees/uk/76b4e84866cfe1e91c38eb8028210147c2b5107d5cbbb08d5e251a7f1752ee82/tier-1.json.gz", + "sha256": "19fb5fbf6165969bb8c48a6c032c77eb17dcf655521e31998f8b4d765286c42c", + "rawBytes": 13679, + "gzipBytes": 1494, + "deflateSha256": "79cd9ebf5adc5046c3da34eedc6d4d91b4ab366a4ef565e3ade8d2818945e94b" + }, + { + "part": "tier-2", + "path": "calibration-trees/uk/76b4e84866cfe1e91c38eb8028210147c2b5107d5cbbb08d5e251a7f1752ee82/tier-2.json.gz", + "sha256": "ffde8c88925f2a6fe24d5eb5be6ca9cc6b37102d1cdef74c2d697c8693ca0270", + "rawBytes": 13566, + "gzipBytes": 1430, + "deflateSha256": "1ca9972e9e6f63ce61232e09bcff204b0f2d5b8d84455bfe5386273d45f45620" + }, + { + "part": "tier-3", + "path": "calibration-trees/uk/76b4e84866cfe1e91c38eb8028210147c2b5107d5cbbb08d5e251a7f1752ee82/tier-3.json.gz", + "sha256": "bb5dc7c9aaeff0427852da9c44abc07ba5781468cadea9f5119d0262eb3a674d", + "rawBytes": 11277, + "gzipBytes": 1068, + "deflateSha256": "0b4debba9f9ba0755def3eb1d19f924e7b6a1f5259ab28a7ad5bc7a212ed106f" + }, + { + "part": "index", + "path": "calibration-trees/uk/76b4e84866cfe1e91c38eb8028210147c2b5107d5cbbb08d5e251a7f1752ee82/index.json.gz", + "sha256": "7ad10415d1da6e693a607f8fd543da2983f8b93bd5f2ef589499f602ea22f93a", + "rawBytes": 9667, + "gzipBytes": 2407, + "deflateSha256": "276306fe5db92d47419db795c64cb54766f9aeae43066b424fee4fce7f07beeb" + } + ], + "readers": { + "programRootSha256": "7d3c7f6931cea0758a8da3828c3a7f5bd6710c2dc0e278384aaad29289ac00d3", + "geographyRootSha256": "b77b8ca27fa64b8d630221a6d60938bee09368110a72c411e6656240efc04886" + } + }, + "uk-staging-staged-bundle": { + "buildArtifactId": "b7df81d8d39b27f48b4b30cbedd00b2ffe5d844bd6dc265ee8481d8f002acba1", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/uk/b7df81d8d39b27f48b4b30cbedd00b2ffe5d844bd6dc265ee8481d8f002acba1/filter-index.json.gz", + "sha256": "d06efa27f18baa1d7d0aab876b3c728d2496643472a6a82dd5a985243cdfe192", + "rawBytes": 749, + "gzipBytes": 346, + "deflateSha256": "a410a1ccf6f861d55e0cf67509019cd5f760341e71a4c5bc9e3ffbf730be9cdb" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/uk/b7df81d8d39b27f48b4b30cbedd00b2ffe5d844bd6dc265ee8481d8f002acba1/target-summary-0001.json.gz", + "sha256": "798172011c376e311dc63180c0c4ffa9feb7867067611053029521f2d4fdd768", + "rawBytes": 26859, + "gzipBytes": 2167, + "deflateSha256": "7c80a15e571c8906762cf42e35b6589fbe26b6e56e65ad9dab4cf6b61098cae1" + }, + { + "part": "target-details-0001", + "path": "calibration-trees/uk/b7df81d8d39b27f48b4b30cbedd00b2ffe5d844bd6dc265ee8481d8f002acba1/target-details-0001.json.gz", + "sha256": "6d5981a6d94c7992f4a7ed99c3ad69f0533633995b56b25808207b1a9df9df5c", + "rawBytes": 36359, + "gzipBytes": 2884, + "deflateSha256": "bac7e96363766893e12f2d56c99c4cf45c0d7dea63ae34de5550054eb0190d6c" + }, + { + "part": "tier-1", + "path": "calibration-trees/uk/b7df81d8d39b27f48b4b30cbedd00b2ffe5d844bd6dc265ee8481d8f002acba1/tier-1.json.gz", + "sha256": "a3e16af357d09c971a4eb5442dd08841530c339bab27f1edbdc4d38b4fd4f6b3", + "rawBytes": 13679, + "gzipBytes": 1495, + "deflateSha256": "8da63c3ce0d36cc4209d05ab6f8d343b09462176c3edec817036351358b45cd8" + }, + { + "part": "tier-2", + "path": "calibration-trees/uk/b7df81d8d39b27f48b4b30cbedd00b2ffe5d844bd6dc265ee8481d8f002acba1/tier-2.json.gz", + "sha256": "07031463189db6a520cff9dfc30bb49f83c92f58012cb3cbac90b73c00d32ad4", + "rawBytes": 13566, + "gzipBytes": 1429, + "deflateSha256": "974a744de6e560282b36a131d75d0601176967cae3a2276e1d473ab852fa768f" + }, + { + "part": "tier-3", + "path": "calibration-trees/uk/b7df81d8d39b27f48b4b30cbedd00b2ffe5d844bd6dc265ee8481d8f002acba1/tier-3.json.gz", + "sha256": "17685f2757333c376af40ccbd2891e4a1425e148193ff11be400acba9fe925cc", + "rawBytes": 11277, + "gzipBytes": 1067, + "deflateSha256": "2f5e31e1a4078e6a4738aaff7341785765983592e117ff955a41ffb6e7b6518b" + }, + { + "part": "index", + "path": "calibration-trees/uk/b7df81d8d39b27f48b4b30cbedd00b2ffe5d844bd6dc265ee8481d8f002acba1/index.json.gz", + "sha256": "6c0fcc717808f6ceb2da5204dd667298c9d888450a21062c5effcc089d8b8bce", + "rawBytes": 10036, + "gzipBytes": 2516, + "deflateSha256": "bab7076b574f9df8e8c85e3f44a057653e3c8b46f741a32c3406e02f0f07985e" + } + ], + "readers": { + "programRootSha256": "ad34108f20d1ae4a69e97abd95e02d8e99e43263fdf85ec2905e9c814e0de619", + "geographyRootSha256": "4ab073886f7a6358e91bb4eff6f424f23929c7ecf68277bdaa48b2305db9774f" + } + } + }, + "comparisons": { + "us-comparison": { + "pairArtifactId": "b25614d1c431f393df8173abd42ec832569ab684b15d2a8e821465a65a349deb", + "reported": { + "buildArtifactId": "3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/us/3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6/filter-index.json.gz", + "sha256": "d47845aca5fd532935f7bd1dc9db7b64ce6799bb00c262ffefb78bb1feec26fd", + "rawBytes": 138363, + "gzipBytes": 37586, + "deflateSha256": "7241762e4051ecaec528807ecfce33c392632066330b63e09880e0c2fbf87ddf" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/us/3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6/target-summary-0001.json.gz", + "sha256": "dcc88dfc2244cf9292730e56072b469bfc26e1b4a7b6cb1002eb2476e857af28", + "rawBytes": 7997882, + "gzipBytes": 433224, + "deflateSha256": "383d7b09b2c2a034bf57ca4b38cc99ecc2717ef23e64d8c952693db478c20964" + }, + { + "part": "target-summary-0002", + "path": "calibration-trees/us/3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6/target-summary-0002.json.gz", + "sha256": "a3a7e86f281719991c08a076a15d2bef83c8191e7139ab016b954d45becc545f", + "rawBytes": 5314378, + "gzipBytes": 226692, + "deflateSha256": "1b5c3134bb5546a0c74fd83459c449c59037024649765fdef87f2e6646736ac0" + }, + { + "part": "tier-1", + "path": "calibration-trees/us/3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6/tier-1.json.gz", + "sha256": "44d202e2d29bd65f28858a97458efee4bf6467ba6a08fbcf1060f1966fdbb375", + "rawBytes": 2599312, + "gzipBytes": 158190, + "deflateSha256": "807cd6aad33f132ad94fe31b51b57bf9b83d8586bb56642d028ee46b041921d1" + }, + { + "part": "tier-2", + "path": "calibration-trees/us/3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6/tier-2.json.gz", + "sha256": "d8c14feaa83c3b64b1cdc1db4e0ad5c1a20705e9cfb55d68bd1af1222dc9522a", + "rawBytes": 5354347, + "gzipBytes": 226836, + "deflateSha256": "4b797cb03ac9a777fa803793f936c4402c5a75b80bdca3fbee81636c68f43ba3" + }, + { + "part": "tier-3", + "path": "calibration-trees/us/3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6/tier-3.json.gz", + "sha256": "6d598c253aec09cb149e4480a6e8b3dc9248f797ab1e99a11d691bd70d6d16df", + "rawBytes": 3846319, + "gzipBytes": 149274, + "deflateSha256": "dc6031a2fd3ad7a7127c4a7b7302ffda63addce38d2ec6cf5ab303846badb0bb" + }, + { + "part": "tier-4", + "path": "calibration-trees/us/3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6/tier-4.json.gz", + "sha256": "f40e82c063b5fb2d1768277210d359925373c0069625a17331823b65e862584e", + "rawBytes": 308154, + "gzipBytes": 12761, + "deflateSha256": "188370eb4c1962ef45309cef98de532bdb148e8dab73cbf6c057eb8f37cebb0b" + }, + { + "part": "index", + "path": "calibration-trees/us/3a2f004d0c7c67538dbc76c5fc44e8c2c6c923ca1f38eb505da1265d9ed0bcb6/index.json.gz", + "sha256": "68f0b33df1cde657216338397852533e2312bc1edc90cb768ee5c9e2fe443ba6", + "rawBytes": 298723, + "gzipBytes": 76095, + "deflateSha256": "e19458452d36d3e7392059f98e9d53b8fe47e1cf9196f5be6aed4e214feacea8" + } + ] + }, + "shared": { + "buildArtifactId": "c68fce91b776c236471e542ce7402f513eae623c6cdd1bdba042d44f71cb828d", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/us/c68fce91b776c236471e542ce7402f513eae623c6cdd1bdba042d44f71cb828d/filter-index.json.gz", + "sha256": "fb55b20e3969a458edd9d1fac30292971d26f045b37e3d74e240219280b3ef2d", + "rawBytes": 34342, + "gzipBytes": 7505, + "deflateSha256": "c71e4a4dda5cbc9b02955cc078126fcf1218327497af06da0959b7ce78ace0d6" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/us/c68fce91b776c236471e542ce7402f513eae623c6cdd1bdba042d44f71cb828d/target-summary-0001.json.gz", + "sha256": "4d18b042228fc76279b764810d7792dc0df81a34177dc73015e4a868ff3b732f", + "rawBytes": 3771003, + "gzipBytes": 246320, + "deflateSha256": "31e0cc3f35c081f19968e93d40525e9729be9d7bea14a2608231b127f5bb10f8" + }, + { + "part": "tier-1", + "path": "calibration-trees/us/c68fce91b776c236471e542ce7402f513eae623c6cdd1bdba042d44f71cb828d/tier-1.json.gz", + "sha256": "1e1a6c134ebcfd75be671662da6d3c8f235a4f15fafe7f3f9570f25e52ea7e5c", + "rawBytes": 552600, + "gzipBytes": 40078, + "deflateSha256": "572ec78175cc365a56cff9f440bb589de0a71a078c456e8d6e2ac7c4c27b5413" + }, + { + "part": "tier-2", + "path": "calibration-trees/us/c68fce91b776c236471e542ce7402f513eae623c6cdd1bdba042d44f71cb828d/tier-2.json.gz", + "sha256": "38eefe09571c5d13f5543aad50db9380c47db98150d234e22510f0d2d77817b5", + "rawBytes": 1197416, + "gzipBytes": 58347, + "deflateSha256": "622a4e5f3bdfe10d328a4bd516d1f80df15b2e47daaa1d6d628df05d03e08287" + }, + { + "part": "tier-3", + "path": "calibration-trees/us/c68fce91b776c236471e542ce7402f513eae623c6cdd1bdba042d44f71cb828d/tier-3.json.gz", + "sha256": "05076f3bb6f15df25018538706332658e0fdf0e174b38a5291e2dd7266665130", + "rawBytes": 2108070, + "gzipBytes": 71811, + "deflateSha256": "6c9cfd058f9dbaeab9996689d6777084f2e3bf9e44f2fb46189fe4065c00a318" + }, + { + "part": "index", + "path": "calibration-trees/us/c68fce91b776c236471e542ce7402f513eae623c6cdd1bdba042d44f71cb828d/index.json.gz", + "sha256": "7e22a0727aae3492eeb7663abea959df57ea66c5a19dcce9bff2c2da17802e55", + "rawBytes": 125916, + "gzipBytes": 22273, + "deflateSha256": "ecb285cf59a35d4e75b4190d1bc3fc482dcab64f19c50178e87ae34a1255015b" + } + ] + }, + "firstTargetDetailSha256": "2591b61cf2a4fa2ddf0376f151fc13ae6fffa429127db6ee089bcac5a49ab937" + }, + "uk-comparison": { + "pairArtifactId": "b1678368efa7eaf1a128d0896d5d75f9e13ee6be2b023117048c9e9d5f379a1a", + "reported": { + "buildArtifactId": "0b3f31ebf3dee573063cca681e506199069ad663b659375ea3318a2246a1437a", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/uk/0b3f31ebf3dee573063cca681e506199069ad663b659375ea3318a2246a1437a/filter-index.json.gz", + "sha256": "37b482e364881ce67af69d6b29241bf9a7c5463b246974646da63696e652eabb", + "rawBytes": 837, + "gzipBytes": 378, + "deflateSha256": "805792dead7ebaafc71d1b08d1f6869d1dbfb8f0b025ebcb7b02fd7ee6ec2f65" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/uk/0b3f31ebf3dee573063cca681e506199069ad663b659375ea3318a2246a1437a/target-summary-0001.json.gz", + "sha256": "772d76e6f26a24e7f408109b3647ac32f36c2c3236996d74a0997c8d705e316c", + "rawBytes": 27817, + "gzipBytes": 2749, + "deflateSha256": "5f1c32c3fa153f5cec7a9290310285ed1adf357fe1b2535be09b0588c22d14d0" + }, + { + "part": "tier-1", + "path": "calibration-trees/uk/0b3f31ebf3dee573063cca681e506199069ad663b659375ea3318a2246a1437a/tier-1.json.gz", + "sha256": "6a6ea301d3da672fc99c69087c55d56bf45797371fd6bb538d9a9e6db8c5b8db", + "rawBytes": 18499, + "gzipBytes": 1544, + "deflateSha256": "9f65130d3d06c1ba739db380c3827cb46225d80554f9781b22a9d065b7d79ac3" + }, + { + "part": "tier-2", + "path": "calibration-trees/uk/0b3f31ebf3dee573063cca681e506199069ad663b659375ea3318a2246a1437a/tier-2.json.gz", + "sha256": "2042af40f746c2360e3557e18f316f62d82cf695c358edea17ddc9461616289d", + "rawBytes": 17941, + "gzipBytes": 1557, + "deflateSha256": "2578507f4e2c47f21d034f3e87fcf1fbed546aec43304f24f65bb06581b4af1c" + }, + { + "part": "tier-3", + "path": "calibration-trees/uk/0b3f31ebf3dee573063cca681e506199069ad663b659375ea3318a2246a1437a/tier-3.json.gz", + "sha256": "e5e323937aadb0b71c5aa612ae753262dd6b12d1d98367b3ff5599f9362ebf9f", + "rawBytes": 14981, + "gzipBytes": 1157, + "deflateSha256": "0fbb14728476f1538ad589c099ebe888bc6dcec6d6eafcb8b5762c22aa7d90e5" + }, + { + "part": "index", + "path": "calibration-trees/uk/0b3f31ebf3dee573063cca681e506199069ad663b659375ea3318a2246a1437a/index.json.gz", + "sha256": "872af5a7521ada4eece7114039f4a481dbabb3bee87e0a385363d8ac557fbf01", + "rawBytes": 13124, + "gzipBytes": 2880, + "deflateSha256": "8c493df1b1cbce84e854758ececcafe5b8f04b356fc88e56553de4737db4b266" + } + ] + }, + "shared": { + "buildArtifactId": "c762478f4780a61eb4f6023ec89252f5408df5b55de6e19b45d2bc15c3a84470", + "files": [ + { + "part": "filter-index", + "path": "calibration-trees/uk/c762478f4780a61eb4f6023ec89252f5408df5b55de6e19b45d2bc15c3a84470/filter-index.json.gz", + "sha256": "e16994f6877c725a1c45752c940db770bb169e05357d73abd60b2460306daac1", + "rawBytes": 837, + "gzipBytes": 380, + "deflateSha256": "21449af7ed6eac7fc814f0c6510feab180b70088e7bc536e83f6d93dc0d70a57" + }, + { + "part": "target-summary-0001", + "path": "calibration-trees/uk/c762478f4780a61eb4f6023ec89252f5408df5b55de6e19b45d2bc15c3a84470/target-summary-0001.json.gz", + "sha256": "3be692fed30853ff6087aa4152203ca525f8ebaa75ed75c46959391f1311898e", + "rawBytes": 27817, + "gzipBytes": 2750, + "deflateSha256": "aa26cc87038a3669df9839a49d8e5111a4479683243b7bc927230e3b190897f0" + }, + { + "part": "tier-1", + "path": "calibration-trees/uk/c762478f4780a61eb4f6023ec89252f5408df5b55de6e19b45d2bc15c3a84470/tier-1.json.gz", + "sha256": "1699cea53373599ee96291ad9b55112e7794aa07bca979fe97c451f820578605", + "rawBytes": 18506, + "gzipBytes": 1543, + "deflateSha256": "3f3ed3d2c9057f9d5937a442dd805d2db7104f80180e30e8cd5439e8f8bf1e23" + }, + { + "part": "tier-2", + "path": "calibration-trees/uk/c762478f4780a61eb4f6023ec89252f5408df5b55de6e19b45d2bc15c3a84470/tier-2.json.gz", + "sha256": "1e2868e454501fd249bf27875303b0e608d9128ffa363fd141cba7c4fc508d1b", + "rawBytes": 17945, + "gzipBytes": 1554, + "deflateSha256": "19d1da91928c0a7256df11a8d8b121b80ae57191d57ce5401b6712ac4fb98c4b" + }, + { + "part": "tier-3", + "path": "calibration-trees/uk/c762478f4780a61eb4f6023ec89252f5408df5b55de6e19b45d2bc15c3a84470/tier-3.json.gz", + "sha256": "bf6a95a7d4d224ebf4ee00a498e4ad869f32a298d913d656e117462fb18b0c88", + "rawBytes": 14975, + "gzipBytes": 1157, + "deflateSha256": "a9d33fb093fe27f37e11dd9773bf395afaf578acb903187740abc8632636a3d7" + }, + { + "part": "index", + "path": "calibration-trees/uk/c762478f4780a61eb4f6023ec89252f5408df5b55de6e19b45d2bc15c3a84470/index.json.gz", + "sha256": "97ccd606b8d1a51f886d029e1554a4145038d909d671cf4d7f0ddcf18b8d1e5a", + "rawBytes": 13135, + "gzipBytes": 2893, + "deflateSha256": "337e27bc583ec0ae08ebc949ce62aeaf2fe26d6d42864c4b13ea9027c5773f8a" + } + ] + }, + "firstTargetDetailSha256": "1e62c70f8872039fa6ae165c5c5c2fc1959cd6137ce575e984e6b479a54ab038" + } + }, + "manifest": { + "sha256": "cfaf8056091029ca88b4ada65c7e0001c52da76a87e4f1f90c024914f254c045", + "rawBytes": 4560 + } +} diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v1/SHA256SUMS b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/SHA256SUMS similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v1/SHA256SUMS rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/SHA256SUMS diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v1/calibration_progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/calibration_progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v1/calibration_progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/calibration_progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v1/events.ndjson b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/events.ndjson similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v1/events.ndjson rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/events.ndjson diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v1/latest_staging.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/latest_staging.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v1/latest_staging.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/latest_staging.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v1/progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v1/progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v1/run_manifest.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/run_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v1/run_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/run_manifest.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v1/runs.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/runs.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v1/runs.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v1/runs.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/SHA256SUMS b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/SHA256SUMS similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/SHA256SUMS rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/SHA256SUMS diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/calibration_progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/calibration_progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/calibration_progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/calibration_progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/events.ndjson b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/events.ndjson similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/events.ndjson rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/events.ndjson diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/run_manifest.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/run_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/run_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/calibration/runs/uk-calibration-v2-fixture/run_manifest.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/events.ndjson b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/events.ndjson similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/events.ndjson rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/events.ndjson diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/run_manifest.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/run_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/run_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/completed-spine/runs/uk-spine-v2-fixture/run_manifest.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/contract-cases.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/contract-cases.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/contract-cases.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/contract-cases.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/events.ndjson b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/events.ndjson similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/events.ndjson rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/events.ndjson diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/run_manifest.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/run_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/run_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v2/failed/runs/uk-failed-v2-fixture/run_manifest.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/SHA256SUMS b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/SHA256SUMS similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/SHA256SUMS rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/SHA256SUMS diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/events.ndjson b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/events.ndjson similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/events.ndjson rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/events.ndjson diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/run_manifest.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/run_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/run_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/blocked/runs/uk-blocked-v3-fixture/run_manifest.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/calibration_progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/calibration_progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/calibration_progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/calibration_progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/events.ndjson b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/events.ndjson similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/events.ndjson rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/events.ndjson diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/run_manifest.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/run_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/run_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/calibration/runs/uk-calibration-v3-fixture/run_manifest.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/events.ndjson b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/events.ndjson similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/events.ndjson rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/events.ndjson diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/run_manifest.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/run_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/run_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/completed-spine/runs/uk-spine-v3-fixture/run_manifest.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/contract-cases.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/contract-cases.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/contract-cases.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/contract-cases.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/events.ndjson b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/events.ndjson similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/events.ndjson rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/events.ndjson diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/progress.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/progress.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/progress.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/progress.json diff --git a/frontend/lib/microcosm/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/run_manifest.json b/frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/run_manifest.json similarity index 100% rename from frontend/lib/microcosm/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/run_manifest.json rename to frontend/packages/calibration-tree/src/fixtures/staging-contract/v3/failed/runs/uk-failed-v3-fixture/run_manifest.json diff --git a/frontend/lib/microcosm/fixtures/target-loss-attribution/malformed-partial.json b/frontend/packages/calibration-tree/src/fixtures/target-loss-attribution/malformed-partial.json similarity index 100% rename from frontend/lib/microcosm/fixtures/target-loss-attribution/malformed-partial.json rename to frontend/packages/calibration-tree/src/fixtures/target-loss-attribution/malformed-partial.json diff --git a/frontend/lib/microcosm/fixtures/target-loss-attribution/unavailable-warning.json b/frontend/packages/calibration-tree/src/fixtures/target-loss-attribution/unavailable-warning.json similarity index 100% rename from frontend/lib/microcosm/fixtures/target-loss-attribution/unavailable-warning.json rename to frontend/packages/calibration-tree/src/fixtures/target-loss-attribution/unavailable-warning.json diff --git a/frontend/lib/microcosm/fixtures/target-loss-attribution/valid-unequal-custom-scales.json b/frontend/packages/calibration-tree/src/fixtures/target-loss-attribution/valid-unequal-custom-scales.json similarity index 100% rename from frontend/lib/microcosm/fixtures/target-loss-attribution/valid-unequal-custom-scales.json rename to frontend/packages/calibration-tree/src/fixtures/target-loss-attribution/valid-unequal-custom-scales.json diff --git a/frontend/lib/microcosm/fixtures/target-loss-attribution/valid-uniform.json b/frontend/packages/calibration-tree/src/fixtures/target-loss-attribution/valid-uniform.json similarity index 100% rename from frontend/lib/microcosm/fixtures/target-loss-attribution/valid-uniform.json rename to frontend/packages/calibration-tree/src/fixtures/target-loss-attribution/valid-uniform.json diff --git a/frontend/lib/microcosm/fixtures/us-legacy-target-identities.json.gz b/frontend/packages/calibration-tree/src/fixtures/us-legacy-target-identities.json.gz similarity index 100% rename from frontend/lib/microcosm/fixtures/us-legacy-target-identities.json.gz rename to frontend/packages/calibration-tree/src/fixtures/us-legacy-target-identities.json.gz diff --git a/frontend/lib/microcosm/hierarchy-target-reader.test.ts b/frontend/packages/calibration-tree/src/hierarchy-target-reader.test.ts similarity index 100% rename from frontend/lib/microcosm/hierarchy-target-reader.test.ts rename to frontend/packages/calibration-tree/src/hierarchy-target-reader.test.ts diff --git a/frontend/lib/microcosm/hierarchy-target-reader.ts b/frontend/packages/calibration-tree/src/hierarchy-target-reader.ts similarity index 100% rename from frontend/lib/microcosm/hierarchy-target-reader.ts rename to frontend/packages/calibration-tree/src/hierarchy-target-reader.ts diff --git a/frontend/lib/microcosm/historical-weight-summary.test.ts b/frontend/packages/calibration-tree/src/historical-weight-summary.test.ts similarity index 100% rename from frontend/lib/microcosm/historical-weight-summary.test.ts rename to frontend/packages/calibration-tree/src/historical-weight-summary.test.ts diff --git a/frontend/lib/microcosm/historical-weight-summary.ts b/frontend/packages/calibration-tree/src/historical-weight-summary.ts similarity index 100% rename from frontend/lib/microcosm/historical-weight-summary.ts rename to frontend/packages/calibration-tree/src/historical-weight-summary.ts diff --git a/frontend/lib/microcosm/latest-artifact.test.ts b/frontend/packages/calibration-tree/src/latest-artifact.test.ts similarity index 100% rename from frontend/lib/microcosm/latest-artifact.test.ts rename to frontend/packages/calibration-tree/src/latest-artifact.test.ts diff --git a/frontend/lib/microcosm/latest-artifact.ts b/frontend/packages/calibration-tree/src/latest-artifact.ts similarity index 99% rename from frontend/lib/microcosm/latest-artifact.ts rename to frontend/packages/calibration-tree/src/latest-artifact.ts index b0ba929b..692006b4 100644 --- a/frontend/lib/microcosm/latest-artifact.ts +++ b/frontend/packages/calibration-tree/src/latest-artifact.ts @@ -8,7 +8,7 @@ import { basename, join } from "node:path"; import { createHash } from "node:crypto"; -import { sourceAuthorityLabel } from "@/lib/source-labels"; +import { sourceAuthorityLabel } from "./source-labels"; import { normalizeChronicleMetadata } from "./chronicle-metadata"; import { diff --git a/frontend/lib/microcosm/legacy-target-reader.ts b/frontend/packages/calibration-tree/src/legacy-target-reader.ts similarity index 100% rename from frontend/lib/microcosm/legacy-target-reader.ts rename to frontend/packages/calibration-tree/src/legacy-target-reader.ts diff --git a/frontend/lib/microcosm/legacy-target-regression.test.ts b/frontend/packages/calibration-tree/src/legacy-target-regression.test.ts similarity index 100% rename from frontend/lib/microcosm/legacy-target-regression.test.ts rename to frontend/packages/calibration-tree/src/legacy-target-regression.test.ts diff --git a/frontend/packages/calibration-tree/src/package-boundary.test.ts b/frontend/packages/calibration-tree/src/package-boundary.test.ts new file mode 100644 index 00000000..7d6846ac --- /dev/null +++ b/frontend/packages/calibration-tree/src/package-boundary.test.ts @@ -0,0 +1,128 @@ +// The package must stay self-contained so it can leave the dashboard's +// repository: its modules import only each other, Node built-ins and the +// dependencies its manifest declares, never the dashboard's `@/` aliases, +// Next.js or `server-only`. Consumers reach it only through its exports. +import { Glob } from "bun"; +import { expect, test } from "bun:test"; +import { existsSync, readFileSync, statSync } from "node:fs"; +import { builtinModules } from "node:module"; +import { basename, dirname, join, relative, resolve } from "node:path"; + +const PACKAGE_ROOT = resolve(import.meta.dir, ".."); +const SOURCE_ROOT = join(PACKAGE_ROOT, "src"); +// Bun lists its own modules (`bun`, `bun:test`, ...) among the built-ins. +const NODE_BUILTINS = new Set(builtinModules.filter((name) => !name.startsWith("bun"))); + +interface PackageManifest { + name: string; + exports: Record; + dependencies?: Record; + devDependencies?: Record; +} + +function packageManifest(): PackageManifest { + return JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8")); +} + +function sourceFiles(): string[] { + return [...new Glob("**/*.ts").scanSync({ cwd: SOURCE_ROOT })].sort(); +} + +// Tests and their support code may also use the test runner, the package's +// own entry points and its development dependencies. +function isTestCode(relativePath: string): boolean { + return relativePath.endsWith(".test.ts") || relativePath.startsWith("test-support/"); +} + +function importSpecifiers(source: string): string[] { + const patterns = [ + /(?:^|\n)\s*(?:import|export)\s[^;]*?\sfrom\s+["']([^"']+)["']/g, + /(?:^|\n)\s*import\s+["']([^"']+)["']/g, + /\bimport\(\s*["']([^"']+)["']\s*\)/g, + /\bmock\.module\(\s*["']([^"']+)["']/g, + ]; + return patterns.flatMap((pattern) => + [...source.matchAll(pattern)].map((match) => match[1]) + ); +} + +function packageName(specifier: string): string { + const segments = specifier.split("/"); + return specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0]; +} + +function resolvesInsidePackage(fromFile: string, specifier: string): boolean { + const base = resolve(dirname(fromFile), specifier); + if (relative(PACKAGE_ROOT, base).startsWith("..")) return false; + return [base, `${base}.ts`, join(base, "index.ts")].some( + (candidate) => existsSync(candidate) && statSync(candidate).isFile(), + ); +} + +function importViolation( + relativePath: string, + specifier: string, + manifest: PackageManifest, +): string | null { + const file = join(SOURCE_ROOT, relativePath); + const testCode = isTestCode(relativePath); + if (specifier.startsWith(".")) { + return resolvesInsidePackage(file, specifier) ? null : "does not resolve inside the package"; + } + if (specifier === "bun" || specifier.startsWith("bun:")) { + return testCode ? null : "is test-runner code outside a test"; + } + if (specifier.startsWith("node:")) { + return NODE_BUILTINS.has(specifier.slice("node:".length)) ? null : "is not a Node built-in"; + } + if (NODE_BUILTINS.has(specifier)) return "must use the node: prefix"; + if (specifier.startsWith("@/")) return "is a dashboard path alias"; + if (packageName(specifier) === manifest.name) { + if (!testCode) return "is a self-reference; package modules import each other relatively"; + return `./${specifier.slice(manifest.name.length + 1)}` in manifest.exports + ? null + : "is not a package export"; + } + const declared = { + ...manifest.dependencies, + ...(testCode ? manifest.devDependencies : {}), + }; + return packageName(specifier) in declared ? null : "is not a declared dependency"; +} + +test("package modules import only package files, Node built-ins and declared dependencies", () => { + const manifest = packageManifest(); + const violations = sourceFiles().flatMap((relativePath) => + importSpecifiers(readFileSync(join(SOURCE_ROOT, relativePath), "utf8")).flatMap((specifier) => { + const violation = importViolation(relativePath, specifier, manifest); + return violation ? [`${relativePath}: "${specifier}" ${violation}`] : []; + }) + ); + + expect(violations).toEqual([]); +}); + +test("every package export resolves to a package module", () => { + const manifest = packageManifest(); + const unresolved = Object.entries(manifest.exports).flatMap(([subpath, target]) => { + const file = resolve(PACKAGE_ROOT, target); + const valid = + subpath === `./${basename(target, ".ts")}` && + !relative(SOURCE_ROOT, file).startsWith("..") && + existsSync(file); + return valid ? [] : [`${subpath} -> ${target}`]; + }); + + expect(Object.keys(manifest.exports).length).toBeGreaterThan(0); + expect(unresolved).toEqual([]); +}); + +test("every package module is exported", () => { + const manifest = packageManifest(); + const exportedTargets = new Set(Object.values(manifest.exports)); + const unexported = sourceFiles() + .filter((relativePath) => !isTestCode(relativePath)) + .filter((relativePath) => !exportedTargets.has(`./src/${relativePath}`)); + + expect(unexported).toEqual([]); +}); diff --git a/frontend/lib/microcosm/program-label.test.ts b/frontend/packages/calibration-tree/src/program-label.test.ts similarity index 100% rename from frontend/lib/microcosm/program-label.test.ts rename to frontend/packages/calibration-tree/src/program-label.test.ts diff --git a/frontend/lib/microcosm/program-label.ts b/frontend/packages/calibration-tree/src/program-label.ts similarity index 100% rename from frontend/lib/microcosm/program-label.ts rename to frontend/packages/calibration-tree/src/program-label.ts diff --git a/frontend/packages/calibration-tree/src/publish-calibration-tree-golden.test.ts b/frontend/packages/calibration-tree/src/publish-calibration-tree-golden.test.ts new file mode 100644 index 00000000..8f1cf64d --- /dev/null +++ b/frontend/packages/calibration-tree/src/publish-calibration-tree-golden.test.ts @@ -0,0 +1,176 @@ +import { afterEach, expect, test } from "bun:test"; + +import recordedGoldens from "./fixtures/calibration-tree-golden.json"; +import { + emptyCalibrationTreeManifest, + withCalibrationTreeManifestEntry, + type CalibrationTreeManifestEntry, +} from "@policyengine/calibration-tree/calibration-tree-manifest"; +import { COUNTRY_REGISTRY } from "@policyengine/calibration-tree/countries"; +import { + goldenReleaseInputs, + withRegisteredRepositories, + type CalibrationTreeGoldenRecord, + type GoldenReleaseInput, +} from "./test-support/calibration-tree-golden-cases"; +import { + reconcileReleaseBuilds, + type CalibrationManifestStore, +} from "@policyengine/calibration-tree/publish-calibration-tree"; + +// A repair rebuilds a published release whose index went missing and refuses +// any rebuilt index whose digest differs from the manifest entry. Seeding the +// entry with the recorded build id and index digest therefore proves, through +// the publisher's own Hugging Face reads and source hashing, that it rebuilds +// the recorded index byte for byte. +const recorded = recordedGoldens as CalibrationTreeGoldenRecord; +const MAIN_REVISION_SHA = "0123456789abcdef0123456789abcdef01234567"; +// Generous, so the suite also passes on a heavily loaded machine. +const BUILD_TIMEOUT_MS = 300_000; +const originalFetch = globalThis.fetch; + +afterEach(() => { + globalThis.fetch = originalFetch; +}); + +type ReleaseCaseName = keyof ReturnType; + +// The dataset repository as the publisher reads it: one tagged release at +// its commit, which `latest.json` on the main branch also names. +function huggingFaceReleaseRepository(release: GoldenReleaseInput): typeof fetch { + const repo = COUNTRY_REGISTRY[release.country].repo; + const api = `https://huggingface.co/api/datasets/${repo}`; + const resolveUrl = (revision: string, path: string) => + `https://huggingface.co/datasets/${repo}/resolve/${revision}/${path}`; + const prefix = `releases/${release.releaseId}`; + const publishedFiles = new Map([ + ["calibration_diagnostics.json", release.diagnosticsText], + ...(release.releaseManifestText == null + ? [] + : [["release_manifest.json", release.releaseManifestText] as [string, string]]), + ]); + const responses = new Map Response>([ + [ + `${api}/tree/main/releases?recursive=true`, + () => Response.json([...publishedFiles.keys()].map((name) => ({ + type: "file", + path: `${prefix}/${name}`, + }))), + ], + [ + `${api}/refs`, + () => Response.json({ + tags: [{ name: release.releaseId, targetCommit: release.hfCommitSha }], + }), + ], + [`${api}/revision/main`, () => Response.json({ sha: MAIN_REVISION_SHA })], + [`${api}/revision/${release.releaseId}`, () => Response.json({ sha: release.hfCommitSha })], + [ + resolveUrl(MAIN_REVISION_SHA, "latest.json"), + () => Response.json({ release_id: release.releaseId }), + ], + ]); + for (const [name, text] of publishedFiles) { + responses.set(resolveUrl(release.hfCommitSha, `${prefix}/${name}`), () => new Response(text)); + responses.set(resolveUrl("main", `${prefix}/${name}`), () => new Response(text)); + } + return (async (input: string | URL | Request) => { + const url = String(input); + const respond = responses.get(url); + if (respond) return respond(); + if (url.startsWith(resolveUrl(release.hfCommitSha, `${prefix}/`))) { + return new Response(null, { status: 404 }); + } + throw new Error(`Unexpected Hugging Face request ${url}`); + }) as typeof fetch; +} + +function recordedReleaseEntry( + release: GoldenReleaseInput, + name: ReleaseCaseName, +): CalibrationTreeManifestEntry { + const record = recorded.sources[name]; + const index = record.files.find((file) => file.part === "index"); + if (!index) throw new Error(`The ${name} record has no index.`); + return { + buildArtifactId: record.buildArtifactId, + kind: "release", + sourceId: release.releaseId, + label: release.releaseId, + releaseId: release.releaseId, + stagingRunId: null, + hfRepo: COUNTRY_REGISTRY[release.country].repo, + hfCommitSha: release.hfCommitSha, + treeSchemaVersion: 6, + indexSha256: index.sha256, + indexBytes: index.rawBytes, + createdAt: release.publishedAt, + updatedAt: release.publishedAt, + }; +} + +// A published release whose stored index has gone missing. +function storeMissingIndex( + release: GoldenReleaseInput, + entry: CalibrationTreeManifestEntry, +): CalibrationManifestStore { + return { + readManifest: async () => withCalibrationTreeManifestEntry( + emptyCalibrationTreeManifest(), + release.country, + entry, + true, + ), + listBlobs: async () => new Map(), + audit: async () => ({ + complete: false, + repairable: true, + reasons: ["index is missing"], + index: null, + }), + updateManifest: async () => { + throw new Error("A dry run must not write the manifest."); + }, + }; +} + +const RELEASE_DESCRIPTIONS: Record<"us-release" | "uk-release" | "be-release", string> = { + "us-release": "US legacy", + "uk-release": "UK hierarchy", + "be-release": "Belgium", +}; + +for (const [name, description] of Object.entries(RELEASE_DESCRIPTIONS) as Array< + [keyof typeof RELEASE_DESCRIPTIONS, string] +>) { + test( + `a dry-run repair of the recorded ${description} release rebuilds its recorded index`, + async () => { + const release = goldenReleaseInputs()[name]; + const entry = recordedReleaseEntry(release, name); + globalThis.fetch = huggingFaceReleaseRepository(release); + + const report = await withRegisteredRepositories(() => + reconcileReleaseBuilds(release.country, "unused-blob-token", true, storeMissingIndex(release, entry)) + ); + + expect(report.outcomes).toEqual([{ + sourceId: release.releaseId, + status: "repaired", + buildArtifactId: recorded.sources[name].buildArtifactId, + reason: "Dry run; no Blob writes performed.", + }]); + }, + BUILD_TIMEOUT_MS, + ); +} + +test("a repair refuses a rebuilt index that differs from the recorded digest", async () => { + const release = goldenReleaseInputs()["uk-release"]; + const entry = { ...recordedReleaseEntry(release, "uk-release"), indexSha256: "0".repeat(64) }; + globalThis.fetch = huggingFaceReleaseRepository(release); + + await expect(withRegisteredRepositories(() => + reconcileReleaseBuilds(release.country, "unused-blob-token", true, storeMissingIndex(release, entry)) + )).rejects.toThrow("Rebuilt calibration index differs from immutable manifest entry"); +}); diff --git a/frontend/scripts/publish-calibration-tree.test.ts b/frontend/packages/calibration-tree/src/publish-calibration-tree.test.ts similarity index 99% rename from frontend/scripts/publish-calibration-tree.test.ts rename to frontend/packages/calibration-tree/src/publish-calibration-tree.test.ts index edbaa457..ce10d492 100644 --- a/frontend/scripts/publish-calibration-tree.test.ts +++ b/frontend/packages/calibration-tree/src/publish-calibration-tree.test.ts @@ -15,7 +15,7 @@ import { withCalibrationTreeManifestEntry, type CalibrationTreeManifest, type CalibrationTreeManifestEntry, -} from "../lib/microcosm/calibration-tree-manifest"; +} from "./calibration-tree-manifest"; const originalFetch = globalThis.fetch; diff --git a/frontend/scripts/publish-calibration-tree.ts b/frontend/packages/calibration-tree/src/publish-calibration-tree.ts similarity index 98% rename from frontend/scripts/publish-calibration-tree.ts rename to frontend/packages/calibration-tree/src/publish-calibration-tree.ts index 7530e634..30e2d71b 100644 --- a/frontend/scripts/publish-calibration-tree.ts +++ b/frontend/packages/calibration-tree/src/publish-calibration-tree.ts @@ -1,11 +1,11 @@ import { CALIBRATION_TREE_SCHEMA_VERSION, type CalibrationTreeSourceArtifact, -} from "../lib/microcosm/calibration-tree-artifact"; +} from "./calibration-tree-artifact"; import { buildCalibrationTreeBundle, type CalibrationTreeBundleFile, -} from "../lib/microcosm/calibration-tree-bundle"; +} from "./calibration-tree-bundle"; import { auditCalibrationTreeBuild, listCalibrationTreeBlobs, @@ -13,12 +13,12 @@ import { updateCalibrationTreeManifest, uploadCalibrationTreeBundle, type CalibrationTreeBuildAudit, -} from "../lib/microcosm/calibration-tree-blob"; +} from "./calibration-tree-blob"; import type { ListBlobResultBlob } from "@vercel/blob"; import type { CalibrationTreeManifest, CalibrationTreeManifestEntry, -} from "../lib/microcosm/calibration-tree-manifest"; +} from "./calibration-tree-manifest"; import { createHash } from "node:crypto"; import { CalibrationReleaseNotFoundError, @@ -26,24 +26,24 @@ import { resolveHfReleaseDirectorySha, resolveHfRepositoryCommitPublishedAt, resolveHfRevisionSha, -} from "../lib/microcosm/calibration-release-locator"; +} from "./calibration-release-locator"; import { isCountry, selectableCountries, type MicrocosmCountry, -} from "../lib/microcosm/countries"; +} from "./countries"; import { buildCalibration, loadReleases, microcosmRepo, microcosmRevision, -} from "../lib/microcosm/latest-artifact"; +} from "./latest-artifact"; import { loadStagingRuns, resolveStagingCalibrationSource, resolveStagingRevisionSha, stagingRepository, -} from "../lib/microcosm/staging-artifact"; +} from "./staging-artifact"; type JsonObject = Record; diff --git a/frontend/lib/microcosm/reforms.test.ts b/frontend/packages/calibration-tree/src/reforms.test.ts similarity index 100% rename from frontend/lib/microcosm/reforms.test.ts rename to frontend/packages/calibration-tree/src/reforms.test.ts diff --git a/frontend/lib/microcosm/reforms.ts b/frontend/packages/calibration-tree/src/reforms.ts similarity index 100% rename from frontend/lib/microcosm/reforms.ts rename to frontend/packages/calibration-tree/src/reforms.ts diff --git a/frontend/lib/microcosm/source-label.test.ts b/frontend/packages/calibration-tree/src/source-label.test.ts similarity index 100% rename from frontend/lib/microcosm/source-label.test.ts rename to frontend/packages/calibration-tree/src/source-label.test.ts diff --git a/frontend/lib/microcosm/source-label.ts b/frontend/packages/calibration-tree/src/source-label.ts similarity index 61% rename from frontend/lib/microcosm/source-label.ts rename to frontend/packages/calibration-tree/src/source-label.ts index 9823a7d5..ebe8d75d 100644 --- a/frontend/lib/microcosm/source-label.ts +++ b/frontend/packages/calibration-tree/src/source-label.ts @@ -1,4 +1,4 @@ -import { sourceAuthorityLabel } from "@/lib/source-labels"; +import { sourceAuthorityLabel } from "./source-labels"; export function sourceLabel(source: string): string { return sourceAuthorityLabel(source); diff --git a/frontend/lib/source-labels.test.ts b/frontend/packages/calibration-tree/src/source-labels.test.ts similarity index 100% rename from frontend/lib/source-labels.test.ts rename to frontend/packages/calibration-tree/src/source-labels.test.ts diff --git a/frontend/lib/source-labels.ts b/frontend/packages/calibration-tree/src/source-labels.ts similarity index 100% rename from frontend/lib/source-labels.ts rename to frontend/packages/calibration-tree/src/source-labels.ts diff --git a/frontend/lib/microcosm/staging-artifact.test.ts b/frontend/packages/calibration-tree/src/staging-artifact.test.ts similarity index 100% rename from frontend/lib/microcosm/staging-artifact.test.ts rename to frontend/packages/calibration-tree/src/staging-artifact.test.ts diff --git a/frontend/lib/microcosm/staging-artifact.ts b/frontend/packages/calibration-tree/src/staging-artifact.ts similarity index 98% rename from frontend/lib/microcosm/staging-artifact.ts rename to frontend/packages/calibration-tree/src/staging-artifact.ts index 84163369..5663eb85 100644 --- a/frontend/lib/microcosm/staging-artifact.ts +++ b/frontend/packages/calibration-tree/src/staging-artifact.ts @@ -13,24 +13,24 @@ import { loadRelease, microcosmCountryGeography, microcosmRepo, -} from "@/lib/microcosm/latest-artifact"; -import type { CalibrationTreeSourceArtifact } from "@/lib/microcosm/calibration-tree-artifact"; +} from "./latest-artifact"; +import type { CalibrationTreeSourceArtifact } from "./calibration-tree-artifact"; import { countryRegistration, resolveRegisteredStagingRepository, type RepositoryEnvironment, type ResolvedRepository, -} from "@/lib/microcosm/countries"; +} from "./countries"; import { type ReformValidation, buildReformValidation, -} from "@/lib/microcosm/reforms"; +} from "./reforms"; import { buildTargetChangeDataset, type TargetChangeDataset, -} from "@/lib/microcosm/target-change"; -import { buildTargetChangeDatasetFromSummaryAndCalibration } from "@/lib/microcosm/calibration-build-comparison"; -import { loadCalibrationComparisonSource } from "@/lib/microcosm/calibration-comparison-blob"; +} from "./target-change"; +import { buildTargetChangeDatasetFromSummaryAndCalibration } from "./calibration-build-comparison"; +import { loadCalibrationComparisonSource } from "./calibration-comparison-blob"; import { IncompatibleStagingDataError, isStructuredStagingDocument, @@ -41,7 +41,7 @@ import { parseStagingRunIndex, validateStagingRunConsistency, type StagingSchemaVersion, -} from "@/lib/microcosm/staging-contract"; +} from "./staging-contract"; type JsonObject = Record; diff --git a/frontend/lib/microcosm/staging-contract.test.ts b/frontend/packages/calibration-tree/src/staging-contract.test.ts similarity index 100% rename from frontend/lib/microcosm/staging-contract.test.ts rename to frontend/packages/calibration-tree/src/staging-contract.test.ts diff --git a/frontend/lib/microcosm/staging-contract.ts b/frontend/packages/calibration-tree/src/staging-contract.ts similarity index 100% rename from frontend/lib/microcosm/staging-contract.ts rename to frontend/packages/calibration-tree/src/staging-contract.ts diff --git a/frontend/lib/microcosm/structured-dimension-reader.test.ts b/frontend/packages/calibration-tree/src/structured-dimension-reader.test.ts similarity index 100% rename from frontend/lib/microcosm/structured-dimension-reader.test.ts rename to frontend/packages/calibration-tree/src/structured-dimension-reader.test.ts diff --git a/frontend/lib/microcosm/structured-dimension-reader.ts b/frontend/packages/calibration-tree/src/structured-dimension-reader.ts similarity index 100% rename from frontend/lib/microcosm/structured-dimension-reader.ts rename to frontend/packages/calibration-tree/src/structured-dimension-reader.ts diff --git a/frontend/lib/microcosm/structured-target-reader.ts b/frontend/packages/calibration-tree/src/structured-target-reader.ts similarity index 100% rename from frontend/lib/microcosm/structured-target-reader.ts rename to frontend/packages/calibration-tree/src/structured-target-reader.ts diff --git a/frontend/lib/microcosm/target-change.test.ts b/frontend/packages/calibration-tree/src/target-change.test.ts similarity index 100% rename from frontend/lib/microcosm/target-change.test.ts rename to frontend/packages/calibration-tree/src/target-change.test.ts diff --git a/frontend/lib/microcosm/target-change.ts b/frontend/packages/calibration-tree/src/target-change.ts similarity index 100% rename from frontend/lib/microcosm/target-change.ts rename to frontend/packages/calibration-tree/src/target-change.ts diff --git a/frontend/lib/microcosm/target-loss-attribution-manifest.ts b/frontend/packages/calibration-tree/src/target-loss-attribution-manifest.ts similarity index 100% rename from frontend/lib/microcosm/target-loss-attribution-manifest.ts rename to frontend/packages/calibration-tree/src/target-loss-attribution-manifest.ts diff --git a/frontend/lib/microcosm/target-loss-attribution.test.ts b/frontend/packages/calibration-tree/src/target-loss-attribution.test.ts similarity index 100% rename from frontend/lib/microcosm/target-loss-attribution.test.ts rename to frontend/packages/calibration-tree/src/target-loss-attribution.test.ts diff --git a/frontend/lib/microcosm/target-loss-attribution.ts b/frontend/packages/calibration-tree/src/target-loss-attribution.ts similarity index 100% rename from frontend/lib/microcosm/target-loss-attribution.ts rename to frontend/packages/calibration-tree/src/target-loss-attribution.ts diff --git a/frontend/lib/microcosm/target-representation.test.ts b/frontend/packages/calibration-tree/src/target-representation.test.ts similarity index 100% rename from frontend/lib/microcosm/target-representation.test.ts rename to frontend/packages/calibration-tree/src/target-representation.test.ts diff --git a/frontend/lib/microcosm/target-representation.ts b/frontend/packages/calibration-tree/src/target-representation.ts similarity index 100% rename from frontend/lib/microcosm/target-representation.ts rename to frontend/packages/calibration-tree/src/target-representation.ts diff --git a/frontend/lib/microcosm/target-surface-matcher.test.ts b/frontend/packages/calibration-tree/src/target-surface-matcher.test.ts similarity index 100% rename from frontend/lib/microcosm/target-surface-matcher.test.ts rename to frontend/packages/calibration-tree/src/target-surface-matcher.test.ts diff --git a/frontend/lib/microcosm/target-surface-matcher.ts b/frontend/packages/calibration-tree/src/target-surface-matcher.ts similarity index 100% rename from frontend/lib/microcosm/target-surface-matcher.ts rename to frontend/packages/calibration-tree/src/target-surface-matcher.ts diff --git a/frontend/packages/calibration-tree/src/test-support/calibration-tree-golden-cases.ts b/frontend/packages/calibration-tree/src/test-support/calibration-tree-golden-cases.ts new file mode 100644 index 00000000..0a7b0b93 --- /dev/null +++ b/frontend/packages/calibration-tree/src/test-support/calibration-tree-golden-cases.ts @@ -0,0 +1,952 @@ +// Golden calibration tree cases: representative builder inputs (US and UK, +// releases and staging runs, and comparisons between them) assembled from the +// fixtures the module tests already use, and a record of everything the +// builder writes for them. The record reduces each written part to its path, +// raw JSON digest and size, gzip size and deflate-stream digest, and adds the +// build ids, the manifest bytes, and what the readers return from the +// written bytes. +// +// fixtures/calibration-tree-golden.json is that record as produced by the +// builder before it moved into this package; the golden tests require the +// builder to reproduce it exactly. The cases import the builder through the +// package's public entry points. Regenerate the record only for an intended +// output change, from the package directory: +// +// bun run src/test-support/calibration-tree-golden-cases.ts +import { createHash } from "node:crypto"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { gunzipSync } from "node:zlib"; + +import { + loadCalibrationComparisonSource, + loadCalibrationComparisonTargetDetail, +} from "@policyengine/calibration-tree/calibration-comparison-blob"; +import { buildCalibrationComparisonBundles } from "@policyengine/calibration-tree/calibration-comparison-bundle"; +import { createExplorerState, type ExplorerState } from "@policyengine/calibration-tree/calibration-explorer"; +import { + CALIBRATION_TREE_SCHEMA_VERSION, + calibrationTreePartPath, + calibrationTreeResponseFromBundle, + calibrationTreeTargetsFromSummaries, + parseCalibrationTreeIndex, + parseCalibrationTreePart, + type CalibrationTreeFilterIndexArtifact, + type CalibrationTreeSourceArtifact, + type CalibrationTreeTargetDetailsArtifact, + type CalibrationTreeTargetSummaryArtifact, + type CalibrationTreeTierArtifact, +} from "@policyengine/calibration-tree/calibration-tree-artifact"; +import type { getCalibrationTreeBlob } from "@policyengine/calibration-tree/calibration-tree-blob"; +import { + buildCalibrationTreeBundle, + type CalibrationTreeBundle, + type CalibrationTreeBundleFile, +} from "@policyengine/calibration-tree/calibration-tree-bundle"; +import { + emptyCalibrationTreeManifest, + serializeCalibrationTreeManifest, + withCalibrationTreeManifestEntry, + type CalibrationTreeManifestEntry, +} from "@policyengine/calibration-tree/calibration-tree-manifest"; +import { + COUNTRY_REGISTRY, + type CountryRegistration, + type MicrocosmCountry, +} from "@policyengine/calibration-tree/countries"; +import { + UK_LOCAL_CALIBRATION_DIR_ENV, + buildCalibration, + microcosmRepo, + type Calibration, +} from "@policyengine/calibration-tree/latest-artifact"; +import { + resolveStagingCalibrationSource, + stagingRepository, +} from "@policyengine/calibration-tree/staging-artifact"; +import { + TARGET_LOSS_BASIS_HASH_ALGORITHM, + TARGET_LOSS_FORMULA, + targetLossBasisHash, +} from "@policyengine/calibration-tree/target-loss-attribution"; + +type JsonObject = Record; + +const FIXTURES = join(import.meta.dir, "..", "fixtures"); +export const CALIBRATION_TREE_GOLDEN_RECORD_PATH = join(FIXTURES, "calibration-tree-golden.json"); + +// gzipSync writes a fixed ten-byte header: magic, deflate, no flags, zero +// mtime, maximum-compression flag, then the operating-system byte, which +// zlib sets per platform (3 on Linux, 19 on macOS). Everything after it is +// the deflate stream and the CRC-32 and size trailer. +export const GZIP_HEADER_WITHOUT_OS = [0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]; +const GZIP_HEADER_BYTES = 10; + +export interface GoldenFileRecord { + part: string; + path: string; + sha256: string; + rawBytes: number; + gzipBytes: number; + deflateSha256: string; +} + +export interface GoldenBundleRecord { + buildArtifactId: string; + files: GoldenFileRecord[]; +} + +export interface GoldenSourceRecord extends GoldenBundleRecord { + readers: { + programRootSha256: string; + geographyRootSha256: string; + }; +} + +export interface GoldenComparisonRecord { + pairArtifactId: string; + reported: GoldenBundleRecord; + shared: GoldenBundleRecord; + firstTargetDetailSha256: string; +} + +export const GOLDEN_SOURCE_CASES = [ + "us-release", + "uk-release", + "uk-release-loss-attribution", + "be-release", + "us-staging", + "uk-staging", + "uk-staging-staged-bundle", +] as const; +export type GoldenSourceCaseName = (typeof GOLDEN_SOURCE_CASES)[number]; + +export const GOLDEN_COMPARISON_CASES = ["us-comparison", "uk-comparison"] as const; +export type GoldenComparisonCaseName = (typeof GOLDEN_COMPARISON_CASES)[number]; + +export interface CalibrationTreeGoldenRecord { + schemaVersion: typeof CALIBRATION_TREE_SCHEMA_VERSION; + sources: Record; + comparisons: Record; + manifest: { sha256: string; rawBytes: number }; +} + +export interface GoldenReleaseInput { + country: MicrocosmCountry; + releaseId: string; + hfCommitSha: string; + publishedAt: string; + diagnosticsText: string; + releaseManifestText: string | null; +} + +interface GoldenSourceBuild { + country: MicrocosmCountry; + bundle: CalibrationTreeBundle; + manifestEntry: CalibrationTreeManifestEntry; +} + +function sha256Text(value: string): string { + return createHash("sha256").update(value, "utf8").digest("hex"); +} + +function sha256Bytes(value: Uint8Array): string { + return createHash("sha256").update(value).digest("hex"); +} + +function fixtureText(relativePath: string): string { + return readFileSync(join(FIXTURES, relativePath), "utf8"); +} + +function compressedFixtureText(relativePath: string): string { + return gunzipSync(readFileSync(join(FIXTURES, relativePath))).toString("utf8"); +} + +// --- Inputs --------------------------------------------------------------- + +const US_RELEASE_ID = "populace-us-2024-buildp-sparse-rmloss100-cae8640-20260728T011454Z"; +const UK_RELEASE_ID = "microcosm-uk-2024-25-national-golden"; +const UK_LOSS_ATTRIBUTION_RELEASE_ID = "microcosm-uk-2024-25-loss-attribution-golden"; +const BE_RELEASE_ID = "microcosm-be-2024-national-golden"; + +const US_STAGING_RUN_ID = "us-calibration-golden-20260930T120000Z"; +const UK_STAGING_RUN_ID = "uk-calibration-golden-20261002T231556Z"; +const UK_STAGED_BUNDLE_RUN_ID = "uk-frs-calibration-attempt-20261003T081500Z-golden"; + +const US_STAGING_REVISION = "5c2a9e1d7b3f4a6c8e0d2b4f6a8c0e2d4b6f8a0c"; +const UK_STAGING_REVISION = "8e4b2d6f0a1c3e5a7c9e1b3d5f7a9c1e3b5d7f9a"; +const UK_STAGED_BUNDLE_REVISION = "40439a3f8d241cd361bb693488b42e7258623a00"; + +// Assign each identity-only legacy target a deterministic target and +// estimates, so the tree carries real fit bands and errors. +function withLegacyValues(targets: JsonObject[]): JsonObject[] { + return targets.map((target, targetIndex) => { + const targetValue = 1_000 * (1 + (targetIndex % 97)); + return { + ...target, + target: targetValue, + initial_estimate: targetValue * (1 + ((targetIndex % 31) - 15) / 50), + final_estimate: targetValue * (1 + ((targetIndex % 23) - 11) / 100), + }; + }); +} + +// Attach the weighted target-error attribution Microcosm reports from +// diagnostics schema 6 on: provided weights and scales, each target's capped +// scaled error and loss contribution, and the basis digest the reader +// verifies. Comparisons are built only between builds that report it. +function withReportedLossAttribution(diagnostics: JsonObject): JsonObject { + const cap = 1; + const weighted = (diagnostics.targets as JsonObject[]).map((target, targetIndex) => ({ + ...target, + name: String(target.name), + target: Number(target.target), + final_estimate: Number(target.final_estimate), + target_loss_weight: 1 + (targetIndex % 4), + target_loss_scale: Math.max(Math.abs(Number(target.target)), 1), + })); + const totalWeight = weighted.reduce((sum, target) => sum + target.target_loss_weight, 0); + const targets = weighted.map((target) => { + const cappedError = Math.min( + Math.abs(target.final_estimate - target.target) / target.target_loss_scale, + cap, + ); + const share = target.target_loss_weight / totalWeight; + return { + ...target, + target_loss_weight_share: share, + final_capped_scaled_error: cappedError, + final_loss_contribution: share * cappedError, + }; + }); + return { + ...diagnostics, + final_loss: targets.reduce((sum, target) => sum + target.final_loss_contribution, 0), + diagnostic_warnings: [], + target_loss_basis: { + formula: TARGET_LOSS_FORMULA, + cap, + target_count: targets.length, + total_target_weight: totalWeight, + weight_kind: "provided", + scale_kind: "provided", + hash_algorithm: TARGET_LOSS_BASIS_HASH_ALGORITHM, + sha256: targetLossBasisHash(targets.map((target) => ({ + name: target.name, + target_loss_weight: target.target_loss_weight, + target_loss_scale: target.target_loss_scale, + }))), + }, + targets, + }; +} + +function usLegacyTargets(): JsonObject[] { + const fixture = JSON.parse(compressedFixtureText("us-legacy-target-identities.json.gz")) as { + targets: JsonObject[]; + }; + return withLegacyValues(fixture.targets); +} + +// Schema 6 keeps the legacy target reader and adds reported attribution. +function usReleaseDiagnostics(): JsonObject { + return withReportedLossAttribution({ schema_version: 6, targets: usLegacyTargets() }); +} + +// The four structured UK geographies and two receipts lines used by the +// schema 7 and schema 8 reader tests. +const UK_GEOGRAPHIES = [ + { id: "K02000001", label: "United Kingdom" }, + { id: "K03000001", label: "Great Britain" }, + { id: "E92000001", label: "England" }, + { id: "S92000003", label: "Scotland" }, +]; +const UK_RECEIPT_LINES = [ + { id: "income_tax", label: "Income tax receipts" }, + { id: "national_insurance", label: "National insurance contributions" }, +]; + +function ukHierarchyTarget(options: { + name: string; + provider: { id: string; label: string }; + category: { id: string; label: string }; + geography: { id: string; label: string }; + dimension: { id: string; label: string } | null; + targetLabel: string; + target: number; + initialEstimate: number; + finalEstimate: number; +}): JsonObject { + return { + name: `${options.name}@2025`, + target_name: options.name, + source: `${options.provider.label} citation`, + target: options.target, + initial_estimate: options.initialEstimate, + final_estimate: options.finalEstimate, + hierarchy: { + provider: options.provider, + category: { ...options.category, provider_id: options.provider.id }, + geography: { ...options.geography, level: "country" }, + dimensions: options.dimension + ? [{ + id: `${options.category.id}_line`, + label: "Economic and fiscal outlook line", + value_id: options.dimension.id, + value_label: options.dimension.label, + }] + : [], + target: { id: options.name, label: options.targetLabel }, + }, + }; +} + +function ukHierarchyTargets(): JsonObject[] { + const obr = { id: "obr", label: "Office for Budget Responsibility" }; + const ons = { id: "ons", label: "Office for National Statistics" }; + const receipts = { id: "obr.efo_receipts", label: "Economic and fiscal outlook receipts" }; + const population = { id: "ons.population", label: "Population estimates" }; + const targets: JsonObject[] = []; + UK_GEOGRAPHIES.forEach((geography, geographyIndex) => { + UK_RECEIPT_LINES.forEach((line, lineIndex) => { + const targetValue = 1_000 * (geographyIndex + 1) + 250 * lineIndex; + targets.push(ukHierarchyTarget({ + name: `obr.${line.id}.${geography.id}`, + provider: obr, + category: receipts, + geography, + dimension: line, + targetLabel: `${line.label}, ${geography.label}`, + target: targetValue, + initialEstimate: targetValue * 0.9, + finalEstimate: targetValue * (1 + 0.004 * (geographyIndex - lineIndex)), + })); + }); + const populationValue = 5_000_000 * (geographyIndex + 1); + targets.push(ukHierarchyTarget({ + name: `ons.population.${geography.id}`, + provider: ons, + category: population, + geography, + dimension: null, + targetLabel: `Population, ${geography.label}`, + target: populationValue, + initialEstimate: populationValue * 1.08, + finalEstimate: populationValue * (1 - 0.0000001 * geographyIndex), + })); + }); + return targets; +} + +function ukHierarchyDiagnostics(targets: JsonObject[]): JsonObject { + return withReportedLossAttribution({ schema_version: 8, release_id: UK_RELEASE_ID, targets }); +} + +// The candidate a later staging run would produce: every receipts estimate +// moves, Scotland's population target is dropped and Wales's is added. +function ukStagingTargets(): JsonObject[] { + const moved: JsonObject[] = ukHierarchyTargets() + .filter((target) => target.target_name !== "ons.population.S92000003") + .map((target, targetIndex) => ({ + ...target, + final_estimate: Number(target.final_estimate) * (1 + 0.01 * ((targetIndex % 5) - 2)), + })); + const wales = { id: "W92000004", label: "Wales" }; + moved.push(ukHierarchyTarget({ + name: `ons.population.${wales.id}`, + provider: { id: "ons", label: "Office for National Statistics" }, + category: { id: "ons.population", label: "Population estimates" }, + geography: wales, + dimension: null, + targetLabel: `Population, ${wales.label}`, + target: 3_100_000, + initialEstimate: 3_200_000, + finalEstimate: 3_099_000, + })); + return moved; +} + +// The candidate a later US staging run would produce: the first 1,500 +// release targets, every estimate moved. +function usStagingDiagnostics(): JsonObject { + const targets = usLegacyTargets().slice(0, 1_500).map((target, targetIndex) => ({ + ...target, + final_estimate: Number(target.final_estimate) * (1 + 0.003 * ((targetIndex % 7) - 3)), + })); + return withReportedLossAttribution({ schema_version: 6, targets }); +} + +export function goldenReleaseInputs(): Record< + "us-release" | "uk-release" | "uk-release-loss-attribution" | "be-release", + GoldenReleaseInput +> { + return { + "us-release": { + country: "us", + releaseId: US_RELEASE_ID, + hfCommitSha: "7d3e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e", + publishedAt: "2026-07-28T01:14:54.000Z", + diagnosticsText: JSON.stringify(usReleaseDiagnostics()), + releaseManifestText: null, + }, + "uk-release": { + country: "uk", + releaseId: UK_RELEASE_ID, + hfCommitSha: "f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1", + publishedAt: "2026-10-04T18:57:22.000Z", + diagnosticsText: JSON.stringify(ukHierarchyDiagnostics(ukHierarchyTargets())), + releaseManifestText: JSON.stringify({ country: { code: "uk" } }), + }, + "uk-release-loss-attribution": { + country: "uk", + releaseId: UK_LOSS_ATTRIBUTION_RELEASE_ID, + hfCommitSha: "a9c1e3b5d7f9a1c3e5b7d9f1a3c5e7b9d1f3a5c7", + publishedAt: "2026-10-06T09:30:00.000Z", + diagnosticsText: fixtureText("target-loss-attribution/valid-uniform.json"), + releaseManifestText: null, + }, + "be-release": { + country: "be", + releaseId: BE_RELEASE_ID, + hfCommitSha: "b2d4f6a8c0e2b4d6f8a0c2e4b6d8f0a2c4e6b8d0", + publishedAt: "2026-09-16T12:00:00.000Z", + diagnosticsText: fixtureText("be-release/calibration_diagnostics.json"), + releaseManifestText: fixtureText("be-release/release_manifest.json"), + }, + }; +} + +// --- Builder, as the publisher drives it ----------------------------------- + +function bundleComparison(calibration: Calibration) { + return { + releaseId: calibration.release_id, + calibrationProvenance: calibration.calibration_provenance, + status: calibration.target_loss_attribution.status, + aggregate: calibration.target_loss_attribution.aggregate, + cap: calibration.target_loss_attribution.cap, + basisIdentifier: calibration.target_loss_attribution.basis_identifier, + targetRepresentation: calibration.target_schema.target_representation, + }; +} + +function indexFile(bundle: CalibrationTreeBundle): CalibrationTreeBundleFile { + const file = bundle.files.find((candidate) => candidate.part === "index"); + if (!file) throw new Error("Calibration tree bundle has no index."); + return file; +} + +// Repository overrides and the UK local-run directory change which inputs +// the builder reads; the record uses each country's registered defaults. +function repositoryEnvironmentNames(): string[] { + const names = new Set([UK_LOCAL_CALIBRATION_DIR_ENV]); + for (const registration of Object.values(COUNTRY_REGISTRY) as CountryRegistration[]) { + for (const name of [ + registration.repo_env, + registration.revision_env, + registration.staging?.repo_env, + registration.staging?.revision_env, + ]) { + if (name) names.add(name); + } + } + return [...names]; +} + +export async function withRegisteredRepositories( + operation: () => Promise, +): Promise { + const saved = new Map(repositoryEnvironmentNames().map((name) => [name, process.env[name]])); + for (const name of saved.keys()) delete process.env[name]; + try { + return await operation(); + } finally { + for (const [name, value] of saved) { + if (value == null) delete process.env[name]; + else process.env[name] = value; + } + } +} + +async function withHuggingFaceFiles( + files: Map, + operation: () => Promise, +): Promise { + const originalFetch = globalThis.fetch; + globalThis.fetch = (async (input: string | URL | Request) => { + const body = files.get(String(input)); + return body == null + ? new Response(null, { status: 404 }) + : new Response(body, { headers: { "Content-Type": "application/json" } }); + }) as typeof fetch; + try { + return await operation(); + } finally { + globalThis.fetch = originalFetch; + } +} + +function buildReleaseSource(input: GoldenReleaseInput): GoldenSourceBuild { + const prefix = `releases/${input.releaseId}`; + const diagnostics = { + json: JSON.parse(input.diagnosticsText) as JsonObject, + source: { + path: `${prefix}/calibration_diagnostics.json`, + sha256: sha256Text(input.diagnosticsText), + } satisfies CalibrationTreeSourceArtifact, + }; + const releaseManifest = input.releaseManifestText == null + ? null + : { + json: JSON.parse(input.releaseManifestText) as JsonObject, + source: { + path: `${prefix}/release_manifest.json`, + sha256: sha256Text(input.releaseManifestText), + } satisfies CalibrationTreeSourceArtifact, + }; + const calibration = buildCalibration( + diagnostics.json, + input.releaseId, + null, + {}, + releaseManifest?.json ?? {}, + {}, + input.country, + "huggingface_immutable", + diagnostics.source.sha256, + input.hfCommitSha, + ); + const bundle = buildCalibrationTreeBundle({ + country: input.country, + createdAt: input.publishedAt, + releaseId: input.releaseId, + hfRepo: microcosmRepo(input.country), + hfCommitSha: input.hfCommitSha, + sourceArtifacts: { + calibrationDiagnostics: diagnostics.source, + buildManifest: null, + releaseManifest: releaseManifest?.source ?? null, + demographics: null, + }, + rows: calibration.rows, + calibrationProvenance: calibration.calibration_provenance, + lossAttributionAvailable: calibration.target_loss_attribution.status !== "unavailable", + comparison: bundleComparison(calibration), + }); + const build = bundle.index.build; + const index = indexFile(bundle); + return { + country: input.country, + bundle, + manifestEntry: { + buildArtifactId: build.buildArtifactId, + kind: "release", + sourceId: build.sourceId, + label: build.label, + releaseId: input.releaseId, + stagingRunId: null, + hfRepo: microcosmRepo(input.country), + hfCommitSha: input.hfCommitSha, + treeSchemaVersion: CALIBRATION_TREE_SCHEMA_VERSION, + indexSha256: index.sha256, + indexBytes: index.rawBytes, + createdAt: build.createdAt, + updatedAt: build.createdAt, + }, + }; +} + +async function buildStagingSource(options: { + country: MicrocosmCountry; + runId: string; + revisionSha: string; + files: Map; +}): Promise { + const repository = stagingRepository(options.country); + if (!repository) throw new Error(`Staging is not configured for ${options.country}.`); + const source = await withHuggingFaceFiles(options.files, () => + resolveStagingCalibrationSource(options.runId, 0, options.country, options.revisionSha) + ); + if (!source) throw new Error(`Staging run ${options.runId} has no calibration diagnostics.`); + const { calibration } = source; + const bundle = buildCalibrationTreeBundle({ + country: options.country, + buildKind: "staging", + sourceId: options.runId, + label: source.candidateReleaseId, + createdAt: source.updatedAt, + releaseId: source.candidateReleaseId, + hfRepo: repository.repo, + hfCommitSha: options.revisionSha, + sourceArtifacts: { ...source.sourceArtifacts, demographics: null }, + rows: calibration.rows, + calibrationProvenance: calibration.calibration_provenance, + lossAttributionAvailable: calibration.target_loss_attribution.status !== "unavailable", + comparison: bundleComparison(calibration), + }); + const index = indexFile(bundle); + return { + country: options.country, + bundle, + manifestEntry: { + buildArtifactId: bundle.index.buildArtifactId, + kind: "staging", + sourceId: options.runId, + label: source.candidateReleaseId, + releaseId: null, + stagingRunId: options.runId, + hfRepo: repository.repo, + hfCommitSha: options.revisionSha, + treeSchemaVersion: CALIBRATION_TREE_SCHEMA_VERSION, + indexSha256: index.sha256, + indexBytes: index.rawBytes, + createdAt: source.updatedAt, + updatedAt: source.updatedAt, + }, + }; +} + +function stagingFileUrl(country: MicrocosmCountry, revision: string, path: string): string { + return `https://huggingface.co/datasets/${stagingRepository(country)!.repo}/resolve/${revision}/${path}`; +} + +// A version 1 run: progress names the candidate and the diagnostics sit at +// the legacy run path. +function usVersion1StagingFiles(): Map { + const runId = US_STAGING_RUN_ID; + const url = (path: string) => stagingFileUrl("us", US_STAGING_REVISION, `runs/${runId}/${path}`); + return new Map([ + [url("progress.json"), JSON.stringify({ + schema_version: 1, + run_id: runId, + candidate_release_id: "populace-us-2024-golden-candidate", + updated_at: "2026-09-30T12:00:00+00:00", + })], + [url("run_manifest.json"), JSON.stringify({ schema_version: 1, run_id: runId })], + [url("calibration_diagnostics.json"), JSON.stringify(usStagingDiagnostics())], + ]); +} + +function version2RunManifest( + runId: string, + updatedAt: string, + artifacts: JsonObject[], +): JsonObject { + return { + schema_name: "microcosm.staging.run-manifest", + schema_version: 2, + run_id: runId, + country_code: "GB", + operation_id: "uk_national_calibration", + pipeline: { id: "uk_national_calibration", version: "2026.09" }, + candidate_id: `${runId}-candidate`, + release_id: null, + run_kind: "calibration", + non_release: true, + started_at: "2026-10-02T20:00:00+00:00", + updated_at: updatedAt, + status: "completed", + current_stage: "complete", + sample: { mode: "full" }, + delivery: { + contract_version: 2, + enabled: true, + mode: "local_only", + run_id: runId, + configured_repository: null, + upload_attempts: 0, + upload_successes: 0, + read_back: "not_requested", + last_error_code: null, + opt_out_reason: null, + }, + artifacts, + failure: null, + paths: { + progress: `runs/${runId}/progress.json`, + events: `runs/${runId}/events.ndjson`, + calibration_progress: null, + }, + }; +} + +// A version 2 run that uploaded its diagnostics with its telemetry. +function ukVersion2StagingFiles(): Map { + const runId = UK_STAGING_RUN_ID; + const diagnosticsText = JSON.stringify(ukHierarchyDiagnostics(ukStagingTargets())); + const manifest = version2RunManifest(runId, "2026-10-02T23:15:56+00:00", [{ + logical_name: "calibration_diagnostics", + artifact_kind: "aggregate_diagnostics", + contract_relative_path: "artifacts/calibration_diagnostics.json", + media_type: "application/json", + sha256: sha256Text(diagnosticsText), + classification: "aggregate", + }]); + const url = (path: string) => stagingFileUrl("uk", UK_STAGING_REVISION, `runs/${runId}/${path}`); + return new Map([ + [url("run_manifest.json"), JSON.stringify(manifest)], + [url("artifacts/calibration_diagnostics.json"), diagnosticsText], + ]); +} + +// A version 2 run whose diagnostics travel in its staged dataset bundle in +// the release repository, located by the staged-dataset receipt. +function ukStagedBundleStagingFiles(): Map { + const runId = UK_STAGED_BUNDLE_RUN_ID; + const diagnosticsText = JSON.stringify(ukHierarchyDiagnostics(ukStagingTargets())); + const releaseRepository = COUNTRY_REGISTRY.uk.repo; + const receiptText = JSON.stringify({ + contract_version: 1, + run_id: runId, + status: "uploaded", + mode: "local_and_remote", + repository: releaseRepository, + prefix: `staged/${runId}`, + revision: UK_STAGED_BUNDLE_REVISION, + error_code: null, + opt_out_reason: null, + files: { + "calibration_diagnostics.json": { + bytes: Buffer.byteLength(diagnosticsText, "utf8"), + sha256: sha256Text(diagnosticsText), + }, + "microcosm_uk_2024_25.h5": { bytes: 1, sha256: "0".repeat(64) }, + }, + }); + const manifest = version2RunManifest(runId, "2026-10-03T08:30:00+00:00", [{ + logical_name: "staged_dataset", + artifact_kind: "build_metadata", + contract_relative_path: "artifacts/staged_dataset.json", + media_type: "application/json", + sha256: sha256Text(receiptText), + classification: "non_row_level", + }]); + const url = (path: string) => stagingFileUrl("uk", UK_STAGING_REVISION, `runs/${runId}/${path}`); + return new Map([ + [url("run_manifest.json"), JSON.stringify(manifest)], + [url("artifacts/staged_dataset.json"), receiptText], + [ + `https://huggingface.co/datasets/${releaseRepository}/resolve/${UK_STAGED_BUNDLE_REVISION}/staged/${runId}/calibration_diagnostics.json`, + diagnosticsText, + ], + ]); +} + +const sourceBuilds = new Map>(); + +function buildSourceCase(name: GoldenSourceCaseName): Promise { + return withRegisteredRepositories(async () => { + switch (name) { + case "us-release": + case "uk-release": + case "uk-release-loss-attribution": + case "be-release": + return buildReleaseSource(goldenReleaseInputs()[name]); + case "us-staging": + return buildStagingSource({ + country: "us", + runId: US_STAGING_RUN_ID, + revisionSha: US_STAGING_REVISION, + files: usVersion1StagingFiles(), + }); + case "uk-staging": + return buildStagingSource({ + country: "uk", + runId: UK_STAGING_RUN_ID, + revisionSha: UK_STAGING_REVISION, + files: ukVersion2StagingFiles(), + }); + case "uk-staging-staged-bundle": + return buildStagingSource({ + country: "uk", + runId: UK_STAGED_BUNDLE_RUN_ID, + revisionSha: UK_STAGING_REVISION, + files: ukStagedBundleStagingFiles(), + }); + } + }); +} + +export function goldenSourceBuild(name: GoldenSourceCaseName): Promise { + let build = sourceBuilds.get(name); + if (!build) { + build = buildSourceCase(name); + sourceBuilds.set(name, build); + } + return build; +} + +// --- Record --------------------------------------------------------------- + +function fileRecord(file: CalibrationTreeBundleFile): GoldenFileRecord { + return { + part: file.part, + path: file.path, + sha256: file.sha256, + rawBytes: file.rawBytes, + gzipBytes: file.gzipBytes, + deflateSha256: sha256Bytes(file.compressed.subarray(GZIP_HEADER_BYTES)), + }; +} + +export function bundleRecord(bundle: CalibrationTreeBundle): GoldenBundleRecord { + return { + buildArtifactId: bundle.index.buildArtifactId, + files: bundle.files.map(fileRecord), + }; +} + +// Stored bundle files by Blob path, served the way the Blob client returns them. +type StoredFiles = Map; + +function storeBundle(storedFiles: StoredFiles, bundle: CalibrationTreeBundle): void { + for (const file of bundle.files) storedFiles.set(file.path, file.compressed); +} + +function storedJson(storedFiles: StoredFiles, path: string): unknown { + const compressed = storedFiles.get(path); + if (!compressed) throw new Error(`Stored calibration tree file ${path} is missing.`); + return JSON.parse(gunzipSync(compressed).toString("utf8")); +} + +function storedBlobReader(storedFiles: StoredFiles): typeof getCalibrationTreeBlob { + return (async (options: Parameters[0]) => { + const path = calibrationTreePartPath(options.country, options.buildArtifactId, options.part); + const compressed = storedFiles.get(path); + if (!compressed) return null; + return { + statusCode: 200 as const, + stream: new Blob([new Uint8Array(compressed)]).stream(), + headers: new Headers(), + blob: { + url: `https://blob.example/${path}`, + downloadUrl: `https://blob.example/${path}?download=1`, + pathname: path, + contentDisposition: "inline", + cacheControl: "public, max-age=31536000", + uploadedAt: new Date("2026-10-15T00:00:00.000Z"), + etag: `"${sha256Bytes(compressed)}"`, + contentType: "application/gzip", + size: compressed.byteLength, + }, + }; + }) as typeof getCalibrationTreeBlob; +} + +// The explorer's root responses, read back from the stored bytes the way the +// dashboard loads a published tree. +function readerRecord(bundle: CalibrationTreeBundle): GoldenSourceRecord["readers"] { + const storedFiles: StoredFiles = new Map(); + storeBundle(storedFiles, bundle); + const index = parseCalibrationTreeIndex( + storedJson(storedFiles, calibrationTreePartPath(bundle.index.country, bundle.index.buildArtifactId, "index")), + ); + const part = (descriptor: { part: Parameters[1]; path: string }) => + parseCalibrationTreePart(storedJson(storedFiles, descriptor.path), descriptor.part); + const loaded = { + index, + tiers: index.parts.tiers.map((descriptor) => part(descriptor) as CalibrationTreeTierArtifact), + filterIndex: part(index.parts.filterIndex) as CalibrationTreeFilterIndexArtifact, + targetSummaries: calibrationTreeTargetsFromSummaries( + index, + index.parts.targetSummaries.map( + (descriptor) => part(descriptor) as CalibrationTreeTargetSummaryArtifact, + ), + ), + targetDetailShard: index.parts.targetDetails[0] + ? part(index.parts.targetDetails[0]) as CalibrationTreeTargetDetailsArtifact + : undefined, + }; + const rootResponse = (breakdown: ExplorerState["breakdown"]) => + sha256Text(JSON.stringify( + calibrationTreeResponseFromBundle(loaded, { ...createExplorerState(), breakdown }), + )); + return { + programRootSha256: rootResponse("program"), + geographyRootSha256: rootResponse("geography"), + }; +} + +export async function goldenSourceRecord(name: GoldenSourceCaseName): Promise { + const { bundle } = await goldenSourceBuild(name); + return { ...bundleRecord(bundle), readers: readerRecord(bundle) }; +} + +const COMPARISON_SOURCES: Record< + GoldenComparisonCaseName, + { current: GoldenSourceCaseName; candidate: GoldenSourceCaseName } +> = { + "us-comparison": { current: "us-release", candidate: "us-staging" }, + "uk-comparison": { current: "uk-release", candidate: "uk-staging" }, +}; + +// Comparisons are built the way the dashboard builds them lazily: both +// sources are read back from their stored bytes. +export async function goldenComparisonRecord( + name: GoldenComparisonCaseName, +): Promise { + const [current, candidate] = await Promise.all([ + goldenSourceBuild(COMPARISON_SOURCES[name].current), + goldenSourceBuild(COMPARISON_SOURCES[name].candidate), + ]); + const storedFiles: StoredFiles = new Map(); + storeBundle(storedFiles, current.bundle); + storeBundle(storedFiles, candidate.bundle); + const getBlob = storedBlobReader(storedFiles); + const country = current.country; + const bundles = buildCalibrationComparisonBundles( + await loadCalibrationComparisonSource(country, current.bundle.index.buildArtifactId, getBlob), + await loadCalibrationComparisonSource(country, candidate.bundle.index.buildArtifactId, getBlob), + ); + storeBundle(storedFiles, bundles.reported); + storeBundle(storedFiles, bundles.shared); + const firstTargetDetail = await loadCalibrationComparisonTargetDetail({ + country, + buildArtifactId: bundles.reported.index.buildArtifactId, + targetOrdinal: 0, + getBlob, + }); + return { + pairArtifactId: bundles.pairArtifactId, + reported: bundleRecord(bundles.reported), + shared: bundleRecord(bundles.shared), + firstTargetDetailSha256: sha256Text(JSON.stringify(firstTargetDetail)), + }; +} + +// The manifest the publisher would hold after publishing every recorded +// source, latest release per country first. +export async function goldenManifestRecord(): Promise { + let manifest = emptyCalibrationTreeManifest(); + const latestReleases = new Set(["us-release", "uk-release", "be-release"]); + for (const name of GOLDEN_SOURCE_CASES) { + const { country, manifestEntry } = await goldenSourceBuild(name); + manifest = withCalibrationTreeManifestEntry( + manifest, + country, + manifestEntry, + latestReleases.has(name), + ); + } + const serialized = serializeCalibrationTreeManifest(manifest); + return { sha256: sha256Text(serialized), rawBytes: Buffer.byteLength(serialized, "utf8") }; +} + +export async function recordCalibrationTreeGoldens(): Promise { + const sources = {} as CalibrationTreeGoldenRecord["sources"]; + for (const name of GOLDEN_SOURCE_CASES) sources[name] = await goldenSourceRecord(name); + const comparisons = {} as CalibrationTreeGoldenRecord["comparisons"]; + for (const name of GOLDEN_COMPARISON_CASES) comparisons[name] = await goldenComparisonRecord(name); + return { + schemaVersion: CALIBRATION_TREE_SCHEMA_VERSION, + sources, + comparisons, + manifest: await goldenManifestRecord(), + }; +} + +if (import.meta.main) { + const record = await recordCalibrationTreeGoldens(); + await Bun.write(CALIBRATION_TREE_GOLDEN_RECORD_PATH, `${JSON.stringify(record, null, 2)}\n`); + console.log(`Recorded ${CALIBRATION_TREE_GOLDEN_RECORD_PATH}`); +} diff --git a/frontend/lib/microcosm/treemap.test.ts b/frontend/packages/calibration-tree/src/treemap.test.ts similarity index 100% rename from frontend/lib/microcosm/treemap.test.ts rename to frontend/packages/calibration-tree/src/treemap.test.ts diff --git a/frontend/packages/calibration-tree/tsconfig.json b/frontend/packages/calibration-tree/tsconfig.json new file mode 100644 index 00000000..af017ab0 --- /dev/null +++ b/frontend/packages/calibration-tree/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ESNext", "DOM"], + "module": "ESNext", + "moduleResolution": "bundler", + "types": ["bun"], + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "isolatedModules": true, + "esModuleInterop": true, + "resolveJsonModule": true + }, + "include": ["src/**/*.ts", "types/**/*.d.ts"] +} diff --git a/frontend/packages/calibration-tree/types/next-fetch-cache.d.ts b/frontend/packages/calibration-tree/types/next-fetch-cache.d.ts new file mode 100644 index 00000000..77060ce2 --- /dev/null +++ b/frontend/packages/calibration-tree/types/next-fetch-cache.d.ts @@ -0,0 +1,8 @@ +// The dashboard calls the release and staging readers inside Next.js, whose +// fetch reads a `next.revalidate` hint to cache Hugging Face responses; Bun +// ignores the field. Next.js declares it on RequestInit for the dashboard. +// This declaration mirrors Next's NextFetchRequestConfig and is seen only by +// the package's own typecheck, so the package typechecks without Next.js. +interface RequestInit { + next?: { revalidate?: number | false; tags?: string[] } | undefined; +} diff --git a/frontend/scripts/calibration-deployment-smoke.test.ts b/frontend/scripts/calibration-deployment-smoke.test.ts index 35b85aea..86723d4c 100644 --- a/frontend/scripts/calibration-deployment-smoke.test.ts +++ b/frontend/scripts/calibration-deployment-smoke.test.ts @@ -1,7 +1,7 @@ import { expect, spyOn, test } from "bun:test"; import { verifyCalibrationDeployment } from "./calibration-deployment-smoke"; -import { hasCapability, selectableCountries } from "../lib/microcosm/countries"; +import { hasCapability, selectableCountries } from "@policyengine/calibration-tree/countries"; type Fetcher = (url: string | URL, init?: RequestInit) => Promise; const noSleep = async (_milliseconds: number) => {}; diff --git a/frontend/scripts/calibration-deployment-smoke.ts b/frontend/scripts/calibration-deployment-smoke.ts index b303de39..6b8a456f 100644 --- a/frontend/scripts/calibration-deployment-smoke.ts +++ b/frontend/scripts/calibration-deployment-smoke.ts @@ -4,7 +4,7 @@ import { hasCapability, selectableCountries, type MicrocosmCountry, -} from "../lib/microcosm/countries"; +} from "@policyengine/calibration-tree/countries"; type JsonObject = Record; type Fetcher = (url: string | URL, init?: RequestInit) => Promise; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 3f8f96b4..de2774dc 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -19,5 +19,5 @@ } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "packages"] }