spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1import type { AiEvidence, AuthorityTier, ClaimScope, ClaimStatus, ConfidenceLevel, DensityView, EventType, FacilityStatus, FacilityType, GeoPrecision, MapLayer, OperatorKind, ProjectStage, Significance, SourceKind } from "@dci/core";23/** Semantic status groups → one colour token each (shared by badges, map, charts). */4export type StatusTone = "operational" | "construction" | "planned" | "permitting" | "delayed" | "closed" | "rumored" | "unknown";56export const STATUS_TONE: Record<FacilityStatus, StatusTone> = {7 operational: "operational",8 partially_operational: "operational",9 expansion: "operational",10 under_construction: "construction",11 announced: "planned",12 proposed: "planned",13 permitting: "permitting",14 approved: "permitting",15 delayed: "delayed",16 cancelled: "closed",17 closed: "closed",18 rumored: "rumored",19 unknown: "unknown",20};2122export const STATUS_LABEL: Record<FacilityStatus, string> = {23 rumored: "Rumored",24 proposed: "Proposed",25 announced: "Announced",26 permitting: "Permitting",27 approved: "Approved",28 under_construction: "Under construction",29 partially_operational: "Partially operational",30 operational: "Operational",31 expansion: "Expansion",32 delayed: "Delayed",33 cancelled: "Cancelled",34 closed: "Closed",35 unknown: "Unknown",36};3738export const STATUS_SHORT: Record<FacilityStatus, string> = {39 ...STATUS_LABEL,40 under_construction: "Construction",41 partially_operational: "Partial",42};4344/** Display order used by legends, stacked bars and filter chips. */45export const STATUS_ORDER: FacilityStatus[] = [46 "operational",47 "partially_operational",48 "expansion",49 "under_construction",50 "approved",51 "permitting",52 "announced",53 "proposed",54 "rumored",55 "delayed",56 "cancelled",57 "closed",58 "unknown",59];6061/** CSS variable name for a tone — resolved at paint time so the map / SVG follow the theme. */62export function statusVar(status: FacilityStatus | StatusTone): string {63 const tone = (status in STATUS_TONE ? STATUS_TONE[status as FacilityStatus] : status) as StatusTone;64 return `var(--st-${tone})`;65}6667export const TYPE_LABEL: Record<FacilityType, string> = {68 colocation: "Colocation",69 hyperscale: "Hyperscale",70 cloud_region: "Cloud region",71 edge: "Edge",72 carrier_hotel: "Carrier hotel",73 internet_exchange: "Internet exchange",74 ai: "AI",75 hpc: "HPC",76 sovereign_cloud: "Sovereign cloud",77 government: "Government",78 research: "Research",79 enterprise: "Enterprise",80 telecom: "Telecom",81 wholesale: "Wholesale",82 unknown: "Unknown",83};8485export const CONFIDENCE_LABEL: Record<ConfidenceLevel, string> = {86 verified: "Verified",87 high: "High confidence",88 moderate: "Moderate confidence",89 estimated: "Estimated",90 unverified: "Unverified",91};92export const CONFIDENCE_SHORT: Record<ConfidenceLevel, string> = {93 verified: "Verified",94 high: "High",95 moderate: "Moderate",96 estimated: "Estimate",97 unverified: "Unverified",98};99/** 0..4 dots filled for the confidence meter. */100export const CONFIDENCE_RANK: Record<ConfidenceLevel, number> = { verified: 4, high: 3, moderate: 2, estimated: 1, unverified: 0 };101102export const PRECISION_LABEL: Record<GeoPrecision, string> = {103 exact: "Exact location",104 parcel: "Parcel",105 street: "Street level",106 city: "City level",107 metro: "Metro level",108 approximate: "Approximate",109 unknown: "Location unknown",110};111export const PRECISION_SHORT: Record<GeoPrecision, string> = {112 exact: "Exact",113 parcel: "Parcel",114 street: "Street",115 city: "City",116 metro: "Metro",117 approximate: "Approx.",118 unknown: "Unknown",119};120/** True when the point should be drawn as a hollow ring (not a precise site). */121export function isImprecise(p: GeoPrecision): boolean {122 return p === "city" || p === "metro" || p === "approximate" || p === "unknown";123}124125export const OPERATOR_KIND_LABEL: Record<OperatorKind, string> = {126 colocation: "Colocation",127 hyperscaler: "Hyperscaler",128 cloud: "Cloud",129 carrier: "Carrier",130 enterprise: "Enterprise",131 government: "Government",132 research: "Research",133 developer: "Developer",134 investor: "Investor",135 ixp_operator: "IXP operator",136 other: "Other",137};138139export const SOURCE_KIND_LABEL: Record<SourceKind, string> = {140 operator: "Operator",141 government: "Government",142 filing: "Filing",143 utility: "Utility",144 dataset: "Dataset",145 community: "Community",146 secondary: "Secondary",147 news: "News",148 cloud_provider: "Cloud provider",149 registry: "Registry",150};151152export const EVENT_LABEL: Record<EventType, string> = {153 facility_discovered: "New facility",154 facility_updated: "Facility updated",155 status_changed: "Status change",156 capacity_changed: "Capacity change",157 planned_capacity_changed: "Planned capacity",158 opening_date_changed: "Opening date",159 operator_changed: "Operator change",160 owner_changed: "Owner change",161 project_announced: "Project announced",162 project_status_changed: "Project status",163 phase_announced: "Phase announced",164 planning_filed: "Planning filed",165 planning_approved: "Planning approved",166 construction_started: "Construction started",167 facility_opened: "Facility opened",168 expansion_announced: "Expansion",169 acquisition: "Acquisition",170 closure: "Closure",171 cloud_region_announced: "Cloud region announced",172 cloud_region_launched: "Cloud region launched",173 cloud_region_updated: "Cloud region updated",174 power_agreement: "Power agreement",175 investment_announced: "Investment",176 incident: "Incident",177 page_changed: "Page changed",178 news: "News",179 land_acquired: "Land acquired",180 grid_connection: "Grid connection",181 grid_constraint: "Grid constraint",182 utility_event: "Utility event",183 operator_expansion: "Operator expansion",184 customer_agreement: "Customer agreement",185 partnership: "Partnership",186 executive_change: "Executive change",187 project_delayed: "Project delayed",188 project_cancelled: "Project cancelled",189};190191/** Event families → tone, so the feed reads by colour without a rainbow. */192export function eventTone(t: EventType): StatusTone | "accent" | "neutral" {193 switch (t) {194 case "facility_opened":195 case "cloud_region_launched":196 case "facility_discovered":197 return "operational";198 case "construction_started":199 case "phase_announced":200 return "construction";201 case "project_announced":202 case "expansion_announced":203 case "cloud_region_announced":204 case "investment_announced":205 case "power_agreement":206 return "accent";207 case "planning_filed":208 case "planning_approved":209 return "permitting";210 case "closure":211 case "incident":212 return "delayed";213 case "status_changed":214 case "project_status_changed":215 case "capacity_changed":216 case "planned_capacity_changed":217 case "opening_date_changed":218 case "operator_changed":219 case "owner_changed":220 case "acquisition":221 return "planned";222 default:223 return "neutral";224 }225}226227export const ENTITY_TYPE_LABEL: Record<string, string> = {228 facility: "Data center",229 campus: "Campus",230 operator: "Operator",231 company: "Company",232 cloud_region: "Cloud region",233 metro: "Market",234 city: "City",235 country: "Country",236 project: "Project",237 ixp: "IXP",238 power_project: "Power project",239 carrier: "Carrier",240 news_event: "News",241};242243/* ------------------------------------------------------------------ claim-first vocabulary (2026-09-12) */244245/** Claim / figure scope — what a number describes. Site scopes may populate a record's own figures; the rest never do. */246export const SCOPE_LABEL: Record<ClaimScope, string> = {247 building: "Building",248 facility: "Facility",249 campus: "Campus",250 metro: "Metro",251 country: "Country",252 portfolio: "Portfolio",253 company: "Company-wide",254 unknown: "Scope unknown",255};256export const SCOPE_SHORT: Record<ClaimScope, string> = { ...SCOPE_LABEL, company: "Company", unknown: "Unknown" };257export function isSiteScopeLabel(s: ClaimScope | string | null | undefined): boolean {258 return s === "building" || s === "facility" || s === "campus";259}260261/** Capacity semantics — the predicate behind a MW figure; also accepts the free-text `capacitySemantics` the API returns. */262export const SEMANTICS_LABEL: Record<string, string> = {263 it_capacity_mw: "IT capacity",264 it_capacity: "IT capacity",265 it_load: "IT capacity",266 critical_power_mw: "Critical power",267 critical_power: "Critical power",268 current_power_mw: "Total power",269 total_power: "Total power",270 total_power_mw: "Total power",271 planned_power_mw: "Planned",272 planned: "Planned",273 grid_connection_mw: "Grid connection",274 grid_connection: "Grid connection",275 utility_capacity_mw: "Utility",276 utility: "Utility",277 utility_capacity: "Utility",278 ultimate_campus_mw: "Ultimate build-out",279 ultimate: "Ultimate build-out",280 phase_mw: "Phase capacity",281 project_investment_usd: "Project investment",282 campus_investment_usd: "Campus investment",283 company_investment_usd: "Company investment",284 country_program_usd: "Country programme",285 multi_year_capex_usd: "Multi-year capex",286 deal_value_usd: "Deal value",287 status: "Status",288};289export function semanticsLabel(s: string | null | undefined): string | null {290 if (!s) return null;291 return SEMANTICS_LABEL[s] ?? s.replace(/_mw$|_usd$/, "").replace(/_/g, " ").replace(/^./, (c) => c.toUpperCase());292}293294export const AUTHORITY_TIER_LABEL: Record<AuthorityTier, string> = {295 A: "Tier A — primary authority for this field",296 B: "Tier B — first-party or provider source",297 C: "Tier C — registry / secondary source",298 D: "Tier D — press coverage",299 E: "Tier E — dataset / community source",300};301302export const CLAIM_STATUS_LABEL: Record<ClaimStatus, string> = {303 current: "Current",304 superseded: "Superseded",305 rejected: "Rejected",306 review: "In review",307 unscoped: "Not site-scoped",308};309310/** Extraction method families for the evidence drawer (from provenance.method / claims.parserName). */311export type ExtractionKind = "structured" | "parser" | "regex" | "reviewed" | "llm" | "unknown";312export function extractionKind(method: string | null | undefined): ExtractionKind {313 if (!method) return "unknown";314 const m = method.toLowerCase();315 if (/review|manual|human|curated/.test(m)) return "reviewed";316 if (/llm|gpt|claude|model/.test(m)) return "llm";317 if (/regex|prose|sentence|text/.test(m)) return "regex";318 if (/json-?ld|dataset|api|structured|embedded|spec|table|field/.test(m)) return "structured";319 return "parser";320}321export const EXTRACTION_LABEL: Record<ExtractionKind, string> = {322 structured: "Structured data",323 parser: "Parser",324 regex: "Sentence extraction",325 reviewed: "Human-reviewed",326 llm: "Model-assisted",327 unknown: "Method not recorded",328};329330export const SIGNIFICANCE_LABEL: Record<Significance, string> = { major: "Major", medium: "Medium", minor: "Minor" };331export function significanceBand(value: number, band?: Significance | null): Significance {332 if (band) return band;333 return value >= 70 ? "major" : value >= 40 ? "medium" : "minor";334}335336export const AI_EVIDENCE_LABEL: Record<AiEvidence, string> = { confirmed: "AI · confirmed", likely: "AI · likely", associated: "AI · associated", unknown: "AI evidence unknown" };337338/** Project lifecycle stages in funnel order (side branches last). */339export const PROJECT_STAGE_ORDER: ProjectStage[] = ["rumored", "proposed", "announced", "permitting", "approved", "under_construction", "partially_operational", "operational", "delayed", "cancelled"];340export const STAGE_LABEL: Record<ProjectStage, string> = {341 rumored: "Rumored",342 proposed: "Proposed",343 announced: "Announced",344 permitting: "Permitting",345 approved: "Approved",346 under_construction: "Construction",347 partially_operational: "Partially open",348 operational: "Opened",349 delayed: "Delayed",350 cancelled: "Cancelled",351};352353export const MAP_LAYER_LABEL: Record<MapLayer, string> = {354 facilities: "Facilities",355 capacity: "Capacity",356 projects: "Projects",357 ai: "AI / HPC",358 cloud: "Cloud",359 ixps: "IXPs",360 connectivity: "Connectivity",361 power: "Power",362 pipeline: "Pipeline",363};364export const DENSITY_LABEL: Record<DensityView, string> = {365 facilities: "Facility density",366 known_mw: "Known MW",367 pipeline_mw: "Pipeline MW",368 ai: "AI facilities",369 operators: "Operator diversity",370 cloud: "Cloud regions",371 ixps: "Internet exchanges",372};373374export const SEARCH_GROUP_LABEL: Record<string, string> = {375 facility: "Data centers",376 operator: "Operators",377 country: "Countries",378 metro: "Markets",379 city: "Cities",380 cloud_region: "Cloud regions",381 project: "Projects",382 ixp: "Internet exchanges",383};384