const SVG_NS = "http://www.w3.org/2000/svg";
function escapeText(value) {
return String(value)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
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 = `
`;
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 = `
`;
container.setAttribute(
"aria-label",
`Development-sample quarterly aggregate non-pass rates, not population estimates. ${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 = `
`;
container.setAttribute(
"aria-label",
`Observed development-sample non-pass rates by vehicle-age band, not population estimates. ${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 = `
`;
container.setAttribute(
"aria-label",
`Supported development-sample cohort associations, not reliability rankings or population estimates. ${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 = `
`;
container.setAttribute(
"aria-label",
coveredCounties.length
? `Development-sample feed availability includes ${coveredCounties.join(", ")}. This is not an outcome ranking; 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 = `
${years.map((year) => `${year}`).join("")}
${eras
.map(
(era) => `${escapeText(era)}${years
.map((year) => {
const row = lookup.get(`${era}-${year}`);
if (!row) return ``;
const alpha = 0.15 + 0.75 * Math.sqrt(row.support_rounded / maxSupport);
return ``;
})
.join("")}`,
)
.join("")}
`;
container.setAttribute(
"aria-label",
`Development-sample source-era coverage from ${years[0]} through ${years[years.length - 1]} for ${eras.join(", ")}.`,
);
}
export { aggregateOverview, compactNumber, percent };