import type { ExpansionVelocity } from "@dci/core"; import { StepLines } from "@/components/intelligence/client-charts"; import { HonestEmpty } from "@/components/intelligence/shared"; import { TBody, THead, Table, Td, Th, Tr } from "@/components/ui/table"; import { fmtInt, fmtMw } from "@/lib/format"; const WINDOW_LABEL: Record = { "12m": "Last 12 months", "3y": "Last 3 years", "5y": "Last 5 years" }; /** * Expansion velocity: three windows (12 m / 3 y / 5 y) as a compact table, then the cumulative * countries / metros / facilities step chart. Every point carries its basis: "opened" = published * opening date; "first_seen" = when the index first observed the facility (NOT when it opened). */ export function ExpansionVelocityBlock({ velocity }: { velocity: ExpansionVelocity }) { const windows = velocity.windows; const series = velocity.countriesOverTime; const bases = new Set(series.map((p) => p.basis)); const firstSeenYears = series.filter((p) => p.basis === "first_seen").map((p) => p.year); const fmtN = (n: number | null | undefined) => (n === null || n === undefined ? — : fmtInt(n)); const fmtM = (n: number | null | undefined) => (n === null || n === undefined ? — : fmtMw(n)); return (
{windows.length ? ( {windows.map((w) => ( ))}
Window New facilities New projects New countries New markets Opened MW Announced MW
{WINDOW_LABEL[w.label]} {fmtN(w.newFacilities)} {fmtN(w.newProjects)} {fmtN(w.newCountries)} {fmtN(w.newMetros)} {fmtM(w.openedMw)} {fmtM(w.announcedMw)}
) : ( )}

“New facilities” counts facilities whose opening date — or, when none is published, the date the index first observed them — falls in the window. MW columns show published figures only.

Cumulative footprint by year
{series.length >= 2 ? ( <> ({ x: p.year, y: p.countries })) }, { name: "Markets", color: "var(--st-planned)", data: series.map((p) => ({ x: p.year, y: p.metros })) }, { name: "Facilities", color: "var(--accent)", dashed: true, data: series.map((p) => ({ x: p.year, y: p.facilities })) }, ]} ariaLabel="Cumulative countries, markets and facilities by year" />
    {series.map((p) => (
  • {p.year} · {p.basis === "opened" ? "opened" : "first seen"}
  • ))}
) : ( )}

Basis per point: opened = published opening date; first seen = the year the index first observed the facility, which is not when it opened. {bases.has("first_seen") && firstSeenYears.length > 0 && <> The {firstSeenYears.length === 1 ? "point for" : "points for"} {firstSeenYears.join(", ")} {firstSeenYears.length === 1 ? "is" : "are"} first-seen values and overstate recent growth.}

); }