spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1import type { Ranking } from "@dci/core";2import type { Metadata } from "next";3import Link from "next/link";4import { SectionNav } from "@/components/entity/section-nav";5import { Badge } from "@/components/ui/badge";6import { Card, CardBody, CardHeader, Page, PageHeader, Section } from "@/components/ui/card";7import { BarList } from "@/components/ui/charts";8import { EmptyState, OfflineNotice } from "@/components/ui/states";9import { api } from "@/lib/api";10import { fmtDate, fmtInt } from "@/lib/format";11import { routes } from "@/lib/routes";12import { pageMetadata } from "@/lib/seo";13import { SCOPE_LABEL, SCOPE_ORDER, firstSentence, fmtRankValue, rowHref } from "../ranking-format";1415export const revalidate = 120;1617export const metadata: Metadata = pageMetadata({18 title: "Rankings",19 description: "Countries, markets and operators ranked by facilities, known capacity and pipeline. Every MW-based ranking carries its coverage; entities below the coverage gate are excluded, not estimated.",20 path: routes.rankings(),21});2223/* Verbatim from docs/RANKINGS.md — “Definitions shared by all rankings”. */24const DEFINITIONS: Array<{ term: string; text: string }> = [25 { term: "Known MW", text: "Σ over operational facilities of IT capacity, or total power when the operator publishes only that. Figures marked as estimates count only when no measured figure exists for the facility, and the facility is flagged mw_is_estimate. We never impute MW to facilities without a published or filed figure." },26 { term: "Pipeline MW", text: "Σ over facilities not yet operational of planned power (or IT/total power when no planned figure is published), split into planned MW (planning statuses) and construction MW (under construction)." },27 { term: "Coverage", text: "Facilities with any MW figure ÷ facilities." },28 { term: "Coverage gate", text: "MW-based and per-capita rankings exclude entities with fewer than 5 facilities or coverage below 35 % (minCoverage), so that thin data does not produce spurious leaders. Count-based rankings have no gate." },29 { term: "Delta", text: "Rank change versus the previous snapshot of the same ranking (positive = moved up)." },30];3132function RankingCard({ r }: { r: Ranking }) {33 const top = (r.rows ?? []).slice(0, 3);34 return (35 <li className="py-4 first:pt-0 last:pb-0">36 <div className="grid gap-3 md:grid-cols-[minmax(0,1fr)_280px] md:gap-6">37 <div className="min-w-0">38 <div className="flex flex-wrap items-center gap-2">39 <Link href={routes.ranking(r.key)} className="text-[14.5px] font-semibold text-ink hover:text-accent hover:underline">40 {r.label}41 </Link>42 <Badge tone="outline" size="xs" mono>43 {r.unit}44 </Badge>45 </div>46 <p className="mt-1 line-clamp-2 text-[12.5px] text-ink-2">{firstSentence(r.methodology ?? "")}</p>47 <div className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-[11.5px] text-ink-3">48 {r.total > 0 ? (49 <span className="figure">50 {fmtInt(r.total)} {r.total === 1 ? "entity" : "entities"} ranked51 </span>52 ) : (53 <span className="text-ink-4">{/mw|per/i.test(r.unit) ? "No entity meets the coverage gate yet" : "No rows computed yet"}</span>54 )}55 <span className="figure">Computed {fmtDate(r.computedAt)}</span>56 <Link href={routes.ranking(r.key)} className="text-ink-2 hover:text-accent">57 Full ranking →58 </Link>59 </div>60 </div>61 {top.length > 0 && (62 <div className="min-w-0">63 <BarList items={top.map((row) => ({ label: row.name, value: row.value, href: rowHref(row, r.scope), rank: row.rank, coverage: row.coverage, display: fmtRankValue(row.value, r.unit) }))} showRank />64 </div>65 )}66 </div>67 </li>68 );69}7071export default async function RankingsPage() {72 const list = await api.rankings();73 const ok = list.length > 0;74 const groups = SCOPE_ORDER.map((scope) => ({ scope, items: list.filter((r) => r.scope === scope).sort((a, b) => a.label.localeCompare(b.label)) })).filter((g) => g.items.length);7576 return (77 <Page>78 <PageHeader79 eyebrow="Rankings"80 title="Rankings"81 description="Countries, markets and operators ranked by facilities, known capacity and pipeline. Every MW-based ranking carries its coverage; entities below the coverage gate are excluded, not estimated."82 />83 {!ok && <OfflineNotice detail="rankings unavailable" className="mb-3" />}84 {ok && <SectionNav items={groups.map((g) => ({ id: g.scope, label: SCOPE_LABEL[g.scope], count: g.items.length }))} className="mb-4" />}85 {!ok && <EmptyState title="Rankings unavailable" description="The index API did not return any ranking. They are recomputed by the worker after each crawl." />}86 <p className="mb-6 rounded-sm border border-dashed border-hair-2 px-3 py-2 text-[12.5px] leading-relaxed text-ink-2">87 <span className="label-caps mr-1.5">Method</span>88 Known / published figures only — every MW value is the sum of figures a source published or filed (containment-aware: a campus and its buildings are never both counted); entities without a figure are absent, not estimated. MW-based rankings apply a coverage gate (≥ 5 facilities and ≥ 35 % of them with a figure), so a short list means thin disclosure, not few players. Count-based rankings have no gate. AI, project and IXP rankings appear here as soon as the worker computes them.89 </p>9091 <div className="flex flex-col gap-8">92 {groups.map((g) => (93 <Section key={g.scope} id={g.scope} title={SCOPE_LABEL[g.scope]} description={`${g.items.length} ${g.items.length === 1 ? "ranking" : "rankings"}`} className="scroll-mt-28">94 <ul className="divide-y divide-hair">95 {g.items.map((r) => (96 <RankingCard key={r.key} r={r} />97 ))}98 </ul>99 </Section>100 ))}101 </div>102103 <Card className="mt-10">104 <CardHeader title="Definitions shared by all rankings" description="Verbatim from the ranking methodology." />105 <CardBody>106 <dl className="grid gap-3 text-[13px] text-ink-2 sm:grid-cols-[140px_minmax(0,1fr)] sm:gap-x-6 sm:gap-y-2">107 {DEFINITIONS.map((d) => (108 <div key={d.term} className="contents">109 <dt className="font-medium text-ink">{d.term}</dt>110 <dd className="mb-2 sm:mb-0">{d.text}</dd>111 </div>112 ))}113 </dl>114 <p className="mt-4 text-[12.5px] text-ink-3">115 Facility = a live record (merged duplicates excluded), any status. Operational = operational, partially operational or expansion. Under construction = under_construction. Planned = rumored, proposed, announced, permitting, approved or delayed.{" "}116 <Link href={`${routes.methodology()}#rankings`} className="text-ink underline decoration-hair-2 hover:text-accent">117 Full methodology118 </Link>119 </p>120 </CardBody>121 </Card>122 </Page>123 );124}125