diff --git a/docs/spec-driven-countries.md b/docs/spec-driven-countries.md index 6d438af0..f34d2394 100644 --- a/docs/spec-driven-countries.md +++ b/docs/spec-driven-countries.md @@ -212,37 +212,57 @@ artifact label, range values such as `0_17` and `65_plus` become `0–17` and A geography-role dimension sets `row.geography` and uses its declared `level` or `"region"`. Other dimensions become `target_dimensions` with `key`, `label`, `value`, `source_key`, `raw_value`, and an optional zero-based `rank`. +The published dimension ID, not its display label, determines `key`. Simple +lowercase IDs retain keys such as `bd_age_band`; IDs containing other +characters receive a lossless query-safe encoding. Consequently, two distinct +dimensions may share a display label without merging into one facet. Facet values use rank order only when every displayed value has a rank; otherwise the legacy facet sorter remains in force. Structured rows are also excluded from whole-population estimate-scope inference. -Adapter selection is per row and never by country: +### Target representation classification -1. A plain-object `targets[].dimensions` selects `structured`, even when the - dictionary is absent or the row object is empty. -2. Otherwise, a filter matched by the shared filter-decomposition specs selects - `legacy_filter`. -3. Every other row selects `legacy_name`, preserving dotted/slash name parsing - and Chronicle metadata dimensions. +The dashboard classifies each target row by structure before normalizing it, +then summarizes the complete `targets` array. Diagnostics schema versions do +not identify the target representation: published schema 5 and schema 6 files +can both contain legacy string fields. The structural classification is: -Each target response records this choice as `dimension_adapter`. Calibration -summary and target-diagnostics responses include: +1. `structured` when every row has a plain-object `source` with a non-empty + `id`, a plain-object `variable` with a non-empty `id`, and a plain-object + `dimensions` field. Use `{}` when a target has no dimensions. +2. `legacy` when no row has object-valued `source`, `variable`, or `dimensions` + fields. +3. `mixed` when complete structured rows and complete legacy rows occur together. +4. `unknown` when there are no target rows. + +Every target row must independently satisfy either the structured or legacy +shape. Partially structured rows are invalid. In a mixed file, each complete +structured row uses the structured reader and each complete legacy row uses the +legacy reader; the file-level `mixed` value is descriptive and does not select a +third normalization strategy. + +Calibration summary and target-diagnostics responses report the classification: ```json { "target_schema": { "diagnostics_schema_version": 7, - "structured_dimensions": true + "structured_dimensions": true, + "target_representation": "structured" } } ``` `structured_dimensions` reports whether the diagnostics published a plain -dimension dictionary; it does not choose every row's adapter. +dimension dictionary. `target_representation` summarizes the collection. The +existing per-row `dimension_adapter` response field remains for compatibility +and describes only whether that row's dimensions came from a structured object, +a known legacy filter, or legacy name and metadata parsing. ### Structured source and variable identifiers -Targets accept their legacy strings or the following objects: +Fully structured targets use the following objects together. The `dimensions` +object is required and may be empty: ```json { @@ -258,34 +278,38 @@ Targets accept their legacy strings or the following objects: "id": "population", "label": "Resident population", "measure": "count" - } + }, + "dimensions": {} } ] } ``` -Publisher-key precedence is Chronicle metadata, then `source.id`, then legacy -name grammar. `source_citation` is the legacy source string or -`source.citation`; `source_url` is the structured URL or `null`. The publisher -label precedence is the manifest map, `source.label`, then the shared -humanizer. - -Variable-ID precedence is `variable.id`, `metadata.variable`, then the existing -artifact/name fallbacks. A structured ID remains an identifier; its display -name is separately returned as `variable_label`. Measure precedence is -`variable.measure`, then the existing first-dimension and Chronicle metadata -logic. Legacy string sources and variables retain their existing values, -families, citations, facets, and grouping. The new response fields are -additive: `source_label`, `source_url`, `variable_label`, and -`dimension_adapter`. +For a `structured` file, navigation identity comes only from `source`, +`variable`, and `dimensions`. Legacy names, filters, registry families, and +Chronicle metadata cannot change its source, statistic, geography, or +breakdown dimensions. The source and variable IDs remain stable selection +keys; their labels are returned separately for display. + +For a `legacy` file, the isolated legacy reader handles the established dotted, +slash, Chronicle metadata, and known filter encodings. It does not interpret an +arbitrary underscore as a structural separator. + +For a `mixed` file, fully legacy rows retain legacy behavior. Partially +structured rows use the compatibility precedence: Chronicle publisher ID, +then `source.id`, then legacy source parsing; `variable.id`, then legacy +variable parsing; structured dimensions, then known filter dimensions, then +legacy metadata and name dimensions. This prevents one partially migrated row +from changing unrelated legacy rows in the same file. ### Producer follow-up Microcosm release producers must publish all of the following before the legacy -presentation and parsing adapters can be retired: +presentation and normalization readers can be retired: - `release_manifest.country`; - `release_manifest.presentation`; - `release_manifest.publisher_labels`; -- `calibration_diagnostics.dimensions` and `targets[].dimensions`; and -- structured `targets[].source` and `targets[].variable` objects. +- `calibration_diagnostics.dimensions`; +- a `targets[].dimensions` object on every row, including `{}` where empty; and +- structured `targets[].source` and `targets[].variable` objects on every row. diff --git a/frontend/app/microcosm/page.tsx b/frontend/app/microcosm/page.tsx index a61f5535..8b000029 100644 --- a/frontend/app/microcosm/page.tsx +++ b/frontend/app/microcosm/page.tsx @@ -1,10 +1,24 @@ import { AppShell } from "@/components/layout/app-shell"; import { MicrocosmOverviewView } from "@/components/microcosm/microcosm-overview-view"; +import { parseCountry } from "@/lib/microcosm/countries"; + +interface MicrocosmOverviewPageProps { + searchParams?: Promise>; +} + +export default async function MicrocosmOverviewPage({ + searchParams, +}: MicrocosmOverviewPageProps) { + const params = await searchParams; + const rawRelease = Array.isArray(params?.release) ? params.release[0] : params?.release; + const rawCountry = Array.isArray(params?.country) ? params.country[0] : params?.country; -export default function MicrocosmOverviewPage() { return ( - + ); } diff --git a/frontend/app/microcosm/targets/page.tsx b/frontend/app/microcosm/targets/page.tsx index 86558d64..6f709322 100644 --- a/frontend/app/microcosm/targets/page.tsx +++ b/frontend/app/microcosm/targets/page.tsx @@ -1,5 +1,6 @@ import { AppShell } from "@/components/layout/app-shell"; import { MicrocosmTargetsView } from "@/components/microcosm/microcosm-targets-view"; +import { parseCountry } from "@/lib/microcosm/countries"; interface MicrocosmTargetsPageProps { searchParams?: Promise>; @@ -13,6 +14,9 @@ export default async function MicrocosmTargetsPage({ const initialScope = rawScope === "healthcare" ? "healthcare" : "all"; const rawSource = Array.isArray(params?.source) ? params.source[0] : params?.source; const rawLevel = Array.isArray(params?.level) ? params.level[0] : params?.level; + const rawRelease = Array.isArray(params?.release) ? params.release[0] : params?.release; + const rawStart = Array.isArray(params?.start) ? params.start[0] : params?.start; + const rawCountry = Array.isArray(params?.country) ? params.country[0] : params?.country; return ( @@ -20,6 +24,9 @@ export default async function MicrocosmTargetsPage({ initialScope={initialScope} initialSource={rawSource ?? ""} initialLevel={rawLevel ?? ""} + initialCountry={parseCountry(rawCountry)} + initialRelease={rawRelease ?? ""} + initialStep={rawStart === "explore" ? "pick" : "results"} /> ); diff --git a/frontend/components/layout/country-context.test.ts b/frontend/components/layout/country-context.test.ts index 10f378f4..10a9ed34 100644 --- a/frontend/components/layout/country-context.test.ts +++ b/frontend/components/layout/country-context.test.ts @@ -1,6 +1,10 @@ import { expect, test } from "bun:test"; -import { countrySwitchUrl, isCountry } from "./country-context"; +import { + countrySwitchUrl, + isCountry, + selectedReleaseForCountry, +} from "./country-context"; test("country switching clears bundle-specific Cross-dataset state", () => { const switched = new URL( @@ -14,16 +18,24 @@ test("country switching clears bundle-specific Cross-dataset state", () => { expect(switched.searchParams.toString()).toBe("country=be"); }); -test("country switching preserves route state outside Cross-dataset", () => { +test("country switching preserves route filters but clears the selected release", () => { const switched = new URL( countrySwitchUrl( - "https://example.test/microcosm/targets?country=us&variable=income_tax", + "https://example.test/microcosm/targets?country=us&release=us-build&variable=income_tax", "uk", ), ); expect(switched.pathname).toBe("/microcosm/targets"); expect(switched.searchParams.toString()).toBe("country=uk&variable=income_tax"); + expect(switched.searchParams.has("release")).toBe(false); +}); + +test("a release selection is used only by the country that supplied it", () => { + const selection = { country: "us" as const, value: "us-build" }; + + expect(selectedReleaseForCountry("us", selection)).toBe("us-build"); + expect(selectedReleaseForCountry("be", selection)).toBe(""); }); test("the client country parser accepts every registered country and nothing else", () => { diff --git a/frontend/components/layout/country-context.tsx b/frontend/components/layout/country-context.tsx index 46f5f74c..4e8d4a4b 100644 --- a/frontend/components/layout/country-context.tsx +++ b/frontend/components/layout/country-context.tsx @@ -7,6 +7,7 @@ import { useState, type ReactNode, } from "react"; +import { useRouter } from "next/navigation"; import { DEFAULT_COUNTRY, @@ -16,6 +17,11 @@ import { export type Country = MicrocosmCountry; +export interface CountryReleaseSelection { + country: Country; + value: string; +} + export { isCountry }; const STORAGE_KEY = "microcosm-country"; @@ -24,11 +30,22 @@ export function countrySwitchUrl(currentUrl: string, next: Country): string { const url = new URL(currentUrl); if (url.pathname.endsWith("/microcosm/datasets")) { url.search = ""; + } else { + // Release identifiers are repository-specific. A release selected for one + // country must never be requested from another country's repository. + url.searchParams.delete("release"); } url.searchParams.set("country", next); return url.toString(); } +export function selectedReleaseForCountry( + country: Country, + selection: CountryReleaseSelection, +): string { + return selection.country === country ? selection.value : ""; +} + function persistCountry(country: Country) { try { window.localStorage.setItem(STORAGE_KEY, country); @@ -48,6 +65,7 @@ const CountryContext = createContext({ }); export function CountryProvider({ children }: { children: ReactNode }) { + const router = useRouter(); const [country, setCountryState] = useState(DEFAULT_COUNTRY); useEffect(() => { @@ -65,11 +83,9 @@ export function CountryProvider({ children }: { children: ReactNode }) { if (next === country) return; setCountryState(next); persistCountry(next); - window.history.replaceState( - window.history.state, - "", - countrySwitchUrl(window.location.href, next), - ); + // Route through Next so server page search parameters are recalculated; + // raw history replacement would leave initialRelease props stale. + router.replace(countrySwitchUrl(window.location.href, next), { scroll: false }); }; return ( diff --git a/frontend/components/layout/nav-items.test.ts b/frontend/components/layout/nav-items.test.ts index fe001f4c..6e5da1f2 100644 --- a/frontend/components/layout/nav-items.test.ts +++ b/frontend/components/layout/nav-items.test.ts @@ -16,15 +16,11 @@ function datasetAccuracyItems() { return group.items; } -test("shows calibration targets directly under calibration fit", () => { +test("keeps calibration targets out of the sidebar", () => { const items = datasetAccuracyItems(); - const labels = items.map((item) => item.label); - const calibrationFitIndex = labels.indexOf("Calibration fit"); - const calibrationTargetsIndex = labels.indexOf("Calibration targets"); - expect(calibrationFitIndex).toBeGreaterThanOrEqual(0); - expect(calibrationTargetsIndex).toBe(calibrationFitIndex + 1); - expect(items[calibrationTargetsIndex]?.href).toBe("/microcosm/targets"); + expect(items.some((item) => item.label === "Calibration targets")).toBe(false); + expect(items.some((item) => item.href === "/microcosm/targets")).toBe(false); }); test("places external checks at the bottom of dataset accuracy", () => { @@ -43,19 +39,17 @@ test("models the external checks icon separately from its label", () => { expect(externalChecks?.external).toBe(true); }); -test("targets path activates calibration targets instead of calibration fit", () => { +test("targets path does not activate calibration fit", () => { const items = datasetAccuracyItems(); const calibrationFit = items.find((item) => item.label === "Calibration fit"); - const calibrationTargets = items.find((item) => item.label === "Calibration targets"); - if (!calibrationFit || !calibrationTargets) { - throw new Error("Calibration nav items not found"); + if (!calibrationFit) { + throw new Error("Calibration fit nav item not found"); } expect(calibrationFit.also ?? []).not.toContain("/microcosm/targets"); expect(isActive("/microcosm", calibrationFit)).toBe(true); expect(isActive("/microcosm/targets", calibrationFit)).toBe(false); - expect(isActive("/microcosm/targets", calibrationTargets)).toBe(true); }); test("opens external navigation in a new tab without changing internal navigation", () => { const items = datasetAccuracyItems(); @@ -105,14 +99,12 @@ test("Belgium navigation keeps country-ready pages and hides pages it lacks capa const items = navGroupsForCountry("be").flatMap((group) => group.items); expect(items.map((item) => item.href)).toEqual([ "/microcosm", - "/microcosm/targets", "/microcosm/datasets", "/microcosm/compare", ]); expect(items.every((item) => hasCapability("be", item.capability!))).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", ]); @@ -127,10 +119,7 @@ test("US navigation lists every page", () => { test("artifact-narrowed capabilities hide pages the release does not serve", () => { const groups = navGroupsForCountry("us", ["calibration", "targets"]); expect(groups.map((group) => group.label)).toEqual(["Dataset accuracy"]); - expect(groups[0].items.map((item) => item.href)).toEqual([ - "/microcosm", - "/microcosm/targets", - ]); + expect(groups[0].items.map((item) => item.href)).toEqual(["/microcosm"]); }); test("shows Cross-dataset navigation for every selectable country", () => { diff --git a/frontend/components/layout/nav-items.ts b/frontend/components/layout/nav-items.ts index d3d48127..9c709605 100644 --- a/frontend/components/layout/nav-items.ts +++ b/frontend/components/layout/nav-items.ts @@ -24,7 +24,6 @@ export const NAV_GROUPS: { label: string; items: NavItem[] }[] = [ label: "Dataset accuracy", items: [ { href: "/microcosm", label: "Calibration fit", capability: "calibration" }, - { href: "/microcosm/targets", label: "Calibration targets", capability: "targets" }, { href: "/microcosm/model-coverage", label: "Validation reach", diff --git a/frontend/components/microcosm/calibration-map.tsx b/frontend/components/microcosm/calibration-map.tsx index e35623de..9e7f9c89 100644 --- a/frontend/components/microcosm/calibration-map.tsx +++ b/frontend/components/microcosm/calibration-map.tsx @@ -38,7 +38,9 @@ function isSynthetic(key: string): boolean { } function variableName(leaf: MicrocosmTreemapLeaf): string { - return isSynthetic(leaf.key) ? leaf.variable : humanizeName(leaf.variable) || leaf.variable; + return isSynthetic(leaf.key) + ? leaf.variable + : leaf.label ?? (humanizeName(leaf.variable) || leaf.variable); } function measureLabel(measure: string | null): string | null { @@ -89,6 +91,7 @@ function aggregateLeaves( key, source, variable, + label: null, measure: null, measure_counts: [], n_targets: leaves.reduce((a, c) => a + c.n_targets, 0), diff --git a/frontend/components/microcosm/calibration-target-navigation-cards.tsx b/frontend/components/microcosm/calibration-target-navigation-cards.tsx new file mode 100644 index 00000000..044473eb --- /dev/null +++ b/frontend/components/microcosm/calibration-target-navigation-cards.tsx @@ -0,0 +1,144 @@ +"use client"; + +import { useMemo } from "react"; +import Link from "next/link"; + +import { useCountry } from "@/components/layout/country-context"; +import { fmt } from "@/components/shared/format"; +import { useMicrocosmTargetDiagnostics } from "@/lib/api/hooks/use-microcosm"; +import { microcosmTargetsIntro } from "@/lib/microcosm/presentation"; + +type TargetDestination = "explore" | "healthcare" | "everything"; +type CardAccent = "teal" | "slate"; + +const ACCENTS: Record< + CardAccent, + { ink: string; border: string; glow: string } +> = { + teal: { + ink: "text-primary", + border: "hover:border-primary/50", + glow: "wiz-glow-teal", + }, + slate: { + ink: "text-muted-foreground", + border: "hover:border-border-dark", + glow: "wiz-glow-neutral", + }, +}; + +function targetHref( + country: string, + release: string, + destination: TargetDestination, +): string { + const params = new URLSearchParams({ country }); + if (release) params.set("release", release); + if (destination === "explore") params.set("start", "explore"); + if (destination === "healthcare") params.set("scope", "healthcare"); + return `/microcosm/targets?${params.toString()}`; +} + +function TargetNavigationCard({ + eyebrow, + title, + body, + stat, + accent, + href, +}: { + eyebrow: string; + title: string; + body: string; + stat: string; + accent: CardAccent; + href: string; +}) { + const styles = ACCENTS[accent]; + return ( + + + {eyebrow} + +
+

