import { load } from "cheerio"; import type { ExtractedRecord, Parser, RawDocument } from "@dci/connectors"; import { cleanText, cloudsIn, coolingFrom, countryFromText, description, extractCode, firstText, ixpsIn, lastSlug, mwWithContext, parseAddress, record, renewableFrom, statusFromText } from "./shared.js"; /** * NTT Global Data Centers facility pages * (services.global.ntt/en-us/services-and-products/global-data-centers/global-locations//). * Sitecore page: h1.hero-image-video__title, p.hero-image-video__strapline ("12.6MW of critical IT load…"), an

with the * street address followed by "Contact number", and h3.icon__title / p.icon__summary spec blocks (Power, Connectivity, Cooling, Security). * Metro index pages end in "-data-centers" and are skipped. Campus-level totals ("224MW campus") are never used as facility MW. */ export const nttFacility: Parser = { name: "ntt_facility_v1", version: "1.0.0", pageTypes: ["facility_page"], parse(doc: RawDocument): ExtractedRecord[] { const url = doc.finalUrl; if (/-data-centers\/?$|\/virtual-tour\/?$|\/global-locations\/?$/.test(url)) return []; const m = url.match(/\/global-locations\/([a-z-]+)\/([a-z0-9-]+)\/?$/); if (!m) return []; const region = m[1]!; const slug = lastSlug(url); const $ = load(doc.text); const h1 = firstText($, "h1.hero-image-video__title", "h1"); if (!h1) return []; const strap = firstText($, "p.hero-image-video__strapline"); const summaryTitle = firstText($, "h2.content-summary-old__title", "h2.content-summary__title"); const desc = description(doc.text); const blocks: Record = {}; $("h3.icon__title").each((_, e) => { const k = cleanText($(e).text())?.toLowerCase(); const v = cleanText($(e).siblings("p.icon__summary").first().text()) ?? cleanText($(e).parent().find("p.icon__summary").first().text()); if (k && v) blocks[k] = v; }); const power = blocks["power"] ?? null; const connectivity = blocks["connectivity"] ?? null; const cooling = blocks["cooling"] ?? null; // Address: the

holding "street, city … Contact number: …" (prefer the one with "Contact number"; card titles with digits are not addresses) const h4s = $("h4").map((_, e) => cleanText($(e).text()) ?? "").get().filter(Boolean); const addrH4 = h4s.find((t) => /Contact number/i.test(t) && /\d/.test(t.split(/Contact number/i)[0] ?? "")) ?? h4s.find((t) => /^\d+[\w\s.,'-]*,\s*[A-Za-z]/.test(t) || /\b\d{4,6}\b.*,/.test(t)); const addrStr: string | null = addrH4 ? (cleanText(addrH4.split(/Contact number/i)[0] ?? "")?.replace(/[,\s]+$/, "").replace(/,(\S)/g, ", $1") ?? null) : null; const pa = parseAddress(addrStr); const mwText = [strap, power, summaryTitle, desc].filter(Boolean).join(" \n "); const itCapacityMw = mwWithContext(mwText, /\s*(?:of\s+)?critical\s+(?:IT\s+)?(?:load|capacity|power)/i) ?? mwWithContext(mwText, /\s*(?:of\s+)?IT\s+(?:load|capacity)/i); const code = extractCode(h1); const cityGuess = cleanText(h1.replace(/\b[A-Z]{2,4}\s?\d{1,2}[A-Za-z]?\b/g, " ").replace(/\b(Data\s+Cent(?:er|re)s?|Campus)\b/gi, " ").replace(/\b\d+[a-z]?\b/g, " ").replace(/\b(I{1,3}V?|and)\b/g, " ")); const countryIso2 = pa?.countryIso2 ?? countryFromText(addrStr) ?? countryFromText(h1) ?? (region === "india" ? "IN" : null); const status = statusFromText(`${h1} ${strap ?? ""}`); const data: Record = { name: h1, code, aliases: code ? [`NTT ${code}`] : [], address: pa?.address ?? addrStr, city: pa?.city ?? (pa?.address ? null : cityGuess), regionName: pa?.region ?? null, postalCode: pa?.postal ?? null, countryIso2, itCapacityMw, coolingType: coolingFrom(cooling), renewableClaim: renewableFrom(power), carriers: [], cloudProviders: cloudsIn(connectivity), ixps: ixpsIn(connectivity), status, description: desc ?? strap, externalIds: { ntt_slug: slug }, }; const methods: Record = { name: "selector:h1.hero-image-video__title", code: "regex:code_v1", address: "selector:h4+parse", city: pa?.city ? "selector:h4+parse" : "selector:h1+strip", regionName: "selector:h4+parse", postalCode: "selector:h4+parse", countryIso2: "selector:h4+parse>country", itCapacityMw: "regex:mw_critical_it_load_v1", coolingType: "selector:p.icon__summary(cooling)+regex", renewableClaim: "selector:p.icon__summary(power)+regex", cloudProviders: "selector:p.icon__summary(connectivity)+regex", ixps: "selector:p.icon__summary(connectivity)+regex", status: "regex:status_v1", description: "meta:description", externalIds: "url:slug", }; return [record("facility", `ntt:${slug}`, url, data, methods, 0.9)]; }, };