initial code

This commit is contained in:
2026-07-15 17:55:53 -06:00
parent 0952a7ffce
commit 05729fc6de
53 changed files with 12965 additions and 1 deletions
+352
View File
@@ -0,0 +1,352 @@
import { loadDashboardData } from "./data.js";
import {
aggregateOverview,
compactNumber,
percent,
renderAgeRisk,
renderCohortDotPlot,
renderCoverageHeatmap,
renderNoCalibration,
renderOutcomeTrend,
renderUtahCoverage,
} from "./charts.js";
const ROUTES = Object.freeze({
overview: "Overview",
reliability: "Reliability explorer",
estimator: "Next-test estimator",
methods: "Data & methods",
});
const UTAH_COUNTIES = [
"Beaver",
"Box Elder",
"Cache",
"Carbon",
"Daggett",
"Davis",
"Duchesne",
"Emery",
"Garfield",
"Grand",
"Iron",
"Juab",
"Kane",
"Millard",
"Morgan",
"Piute",
"Rich",
"Salt Lake",
"San Juan",
"Sanpete",
"Sevier",
"Summit",
"Tooele",
"Uintah",
"Utah",
"Wasatch",
"Washington",
"Wayne",
"Weber",
];
let dashboardData = null;
let visibleScorecards = [];
function byId(id) {
return document.getElementById(id);
}
function setText(id, value) {
const element = byId(id);
if (element) element.textContent = value;
}
function normalizeCounty(value) {
return String(value).trim().toLowerCase().replaceAll("_", " ").replace(/\s+/g, " ");
}
function displayCategory(value) {
return String(value)
.replaceAll("_", " ")
.toLowerCase()
.replace(/\b\w/g, (letter) => letter.toUpperCase());
}
function routeFromHash() {
const candidate = window.location.hash.replace(/^#/, "").toLowerCase();
return Object.hasOwn(ROUTES, candidate) ? candidate : "overview";
}
function showRoute({ announce = true, focus = false } = {}) {
const route = routeFromHash();
for (const view of document.querySelectorAll("[data-view]")) {
view.hidden = view.dataset.view !== route;
}
for (const link of document.querySelectorAll("[data-route]")) {
if (link.dataset.route === route) link.setAttribute("aria-current", "page");
else link.removeAttribute("aria-current");
}
document.title = `${ROUTES[route]} · Utah Vehicle Health`;
if (announce) setText("route-announcer", `${ROUTES[route]} view`);
if (focus) {
const heading = document.querySelector(`[data-view="${route}"] h1`);
if (heading) {
heading.setAttribute("tabindex", "-1");
heading.focus({ preventScroll: true });
heading.scrollIntoView({ behavior: "smooth", block: "start" });
}
}
}
function initializeRouting() {
showRoute({ announce: false, focus: false });
window.addEventListener("hashchange", () => showRoute({ focus: true }));
}
function setHeaderStatus(state, message) {
const status = byId("header-status");
if (!status) return;
status.dataset.state = state;
const text = status.querySelector("span:last-child");
if (text) text.textContent = message;
}
function setPreviewBanner(manifest) {
const preview = manifest.development_preview || !manifest.population_estimate_allowed;
const banner = byId("preview-banner");
banner.hidden = !preview;
if (!preview) return;
setText("preview-title", "Development preview");
setText(
"preview-copy",
"These suppressed sample aggregates are not population estimates and must not be used for individual decisions.",
);
}
function setUnavailable(error) {
const safeMessage =
error instanceof Error && error.message
? `${error.message} No estimates are being shown.`
: "The approved aggregate files could not be validated. No estimates are being shown.";
for (const state of document.querySelectorAll("[data-unavailable]")) {
state.hidden = false;
const copy = state.querySelector("[data-unavailable-message]");
if (copy) copy.textContent = safeMessage;
}
for (const region of document.querySelectorAll("[data-requires-data]")) {
region.hidden = true;
}
const banner = byId("preview-banner");
banner.hidden = false;
setText("preview-title", "Data unavailable");
setText("preview-copy", "This static shell could not validate every required public aggregate. No fallback or estimated values are displayed.");
setHeaderStatus("error", "Approved data unavailable");
}
function populateSelect(id, values, formatter = displayCategory) {
const select = byId(id);
if (!select) return;
for (const value of values) {
const option = document.createElement("option");
option.value = String(value);
option.textContent = formatter(value);
select.append(option);
}
}
function disableUnsupportedFilter(id, explanation) {
const select = byId(id);
if (!select) return;
select.disabled = true;
select.title = explanation;
select.options[0].textContent = explanation;
}
function renderCountyList(coveredCounties) {
const container = byId("county-coverage-list");
container.replaceChildren();
const normalized = new Set(coveredCounties.map(normalizeCounty));
for (const county of UTAH_COUNTIES) {
const chip = document.createElement("span");
chip.className = "county-chip";
chip.dataset.covered = String(normalized.has(normalizeCounty(county)));
chip.textContent = county;
container.append(chip);
}
}
function renderOverview(data) {
const rows = data.overview.rows;
const periods = aggregateOverview(rows);
const totalSupport = rows.reduce((sum, row) => sum + row.support_rounded, 0);
const weightedNonpass = rows.reduce(
(sum, row) => sum + row.support_rounded * row.nonpass_rate,
0,
);
const nonpassRate = totalSupport > 0 ? weightedNonpass / totalSupport : 0;
const counties = [...new Set(rows.map((row) => row.public_county))].sort();
const latest = periods[periods.length - 1];
setText("kpi-eligible", `≈${compactNumber(totalSupport)}`);
setText("kpi-pass", `≈${percent(1 - nonpassRate)}`);
setText("kpi-nonpass", `≈${percent(nonpassRate)}`);
setText("kpi-counties", String(counties.length));
setText("data-cutoff", latest ? `${latest.year} Q${latest.quarter}` : "Unavailable");
setText(
"model-version",
`Release ${data.manifest.release_id.slice(0, 8)} · ${data.manifest.model_versions.join(" / ")}`,
);
renderOutcomeTrend(byId("outcome-trend-chart"), rows);
setText(
"outcome-trend-note",
"The current approved bundle publishes binary non-pass rates only. Four-class outcome mix and blank rates are not inferred or displayed.",
);
renderAgeRisk(byId("age-risk-chart"), data.ageRisk.rows);
renderUtahCoverage(byId("utah-coverage-map"), counties);
renderCountyList(counties);
}
function scorecardLabel(row) {
return `${row.prior_make} ${row.prior_model}`.trim();
}
function sortedScorecards(rows, mode) {
const sorted = [...rows];
if (mode === "risk-desc") sorted.sort((a, b) => b.nonpass_rate - a.nonpass_rate);
else if (mode === "risk-asc") sorted.sort((a, b) => a.nonpass_rate - b.nonpass_rate);
else if (mode === "name") sorted.sort((a, b) => scorecardLabel(a).localeCompare(scorecardLabel(b)));
else sorted.sort((a, b) => b.support_rounded - a.support_rounded);
return sorted;
}
function renderScorecardCards(rows) {
const container = byId("cohort-cards");
container.replaceChildren();
for (const row of rows.slice(0, 18)) {
const article = document.createElement("article");
article.className = "cohort-card";
const heading = document.createElement("h3");
heading.textContent = scorecardLabel(row);
const meta = document.createElement("div");
meta.className = "cohort-card__meta";
meta.textContent = `${compactNumber(row.support_rounded)} rounded support`;
const bar = document.createElement("div");
bar.className = "risk-bar";
bar.setAttribute("aria-hidden", "true");
const fill = document.createElement("span");
fill.style.width = `${Math.min(100, row.nonpass_rate * 100)}%`;
bar.append(fill);
const value = document.createElement("div");
value.className = "cohort-card__value";
const strong = document.createElement("strong");
strong.textContent = percent(row.nonpass_rate);
const small = document.createElement("small");
small.textContent = "Observed non-pass";
value.append(strong, small);
article.append(heading, meta, bar, value);
container.append(article);
}
}
function updateExplorer() {
if (!dashboardData) return;
const query = byId("cohort-search").value.trim().toLowerCase();
const sortMode = byId("result-sort").value;
const matches = dashboardData.scorecards.rows.filter((row) =>
scorecardLabel(row).toLowerCase().includes(query),
);
visibleScorecards = sortedScorecards(matches, sortMode);
setText(
"result-summary",
`${visibleScorecards.length} supported cohort${visibleScorecards.length === 1 ? "" : "s"}; showing up to 18 cards and 12 chart rows.`,
);
byId("empty-results").hidden = visibleScorecards.length > 0;
renderCohortDotPlot(byId("cohort-dot-plot"), visibleScorecards);
renderScorecardCards(visibleScorecards);
}
function initializeExplorer(data) {
populateSelect("filter-county", data.filters.public_counties);
populateSelect("filter-age", data.filters.age_bands, (value) => String(value));
populateSelect(
"filter-period",
data.filters.periods.map((period) => period.year),
(value) => String(value),
);
// Current scorecards are make/model aggregates only. These planned controls
// remain visible but disabled so the UI never implies unsupported slicing.
disableUnsupportedFilter("filter-county", "Unavailable at current scorecard grain");
disableUnsupportedFilter("filter-age", "Unavailable at current scorecard grain");
disableUnsupportedFilter("filter-fuel", "Fuel not published in this bundle");
disableUnsupportedFilter("filter-program", "Program not published in this bundle");
disableUnsupportedFilter("filter-period", "Period not published in this scorecard");
const adjusted = document.querySelector('input[name="risk-view"][value="adjusted"]');
adjusted.disabled = true;
adjusted.parentElement.title = "Model-adjusted cohort scorecards are not published.";
byId("cohort-search").addEventListener("input", updateExplorer);
byId("result-sort").addEventListener("change", updateExplorer);
byId("reset-filters").addEventListener("click", () => {
byId("explorer-filters").reset();
byId("cohort-search").value = "";
byId("result-sort").value = "support";
updateExplorer();
});
updateExplorer();
}
function chooseDiagnostic(rows) {
const partitionRank = { calibrate: 3, tune: 2, train: 1 };
return [...rows].sort((left, right) => {
const preferredLeft = left.model === "logistic_platt" ? 1 : 0;
const preferredRight = right.model === "logistic_platt" ? 1 : 0;
return (
preferredRight - preferredLeft ||
(partitionRank[right.partition] ?? 0) - (partitionRank[left.partition] ?? 0)
);
})[0];
}
function renderMethods(data) {
renderCoverageHeatmap(byId("coverage-heatmap"), data.coverage.rows);
const diagnostic = chooseDiagnostic(data.diagnostics.rows);
setText(
"diagnostic-scope-label",
diagnostic
? `${diagnostic.partition === "calibrate" ? "Calibration cohort" : "Development diagnostics"} · ${diagnostic.model}`
: "Development diagnostics · model unavailable",
);
setText("diagnostic-ap", diagnostic ? diagnostic.average_precision.toFixed(3) : "—");
setText("diagnostic-brier", diagnostic ? diagnostic.brier.toFixed(3) : "—");
setText("diagnostic-n", "Not published");
renderNoCalibration(byId("calibration-chart"));
}
function renderDashboard(data) {
dashboardData = data;
setPreviewBanner(data.manifest);
renderOverview(data);
initializeExplorer(data);
renderMethods(data);
setHeaderStatus(
"ready",
data.manifest.development_preview ? "Validated development aggregates" : "Validated public aggregates",
);
}
async function initialize() {
initializeRouting();
try {
const data = await loadDashboardData();
renderDashboard(data);
} catch (error) {
console.error("Dashboard data contract rejected the public assets.", error);
setUnavailable(error);
}
}
initialize();
export { displayCategory, normalizeCounty, routeFromHash };
+281
View File
@@ -0,0 +1,281 @@
const SVG_NS = "http://www.w3.org/2000/svg";
function escapeText(value) {
return String(value)
.replaceAll("&", "&")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}
function percent(value, digits = 1) {
return new Intl.NumberFormat("en-US", {
style: "percent",
minimumFractionDigits: digits,
maximumFractionDigits: digits,
}).format(value);
}
function compactNumber(value) {
return new Intl.NumberFormat("en-US", {
notation: value >= 10_000 ? "compact" : "standard",
maximumFractionDigits: 1,
}).format(value);
}
function emptyChart(container, message) {
container.innerHTML = `<div class="chart-empty"><p>${escapeText(message)}</p></div>`;
container.setAttribute("aria-label", message);
}
function aggregateOverview(rows) {
const groups = new Map();
for (const row of rows) {
const key = `${row.year}-Q${row.quarter}`;
const current = groups.get(key) ?? {
label: `${row.year} Q${row.quarter}`,
year: row.year,
quarter: row.quarter,
support: 0,
weightedRisk: 0,
};
current.support += row.support_rounded;
current.weightedRisk += row.support_rounded * row.nonpass_rate;
groups.set(key, current);
}
return [...groups.values()]
.map((group) => ({
...group,
risk: group.support > 0 ? group.weightedRisk / group.support : 0,
}))
.sort((left, right) => left.year - right.year || left.quarter - right.quarter);
}
export function renderOutcomeTrend(container, rows) {
const points = aggregateOverview(rows);
if (points.length < 2) {
emptyChart(container, "Not enough approved periods to draw a trend.");
return;
}
const width = 920;
const height = 330;
const margin = { top: 24, right: 22, bottom: 54, left: 58 };
const plotWidth = width - margin.left - margin.right;
const plotHeight = height - margin.top - margin.bottom;
const maxRisk = Math.max(0.05, ...points.map((point) => point.risk));
const yMax = Math.min(1, Math.ceil(maxRisk * 20) / 20);
const x = (index) => margin.left + (index / (points.length - 1)) * plotWidth;
const y = (value) => margin.top + plotHeight - (value / yMax) * plotHeight;
const path = points
.map((point, index) => `${index === 0 ? "M" : "L"}${x(index).toFixed(1)},${y(point.risk).toFixed(1)}`)
.join(" ");
const yTicks = Array.from({ length: 5 }, (_, index) => (index / 4) * yMax);
const tickEvery = Math.max(1, Math.ceil(points.length / 8));
const labelled = points.filter(
(_point, index) => index % tickEvery === 0 || index === points.length - 1,
);
const description = points
.map((point) => `${point.label}: ${percent(point.risk)}`)
.join("; ");
container.innerHTML = `
<svg viewBox="0 0 ${width} ${height}" aria-hidden="true" focusable="false">
${yTicks
.map(
(tick) => `
<line class="grid-line" x1="${margin.left}" x2="${width - margin.right}" y1="${y(tick)}" y2="${y(tick)}"></line>
<text class="axis-label" x="${margin.left - 10}" y="${y(tick) + 4}" text-anchor="end">${percent(tick, 0)}</text>`,
)
.join("")}
<line class="axis-line" x1="${margin.left}" x2="${width - margin.right}" y1="${height - margin.bottom}" y2="${height - margin.bottom}"></line>
<path class="chart-line" d="${path}"></path>
${points
.map(
(point, index) => `
<circle class="chart-dot" cx="${x(index)}" cy="${y(point.risk)}" r="4">
<title>${escapeText(point.label)}: ${percent(point.risk)} non-pass (${compactNumber(point.support)} rounded support)</title>
</circle>`,
)
.join("")}
${labelled
.map((point) => {
const index = points.indexOf(point);
return `<text class="axis-label" x="${x(index)}" y="${height - 24}" text-anchor="middle">${escapeText(point.label.replace(" ", "\u00a0"))}</text>`;
})
.join("")}
<text class="axis-label" transform="translate(16 ${height / 2}) rotate(-90)" text-anchor="middle">Non-pass rate</text>
</svg>`;
container.setAttribute(
"aria-label",
`Quarterly aggregate non-pass trend. ${description}`,
);
}
export function renderAgeRisk(container, rows) {
if (!rows.length) {
emptyChart(container, "No supported vehicle-age bands are available.");
return;
}
const points = [...rows];
const width = 600;
const height = 300;
const margin = { top: 24, right: 20, bottom: 58, left: 52 };
const plotWidth = width - margin.left - margin.right;
const plotHeight = height - margin.top - margin.bottom;
const maxRisk = Math.max(0.05, ...points.map((point) => point.nonpass_rate));
const yMax = Math.min(1, Math.ceil(maxRisk * 20) / 20);
const denominator = Math.max(1, points.length - 1);
const x = (index) => margin.left + (index / denominator) * plotWidth;
const y = (value) => margin.top + plotHeight - (value / yMax) * plotHeight;
const path = points
.map((point, index) => `${index === 0 ? "M" : "L"}${x(index)},${y(point.nonpass_rate)}`)
.join(" ");
container.innerHTML = `
<svg viewBox="0 0 ${width} ${height}" aria-hidden="true" focusable="false">
${[0, 0.25, 0.5, 0.75, 1]
.map((ratio) => {
const tick = ratio * yMax;
return `<line class="grid-line" x1="${margin.left}" x2="${width - margin.right}" y1="${y(tick)}" y2="${y(tick)}"></line>
<text class="axis-label" x="${margin.left - 8}" y="${y(tick) + 4}" text-anchor="end">${percent(tick, 0)}</text>`;
})
.join("")}
<path class="chart-line" d="${path}"></path>
${points
.map(
(point, index) => `
<circle class="chart-dot" cx="${x(index)}" cy="${y(point.nonpass_rate)}" r="5">
<title>${escapeText(point.age_band)}: ${percent(point.nonpass_rate)} (${compactNumber(point.support_rounded)} rounded support)</title>
</circle>
<text class="axis-label" x="${x(index)}" y="${height - 25}" text-anchor="middle">${escapeText(point.age_band)}</text>`,
)
.join("")}
</svg>`;
container.setAttribute(
"aria-label",
`Non-pass risk by vehicle-age band. ${points
.map((point) => `${point.age_band}: ${percent(point.nonpass_rate)}`)
.join("; ")}`,
);
}
export function renderCohortDotPlot(container, rows) {
const points = rows.slice(0, 12);
if (!points.length) {
emptyChart(container, "No supported cohorts match the current filters.");
return;
}
const width = 820;
const rowHeight = 34;
const margin = { top: 24, right: 60, bottom: 42, left: 220 };
const height = margin.top + margin.bottom + points.length * rowHeight;
const plotWidth = width - margin.left - margin.right;
const maxRisk = Math.max(0.05, ...points.map((point) => point.nonpass_rate));
const xMax = Math.min(1, Math.ceil(maxRisk * 20) / 20);
const x = (value) => margin.left + (value / xMax) * plotWidth;
container.innerHTML = `
<svg viewBox="0 0 ${width} ${height}" aria-hidden="true" focusable="false">
${[0, 0.25, 0.5, 0.75, 1]
.map((ratio) => {
const value = ratio * xMax;
return `<line class="grid-line" x1="${x(value)}" x2="${x(value)}" y1="${margin.top - 8}" y2="${height - margin.bottom + 5}"></line>
<text class="axis-label" x="${x(value)}" y="${height - 15}" text-anchor="middle">${percent(value, 0)}</text>`;
})
.join("")}
${points
.map((point, index) => {
const y = margin.top + index * rowHeight + rowHeight / 2;
const label = `${point.prior_make} ${point.prior_model}`;
return `
<text class="axis-label" x="${margin.left - 12}" y="${y + 4}" text-anchor="end">${escapeText(label)}</text>
<line x1="${margin.left}" x2="${x(point.nonpass_rate)}" y1="${y}" y2="${y}" stroke="var(--red-100)" stroke-width="8" stroke-linecap="round"></line>
<circle class="chart-dot" cx="${x(point.nonpass_rate)}" cy="${y}" r="5"><title>${escapeText(label)}: ${percent(point.nonpass_rate)}, ${compactNumber(point.support_rounded)} rounded support</title></circle>
<text class="axis-label" x="${x(point.nonpass_rate) + 10}" y="${y + 4}">${percent(point.nonpass_rate)}</text>`;
})
.join("")}
</svg>`;
container.setAttribute(
"aria-label",
`Ranked supported cohort non-pass risk. ${points
.map((point) => `${point.prior_make} ${point.prior_model}: ${percent(point.nonpass_rate)}`)
.join("; ")}`,
);
}
const COUNTY_POINTS = {
cache: [154, 40],
weber: [137, 86],
davis: [126, 108],
salt_lake: [133, 133],
"salt lake": [133, 133],
utah: [140, 170],
};
export function renderUtahCoverage(container, coveredCounties) {
const normalized = new Set(coveredCounties.map((county) => county.toLowerCase()));
const points = [...normalized]
.map((county) => ({ county, coordinates: COUNTY_POINTS[county] }))
.filter((item) => item.coordinates);
container.innerHTML = `
<svg viewBox="0 0 270 300" aria-hidden="true" focusable="false">
<path d="M79 18h101v54l18 18v183H52V116l27-27V18Z" fill="var(--gray-100)" stroke="var(--gray-300)" stroke-width="3"></path>
<path d="M80 20h98v53l17 18v179H55V117l25-27V20Z" fill="none" stroke="var(--sand-200)" stroke-width="1.5" stroke-dasharray="4 5"></path>
${points
.map(
({ county, coordinates }) => `
<circle cx="${coordinates[0]}" cy="${coordinates[1]}" r="8" fill="var(--teal-700)" stroke="var(--paper)" stroke-width="3"><title>${escapeText(county.replace("_", " "))} feed available</title></circle>`,
)
.join("")}
<text x="135" y="288" text-anchor="middle" class="axis-label">Participating feeds highlighted</text>
</svg>`;
container.setAttribute(
"aria-label",
coveredCounties.length
? `Utah feed coverage includes ${coveredCounties.join(", ")}. Other counties are unavailable.`
: "No county feed coverage is available.",
);
}
export function renderCoverageHeatmap(container, rows) {
if (!rows.length) {
emptyChart(container, "No source-era coverage rows are available.");
return;
}
const years = [...new Set(rows.map((row) => row.year))].sort((a, b) => a - b);
const eras = [...new Set(rows.map((row) => row.source_era))].sort();
const lookup = new Map(rows.map((row) => [`${row.source_era}-${row.year}`, row]));
const maxSupport = Math.max(1, ...rows.map((row) => row.support_rounded));
const columns = `90px repeat(${years.length}, minmax(34px, 1fr))`;
container.innerHTML = `
<div class="heatmap-grid" style="grid-template-columns:${columns}">
<span></span>${years.map((year) => `<span class="heatmap-label">${year}</span>`).join("")}
${eras
.map(
(era) => `<span class="heatmap-label">${escapeText(era)}</span>${years
.map((year) => {
const row = lookup.get(`${era}-${year}`);
if (!row) return `<span class="heatmap-cell" title="${escapeText(era)} ${year}: unavailable"></span>`;
const alpha = 0.15 + 0.75 * Math.sqrt(row.support_rounded / maxSupport);
return `<span class="heatmap-cell" style="background:rgb(25 116 119 / ${alpha.toFixed(2)})" title="${escapeText(era)} ${year}: ${compactNumber(row.support_rounded)} rounded support; ${percent(row.labeled_rate)} labeled"></span>`;
})
.join("")}`,
)
.join("")}
</div>`;
container.setAttribute(
"aria-label",
`Source-era coverage from ${years[0]} through ${years[years.length - 1]} for ${eras.join(", ")}.`,
);
}
export function renderNoCalibration(container) {
emptyChart(
container,
"Calibration-bin data is not published in this development bundle. No calibration curve is shown.",
);
}
export { aggregateOverview, compactNumber, percent };
+574
View File
@@ -0,0 +1,574 @@
const SCHEMA_VERSION = "dashboard_data_v1";
const APPROVED_PARTITIONS = Object.freeze(["train", "tune", "calibrate"]);
const ENVELOPE_KEYS = Object.freeze([
"development_preview",
"population_estimate_allowed",
"schema_version",
]);
export const REQUIRED_ASSETS = Object.freeze({
manifest: "data_manifest.json",
overview: "overview_period_county.json",
ageRisk: "age_risk_curve.json",
scorecards: "cohort_scorecard.json",
diagnostics: "model_diagnostics.json",
coverage: "coverage_quality.json",
filters: "filter_catalog.json",
shaManifest: "sha256_manifest.json",
});
const SENSITIVE_KEY_PARTS = new Set([
"address",
"certificate",
"email",
"internal",
"ip",
"owner",
"pid",
"plate",
"raw",
"session",
"station",
"token",
"user",
"vin",
"zip",
]);
const ASSET_VALIDATORS = {
overview: (value) => validateRows(value, validateOverviewRow, "overview_period_county"),
ageRisk: (value) => validateRows(value, validateAgeRiskRow, "age_risk_curve"),
scorecards: (value) => validateRows(value, validateScorecardRow, "cohort_scorecard"),
diagnostics: (value) => validateRows(value, validateDiagnosticRow, "model_diagnostics"),
coverage: (value) => validateRows(value, validateCoverageRow, "coverage_quality"),
filters: validateFilterCatalog,
shaManifest: validateShaManifest,
};
export class DataContractError extends Error {
constructor(message) {
super(message);
this.name = "DataContractError";
}
}
function isPlainObject(value) {
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
}
function requirePlainObject(value, label) {
if (!isPlainObject(value)) {
throw new DataContractError(`${label} must be a JSON object.`);
}
}
function requireExactKeys(value, expectedKeys, label) {
const observed = Object.keys(value).sort();
const expected = [...expectedKeys].sort();
if (JSON.stringify(observed) !== JSON.stringify(expected)) {
throw new DataContractError(`${label} does not match the approved field schema.`);
}
}
function requireExactStringSet(value, expectedValues, label) {
validateStringArray(value, label);
const observed = [...value].sort();
const expected = [...expectedValues].sort();
if (JSON.stringify(observed) !== JSON.stringify(expected)) {
throw new DataContractError(`${label} does not match the approved values.`);
}
}
function requireBoolean(value, label) {
if (typeof value !== "boolean") {
throw new DataContractError(`${label} must be true or false.`);
}
}
function requireString(value, label) {
if (typeof value !== "string" || value.trim() === "") {
throw new DataContractError(`${label} must be a non-empty string.`);
}
}
function requireSafeModelVersion(value, label) {
requireString(value, label);
if (!/^[A-Za-z0-9._-]{1,64}$/.test(value)) {
throw new DataContractError(`${label} is not an approved model version.`);
}
}
function requireNumber(value, label, { min = -Infinity, max = Infinity } = {}) {
if (typeof value !== "number" || !Number.isFinite(value) || value < min || value > max) {
throw new DataContractError(`${label} must be a finite number in the approved range.`);
}
}
function requireInteger(value, label, { min = -Infinity, max = Infinity } = {}) {
requireNumber(value, label, { min, max });
if (!Number.isInteger(value)) {
throw new DataContractError(`${label} must be an integer.`);
}
}
function validateEnvelope(value, label) {
requirePlainObject(value, label);
if (value.schema_version !== SCHEMA_VERSION) {
throw new DataContractError(`${label} uses an unsupported schema version.`);
}
requireBoolean(value.development_preview, `${label}.development_preview`);
requireBoolean(
value.population_estimate_allowed,
`${label}.population_estimate_allowed`,
);
rejectSensitiveKeys(value, label);
}
function validateRows(value, rowValidator, label) {
validateEnvelope(value, label);
requireExactKeys(value, [...ENVELOPE_KEYS, "rows"], label);
if (!Array.isArray(value.rows)) {
throw new DataContractError(`${label}.rows must be an array.`);
}
value.rows.forEach((row, index) => {
requirePlainObject(row, `${label}.rows[${index}]`);
rowValidator(row, `${label}.rows[${index}]`);
});
return value;
}
function validateOverviewRow(row, label) {
requireExactKeys(
row,
["year", "quarter", "public_county", "support_rounded", "nonpass_rate"],
label,
);
requireInteger(row.year, `${label}.year`, { min: 2010, max: 2100 });
requireInteger(row.quarter, `${label}.quarter`, { min: 1, max: 4 });
requireString(row.public_county, `${label}.public_county`);
requireInteger(row.support_rounded, `${label}.support_rounded`, { min: 0 });
requireNumber(row.nonpass_rate, `${label}.nonpass_rate`, { min: 0, max: 1 });
}
function validateAgeRiskRow(row, label) {
requireExactKeys(row, ["age_band", "support_rounded", "nonpass_rate"], label);
requireString(row.age_band, `${label}.age_band`);
requireInteger(row.support_rounded, `${label}.support_rounded`, { min: 0 });
requireNumber(row.nonpass_rate, `${label}.nonpass_rate`, { min: 0, max: 1 });
}
function validateScorecardRow(row, label) {
requireExactKeys(
row,
["prior_make", "prior_model", "support_rounded", "nonpass_rate"],
label,
);
requireString(row.prior_make, `${label}.prior_make`);
requireString(row.prior_model, `${label}.prior_model`);
requireInteger(row.support_rounded, `${label}.support_rounded`, { min: 0 });
requireNumber(row.nonpass_rate, `${label}.nonpass_rate`, { min: 0, max: 1 });
}
function validateDiagnosticRow(row, label) {
requireExactKeys(
row,
[
"model",
"partition",
"average_precision",
"brier",
"log_loss",
"roc_auc",
"top_10_capture",
],
label,
);
requireString(row.model, `${label}.model`);
requireString(row.partition, `${label}.partition`);
if (!APPROVED_PARTITIONS.includes(row.partition)) {
throw new DataContractError(`${label}.partition is not approved for this public schema.`);
}
for (const metric of [
"average_precision",
"brier",
"log_loss",
"roc_auc",
"top_10_capture",
]) {
requireNumber(row[metric], `${label}.${metric}`, { min: 0 });
}
if (row.average_precision > 1 || row.brier > 1 || row.roc_auc > 1 || row.top_10_capture > 1) {
throw new DataContractError(`${label} contains a probability metric above 1.`);
}
}
function validateCoverageRow(row, label) {
requireExactKeys(
row,
[
"year",
"source_era",
"support_rounded",
"labeled_rate",
"overall_result_share",
"utah_obd_proxy_share",
],
label,
);
requireInteger(row.year, `${label}.year`, { min: 2010, max: 2100 });
requireString(row.source_era, `${label}.source_era`);
requireInteger(row.support_rounded, `${label}.support_rounded`, { min: 0 });
for (const metric of ["labeled_rate", "overall_result_share", "utah_obd_proxy_share"]) {
requireNumber(row[metric], `${label}.${metric}`, { min: 0, max: 1 });
}
}
function validateStringArray(value, label) {
if (!Array.isArray(value)) throw new DataContractError(`${label} must be an array.`);
value.forEach((item, index) => requireString(item, `${label}[${index}]`));
}
function validateFilterCatalog(value) {
const label = "filter_catalog";
validateEnvelope(value, label);
requireExactKeys(
value,
[
...ENVELOPE_KEYS,
"public_counties",
"age_bands",
"prior_make_models",
"periods",
"models",
"partitions",
],
label,
);
validateStringArray(value.public_counties, `${label}.public_counties`);
validateStringArray(value.age_bands, `${label}.age_bands`);
validateStringArray(value.models, `${label}.models`);
requireExactStringSet(value.partitions, APPROVED_PARTITIONS, `${label}.partitions`);
if (!Array.isArray(value.prior_make_models)) {
throw new DataContractError(`${label}.prior_make_models must be an array.`);
}
value.prior_make_models.forEach((row, index) => {
requirePlainObject(row, `${label}.prior_make_models[${index}]`);
requireExactKeys(
row,
["prior_make", "prior_model"],
`${label}.prior_make_models[${index}]`,
);
requireString(row.prior_make, `${label}.prior_make_models[${index}].prior_make`);
requireString(row.prior_model, `${label}.prior_make_models[${index}].prior_model`);
});
if (!Array.isArray(value.periods)) {
throw new DataContractError(`${label}.periods must be an array.`);
}
value.periods.forEach((period, index) => {
requirePlainObject(period, `${label}.periods[${index}]`);
requireExactKeys(period, ["year", "quarters"], `${label}.periods[${index}]`);
requireInteger(period.year, `${label}.periods[${index}].year`, {
min: 2010,
max: 2100,
});
if (!Array.isArray(period.quarters) || period.quarters.length === 0) {
throw new DataContractError(`${label}.periods[${index}].quarters must be non-empty.`);
}
period.quarters.forEach((quarter, quarterIndex) =>
requireInteger(quarter, `${label}.periods[${index}].quarters[${quarterIndex}]`, {
min: 1,
max: 4,
}),
);
});
return value;
}
function validateShaManifest(value) {
const label = "sha256_manifest";
validateEnvelope(value, label);
requireExactKeys(value, [...ENVELOPE_KEYS, "files"], label);
if (!Array.isArray(value.files)) {
throw new DataContractError(`${label}.files must be an array.`);
}
const expectedNames = Object.entries(REQUIRED_ASSETS)
.filter(([name]) => name !== "shaManifest")
.map(([, filename]) => filename)
.sort();
const observedNames = [];
for (const [index, file] of value.files.entries()) {
requirePlainObject(file, `${label}.files[${index}]`);
requireExactKeys(file, ["name", "sha256"], `${label}.files[${index}]`);
requireString(file.name, `${label}.files[${index}].name`);
requireString(file.sha256, `${label}.files[${index}].sha256`);
if (!/^[0-9a-f]{64}$/.test(file.sha256)) {
throw new DataContractError(`${label}.files[${index}].sha256 is invalid.`);
}
observedNames.push(file.name);
}
observedNames.sort();
if (JSON.stringify(observedNames) !== JSON.stringify(expectedNames)) {
throw new DataContractError(`${label} does not cover the exact approved asset set.`);
}
return value;
}
function rejectSensitiveKeys(value, label, seen = new WeakSet()) {
if (value === null || typeof value !== "object") return;
if (seen.has(value)) return;
seen.add(value);
if (Array.isArray(value)) {
value.forEach((item, index) => rejectSensitiveKeys(item, `${label}[${index}]`, seen));
return;
}
for (const [key, child] of Object.entries(value)) {
const normalizedParts = key.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
if (normalizedParts.some((part) => SENSITIVE_KEY_PARTS.has(part))) {
throw new DataContractError(`${label} contains a field outside the public data contract.`);
}
rejectSensitiveKeys(child, `${label}.${key}`, seen);
}
}
export function validateAssetSet(rawAssets) {
requirePlainObject(rawAssets, "asset set");
requireExactKeys(rawAssets, Object.keys(REQUIRED_ASSETS), "asset set");
const manifest = rawAssets.manifest;
validateEnvelope(manifest, "data_manifest");
requireExactKeys(
manifest,
[
...ENVELOPE_KEYS,
"assets",
"data_scope",
"definitions",
"model_versions",
"release_id",
],
"data_manifest",
);
if (Object.hasOwn(manifest, "rows")) {
throw new DataContractError("data_manifest must not contain row data.");
}
requirePlainObject(manifest.data_scope, "data_manifest.data_scope");
requirePlainObject(manifest.definitions, "data_manifest.definitions");
requireString(manifest.release_id, "data_manifest.release_id");
if (!/^[0-9a-f]{64}$/.test(manifest.release_id)) {
throw new DataContractError("data_manifest.release_id is invalid.");
}
if (!Array.isArray(manifest.model_versions) || manifest.model_versions.length === 0) {
throw new DataContractError("data_manifest.model_versions must be a non-empty array.");
}
manifest.model_versions.forEach((version, index) =>
requireSafeModelVersion(version, `data_manifest.model_versions[${index}]`),
);
if (
JSON.stringify(manifest.model_versions) !==
JSON.stringify([...new Set(manifest.model_versions)].sort())
) {
throw new DataContractError("data_manifest.model_versions must be sorted and unique.");
}
requireExactKeys(
manifest.data_scope,
["first_year", "last_year", "model_names", "partitions"],
"data_manifest.data_scope",
);
requireInteger(manifest.data_scope.first_year, "data_manifest.data_scope.first_year", {
min: 2010,
max: 2100,
});
requireInteger(manifest.data_scope.last_year, "data_manifest.data_scope.last_year", {
min: manifest.data_scope.first_year,
max: 2100,
});
validateStringArray(manifest.data_scope.model_names, "data_manifest.data_scope.model_names");
requireExactStringSet(
manifest.data_scope.partitions,
APPROVED_PARTITIONS,
"data_manifest.data_scope.partitions",
);
requireExactKeys(
manifest.definitions,
[
"episode_gap_days",
"locked_test_metrics_published",
"support_rounding",
"suppression_min_nonpass",
"suppression_min_pass",
"suppression_min_support",
"suppression_min_distinct_vehicles",
"suppression_min_distinct_pass_vehicles",
"suppression_min_distinct_nonpass_vehicles",
"target",
],
"data_manifest.definitions",
);
requireInteger(manifest.definitions.episode_gap_days, "data_manifest.definitions.episode_gap_days", {
min: 1,
});
requireBoolean(
manifest.definitions.locked_test_metrics_published,
"data_manifest.definitions.locked_test_metrics_published",
);
if (manifest.definitions.locked_test_metrics_published !== false) {
throw new DataContractError("Locked-test metrics are not approved for this public preview.");
}
for (const key of [
"support_rounding",
"suppression_min_nonpass",
"suppression_min_pass",
"suppression_min_support",
"suppression_min_distinct_vehicles",
"suppression_min_distinct_pass_vehicles",
"suppression_min_distinct_nonpass_vehicles",
]) {
requireInteger(manifest.definitions[key], `data_manifest.definitions.${key}`, { min: 1 });
}
requireString(manifest.definitions.target, "data_manifest.definitions.target");
if (!Array.isArray(manifest.assets)) {
throw new DataContractError("data_manifest.assets must be an array.");
}
validateStringArray(manifest.assets, "data_manifest.assets");
const expectedManifestAssets = Object.entries(REQUIRED_ASSETS)
.filter(([name]) => !["manifest", "shaManifest"].includes(name))
.map(([, filename]) => filename)
.sort();
const declaredAssets = [...manifest.assets].sort();
if (JSON.stringify(declaredAssets) !== JSON.stringify(expectedManifestAssets)) {
throw new DataContractError("data_manifest.assets does not match the approved asset set.");
}
const validated = { manifest };
for (const [name, validator] of Object.entries(ASSET_VALIDATORS)) {
if (!Object.hasOwn(rawAssets, name)) {
throw new DataContractError(`A required approved aggregate is missing: ${name}.`);
}
validated[name] = validator(rawAssets[name]);
if (
validated[name].development_preview !== manifest.development_preview ||
validated[name].population_estimate_allowed !== manifest.population_estimate_allowed
) {
throw new DataContractError(`${name} publication flags disagree with data_manifest.`);
}
}
requireExactStringSet(
validated.filters.models,
manifest.data_scope.model_names,
"filter_catalog.models",
);
for (const [index, row] of validated.diagnostics.rows.entries()) {
if (!validated.filters.models.includes(row.model)) {
throw new DataContractError(`model_diagnostics.rows[${index}].model is not cataloged.`);
}
}
const minimumSupport = manifest.definitions.suppression_min_support;
const supportRounding = manifest.definitions.support_rounding;
for (const name of ["overview", "ageRisk", "scorecards", "coverage"]) {
for (const [index, row] of validated[name].rows.entries()) {
if (
row.support_rounded < minimumSupport ||
row.support_rounded % supportRounding !== 0
) {
throw new DataContractError(
`${name}.rows[${index}].support_rounded violates the publication thresholds.`,
);
}
}
}
return Object.freeze(validated);
}
async function fetchBytes(path, fetchImplementation) {
let response;
try {
response = await fetchImplementation(path, {
cache: "no-store",
credentials: "same-origin",
});
} catch {
throw new DataContractError("Approved dashboard assets could not be reached.");
}
if (!response.ok) {
throw new DataContractError("One or more approved dashboard assets are unavailable.");
}
try {
return await response.arrayBuffer();
} catch {
throw new DataContractError("An approved dashboard asset could not be read safely.");
}
}
function parseJsonBytes(bytes, label) {
if (typeof TextDecoder !== "function") {
throw new DataContractError("This browser cannot decode dashboard assets safely.");
}
try {
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
} catch {
throw new DataContractError(`${label} is not valid UTF-8 JSON.`);
}
}
async function sha256Hex(bytes, cryptoImplementation) {
if (!cryptoImplementation?.subtle || typeof cryptoImplementation.subtle.digest !== "function") {
throw new DataContractError("This browser cannot verify dashboard asset integrity.");
}
let digest;
try {
digest = await cryptoImplementation.subtle.digest("SHA-256", bytes);
} catch {
throw new DataContractError("Dashboard asset integrity verification failed.");
}
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
}
export async function loadDashboardData({
basePath = "./public/data/",
fetchImplementation = globalThis.fetch,
cryptoImplementation = globalThis.crypto,
} = {}) {
if (typeof fetchImplementation !== "function") {
throw new DataContractError("This browser cannot load dashboard assets safely.");
}
const baseUrl = new URL(basePath, globalThis.location?.href ?? "http://local/");
const shaBytes = await fetchBytes(
new URL(REQUIRED_ASSETS.shaManifest, baseUrl),
fetchImplementation,
);
const shaManifest = validateShaManifest(
parseJsonBytes(shaBytes, "The checksum manifest"),
);
const expectedDigests = new Map(
shaManifest.files.map((entry) => [entry.name, entry.sha256]),
);
const assetEntries = Object.entries(REQUIRED_ASSETS).filter(
([name]) => name !== "shaManifest",
);
const rawEntries = await Promise.all(
assetEntries.map(async ([name, filename]) => [
name,
filename,
await fetchBytes(new URL(filename, baseUrl), fetchImplementation),
]),
);
await Promise.all(
rawEntries.map(async ([, filename, bytes]) => {
const observed = await sha256Hex(bytes, cryptoImplementation);
if (observed !== expectedDigests.get(filename)) {
throw new DataContractError(`Integrity verification failed for ${filename}.`);
}
}),
);
const parsedEntries = rawEntries.map(([name, filename, bytes]) => [
name,
parseJsonBytes(bytes, filename),
]);
parsedEntries.push(["shaManifest", shaManifest]);
return validateAssetSet(Object.fromEntries(parsedEntries));
}
export { SCHEMA_VERSION };