+ {title} +

+

{body}

+
+
+ {stat} + + → + +
+ + ); +} + +export function CalibrationTargetNavigationCards({ release }: { release?: string }) { + const { country } = useCountry(); + const { data, isPlaceholderData } = useMicrocosmTargetDiagnostics({ + release, + limit: 1, + }); + const variableGroupCount = useMemo( + () => + new Set( + (data?.variables ?? []).map((variable) => + [variable.source, variable.level, variable.variable].join("::"), + ), + ).size, + [data?.variables], + ); + const hasHealthcareTargets = + !isPlaceholderData && (data?.scope_counts?.healthcare ?? 0) > 0; + const allTargets = data?.total_targets ?? null; + + return ( +
+ + {hasHealthcareTargets ? ( + + ) : null} + +
+ ); +} diff --git a/frontend/components/microcosm/cluster-detail.tsx b/frontend/components/microcosm/cluster-detail.tsx index 06e66e6d..54a6c390 100644 --- a/frontend/components/microcosm/cluster-detail.tsx +++ b/frontend/components/microcosm/cluster-detail.tsx @@ -141,7 +141,9 @@ export function ClusterDetail({ .filter((d) => d.values.length > 0); const filteredTotal = data?.filtered_total ?? rows.length; const within = leaf.scored > 0 ? leaf.within_10pct / leaf.scored : null; - const name = synthetic ? leaf.variable : humanizeName(leaf.variable) || leaf.variable; + const name = synthetic + ? leaf.variable + : leaf.label ?? (humanizeName(leaf.variable) || leaf.variable); const measureOptions = (leaf.measure_counts ?? []).filter((option) => option.measure); const showMeasureFilter = !synthetic && Boolean(filters.program) && measureOptions.length > 1; const hasFilters = diff --git a/frontend/components/microcosm/microcosm-overview-view.tsx b/frontend/components/microcosm/microcosm-overview-view.tsx index 87b3f6d1..f5f3a757 100644 --- a/frontend/components/microcosm/microcosm-overview-view.tsx +++ b/frontend/components/microcosm/microcosm-overview-view.tsx @@ -7,9 +7,14 @@ import { CalibrationExplorerDataPrefetch, CalibrationExplorerMap, } from "@/components/microcosm/calibration-explorer-map"; +import { CalibrationTargetNavigationCards } from "@/components/microcosm/calibration-target-navigation-cards"; import { ArtifactDescriptionBanner } from "@/components/microcosm/artifact-description-banner"; import { WEIGHTED_TARGET_ERROR_HELP } from "@/components/microcosm/calibration-explorer-view"; -import { useCountry } from "@/components/layout/country-context"; +import { + selectedReleaseForCountry, + useCountry, + type Country, +} from "@/components/layout/country-context"; import { EmptyState } from "@/components/shared/empty-state"; import { fmt, fmtCompact } from "@/components/shared/format"; import { HelpHint } from "@/components/shared/help-hint"; @@ -70,9 +75,19 @@ function OverviewMetric({ label, value }: { label: ReactNode; value: string }) { ); } -export function MicrocosmOverviewView() { +export function MicrocosmOverviewView({ + initialCountry = "us", + initialRelease = "", +}: { + initialCountry?: Country; + initialRelease?: string; +}) { const { country } = useCountry(); - const [release, setRelease] = useState(""); + const [releaseSelection, setReleaseSelection] = useState({ + country: initialCountry, + value: initialRelease, + }); + const release = selectedReleaseForCountry(country, releaseSelection); const [pageIntroHeight, setPageIntroHeight] = useState(0); const { data: releaseData } = useMicrocosmReleases(); const { data, isLoading, error } = useMicrocosm(release || undefined); @@ -140,7 +155,7 @@ export function MicrocosmOverviewView() { setReleaseSelection({ country, value })} options={releaseOptions} /> - ); -} +type WizardStep = "pick" | "refine" | "results"; export function MicrocosmTargetsView({ initialScope = "all", initialSource = "", initialLevel = "", + initialCountry = "us", + initialRelease = "", + initialStep = "results", }: { initialScope?: TargetScope; initialSource?: string; initialLevel?: string; + initialCountry?: Country; + initialRelease?: string; + initialStep?: "pick" | "results"; }) { - const [release, setRelease] = useState(""); + const [releaseSelection, setReleaseSelection] = useState({ + country: initialCountry, + value: initialRelease, + }); const [scope, setScope] = useState(initialScope); const [variable, setVariable] = useState(""); const [source, setSource] = useState(initialSource); @@ -569,11 +512,13 @@ export function MicrocosmTargetsView({ const [selected, setSelected] = useState(null); const [page, setPage] = useState(0); const [sort, setSort] = useState({ by: "abs_relative_error", dir: "desc" }); - const [step, setStep] = useState(initialSource ? "results" : "home"); + const [step, setStep] = useState(initialSource ? "results" : initialStep); const [showAdvanced, setShowAdvanced] = useState(false); const [refineIndex, setRefineIndex] = useState(0); const { country } = useCountry(); + const release = selectedReleaseForCountry(country, releaseSelection); + const router = useRouter(); const { data: releaseData } = useMicrocosmReleases(); const { data: stagingData } = useMicrocosmStagingRuns(); const releaseOptions = useMemo( @@ -593,7 +538,7 @@ export function MicrocosmTargetsView({ function pickRelease(value: string) { // A different release is a different surface — reset everything below it. - setRelease(value); + setReleaseSelection({ country, value }); setVariable(""); setFacetFilters({}); setSource(""); @@ -621,28 +566,14 @@ export function MicrocosmTargetsView({ setPage(0); } - function startOver() { - resetFilters(); - setStep("home"); - } - - function startExplore() { - resetFilters(); - setStep("pick"); - } - - function startHealthcare() { - resetFilters(); - setScope("healthcare"); - setStep("results"); - } - - function startEverything() { + function returnToCalibrationFit() { resetFilters(); - setStep("results"); + const query = new URLSearchParams({ country }); + if (release) query.set("release", release); + router.push(`/microcosm?${query.toString()}`); } - // Step back one level toward the starting cards. + // Step back one level toward the statistic picker. function goBack() { if (step === "results" && activeVariable) { setStep("refine"); @@ -652,7 +583,7 @@ export function MicrocosmTargetsView({ setStep("pick"); return; } - startOver(); + returnToCalibrationFit(); } const facetParam = useMemo( @@ -697,7 +628,7 @@ export function MicrocosmTargetsView({ ], ); - const { data, isLoading, isFetching, error, isPlaceholderData } = + const { data, isLoading, isFetching, error } = useMicrocosmTargetDiagnostics(params); const variables = data?.variables ?? []; @@ -711,12 +642,6 @@ export function MicrocosmTargetsView({ [scope, variables], ); const filteredTotal = data?.filtered_total ?? 0; - const allTargets = data?.total_targets ?? null; - // The healthcare focus is offered when the release has healthcare targets. - // Kept-previous placeholder data belongs to the prior country/release, so it - // must not decide the card. - const hasHealthcareTargets = - !isPlaceholderData && (data?.scope_counts?.healthcare ?? 0) > 0; const pageCount = Math.max(Math.ceil(filteredTotal / PAGE_SIZE), 1); const activeVariable = variables.find((v) => v.variable_key === variable); @@ -1053,19 +978,7 @@ export function MicrocosmTargetsView({ - See how closely the calibrated weights reproduce each official statistic — by - source, measure, and breakdown. This is the drill-down behind the{" "} - - calibration map - - . - - } + description="See how closely the calibrated weights reproduce each official statistic by source, measure, and breakdown." actions={ - {step === "home" && ( -
-

- Where would you like to start? -

-
- - {hasHealthcareTargets ? ( - - ) : null} - -
-
- )} - {step === "pick" && (
/ Pick a statistic
- - - + {isLoading ? ( + + ) : error || !data ? ( + + ) : ( + + + + )}
)} @@ -1140,7 +1026,7 @@ export function MicrocosmTargetsView({
- / @@ -1151,7 +1037,7 @@ export function MicrocosmTargetsView({
@@ -1307,7 +1193,7 @@ export function MicrocosmTargetsView({ )}