import type { FastifyInstance } from "fastify"; import { z } from "zod"; import { publicGet } from "../../lib/route.js"; import { TTL, csv, notFound } from "../../lib/http.js"; import { facilityFiltersSchema, csvParam, numParam, strParam } from "../../lib/params.js"; import { METHODOLOGY_MW, METHODOLOGY_CONTAINMENT } from "../../lib/sql.js"; import { sourcesForEntities } from "../../lib/source-history.js"; import { getFacilityClaims, getFacilityDetail, getFacilityHistory, getFacilityProvenance, listFacilities, toFacilityQuery } from "../../repositories/facilities.js"; const LIST_NOTE = `${METHODOLOGY_MW} ${METHODOLOGY_CONTAINMENT} List rows may show both a campus (recordScope=campus) and its buildings (parentFacility set) — aggregates never count both.`; const radiusQuery = z.object({ radius_km: numParam }); const claimsQuery = z.object({ status: csvParam, predicate: strParam }); export async function facilityRoutes(app: FastifyInstance): Promise { publicGet(app, { url: "/datacenters", ttl: TTL.list, query: facilityFiltersSchema, summary: "List facilities (FacilitySummary[]) with filters, sorting and pagination", tags: ["facilities"], response: { type: "FacilitySummary[]", description: "Facilities matching the filters; `sources` = distinct sources behind the returned rows (≤ 30).", example: [{ id: "fac_…", slug: "example-dc1", name: "Example DC1", status: "operational", itCapacityMw: 12.5, recordScope: "facility", aiEvidence: "unknown" }] } }, async (q) => { const res = await listFacilities(toFacilityQuery(q)); return { data: res.items, meta: { total: res.total, page: res.page, perPage: res.perPage, methodology: LIST_NOTE }, sources: await sourcesForEntities("facility", res.items.map((f) => f.id)) }; }); publicGet(app, { url: "/datacenters/:idOrSlug", ttl: TTL.detail, query: radiusQuery, summary: "Facility detail by slug or id (FacilityDetail): buildings, tenants, claims, capacity history, nearby infrastructure (?radius_km ≤ 200, default 25), power context, data quality", tags: ["facilities"], params: { idOrSlug: "facility slug or fac_… id" }, response: { type: "FacilityDetail" } }, async (q, params) => { const res = await getFacilityDetail(params.idOrSlug!, { radiusKm: q.radius_km != null ? Math.min(200, Math.max(0.1, q.radius_km)) : undefined }); if (!res) throw notFound("facility"); return { data: res.detail, sources: res.sources, meta: { methodology: `${METHODOLOGY_MW} Claims list every figure published about this site with its scope; only site-scoped, non-rejected claims may back the displayed values (isWinner).` } }; }); publicGet(app, { url: "/datacenters/:idOrSlug/history", ttl: TTL.detail, summary: "Field history (EntityHistory): dated observations, claims and value changes per field", tags: ["facilities"], params: { idOrSlug: "facility slug or fac_… id" }, response: { type: "EntityHistory", example: { entityType: "facility", entityId: "fac_…", fields: { itCapacityMw: [{ date: "2025-03-01T00:00:00.000Z", field: "itCapacityMw", value: 12.5, kind: "observed", sourceId: "src_…", sourceName: "Operator site", sourceKind: "operator", url: "https://…" }] }, changes: [] } } }, async (_q, params) => { const res = await getFacilityHistory(params.idOrSlug!); if (!res) throw notFound("facility"); return { data: res.history, sources: res.sources, meta: { methodology: "observed = a source page stated the value (first observation date); claim = a figure asserted by a document (published date when known); changed = a detected value change with old → new. Dates are ISO or partial (YYYY, YYYY-MM)." } }; }); publicGet(app, { url: "/datacenters/:idOrSlug/claims", ttl: TTL.detail, query: claimsQuery, summary: "Claims about a facility (ClaimDTO[]) — every published figure with scope, evidence sentence, authority tier and status (?status=current,unscoped&predicate=)", tags: ["facilities"], params: { idOrSlug: "facility slug or fac_… id" }, response: { type: "ClaimDTO[]" } }, async (q, params) => { const res = await getFacilityClaims(params.idOrSlug!, { status: csv(q.status), predicate: q.predicate }); if (!res) throw notFound("facility"); return { data: res.claims, sources: res.sources, meta: { total: res.claims.length, facilityId: res.id, methodology: "A claim is one figure asserted by one document about this site. Scope building/facility/campus may back the displayed value (isWinner); portfolio/company/country/metro/unknown scopes are kept as claims only (status unscoped)." } }; }); publicGet(app, { url: "/datacenters/:idOrSlug/provenance", ttl: TTL.detail, summary: "All provenance observations for a facility (ProvenanceDTO[]), current and superseded, with winner flag", tags: ["facilities"], params: { idOrSlug: "facility slug or fac_… id" }, response: { type: "ProvenanceDTO[]" } }, async (_q, params) => { const res = await getFacilityProvenance(params.idOrSlug!); if (!res) throw notFound("facility"); return { data: res.provenance, sources: res.sources, meta: { total: res.provenance.length, current: res.current, superseded: res.provenance.length - res.current, facilityId: res.id } }; }); }