import type { AuthorityField, ClaimScope, SourceKind } from "@dci/core"; import { AI_EVIDENCE_LEVELS, AUTHORITY_FIELDS, CAPACITY_PREDICATE_LABEL, CLAIM_SCOPES, FIELD_AUTHORITY, INVESTMENT_PREDICATE_LABEL, PROJECT_CLASSES, SOURCE_KINDS, isSiteScope } from "@dci/core"; import Link from "next/link"; import type { ReactNode } from "react"; import { AiMark, ScopeBadge, StageBadge, TierMark } from "@/components/ui/badge"; import { Section } from "@/components/ui/card"; import { TBody, THead, Table, Td, Th, Tr } from "@/components/ui/table"; import { AI_EVIDENCE_LABEL, AUTHORITY_TIER_LABEL, PROJECT_STAGE_ORDER, SOURCE_KIND_LABEL } from "@/lib/labels"; import { routes } from "@/lib/routes"; /* ------------------------------------------------------------------------------------------ Methodology §9–17: the claim-first architecture (docs/CLAIMS.md), rendered from the same code tables the pipeline uses (@dci/core claims.ts) so the page can never drift from the rules. ------------------------------------------------------------------------------------------ */ const linkCls = "text-ink underline decoration-hair-2 hover:text-accent"; function P({ children }: { children: ReactNode }) { return

{children}

; } function Code({ children }: { children: ReactNode }) { return {children}; } const CLAIM_COLUMNS: Array<[string, string]> = [ ["subject_type, subject_id", "facility / campus / project / operator / market / country the figure is about"], ["predicate", "what is asserted — it_capacity_mw, critical_power_mw, utility_capacity_mw, grid_connection_mw, current_power_mw, planned_power_mw, ultimate_campus_mw, phase_mw, project_investment_usd, campus_investment_usd, company_investment_usd, country_program_usd, multi_year_capex_usd, deal_value_usd, status…"], ["value, value_text, unit", "the figure (MW, USD) or text"], ["scope, scope_reason", "building · facility · campus · metro · country · portfolio · company · unknown, and the rule that decided"], ["evidence_text, evidence_start, evidence_end", "the supporting sentence and its offsets in the parsed text — no sentence, no numerical claim (structured spec tables record the extraction method instead)"], ["source_id, url, document_id, published_at, retrieved_at", "where and when"], ["authority_tier", "A–E for this field (see field-level authority)"], ["parser_name, parser_version, run_id", "who extracted it — enables reprocessing and rollback"], ["status", "current · superseded (same source, same page, new value) · rejected (review / rollback) · review (critical sanity flag) · unscoped (describes a portfolio, a company, a country, a metro, or its scope is unknown)"], ]; const SCOPE_MEANING: Record = { building: "One hall or building on a site — may populate a building record; never stands for its campus.", facility: "A data center as one record — populates the facility's own columns.", campus: "The whole campus / park — populates the campus row; never written to a building.", metro: "A market statistic (“the Northern Virginia market adds 1 GW”) — evidence only.", country: "A national programme or country total — evidence only.", portfolio: "A company's footprint or pipeline (“across its portfolio”) — evidence only.", company: "Capex, guidance, multi-year plans, market-research figures — evidence only.", unknown: "The sentence gives no scope signal and the record has none — stored, never assigned.", }; const BLOCKING: Array<[string, string]> = [ ["scope_*", "figure describes a portfolio, company, country, metro or unknown scope — kept as a claim, not assigned"], ["scope_campus_on_building", "campus-level figure on a building record"], ["mw_market_statistic", "≥ 20 GW — an industry / market statistic, not a site"], ["mw_invalid", "zero, negative or non-numeric MW"], ["inv_deal_value_usd · inv_multi_year_capex_usd · inv_company_investment_usd · inv_country_program_usd", "deal value, capex, company or country programme filed as a project investment"], ["inv_gt_500b", "more than $500 B on one record"], ]; const NON_BLOCKING: Array<[string, string, string]> = [ ["mw_single_site_gt_1000", "critical", "single facility above 1 000 MW without a campus designation"], ["mw_building_gt_500", "warn", "one building above 500 MW"], ["mw_change_5x", "warn", "figure changed more than 5× against the previous value"], ["mw_money_collision", "critical", "a money figure with the same number sits in the same sentence — unit to verify"], ["inv_single_site_gt_50b", "critical", "more than $50 B on one site"], ["mw_semantics_default", "info", "the sentence does not say what kind of MW this is; semantics defaulted from the record status"], ["mw_utility_not_it", "info", "utility / grid figure — supply side, never IT load"], ]; const CLASS_GROUPS: Array<{ title: string; note: string; classes: string[] }> = [ { title: "Physical — may create or modify a project", note: "Creation additionally needs the evidence threshold: an explicit name or operator, a location, and a development verb. Company domiciles (“Denver-based”, “headquartered in”) are stripped before locating the project.", classes: ["NEW_BUILD", "EXPANSION", "CONSTRUCTION_START", "PERMIT", "LAND_ACQUISITION", "GRID_CONNECTION"] }, { title: "Associated — attach an event to an identifiable project, never create one", note: "Power agreements, financing and partnerships are corporate context; on operator pages they are listed apart from physical projects.", classes: ["POWER_AGREEMENT", "FINANCING", "ACQUISITION", "PARTNERSHIP", "CUSTOMER_AGREEMENT"] }, { title: "Never a project", note: "Executive appointments, sustainability reports, product news and general company news are recorded as events at most.", classes: PROJECT_CLASSES.filter((c) => !["NEW_BUILD", "EXPANSION", "CONSTRUCTION_START", "PERMIT", "LAND_ACQUISITION", "GRID_CONNECTION", "POWER_AGREEMENT", "FINANCING", "ACQUISITION", "PARTNERSHIP", "CUSTOMER_AGREEMENT"].includes(c)) }, ]; export const CLAIMS_NAV = [ { id: "claims", label: "Claims" }, { id: "scopes", label: "Scopes" }, { id: "semantics", label: "Capacity semantics" }, { id: "authority", label: "Authority" }, { id: "quality", label: "Sanity & quality" }, { id: "classes", label: "Announcement classes" }, { id: "lifecycle", label: "Lifecycle" }, { id: "ai-evidence", label: "AI evidence" }, { id: "coverage-ranking", label: "Coverage-aware ranking" }, ]; export function ClaimsSections({ from }: { from: number }) { const n = (i: number) => `${from + i} · `; return ( <>

Since 2026-09-12 every numerical figure the index extracts is stored as a claim before it can become a displayed value. A claim is one assertion by one document about one subject, with its scope, its semantics, the sentence that supports it, the parser that produced it and the field-level authority of its source. Reconciliation then decides which claim (if any) populates a column. Nothing is deleted when sources disagree — every claim stays visible in the evidence drawer.

{CLAIM_COLUMNS.map(([c, m]) => ( ))}
Column Meaning
{c} {m}

The winner behind each displayed value is marked (is_winner); run_id on claims, provenance rows, events and document versions ties every change to a connector run, which can be rolled back as a whole. Daily snapshots keep global totals, per-operator and per-country totals, rankings and stage counts for “as of” views and automated regression checks (facilities −5 %, known MW +20 %, one operator +10 GW in a day, one connector > 500 records a day, > 200 location changes a day).

Only site scopes (building, facility, campus) may populate a facility’s or project’s capacity and investment columns. Portfolio, company, country, metro and unknown scopes are stored and shown as evidence, never summed. Scope is classified deterministically from the sentence (classifyScope): company → portfolio → country → metro → campus → building → facility → the record’s own scope when the sentence has no signal (structured parsers) → unknown. A campus figure is never written to a building record; a building’s figure never stands for its campus.

{CLAIM_SCOPES.map((s) => ( ))}
Scope Populates a site’s figures? Meaning
{isSiteScope(s) ? yes — site scope : no — evidence only} {SCOPE_MEANING[s]} {isSiteScope(s) ? "(site scope)" : "(evidence only)"}

IT load, critical power, utility capacity, grid connection, current power, planned capacity, ultimate build-out and phase capacity are different predicates and different columns. The displayed figure carries capacityScope and capacitySemantics so a page can say what “300 MW” means. Utility and grid figures are supply-side — never IT load and never comparable with IT capacity. When a sentence gives no cue the predicate defaults from the field / record status and the claim is flagged mw_semantics_default.

{Object.entries(CAPACITY_PREDICATE_LABEL).map(([k, v]) => ( ))}
Capacity predicate Label
{k} {v}
{Object.entries(INVESTMENT_PREDICATE_LABEL).map(([k, v]) => ( ))}
Investment predicate Label
{k} {v}

Only project and campus investment may stand for a site; deal values, capex, company investment and country programmes are stored as evidence.

One universal source rank is wrong: OpenStreetMap is excellent for geometry and useless for capacity; PeeringDB is the reference for interconnection; planning filings beat press releases for approved capacity; utility filings beat everything for grid requirements. FIELD_AUTHORITY therefore gives a tier per field. Estimates and LLM-only extractions drop one tier; a human review raises one. The table below is rendered from the code the pipeline runs.

{SOURCE_KINDS.map((k) => ( ))} {AUTHORITY_FIELDS.map((f) => ( {SOURCE_KINDS.map((k) => ( ))} ))}
Field {SOURCE_KIND_LABEL[k as SourceKind]}
{f as AuthorityField}

capacitySanity and investmentSanity run on every claim. Blocking flags keep the figure out of the columns (it stays a claim). Non-blocking critical flags assign the figure but open a quality flag prioritised by impact; the figure stays visible and marked until a reviewer resolves or dismisses the flag. Every entity page lists its open flags in the data-quality block.

Blocking

    {BLOCKING.map(([c, m]) => (
  • {c}
    {m}
  • ))}

Non-blocking (flagged)

    {NON_BLOCKING.map(([c, sev, m]) => (
  • {c} · {sev}
    {m}
  • ))}

Review priority weighs the figure’s magnitude, how many pages display it and the number of sources that disagree; the admin console works the queue from the top.

Before a news article can create a project, classifyProjectEvent labels it. Only physical classes may create or modify a project; associated classes attach an event to an identifiable project; the rest never become projects. This is what keeps executive appointments, market-research articles and capex guidance out of the pipeline.

{CLASS_GROUPS.map((g) => (

{g.title}

{g.classes.map((c) => ( {c} ))}

{g.note}

))}

projectTransition(from, to) accepts forward moves along the funnel; delayed and cancelled are side branches; leaving delayed resumes where the project was. A backward move needs a source that outranks the stored one for the status field, otherwise it is flagged status_backward. Stage dates (permit_filed_on, approved_on, construction_started_on, opened_on) are set from the announcement that moved the stage — earliest evidence wins.

    {PROJECT_STAGE_ORDER.filter((s) => s !== "delayed" && s !== "cancelled").map((s, i, arr) => (
  1. {i < arr.length - 1 && →}
  2. ))}
side branches:

A single keyword never confirms an AI site. Each facility and project carries an evidence level; AI counts and MW on the site cover confirmed + likely only, and figures are published, site-scoped values (containment-aware), never extrapolated.

See the AI infrastructure index.

Every aggregate uses the containment-aware facility view: a campus whose buildings publish figures contributes no MW itself; a building without a figure under a campus with one is “covered”; a campus with buildings is not counted as an extra facility. Each MW aggregate exposes its coverage (facilities with any figure ÷ facilities) and rankings gate on it (≥ 5 facilities, ≥ 35 %). Market concentration (HHI, top-3 / top-5 shares) is computed on facility counts and, separately, on known MW with its coverage — the MW view is labelled partial when coverage is low. Momentum is shown as separate counters (projects announced, MW entering construction, new entrants, openings, cloud additions, grid events), never collapsed into a score.

Browse the rankings and the coverage report.

); }