From c0e058f86ff22603eec328a55d86c332a41e0bf8 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 23 Aug 2026 19:56:03 +0200 Subject: [PATCH] Add per-country cross-dataset bundles --- AGENTS.md | 2 +- CLAUDE.md | 2 +- LANE_E4_REPORT.md | 179 +++++++++++++++++ docs/cross-dataset-api.md | 35 +++- .../api/microcosm/cross-dataset/route.test.ts | 57 ++++++ .../app/api/microcosm/cross-dataset/route.ts | 4 +- .../components/layout/country-context.test.ts | 27 +++ .../components/layout/country-context.tsx | 18 +- frontend/components/layout/nav-items.test.ts | 13 +- frontend/components/layout/nav-items.ts | 2 +- .../microcosm/cross-dataset-facts-view.tsx | 39 ++-- .../microcosm/cross-dataset-view.tsx | 132 +++++++++---- frontend/lib/cross-dataset/artifact.test.ts | 54 ++++++ frontend/lib/cross-dataset/artifact.ts | 31 +++ .../cross-dataset/fact-presentation.test.ts | 19 +- .../lib/cross-dataset/fact-presentation.ts | 16 +- .../fixtures/be-frontend-bundle/manifest.json | 1 + .../fixtures/be-frontend-bundle/summary.json | 1 + .../lib/cross-dataset/presentation.test.ts | 74 ++++--- frontend/lib/cross-dataset/presentation.ts | 116 +++++------ frontend/lib/cross-dataset/source.test.ts | 182 ++++++++++++++++++ frontend/lib/cross-dataset/source.ts | 89 +++++++-- 22 files changed, 909 insertions(+), 184 deletions(-) create mode 100644 LANE_E4_REPORT.md create mode 100644 frontend/app/api/microcosm/cross-dataset/route.test.ts create mode 100644 frontend/components/layout/country-context.test.ts create mode 100644 frontend/lib/cross-dataset/fixtures/be-frontend-bundle/manifest.json create mode 100644 frontend/lib/cross-dataset/fixtures/be-frontend-bundle/summary.json create mode 100644 frontend/lib/cross-dataset/source.test.ts diff --git a/AGENTS.md b/AGENTS.md index 40816693..6aad23f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ # AI assistant guidance - 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 artifact location as described in [the Cross-dataset application configuration](docs/cross-dataset-api.md#configure-the-application). +- 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). diff --git a/CLAUDE.md b/CLAUDE.md index e24bc337..74f83c4b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,4 +1,4 @@ # Claude Code guidance - 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 artifact location as described in [the Cross-dataset application configuration](docs/cross-dataset-api.md#configure-the-application). +- 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). diff --git a/LANE_E4_REPORT.md b/LANE_E4_REPORT.md new file mode 100644 index 00000000..e19cf4d8 --- /dev/null +++ b/LANE_E4_REPORT.md @@ -0,0 +1,179 @@ +# Lane E4 report + +## Outcome + +Cross-dataset artifacts are now selected independently for US, UK, and Belgium. +The unsuffixed configuration remains the US contract, UK and Belgium use +country-suffixed variables, and each country has an independent cached reader. +Every configured reader validates that its manifest contains the selected +jurisdiction before any view is served. + +`country` now reaches every Cross-dataset API and client query. Cross-dataset is +visible in every selectable country's navigation, while an unconfigured country +fails closed into the page's existing unavailable state with a message naming +the country and applicable environment variables. The overview and fact views +use bundle-provided source labels and group metadata instead of US-specific +presentation assumptions. Country switches also discard bundle-specific fact, +source, and filter URL state rather than applying it to the next country. + +## File-by-file changes + +- `AGENTS.md`: clarifies that each country used by Cross-dataset work must have + exactly one configured directory or URL. +- `CLAUDE.md`: mirrors the per-country application-start configuration rule. +- `docs/cross-dataset-api.md`: documents all country-specific variables, the + per-country exclusivity rule, the live Belgium URL, jurisdiction validation, + and the `country=us|uk|be` API parameter. +- `frontend/app/api/microcosm/cross-dataset/route.ts`: parses `country` with the + shared country parser and selects that country's reader before dispatching any + Cross-dataset view. +- `frontend/app/api/microcosm/cross-dataset/route.test.ts`: proves a BE-only + configuration serves Belgium and leaves default/explicit US requests in the + existing 503-style unconfigured state. +- `frontend/components/layout/country-context.tsx`: clears Cross-dataset's + bundle-specific view and filters when the global country selector changes, + while preserving query state on other pages. +- `frontend/components/layout/country-context.test.ts`: covers both sides of + that country-switch URL behavior. +- `frontend/components/layout/nav-items.ts`: removes the `usOnly` gate from the + Cross-dataset item. +- `frontend/components/layout/nav-items.test.ts`: verifies the unchanged label + and route and visibility for US, UK, and Belgium. +- `frontend/components/microcosm/cross-dataset-view.tsx`: scopes overview query + keys and API requests by country; derives geography, sample, and grouping + controls from the bundle; validates filter selections against the active + bundle; preserves the capped-relative-error definition; and removes + US-specific page copy. +- `frontend/components/microcosm/cross-dataset-facts-view.tsx`: threads country + through summary, catalog, and detail requests, query keys, and links, and does + not reuse one country's placeholder data for another. +- `frontend/lib/cross-dataset/artifact.ts`: validates and retains manifest + `jurisdictions`, then fails closed when none of the selected country's accepted + codes appears in the manifest. +- `frontend/lib/cross-dataset/artifact.test.ts`: covers exact jurisdiction + matches, the UK/GB alias, missing/mismatched jurisdictions, and malformed + jurisdiction arrays. +- `frontend/lib/cross-dataset/fact-presentation.ts`: includes country in catalog + parameters and generated catalog/detail URLs, with the current selector state + taking precedence over stale URL country state. +- `frontend/lib/cross-dataset/fact-presentation.test.ts`: covers country parsing, + overrides, and URL preservation. +- `frontend/lib/cross-dataset/presentation.ts`: uses bundle source/group labels, + preserves bundle source order, derives available controls from group + dimensions, and places country in group-to-fact links. +- `frontend/lib/cross-dataset/presentation.test.ts`: replaces US-ordering and + hard-coded-label expectations with bundle-driven presentation and + country-aware link coverage. +- `frontend/lib/cross-dataset/source.ts`: resolves independent US/UK/BE + directory, URL, and expected-run settings; enforces directory/URL exclusivity + per country; supplies the jurisdiction check; and caches one reader per + country/configuration. +- `frontend/lib/cross-dataset/source.test.ts`: covers each environment-variable + family, per-country conflicts and missing configuration, simultaneous-country + configuration, expected run IDs, jurisdiction rejection, cache isolation, and + the trimmed Belgium bundle. +- `frontend/lib/cross-dataset/fixtures/be-frontend-bundle/manifest.json`: trimmed + manifest fixture based on `.lane-fixtures/be-frontend-bundle`, retaining the + real bundle shape, hashes, 726-fact count, three source IDs, and + `jurisdictions: ["BE"]`. +- `frontend/lib/cross-dataset/fixtures/be-frontend-bundle/summary.json`: matching + real-shape summary fixture with the three Belgium source labels. The source + `.lane-fixtures` directory is not included. +- `LANE_E4_REPORT.md`: this report. + +## Environment and jurisdiction contract + +| Country | Directory | Base URL | Optional expected run ID | Accepted manifest code | +| --- | --- | --- | --- | --- | +| US | `CROSS_DATASET_ARTIFACT_DIR` | `CROSS_DATASET_ARTIFACT_BASE_URL` | `CROSS_DATASET_EXPECTED_RUN_ID` | `US` | +| UK | `CROSS_DATASET_ARTIFACT_DIR_UK` | `CROSS_DATASET_ARTIFACT_BASE_URL_UK` | `CROSS_DATASET_EXPECTED_RUN_ID_UK` | `UK` or `GB` | +| Belgium | `CROSS_DATASET_ARTIFACT_DIR_BE` | `CROSS_DATASET_ARTIFACT_BASE_URL_BE` | `CROSS_DATASET_EXPECTED_RUN_ID_BE` | `BE` | + +For each country, configuring both its directory and base URL is invalid. +Different countries may be configured simultaneously. The expected-run variable +for the selected country retains the prior exact run-ID match semantics. A +missing jurisdiction list also fails closed. A mismatch raises the same +503-rendered `ArtifactError` family, with `stale_artifact` and text of the form: + +```text +Cross-dataset bundle is for jurisdictions BE, not US. +``` + +An unconfigured Belgium request, for example, names both applicable location +variables: + +```text +Cross-dataset artifacts are not configured for BE. Set CROSS_DATASET_ARTIFACT_DIR_BE or CROSS_DATASET_ARTIFACT_BASE_URL_BE. +``` + +## Verification + +Two independent read-only reviews covered the server/API path and the +UI/navigation/country-switch path. Both completed with no remaining findings. +The protected `frontend/app/layout.tsx`, `frontend/app/globals.css`, and package +scripts are unchanged. `git diff --check` produced no output. + +All gates ran from `frontend/`. + +### `bun test` + +```text +bun test v1.3.11 (af24e281) + + 219 pass + 4 todo + 0 fail + 855 expect() calls +Ran 223 tests across 30 files. [407.00ms] +``` + +### `bun run lint` + +```text +$ tsc --noEmit +``` + +### `IS_WEBPACK_TEST=1 CROSS_DATASET_ARTIFACT_DIR_BE=/Users/maxghenis/PolicyEngine/_worktrees/caldiag-e4/frontend/lib/cross-dataset/fixtures/be-frontend-bundle bun run build` + +```text +Route (app) +┌ ○ / +├ ○ /_not-found +├ ƒ /api/hf-webhook +├ ƒ /api/microcosm +├ ƒ /api/microcosm/compare +├ ƒ /api/microcosm/cross-dataset +├ ƒ /api/microcosm/releases +├ ƒ /api/microcosm/staging/compare +├ ƒ /api/microcosm/staging/run +├ ƒ /api/microcosm/staging/runs +├ ƒ /api/microcosm/staging/target-diagnostics +├ ƒ /api/microcosm/target-diagnostics +├ ƒ /api/microcosm/target-investigation +├ ƒ /api/microcosm/target-tree +├ ƒ /api/microcosm/target-treemap +├ ƒ /api/microcosm/variable +├ ○ /icon.svg +├ ○ /microcosm +├ ○ /microcosm/compare +├ ○ /microcosm/datasets +├ ƒ /microcosm/model-coverage +├ ○ /microcosm/pipeline +├ ○ /microcosm/staging +├ ƒ /microcosm/targets +└ ○ /microcosm/variables + + +○ (Static) prerendered as static content +ƒ (Dynamic) server-rendered on demand +``` + +The successful production build configured exactly one artifact location: the +local Belgium fixture via `CROSS_DATASET_ARTIFACT_DIR_BE`. It used the unchanged +`build` script with Next's webpack selector because the literal Turbopack path +attempts prohibited network/worker-port operations in this lane sandbox. The +green run compiled successfully, ran TypeScript, generated all 20 static pages, +and collected build traces. No build workaround or package-script change is +committed. + +LANE E4 DONE diff --git a/docs/cross-dataset-api.md b/docs/cross-dataset-api.md index 238e024a..3e87b870 100644 --- a/docs/cross-dataset-api.md +++ b/docs/cross-dataset-api.md @@ -55,12 +55,21 @@ snapshot IDs and has a SHA-256 recorded in the manifest. ## Configure the application -Set exactly one of `CROSS_DATASET_ARTIFACT_DIR` or -`CROSS_DATASET_ARTIFACT_BASE_URL` before starting the application for -Cross-dataset work. The Cross-dataset API has no default artifact location: if -neither variable is set, the application process starts, but the Cross-dataset -page is unavailable and its API returns HTTP 503. Setting both variables is -also invalid. +Configure at most one local directory or remote base URL for each country. The +unsuffixed variables remain the US configuration so existing deployments keep +working; UK and Belgium use country suffixes: + +| Country | Local bundle | Remote bundle | Optional pinned run | +| --- | --- | --- | --- | +| US | `CROSS_DATASET_ARTIFACT_DIR` | `CROSS_DATASET_ARTIFACT_BASE_URL` | `CROSS_DATASET_EXPECTED_RUN_ID` | +| UK | `CROSS_DATASET_ARTIFACT_DIR_UK` | `CROSS_DATASET_ARTIFACT_BASE_URL_UK` | `CROSS_DATASET_EXPECTED_RUN_ID_UK` | +| Belgium | `CROSS_DATASET_ARTIFACT_DIR_BE` | `CROSS_DATASET_ARTIFACT_BASE_URL_BE` | `CROSS_DATASET_EXPECTED_RUN_ID_BE` | + +Setting both the directory and URL for the same country is invalid. Configuring +different countries at the same time is valid. If neither location is set for +the selected country, the application process still starts, but that country's +Cross-dataset page is unavailable and its API returns HTTP 503 naming the +applicable variables. For a bundle on the local filesystem, set `CROSS_DATASET_ARTIFACT_DIR` to the generated frontend bundle directory. `CROSS_DATASET_EXPECTED_RUN_ID` is @@ -80,12 +89,22 @@ export CROSS_DATASET_ARTIFACT_BASE_URL=https://example.org/evaluation-run/fronte make dev ``` +For example, the published Belgium bundle can be selected with: + +```bash +export CROSS_DATASET_ARTIFACT_BASE_URL_BE=https://huggingface.co/datasets/policyengine/microcosm-evaluation/resolve/main/be/evaluation-f28ca06a0b0d2baf13c87f2f/frontend/ +make dev +``` + A missing, stale, partial, malformed, or hash-mismatched bundle returns HTTP -503 rather than serving mixed results. +503 rather than serving mixed results. The API also verifies the selected +country against the manifest and fails closed on a mismatch: US requires `US`, +Belgium requires `BE`, and UK accepts either `UK` or `GB`. ## Read the API -`GET /api/microcosm/cross-dataset` accepts these views: +`GET /api/microcosm/cross-dataset` accepts `country=us|uk|be` (default `us`) +and these views: - `view=summary` (default): source-level score, coverage, capability statuses, unsupported reasons, period treatments, and target-performance buckets. diff --git a/frontend/app/api/microcosm/cross-dataset/route.test.ts b/frontend/app/api/microcosm/cross-dataset/route.test.ts new file mode 100644 index 00000000..3abca82a --- /dev/null +++ b/frontend/app/api/microcosm/cross-dataset/route.test.ts @@ -0,0 +1,57 @@ +import { fileURLToPath } from "node:url"; + +import { expect, test } from "bun:test"; + +import { GET } from "./route"; + +const ENV_KEYS = [ + "CROSS_DATASET_ARTIFACT_DIR", + "CROSS_DATASET_ARTIFACT_BASE_URL", + "CROSS_DATASET_EXPECTED_RUN_ID", + "CROSS_DATASET_ARTIFACT_DIR_BE", + "CROSS_DATASET_ARTIFACT_BASE_URL_BE", + "CROSS_DATASET_EXPECTED_RUN_ID_BE", +] as const; +const BELGIUM_FIXTURE_DIRECTORY = fileURLToPath( + new URL("../../../../lib/cross-dataset/fixtures/be-frontend-bundle/", import.meta.url), +); + +test("threads country to the configured Cross-dataset reader", async () => { + const previousEnvironment = Object.fromEntries( + ENV_KEYS.map((key) => [key, process.env[key]]), + ) as Record<(typeof ENV_KEYS)[number], string | undefined>; + + try { + for (const key of ENV_KEYS) delete process.env[key]; + process.env.CROSS_DATASET_ARTIFACT_DIR_BE = BELGIUM_FIXTURE_DIRECTORY; + + const belgiumResponse = await GET( + new Request("http://example.test/api/microcosm/cross-dataset?view=summary&country=be"), + ); + expect(belgiumResponse.status).toBe(200); + expect(await belgiumResponse.json()).toMatchObject({ + jurisdictions: ["BE"], + fact_count: 726, + sources: [ + { label: "EUROMOD BE_2025 on EU-SILC (JRC country report 2025)" }, + { label: "Microcosm-BE v0.4 × Axiom rules engine" }, + { label: "Microcosm-BE v0.4 × EUROMOD BE_2025" }, + ], + }); + + for (const url of [ + "http://example.test/api/microcosm/cross-dataset?view=summary", + "http://example.test/api/microcosm/cross-dataset?view=summary&country=us", + ]) { + const response = await GET(new Request(url)); + expect(response.status).toBe(503); + expect(await response.json()).toMatchObject({ artifact_error: "partial_artifact" }); + } + } finally { + for (const key of ENV_KEYS) { + const value = previousEnvironment[key]; + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + } +}); diff --git a/frontend/app/api/microcosm/cross-dataset/route.ts b/frontend/app/api/microcosm/cross-dataset/route.ts index 8d6b2ae2..10ec163f 100644 --- a/frontend/app/api/microcosm/cross-dataset/route.ts +++ b/frontend/app/api/microcosm/cross-dataset/route.ts @@ -3,15 +3,17 @@ 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"; export const revalidate = 300; export const runtime = "nodejs"; export async function GET(request: Request) { + const country = parseCountry(new URL(request.url).searchParams.get("country")); try { const response = await crossDatasetApiResponse( request.url, - configuredCrossDatasetReader(), + configuredCrossDatasetReader(country), ); return NextResponse.json(response.body, { status: response.status }); } catch (error) { diff --git a/frontend/components/layout/country-context.test.ts b/frontend/components/layout/country-context.test.ts new file mode 100644 index 00000000..f8280ab5 --- /dev/null +++ b/frontend/components/layout/country-context.test.ts @@ -0,0 +1,27 @@ +import { expect, test } from "bun:test"; + +import { countrySwitchUrl } from "./country-context"; + +test("country switching clears bundle-specific Cross-dataset state", () => { + const switched = new URL( + countrySwitchUrl( + "https://example.test/microcosm/datasets?country=us&view=fact&fact_key=us-only&source=us-model", + "be", + ), + ); + + expect(switched.pathname).toBe("/microcosm/datasets"); + expect(switched.searchParams.toString()).toBe("country=be"); +}); + +test("country switching preserves route state outside Cross-dataset", () => { + const switched = new URL( + countrySwitchUrl( + "https://example.test/microcosm/targets?country=us&variable=income_tax", + "uk", + ), + ); + + expect(switched.pathname).toBe("/microcosm/targets"); + expect(switched.searchParams.toString()).toBe("country=uk&variable=income_tax"); +}); diff --git a/frontend/components/layout/country-context.tsx b/frontend/components/layout/country-context.tsx index 854bd3c3..1ae58dd8 100644 --- a/frontend/components/layout/country-context.tsx +++ b/frontend/components/layout/country-context.tsx @@ -18,6 +18,15 @@ export function isCountry(value: string | null): value is Country { const STORAGE_KEY = "microcosm-country"; +export function countrySwitchUrl(currentUrl: string, next: Country): string { + const url = new URL(currentUrl); + if (url.pathname.endsWith("/microcosm/datasets")) { + url.search = ""; + } + url.searchParams.set("country", next); + return url.toString(); +} + function persistCountry(country: Country) { try { window.localStorage.setItem(STORAGE_KEY, country); @@ -51,11 +60,14 @@ export function CountryProvider({ children }: { children: ReactNode }) { }, []); const setCountry = (next: Country) => { + if (next === country) return; setCountryState(next); persistCountry(next); - const url = new URL(window.location.href); - url.searchParams.set("country", next); - window.history.replaceState(window.history.state, "", url); + window.history.replaceState( + window.history.state, + "", + countrySwitchUrl(window.location.href, next), + ); }; return ( diff --git a/frontend/components/layout/nav-items.test.ts b/frontend/components/layout/nav-items.test.ts index f4b6ebdf..35340fa5 100644 --- a/frontend/components/layout/nav-items.test.ts +++ b/frontend/components/layout/nav-items.test.ts @@ -78,7 +78,6 @@ test("preserves the Cross-dataset navigation label and route", () => { expect(item).toEqual({ href: "/microcosm/datasets", label: "Cross-dataset", - usOnly: true, }); expect(isActive("/microcosm/datasets", item!)).toBe(true); }); @@ -88,12 +87,24 @@ test("Belgium navigation keeps country-ready pages and hides US-only tools", () expect(items.map((item) => item.href)).toEqual([ "/microcosm", "/microcosm/targets", + "/microcosm/datasets", "/microcosm/compare", ]); expect(items.every((item) => item.usOnly !== true)).toBe(true); expect(items.map((item) => navItemHref(item, "be"))).toEqual([ "/microcosm?country=be", "/microcosm/targets?country=be", + "/microcosm/datasets?country=be", "/microcosm/compare?country=be", ]); }); + +test("shows Cross-dataset navigation for every selectable country", () => { + for (const country of ["us", "uk", "be"] as const) { + expect( + navGroupsForCountry(country) + .flatMap((group) => group.items) + .some((item) => item.href === "/microcosm/datasets"), + ).toBe(true); + } +}); diff --git a/frontend/components/layout/nav-items.ts b/frontend/components/layout/nav-items.ts index 033987a4..70655000 100644 --- a/frontend/components/layout/nav-items.ts +++ b/frontend/components/layout/nav-items.ts @@ -23,7 +23,7 @@ export const NAV_GROUPS: { label: string; items: NavItem[] }[] = [ // External checks (reform scores vs JCT/fiscal notes/admin actuals) // moved to the PolicyEngine scorecard, which owns all external // comparisons; per-release history was ingested there (issue #15). - { href: "/microcosm/datasets", label: "Cross-dataset", usOnly: true }, + { href: "/microcosm/datasets", label: "Cross-dataset" }, { href: "https://www.policyengine.org/scorecard", label: "External checks", diff --git a/frontend/components/microcosm/cross-dataset-facts-view.tsx b/frontend/components/microcosm/cross-dataset-facts-view.tsx index dcacd840..3b0cbeba 100644 --- a/frontend/components/microcosm/cross-dataset-facts-view.tsx +++ b/frontend/components/microcosm/cross-dataset-facts-view.tsx @@ -5,6 +5,10 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import { useQuery } from "@tanstack/react-query"; +import { + useCountry, + type Country, +} from "@/components/layout/country-context"; import { EmptyState } from "@/components/shared/empty-state"; import { LoadingBlock } from "@/components/shared/LoadingBlock"; import { PageHeader } from "@/components/shared/page-header"; @@ -58,6 +62,7 @@ const SORT_OPTIONS = [ function apiFactParams(params: FactCatalogParams): Record { return { view: "facts", + country: params.country, source: params.source || undefined, status: params.status || undefined, ledger_source: params.chronicleSource || undefined, @@ -75,10 +80,13 @@ function apiFactParams(params: FactCatalogParams): Record => { const [summary, page] = await Promise.all([ - apiGet("/microcosm/cross-dataset", { view: "summary" }), + apiGet("/microcosm/cross-dataset", { + view: "summary", + country: params.country, + }), apiGet("/microcosm/cross-dataset", apiFactParams(params)), ]); if (summary.run_id !== page.run_id || summary.snapshot_id !== page.snapshot_id) { @@ -86,7 +94,6 @@ function useFactCatalog(params: FactCatalogParams) { } return { summary, page }; }, - placeholderData: (previous) => previous, staleTime: Infinity, retry: false, }); @@ -157,9 +164,10 @@ function sourceTone(supported: boolean): "success" | "neutral" { export function CrossDatasetFactsView({ search }: { search: string }) { const router = useRouter(); + const { country } = useCountry(); const params = useMemo( - () => parseFactCatalogParams(new URLSearchParams(search)), - [search], + () => parseFactCatalogParams(new URLSearchParams(search), country), + [country, search], ); const query = useFactCatalog(params); const [searchInput, setSearchInput] = useState(params.search); @@ -227,7 +235,7 @@ export function CrossDatasetFactsView({ search }: { search: string }) { description="Browse every Chronicle observation and the corresponding capability cell for each model. Unsupported cells remain visible with a specific reason." actions={ ← Overview @@ -327,7 +335,7 @@ export function CrossDatasetFactsView({ search }: { search: string }) { /> ))} Clear all @@ -482,15 +490,19 @@ function FieldList({ fields, empty }: { fields: DisplayField[]; empty: string }) ); } -function useFactDetail(factKey: string) { +function useFactDetail(factKey: string, country: Country) { return useQuery({ - queryKey: ["cross-dataset", "fact", factKey], + queryKey: ["cross-dataset", "fact", country, factKey], queryFn: async () => { const [summary, response] = await Promise.all([ - apiGet("/microcosm/cross-dataset", { view: "summary" }), + apiGet("/microcosm/cross-dataset", { + view: "summary", + country, + }), apiGet("/microcosm/cross-dataset", { view: "fact", fact_key: factKey, + country, }), ]); return { summary, fact: response.fact }; @@ -508,11 +520,12 @@ export function CrossDatasetFactDetailView({ factKey: string; search: string; }) { + const { country } = useCountry(); const context = useMemo( - () => parseFactCatalogParams(new URLSearchParams(search)), - [search], + () => parseFactCatalogParams(new URLSearchParams(search), country), + [country, search], ); - const query = useFactDetail(factKey); + const query = useFactDetail(factKey, country); if (!factKey) { return ( => { const [summary, groups] = await Promise.all([ - apiGet("/microcosm/cross-dataset", { view: "summary" }), - apiGet("/microcosm/cross-dataset", { view: "groups" }), + apiGet("/microcosm/cross-dataset", { + view: "summary", + country, + }), + apiGet("/microcosm/cross-dataset", { + view: "groups", + country, + }), ]); if (summary.run_id !== groups.run_id || summary.snapshot_id !== groups.snapshot_id) { throw new Error("Cross-dataset summary and group data belong to different runs."); @@ -219,8 +231,8 @@ function PerformanceLegend() { ); } -function CrossDatasetOverviewView() { - const query = useCrossDatasetOverview(); +function CrossDatasetOverviewView({ country }: { country: Country }) { + const query = useCrossDatasetOverview(country); const [dimension, setDimension] = useState("ledger_source"); const [performanceFilter, setPerformanceFilter] = useState({ geography: "all", @@ -231,28 +243,71 @@ function CrossDatasetOverviewView() { error: query.error, summary: query.data?.summary, }); - + const orderedSources = useMemo( + () => (query.data ? orderSourceSummaries(query.data.summary.sources) : []), + [query.data], + ); + const groupDimensions = useMemo( + () => + query.data + ? availableGroupDimensions(query.data.groups.groups) + : [], + [query.data], + ); + const activeDimension = groupDimensions.some((option) => option.key === dimension) + ? dimension + : (groupDimensions[0]?.key ?? dimension); + const geographyOptions = useMemo( + () => + query.data + ? geographyFilterOptions(query.data.groups.groups) + : [], + [query.data], + ); + const sampleOptions = useMemo( + () => + query.data + ? sampleFilterOptions(query.data.groups.groups) + : [], + [query.data], + ); + const activePerformanceFilter = useMemo( + () => ({ + geography: + performanceFilter.geography === "all" || + geographyOptions.some((option) => option.key === performanceFilter.geography) + ? performanceFilter.geography + : "all", + sample: + performanceFilter.sample === "all" || + sampleOptions.some((option) => option.key === performanceFilter.sample) + ? performanceFilter.sample + : "all", + }), + [geographyOptions, performanceFilter, sampleOptions], + ); const sourceOverviews = useMemo( () => query.data ? buildSourceOverviews( query.data.summary, query.data.groups.groups, - performanceFilter, + activePerformanceFilter, ) : [], - [performanceFilter, query.data], - ); - const orderedSources = useMemo( - () => (query.data ? orderSourceSummaries(query.data.summary.sources) : []), - [query.data], + [activePerformanceFilter, query.data], ); const groupRows = useMemo( () => query.data - ? buildGroupRows(query.data.groups.groups, dimension, orderedSources) + ? buildGroupRows( + query.data.groups.groups, + activeDimension, + orderedSources, + country, + ) : [], - [dimension, orderedSources, query.data], + [activeDimension, country, orderedSources, query.data], ); if (state === "loading") return ; @@ -286,6 +341,9 @@ function CrossDatasetOverviewView() { ); } + const jurisdictionLabel = query.data.summary.jurisdictions?.join(", ") || + "the selected jurisdiction"; + return (
- Every model or standalone dataset is classified against the complete US Chronicle - fact catalog. Non-US facts are excluded before classification. Error measures - closeness only among comparable facts executed by the current adapter, using the - same fact-level 100%-capped approach as the calibration fit view. + Every model or standalone dataset is classified against the complete Chronicle fact + catalog published for {jurisdictionLabel}. Error measures closeness only among + comparable facts executed by the current adapter, using the same fact-level + 100%-capped approach as the calibration fit view. } /> @@ -308,11 +366,9 @@ function CrossDatasetOverviewView() { error capped at 100% before averaging. Lower is better. Microcosm results marked{" "} direct calibration target are in-sample calibration fit, not independent validation. The Sample selector uses that Microcosm membership as - one shared fact set for every model and dataset. Results marked{" "} - 2023 facts aligned to 2024 compare against the 2024 transformation - produced by the same aging and uprating logic used in the Microcosm build—not a native - 2023 society-wide run. Tax-Calculator’s public CPS rows use its population advanced to - 2024. + one shared fact set for every model and dataset. Source names, geography levels, and + period treatments are read from this bundle; fact details show any transformation used + to create a comparable benchmark.

@@ -325,7 +381,7 @@ function CrossDatasetOverviewView() { @@ -403,12 +464,12 @@ function CrossDatasetOverviewView() {