SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
6 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
29.6 KB · 342 lines tsx
Raw Blame History
1import type { ConfidenceLevel, GeoPrecision } from "@dci/core";2import type { Metadata } from "next";3import Link from "next/link";4import type { ReactNode } from "react";5import { Breadcrumbs } from "@/components/entity/breadcrumbs";6import { SectionNav } from "@/components/entity/section-nav";7import { ConfidenceBadge, EstimateMark, PrecisionBadge } from "@/components/ui/badge";8import { Page, PageHeader, Section } from "@/components/ui/card";9import { TBody, THead, Table, Td, Th, Tr } from "@/components/ui/table";10import { routes } from "@/lib/routes";11import { pageMetadata } from "@/lib/seo";12import { CLAIMS_NAV, ClaimsSections } from "./claims-sections";1314export const metadata: Metadata = pageMetadata({15  title: "Methodology",16  description: "How DataCenterIndex builds its graph: pipeline, provenance, confidence levels, location precision, capacity and estimate rules, reconciliation, change detection, rankings, the claim-first architecture (scopes, capacity semantics, field-level authority, sanity engines, announcement classes, lifecycle), AI evidence, coverage-aware ranking and the legal crawling policy.",17  path: routes.methodology(),18});1920const NAV = [21  { id: "principles", label: "Principles" },22  { id: "pipeline", label: "Pipeline" },23  { id: "confidence", label: "Confidence" },24  { id: "precision", label: "Precision" },25  { id: "capacity", label: "Capacity" },26  { id: "reconciliation", label: "Reconciliation" },27  { id: "changes", label: "Changes" },28  { id: "rankings", label: "Rankings" },29  ...CLAIMS_NAV,30  { id: "crawling", label: "Crawling" },31  { id: "limitations", label: "Limitations" },32];3334const CONFIDENCE: Array<{ level: ConfidenceLevel; rule: string; example: string }> = [35  { level: "verified", rule: "At least two independent primary source kinds agree on the record (for example the operator's page and a planning filing, or a utility and a government registry).", example: "Operator facility page + permit filing both give 48 MW IT." },36  { level: "high", rule: "One primary source: operator, government, filing, utility, cloud provider or registry.", example: "Capacity read from the operator's own spec sheet." },37  { level: "moderate", rule: "A dataset or community source (Wikidata, OpenStreetMap, PeeringDB) or a primary source that only covers part of the fields.", example: "Location and opening year from Wikidata, no capacity." },38  { level: "estimated", rule: "Every observation behind the record is flagged as an estimate by its source. Figures carry the ≈ mark.", example: "Analyst note giving “approximately 30 MW”." },39  { level: "unverified", rule: "Only secondary or news sources so far, or the record is a possible duplicate awaiting review (score 0.60–0.92, see reconciliation).", example: "A newly created facility that matched an existing one at 0.7." },40];4142const PRECISION: Array<{ level: GeoPrecision; rule: string; radius: string }> = [43  { level: "exact", rule: "Coordinates of the building itself, from the operator, a filing, OpenStreetMap or a surveyed address.", radius: "≈ 250 m" },44  { level: "parcel", rule: "Coordinates of the land parcel or campus, not a specific hall.", radius: "≈ 500 m" },45  { level: "street", rule: "Geocoded street address; the point sits on the street segment.", radius: "≈ 1 km" },46  { level: "approximate", rule: "Hand-entered or interpolated point without a documented address.", radius: "≈ 5 km" },47  { level: "city", rule: "Only the city is known (typical for cloud regions and press releases). Drawn as a hollow ring on the map, never labelled exact.", radius: "≈ 15 km" },48  { level: "metro", rule: "Only the metro market is known (for example “Northern Virginia”).", radius: "≈ 40 km" },49  { level: "unknown", rule: "No usable location; the facility is listed but not mapped.", radius: "—" },50];5152const SCORE: Array<{ signal: string; weight: string; value: string }> = [53  { signal: "Name", weight: "0.40", value: "1 for equal normalized names (noise words removed), equal code-preserving names or an alias hit; otherwise the best of token Jaccard and 0.85 × containment." },54  { signal: "Facility codes (DC12, FR5, LD8)", weight: "0.15", value: "1 when both names carry codes and one matches, 0 when they conflict; skipped when either side has no code." },55  { signal: "Operator", weight: "0.20", value: "1 same operator, 0.5 unknown on either side, 0 different." },56  { signal: "Distance", weight: "0.15", value: "1 − 0.4·d/r inside the match radius r derived from both precisions (exact 250 m … metro 40 km), decaying to 0 at 4r; 0.6 for the same city without coordinates." },57  { signal: "Address", weight: "0.10", value: "Normalized address equality → 1; same house number → ≥ 0.75; different house numbers → ≤ 0.4; otherwise token Jaccard." },58];5960const COUNTRY_RANKINGS: Array<[string, string, string, string]> = [61  ["countries_facilities", "Facility count", "facilities", "Number of facilities located in the country. Secondary: known MW."],62  ["countries_known_mw", "Known operational MW", "MW", "Known MW over operational facilities. Coverage gate applies. Secondary: operational facilities."],63  ["countries_planned_mw", "Planned MW", "MW", "Pipeline MW over planning statuses. Gate applies."],64  ["countries_construction_mw", "MW under construction", "MW", "Pipeline MW over facilities under construction. Gate applies."],65  ["countries_hyperscale", "Hyperscale facilities", "facilities", "Facilities flagged hyperscale (self-described, or operated by AWS, Microsoft, Google, Meta, Oracle, Alibaba Cloud, Tencent Cloud, Apple, IBM or Huawei Cloud)."],66  ["countries_ai", "AI facilities", "facilities", "Facilities flagged as AI / accelerated-computing sites by their operator or a primary source."],67  ["countries_cloud_regions", "Cloud regions", "regions", "Public cloud regions (announced or operational; retired excluded) across indexed providers."],68  ["countries_facilities_per_million", "Facilities per million residents", "per million", "facilities ÷ (population / 1 000 000). Requires ≥ 5 facilities and a population figure."],69  ["countries_mw_per_capita", "Known MW per capita", "kW per 1 000 residents", "known MW × 1 000 000 ÷ population. Gate applies."],70  ["countries_mw_per_gdp", "Known MW per $bn GDP", "MW per $bn", "known MW ÷ (GDP in current US$ / 10⁹). Gate applies."],71  ["countries_growth", "Facilities opened in the last 3 years", "facilities", "Facilities whose (possibly partial) opening date is in the current year − 3 or later. Facilities without an opening date are not counted, so growth is understated where operators do not publish dates."],72];73const METRO_RANKINGS: Array<[string, string, string, string]> = [74  ["metros_facilities", "Facility count", "facilities", "Facilities assigned to the metro (within its 40–80 km radius, or by listed sub-market when no coordinates). Metros never span borders."],75  ["metros_known_mw", "Known operational MW", "MW", "Known MW. Gate applies."],76  ["metros_pipeline_mw", "Pipeline MW", "MW", "Planned MW + construction MW. Gate applies."],77  ["metros_operators", "Operator count", "operators", "Distinct operators with ≥ 1 facility in the metro."],78];79const OPERATOR_RANKINGS: Array<[string, string, string, string]> = [80  ["operators_facilities", "Facility count", "facilities", "Facilities operated (operator_id; owners and tenants are not counted). Secondary: countries."],81  ["operators_countries", "Country footprint", "countries", "Distinct countries with ≥ 1 operated facility."],82  ["operators_metros", "Metro footprint", "metros", "Distinct metros with ≥ 1 operated facility (facilities outside any defined metro are not counted)."],83  ["operators_known_mw", "Known operational MW", "MW", "Known MW. Gate applies."],84  ["operators_pipeline_mw", "Pipeline MW", "MW", "Planned MW + construction MW. Gate applies."],85];8687function P({ children }: { children: ReactNode }) {88  return <p className="mt-3 first:mt-0">{children}</p>;89}9091function RankingTable({ rows }: { rows: Array<[string, string, string, string]> }) {92  return (93    <Table dense>94      <THead>95        <tr>96          <Th>Ranking</Th>97          <Th className="hidden sm:table-cell">Unit</Th>98          <Th>Method</Th>99        </tr>100      </THead>101      <TBody>102        {rows.map(([key, label, unit, method]) => (103          <Tr key={key}>104            <Td primary>105              <Link href={routes.ranking(key)} className="font-medium text-ink hover:text-accent hover:underline">106                {label}107              </Link>108              <div className="figure text-[11px] text-ink-4">{key}</div>109            </Td>110            <Td label="Unit" className="hidden text-[12.5px] text-ink-3 sm:table-cell">111              {unit}112            </Td>113            <Td label="Method" className="text-[12.5px] text-ink-2">114              {method}115            </Td>116          </Tr>117        ))}118      </TBody>119    </Table>120  );121}122123const linkCls = "text-ink underline decoration-hair-2 hover:text-accent";124125export default function MethodologyPage() {126  return (127    <Page className="max-w-[880px]">128      <Breadcrumbs items={[{ name: "Methodology", href: routes.methodology() }]} className="pt-3" />129      <PageHeader eyebrow="Methodology" title="How the index is built" description="Every figure in DataCenterIndex is observed, never imputed. This page explains the pipeline, the confidence and precision scales, how capacity is counted, how records from different sources are reconciled, how changes are detected, how rankings are computed and how we crawl." className="pt-3" />130      <SectionNav items={NAV} />131132      <div className="mt-6 flex flex-col gap-10 text-[14.5px] leading-relaxed text-ink-2">133        <Section id="principles" className="scroll-mt-28" title="1 · Principles">134          <P>135            <strong className="text-ink">Nothing is invented.</strong> There are no synthetic facilities, capacities, coordinates or dates. When a source does not publish a figure the page shows “—”, never zero, and totals are <em>lower bounds</em> computed from known values only. A figure a source itself calls an estimate is marked <EstimateMark /> and stored with an <code className="figure text-[12.5px]">isEstimate</code> flag.136          </P>137          <P>138            <strong className="text-ink">Provenance on every field.</strong> Each value — a name, a status, an IT capacity, an opening year, a coordinate — is backed by a provenance row: the source, the URL it was read from, when it was first and last observed, when it was retrieved, the extraction method and a confidence level. Facility and project pages expose this table in full, and every figure carries a provenance popover.139          </P>140          <P>141            <strong className="text-ink">Location honesty.</strong> A coordinate that only identifies a city is stored with precision <em>city</em> and drawn as a hollow ring on the map. It is never upgraded to <em>exact</em> and never overwritten by a less precise point.142          </P>143          <P>144            <strong className="text-ink">The product is the graph and its history</strong>, not the crawler. Records are versioned; changes are detected, deduplicated and published to the <Link href={routes.live()} className={linkCls}>live feed</Link> with their significance.145          </P>146        </Section>147148        <Section id="pipeline" className="scroll-mt-28" title="2 · Pipeline">149          <P>Every source goes through the same fixed sequence; there is no shortcut from a scraped page to the site.</P>150          <ol className="mt-3 flex flex-col gap-2.5 border-l border-hair pl-4">151            {[152              ["Discover", "URLs are found through sitemaps, RSS feeds, index pages and link following, scoped to the source's domain and page types (facility pages, newsroom, region lists, filings)."],153              ["Fetch", "Pages are retrieved politely (robots.txt, per-host rate limits, conditional requests). Four escalation levels exist: L1 direct HTTP with the bot identity, L2 direct with a browser identity, L3 a rendering service (Firecrawl), L4 a rendered fetch with anti-bot handling (Scrapfly). We escalate only when a level is blocked or returns a JavaScript shell, and premium levels have daily budgets."],154              ["Archive", "Every fetched body is content-hashed and stored compressed in object storage with its headers, so any value can be traced back to the exact document version it came from."],155              ["Extract", "Structured data first (JSON-LD, embedded JSON, tables), then declarative selectors, then source-specific parsers for PDFs and irregular pages. Extraction is validated against live pages before a connector ships."],156              ["Normalize", "Units and formats are unified: MW from kW/MVA, partial dates (2027, 2027-06, 2027-Q2), statuses and facility types to a fixed vocabulary, money to USD with the original currency kept."],157              ["Reconcile", "The normalized record is matched against existing facilities, operators, projects, cloud regions and internet exchanges (see section 6). Conflicting values are merged by source authority."],158              ["Validate", "Range and consistency checks (a 5 GW single building is rejected; an opening date in the future for an operational facility is flagged). Invalid records are counted, not silently dropped."],159              ["Version", "Each field change creates a new provenance observation; the previous value remains queryable."],160              ["Detect changes", "Tracked fields are compared with the current record; a change becomes an event with a significance score, deduplicated per day (section 7)."],161              ["Publish", "The API and the site read only reconciled, validated records with their provenance and events."],162            ].map(([t, d]) => (163              <li key={t} className="relative">164                <span aria-hidden className="absolute -left-[20.5px] top-[7px] size-2 rounded-full bg-ink-4 ring-2 ring-paper" />165                <span className="font-medium text-ink">{t}.</span> {d}166              </li>167            ))}168          </ol>169        </Section>170171        <Section id="confidence" className="scroll-mt-28" title="3 · Confidence levels">172          <P>Confidence is computed per record from the kind of its best source and the number of distinct primary source kinds that corroborate it. Primary kinds are operator, government, filing, utility, cloud provider and registry; dataset and community sources are secondary; news is tertiary.</P>173          <div className="mt-4">174            <Table dense>175              <THead>176                <tr>177                  <Th>Level</Th>178                  <Th>Rule</Th>179                  <Th className="hidden md:table-cell">Example</Th>180                </tr>181              </THead>182              <TBody>183                {CONFIDENCE.map((c) => (184                  <Tr key={c.level}>185                    <Td primary>186                      <ConfidenceBadge level={c.level} short={false} />187                    </Td>188                    <Td label="Rule" className="text-[13px]">189                      {c.rule}190                    </Td>191                    <Td label="Example" className="hidden text-[12.5px] text-ink-3 md:table-cell">192                      {c.example}193                    </Td>194                  </Tr>195                ))}196              </TBody>197            </Table>198          </div>199          <P>200            <strong className="text-ink">Completeness</strong> (0–100) is a separate, purely mechanical score of how many fields are filled, recomputed after every write: location 15 (8 when city/metro/approximate), operator 10, address 10, status 10, capacity 20 (14 when estimated; 12 or 8 when only a planned figure exists), type 5, opening date 10, website 5, description 5, tenants and internet exchanges 10. It says how much we know, not how sure we are.201          </P>202        </Section>203204        <Section id="precision" className="scroll-mt-28" title="4 · Location precision">205          <P>Each coordinate carries a precision level. The map draws exact, parcel and street points as filled dots and everything less precise as hollow rings; the facility page states the level in words. Reconciliation never replaces a point with a less precise one (rank: exact › parcel › street › approximate › city › metro › unknown); at equal precision the newest observation wins.</P>206          <div className="mt-4">207            <Table dense>208              <THead>209                <tr>210                  <Th>Level</Th>211                  <Th>Meaning</Th>212                  <Th className="hidden sm:table-cell">Match radius</Th>213                </tr>214              </THead>215              <TBody>216                {PRECISION.map((p) => (217                  <Tr key={p.level}>218                    <Td primary>219                      <PrecisionBadge precision={p.level} short={false} />220                    </Td>221                    <Td label="Meaning" className="text-[13px]">222                      {p.rule}223                    </Td>224                    <Td label="Match radius" className="figure hidden text-[12.5px] text-ink-3 sm:table-cell">225                      {p.radius}226                    </Td>227                  </Tr>228                ))}229              </TBody>230            </Table>231          </div>232        </Section>233234        <Section id="capacity" className="scroll-mt-28" title="5 · Capacity & estimates">235          <P>Three capacity figures are stored separately and never mixed on a facility page: <strong className="text-ink">IT capacity</strong> (critical load available to equipment), <strong className="text-ink">total power</strong> (grid connection or facility power, when the operator publishes only that) and <strong className="text-ink">planned power</strong> (announced for a future phase). Lists and rankings use the best available figure, <code className="figure text-[12.5px]">mw = COALESCE(it_capacity_mw, total_power_mw, planned_power_mw)</code>, and say which one it is.</P>236          <P>237            Aggregates are grouped by status: <em>known MW</em> sums operational, partially operational and expansion facilities; <em>construction MW</em> sums facilities under construction; <em>planned MW</em> sums rumored, proposed, announced, permitting, approved and delayed facilities (planned figure first). Each aggregate exposes its <em>coverage</em> — the share of facilities that carry any MW figure — because a country where 20 % of facilities publish capacity cannot be compared with one where 80 % do.238          </P>239          <P>240            A facility is flagged <code className="figure text-[12.5px]">mw_is_estimate</code> only when <em>every</em> current capacity observation is an estimate; a single measured figure clears the flag. Datasets, community, secondary and news sources never overwrite a capacity that came from an operator, government, filing, utility, cloud-provider or registry source. Nothing is imputed to facilities without a published or filed figure.241          </P>242        </Section>243244        <Section id="reconciliation" className="scroll-mt-28" title="6 · Reconciliation">245          <P>An incoming facility record is resolved in a fixed order: (1) its connector key (for example <code className="figure text-[12.5px]">peeringdb:fac:12</code>) already mapped to a facility always wins; (2) any shared external identifier (PeeringDB, OpenStreetMap, Wikidata…) on a live facility; (3) otherwise candidates within a 40 km box, with the same normalized name or alias in the same country, or belonging to the same operator in the same country are scored.</P>246          <div className="mt-4">247            <Table dense>248              <THead>249                <tr>250                  <Th>Signal</Th>251                  <Th numeric>Weight</Th>252                  <Th>Value</Th>253                </tr>254              </THead>255              <TBody>256                {SCORE.map((s) => (257                  <Tr key={s.signal}>258                    <Td primary>{s.signal}</Td>259                    <Td label="Weight" numeric>260                      {s.weight}261                    </Td>262                    <Td label="Value" className="text-[12.5px]">263                      {s.value}264                    </Td>265                  </Tr>266                ))}267              </TBody>268            </Table>269          </div>270          <P>Weights are redistributed over the signals available on both sides. Extra rules: same operator with a matching facility code and compatible geography raises the name signal to 0.95 (this is what folds “DC12” into “Equinix DC12”); conflicting codes cap it at 0.45; different countries cap the score at 0.30; precise points more than four radii apart cap it at 0.50; two different known operators never merge unless both the name and the address are exact matches (an operator change on the same building).</P>271          <P>272            <strong className="text-ink">Decisions.</strong> A score of <strong className="text-ink">0.92 or more</strong> merges automatically (audited). Between <strong className="text-ink">0.60 and 0.92</strong> the record is created as a separate facility with confidence <em>unverified</em> and queued for human review — nothing is hidden and nothing is lost; approving the match later folds the two records (aliases, provenance, tenants, events and keys move to the survivor). Below 0.60 a new facility is created.273          </P>274          <P>275            <strong className="text-ink">Field merge.</strong> An empty value is filled; a source re-observing its own value refreshes it; a measured value beats an estimate whatever the source; otherwise the higher authority wins — operator, government and filings (4), utility and cloud provider (3.5), registry (3), dataset (2.5), community (2), secondary (1.5), news (1), estimates −1.5 — and ties go to the most recent observation. Flags such as AI and hyperscale only ever turn on; certifications, aliases and identifiers accumulate; a replaced name becomes an alias; <em>unknown</em> never overwrites a known status or type.276          </P>277          <P>278            <strong className="text-ink">Operators</strong> resolve through a curated table of about 175 brands and their subsidiaries (Interxion, Telx and DuPont Fabros fold into Digital Realty; RagingWire, e-shelter, Gyron and NetMagic into NTT Global Data Centers), then exact normalized name, alias, and finally trigram similarity ≥ 0.92 combined with the same website domain. News text can only link to a known operator, never create one. <strong className="text-ink">Projects</strong> resolve by key, external ids, identical source URL, then same country + same or unknown operator + name similarity ≥ 0.85; timeline rows are deduplicated on date, type and description.279          </P>280        </Section>281282        <Section id="changes" className="scroll-mt-28" title="7 · Change detection & events">283          <P>Tracked facility fields are status, IT / total / planned MW, opening, announcement and construction-start dates, operator, owner, facility type and name. Each change becomes an event with a significance from 0 to 100: status 90, operator 85, owner 70, capacity change of 20 % or more 80 (otherwise 50), dates 60, other fields 20. First-time values are capped at 50, and enrichment of a name, type or operator that was previously unknown is not an event.</P>284          <P>New records emit <em>facility discovered</em> (65 when the facility has ≥ 50 MW or is in the pipeline, else 40) and <em>project announced</em> (75 when ≥ 100 MW planned). News articles emit one event per article when they carry a recognised event type with significance ≥ 40. Events are deduplicated by a fingerprint of entity, type, new value and day, so the same change seen on several pages the same day is one event. Every event links to the page it was detected on and shows the old and new value; events can be reviewed and rejected by an editor, in which case they leave the feed.</P>285        </Section>286287        <Section id="rankings" className="scroll-mt-28" title="8 · Rankings">288          <P>Rankings are recomputed by the worker and are only as good as the coverage of the index; every MW-based row therefore shows its coverage. Definitions shared by all rankings:</P>289          <ul className="mt-3 list-disc space-y-1.5 pl-5">290            <li><strong className="text-ink">Facility</strong> — a live record, any status; duplicates folded by reconciliation are excluded.</li>291            <li><strong className="text-ink">Operational</strong> — operational, partially operational or expansion. <strong className="text-ink">Under construction</strong> — under_construction. <strong className="text-ink">Planned</strong> — rumored, proposed, announced, permitting, approved or delayed.</li>292            <li><strong className="text-ink">Known MW</strong> — Σ over operational facilities of IT capacity, or total power when only that is published. Estimates count only when no measured figure exists and are flagged. No MW is imputed.</li>293            <li><strong className="text-ink">Pipeline MW</strong> — Σ over not-yet-operational facilities of planned power (or IT / total power when no planned figure exists), split into planned MW and construction MW.</li>294            <li><strong className="text-ink">Coverage</strong> — facilities with any MW figure ÷ facilities.</li>295            <li><strong className="text-ink">Coverage gate</strong> — MW-based and per-capita rankings exclude entities with fewer than <strong className="text-ink">5 facilities</strong> or coverage below <strong className="text-ink">35 %</strong>, so thin data cannot produce spurious leaders. Count-based rankings have no gate. Excluded entities are simply absent — they are not estimated.</li>296            <li><strong className="text-ink">Delta</strong> — rank change versus the previous snapshot of the same ranking (positive = moved up).</li>297          </ul>298          <P>Population and GDP come from the latest World Bank vintage stored on the country; the vintage is shown on country pages.</P>299          <h3 className="mt-6 text-[14px] font-semibold text-ink">Countries</h3>300          <div className="mt-2">301            <RankingTable rows={COUNTRY_RANKINGS} />302          </div>303          <h3 className="mt-6 text-[14px] font-semibold text-ink">Markets (metros)</h3>304          <div className="mt-2">305            <RankingTable rows={METRO_RANKINGS} />306          </div>307          <h3 className="mt-6 text-[14px] font-semibold text-ink">Operators</h3>308          <div className="mt-2">309            <RankingTable rows={OPERATOR_RANKINGS} />310          </div>311          <P>312            Known limitations: known MW mixes IT capacity and total power when operators publish only one of them (the facility page shows which); primary-source disclosure is much higher in North America and Europe than elsewhere, so cross-region MW comparisons must be read with the coverage column; per-capita and per-GDP rankings depend on the World Bank vintage. See all <Link href={routes.rankings()} className={linkCls}>rankings</Link>.313          </P>314        </Section>315316        <ClaimsSections from={9} />317318        <Section id="crawling" className="scroll-mt-28" title="18 · Legal crawling policy">319          <ul className="list-disc space-y-1.5 pl-5">320            <li><strong className="text-ink">robots.txt is respected</strong> for every host, including <code className="figure text-[12.5px]">Crawl-delay</code>; rules are re-read at least daily. The few exceptions are public query APIs governed by their own published limits (for example the Wikidata SPARQL endpoint), and they are documented on the <Link href={routes.sources()} className={linkCls}>source page</Link>.</li>321            <li><strong className="text-ink">Rate limits per host</strong> — by default 30 requests per minute with at most 2 concurrent connections, lower where the source asks for it.</li>322            <li><strong className="text-ink">Conditional requests and content hashing</strong> — pages are fetched with <code className="figure text-[12.5px]">If-None-Match</code> / <code className="figure text-[12.5px]">If-Modified-Since</code>, and an unchanged body (same hash) is never reprocessed; premium rendering credits are never spent on unchanged pages.</li>323            <li><strong className="text-ink">No circumvention</strong> — no login, paywall or CAPTCHA is bypassed; only publicly reachable pages are read.</li>324            <li><strong className="text-ink">Escalation only when blocked</strong> — a rendering service is used only when the direct fetch returns a JavaScript shell or a block page, within daily budgets.</li>325            <li><strong className="text-ink">Attribution and licence</strong> are recorded for every source and displayed wherever its values appear. We extract facts (a capacity, a date, an address), not copyrighted prose; descriptions are short and attributed.</li>326          </ul>327          <P>328            The crawler identifies itself as <Link href={routes.bot()} className={linkCls}>DataCenterIndexBot</Link>; the bot page explains how to opt out or request a correction.329          </P>330        </Section>331332        <Section id="limitations" className="scroll-mt-28" title="19 · Limitations & corrections">333          <P>Coverage is uneven: it follows what operators, regulators and registries publish, which is far denser in North America and Europe than in other regions, and denser for colocation and hyperscale sites than for enterprise or government facilities. Capacity figures are lower bounds; the absence of a figure means it was not published, not that it is zero. Cloud-region coordinates are city-level by construction. Rankings are recomputed as coverage grows and their history is kept.</P>334          <P>335            Corrections, additional sources and removal requests: <a href="mailto:contact@spboucher.ai" className={linkCls}>contact@spboucher.ai</a>. Every value on the site can be traced to a document version, so a correction is applied at the source and propagates through the same pipeline as any other observation.336          </P>337        </Section>338      </div>339    </Page>340  );341}342