Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock.json
1 change: 1 addition & 0 deletions frontend/app/api/populace/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export async function GET(request: Request) {
{ name: "build_manifest", path: `${prefix}/build_manifest.json`, url: hfResolveUrl(`${prefix}/build_manifest.json`, country) },
{ name: "release_manifest", path: `${prefix}/release_manifest.json`, url: hfResolveUrl(`${prefix}/release_manifest.json`, country) },
{ name: "calibration_diagnostics", path: `${prefix}/calibration_diagnostics.json`, url: hfResolveUrl(`${prefix}/calibration_diagnostics.json`, country) },
{ name: "demographics", path: `${prefix}/demographics.json`, url: hfResolveUrl(`${prefix}/demographics.json`, country) },
],
limitations: [
`Everything on this page is read live from the ${populaceRepo(country)} Hugging Face dataset; the current release is resolved through latest.json.`,
Expand Down
78 changes: 78 additions & 0 deletions frontend/components/populace/populace-overview-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
usePopulaceReleases,
usePopulaceTargetTreemap,
} from "@/lib/api/hooks/use-populace";
import type { GeographyCoverageBlock } from "@/lib/api/hooks/use-populace";

function formatPublishedAt(value: string | null | undefined): string {
if (!value) return "—";
Expand Down Expand Up @@ -176,6 +177,8 @@ export function PopulaceOverviewView() {
<KpiCard label="Published" value={formatPublishedAt(data.updated_at)} />
</div>

<GeographyCoverageSection coverage={cal.geography_coverage ?? null} />

<SectionCard
title="Calibration map"
actions={
Expand Down Expand Up @@ -347,3 +350,78 @@ export function PopulaceOverviewView() {
</div>
);
}

/** Household-record counts by geography — the release's sub-national
* resolution floor. Records, not weights: no calibration can rescue a
* geography with too few underlying records (48 districts under 50 in the
* 2026-07 national-only release blocked district features downstream). */
function GeographyCoverageSection({
coverage,
}: {
coverage: {
unit?: string;
states?: GeographyCoverageBlock | null;
congressional_districts?: GeographyCoverageBlock | null;
} | null;
}) {
const districts = coverage?.congressional_districts ?? null;
const states = coverage?.states ?? null;
if (!districts && !states) return null;
const under50 = districts?.n_under_50 ?? null;
const thinnest = districts?.counts
? Object.entries(districts.counts)
.sort((a, b) => a[1] - b[1])
.slice(0, 10)
: [];
return (
<SectionCard
title="Geography coverage"
description="Unweighted household records per geography — the resolution floor for sub-national analysis. Districts need enough records, not just calibrated weights."
>
<div className="grid grid-cols-2 gap-3 md:grid-cols-4">
<KpiCard
label="Congressional districts"
value={districts?.n_geographies == null ? "—" : fmt(districts.n_geographies, { digits: 0 })}
/>
<KpiCard
label="Median records / district"
value={districts?.household_records_median == null ? "—" : fmt(districts.household_records_median, { digits: 0 })}
/>
<KpiCard
label={
<HelpHint
label="Districts under 50 records"
tooltip="Districts with fewer than 50 household records cannot support district-level rate estimates. Zero here is the readiness bar for congressional-district features."
/>
}
value={under50 == null ? "—" : fmt(under50, { digits: 0 })}
delta={under50 == null ? undefined : under50 === 0 ? "district-ready" : "blocks district analysis"}
tone={under50 == null ? undefined : under50 === 0 ? "positive" : "negative"}
/>
<KpiCard
label="Min records / state"
value={states?.household_records_min == null ? "—" : fmt(states.household_records_min, { digits: 0 })}
/>
</div>
{thinnest.length > 0 && (under50 ?? 0) > 0 && (
<div className="mt-4">
<div className="mb-1 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
Thinnest districts
</div>
<div className="flex flex-wrap gap-2 text-xs tabular-nums">
{thinnest.map(([district, count]) => (
<span
key={district}
className={`rounded border border-border px-2 py-0.5 ${
count < 50 ? "text-destructive" : "text-muted-foreground"
}`}
>
{district}: {count}
</span>
))}
</div>
</div>
)}
</SectionCard>
);
}
15 changes: 15 additions & 0 deletions frontend/lib/api/hooks/use-populace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,16 @@ export interface PopulaceFamilyFitRow {
mean_abs_relative_error: number | null;
}

export interface GeographyCoverageBlock {
n_geographies?: number | null;
household_records_min?: number | null;
household_records_median?: number | null;
household_records_max?: number | null;
n_under_50?: number | null;
n_under_100?: number | null;
counts?: Record<string, number>;
}

export interface PopulaceCalibration {
available: boolean;
path?: string | null;
Expand All @@ -159,6 +169,11 @@ export interface PopulaceCalibration {
l0_lambda?: number | null;
n_nonzero?: number | null;
n_records?: number | null;
geography_coverage?: {
unit?: string;
states?: GeographyCoverageBlock | null;
congressional_districts?: GeographyCoverageBlock | null;
} | null;
initial_loss?: number | null;
final_loss?: number | null;
loss_kind?: "normalized_target_loss" | "raw_optimizer_objective";
Expand Down
14 changes: 12 additions & 2 deletions frontend/lib/populace/latest-artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1463,6 +1463,10 @@ export interface Calibration {
included_target_count: number;
build_manifest: JsonObject;
release_manifest: JsonObject;
// demographics.json geography_coverage: unweighted household-record counts
// by state / congressional district (the release's sub-national resolution
// floor). Null for releases published before the section existed.
geography_coverage: JsonObject | null;
rows: TargetRow[];
}

Expand Down Expand Up @@ -1499,6 +1503,7 @@ export function buildCalibration(
updatedAt: string | null = null,
buildManifest: JsonObject = {},
releaseManifest: JsonObject = {},
demographics: JsonObject = {},
): Calibration {
const targets = Array.isArray(diag.targets) ? (diag.targets as TargetRow[]) : [];
const skipped = Array.isArray(diag.skipped) ? (diag.skipped as JsonObject[]) : [];
Expand Down Expand Up @@ -1536,6 +1541,9 @@ export function buildCalibration(
included_target_count: includedTargetCount,
build_manifest: buildManifest,
release_manifest: releaseManifest,
geography_coverage: Object.keys(asObject(demographics.geography_coverage)).length
? asObject(demographics.geography_coverage)
: null,
rows,
};
}
Expand Down Expand Up @@ -1674,12 +1682,13 @@ async function loadReleaseUncached(
updatedAt = ptr.updated_at;
}
const prefix = `releases/${id}`;
const [diag, buildManifest, releaseManifest] = await Promise.all([
const [diag, buildManifest, releaseManifest, demographics] = await Promise.all([
hfJson(hfResolveUrl(`${prefix}/calibration_diagnostics.json`, country), revalidate),
hfJson(hfResolveUrl(`${prefix}/build_manifest.json`, country), revalidate).catch(() => ({})),
hfJson(hfResolveUrl(`${prefix}/release_manifest.json`, country), revalidate).catch(() => ({})),
hfJson(hfResolveUrl(`${prefix}/demographics.json`, country), revalidate).catch(() => ({})),
]);
return buildCalibration(diag, id, updatedAt, buildManifest, releaseManifest);
return buildCalibration(diag, id, updatedAt, buildManifest, releaseManifest, demographics);
}

function targetDiagnosticsMetadata(rows: TargetRow[]): TargetDiagnosticsMetadata {
Expand Down Expand Up @@ -1938,6 +1947,7 @@ export function latestPopulaceCalibrationSummary(cal: Calibration) {
total_targets: cal.rows.length,
within_tolerance_count: withinToleranceCount(cal.rows),
family_fit: familyFitSummary(cal.rows),
geography_coverage: cal.geography_coverage,
};
}

Expand Down