From 395dd95202652c3be9833fe35457bd5dd89db073 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Tue, 7 Jul 2026 10:02:33 -0400 Subject: [PATCH] Format rate-type validation rows as percentages Populace PR #348 adds Census state SPM poverty-rate backtests whose level rows carry unit: "percent" with decimal-fraction values. Thread the unit through the payload parser and row types, and format value cells with a shared fmtUnitValue (0.134 -> "13.4%") in the reforms and staging views. Rows without a unit keep the USD formatting, so pre-#348 payloads render unchanged. Co-Authored-By: Claude Fable 5 --- .../components/populace/populace-reforms-view.tsx | 6 +++--- .../components/populace/populace-staging-view.tsx | 5 +++-- frontend/components/shared/format.ts | 14 ++++++++++++++ frontend/lib/api/hooks/use-populace.ts | 2 ++ frontend/lib/populace/reforms.ts | 5 +++++ 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/frontend/components/populace/populace-reforms-view.tsx b/frontend/components/populace/populace-reforms-view.tsx index 1935160..24e3bd1 100644 --- a/frontend/components/populace/populace-reforms-view.tsx +++ b/frontend/components/populace/populace-reforms-view.tsx @@ -3,7 +3,7 @@ import { Fragment, useEffect, useMemo, useState } from "react"; import { EmptyState } from "@/components/shared/empty-state"; -import { fmt, fmtMoney, releaseLabel } from "@/components/shared/format"; +import { fmt, fmtMoney, fmtUnitValue, releaseLabel } from "@/components/shared/format"; import { KpiCard } from "@/components/shared/kpi-card"; import { LoadingBlock } from "@/components/shared/LoadingBlock"; import { PageHeader } from "@/components/shared/page-header"; @@ -253,10 +253,10 @@ function ReformTable({ - {fmtMoney(row.jct_score)} + {fmtUnitValue(row.jct_score, row.unit)} - {fmtMoney(row.populace_estimate)} + {fmtUnitValue(row.populace_estimate, row.unit)} diff --git a/frontend/components/populace/populace-staging-view.tsx b/frontend/components/populace/populace-staging-view.tsx index 33809c9..28424b8 100644 --- a/frontend/components/populace/populace-staging-view.tsx +++ b/frontend/components/populace/populace-staging-view.tsx @@ -4,6 +4,7 @@ import { useEffect, useMemo, useState } from "react"; import { EmptyState } from "@/components/shared/empty-state"; import { + fmtUnitValue, fmt, fmtCompact, fmtMoney, @@ -245,10 +246,10 @@ function ReformValidationTable({ - {fmtMoney(row.jct_score)} + {fmtUnitValue(row.jct_score, row.unit)} - {fmtMoney(row.populace_estimate)} + {fmtUnitValue(row.populace_estimate, row.unit)} "13.4%"); everything else is USD. */ +export function fmtUnitValue( + value: number | null | undefined, + unit: string | null | undefined, +): string { + if (unit === "percent") { + return value == null || !Number.isFinite(value) + ? "—" + : `${(value * 100).toFixed(1)}%`; + } + return fmtMoney(value); +} + export function fmtMoney(value: number | null | undefined): string { if (value == null || !Number.isFinite(value)) return "—"; const abs = Math.abs(value); diff --git a/frontend/lib/api/hooks/use-populace.ts b/frontend/lib/api/hooks/use-populace.ts index 3168c7d..dbfcc93 100644 --- a/frontend/lib/api/hooks/use-populace.ts +++ b/frontend/lib/api/hooks/use-populace.ts @@ -489,6 +489,8 @@ export interface ReformValidationRow { populace_estimate?: number | null; populace_window?: string | null; populace_annual?: Record | null; + /** "percent" for rate backtests (decimal fractions); USD otherwise. */ + unit?: "currency-USD" | "percent" | null; abs_error?: number | null; relative_error?: number | null; abs_relative_error?: number | null; diff --git a/frontend/lib/populace/reforms.ts b/frontend/lib/populace/reforms.ts index 8c8c4e4..b443f00 100644 --- a/frontend/lib/populace/reforms.ts +++ b/frontend/lib/populace/reforms.ts @@ -93,6 +93,10 @@ export interface ReformValidationRow { populace_estimate: number | null; populace_window: string | null; populace_annual: Record | null; + // Value unit: "currency-USD" (default) or "percent" for rate backtests + // (e.g. Census state SPM poverty rates, where the values are decimal + // fractions like 0.134 rather than dollars). + unit: "currency-USD" | "percent"; // Derived (relative to jct_score, i.e. the FY2027 benchmark where available). abs_error: number | null; // populace − jct (USD) relative_error: number | null; // (populace − jct) / |jct| @@ -157,6 +161,7 @@ function enrichReform(raw: JsonObject): ReformValidationRow { description: stringOrNull(raw.description), in_sample: raw.in_sample === true, period: numberOrNull(raw.period), + unit: raw.unit === "percent" ? "percent" : "currency-USD", jct_score: benchmark, jct_score_fy2026: jctFy2026, jct_score_type: stringOrNull(jct.score_type),