spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1import { load } from "cheerio";2import type { ExtractedRecord, Parser, RawDocument } from "@dci/connectors";3import { cleanText, cloudsIn, coolingFrom, countryFromText, description, extractCode, firstText, ixpsIn, lastSlug, mwWithContext, parseAddress, record, renewableFrom, statusFromText } from "./shared.js";45/**6 * NTT Global Data Centers facility pages7 * (services.global.ntt/en-us/services-and-products/global-data-centers/global-locations/<region>/<slug>).8 * Sitecore page: h1.hero-image-video__title, p.hero-image-video__strapline ("12.6MW of critical IT load…"), an <h4> with the9 * street address followed by "Contact number", and h3.icon__title / p.icon__summary spec blocks (Power, Connectivity, Cooling, Security).10 * Metro index pages end in "-data-centers" and are skipped. Campus-level totals ("224MW campus") are never used as facility MW.11 */12export const nttFacility: Parser = {13 name: "ntt_facility_v1",14 version: "1.0.0",15 pageTypes: ["facility_page"],16 parse(doc: RawDocument): ExtractedRecord[] {17 const url = doc.finalUrl;18 if (/-data-centers\/?$|\/virtual-tour\/?$|\/global-locations\/?$/.test(url)) return [];19 const m = url.match(/\/global-locations\/([a-z-]+)\/([a-z0-9-]+)\/?$/);20 if (!m) return [];21 const region = m[1]!;22 const slug = lastSlug(url);23 const $ = load(doc.text);24 const h1 = firstText($, "h1.hero-image-video__title", "h1");25 if (!h1) return [];26 const strap = firstText($, "p.hero-image-video__strapline");27 const summaryTitle = firstText($, "h2.content-summary-old__title", "h2.content-summary__title");28 const desc = description(doc.text);2930 const blocks: Record<string, string> = {};31 $("h3.icon__title").each((_, e) => {32 const k = cleanText($(e).text())?.toLowerCase();33 const v = cleanText($(e).siblings("p.icon__summary").first().text()) ?? cleanText($(e).parent().find("p.icon__summary").first().text());34 if (k && v) blocks[k] = v;35 });36 const power = blocks["power"] ?? null;37 const connectivity = blocks["connectivity"] ?? null;38 const cooling = blocks["cooling"] ?? null;3940 // Address: the <h4> holding "street, city … Contact number: …" (prefer the one with "Contact number"; card titles with digits are not addresses)41 const h4s = $("h4").map((_, e) => cleanText($(e).text()) ?? "").get().filter(Boolean);42 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));43 const addrStr: string | null = addrH4 ? (cleanText(addrH4.split(/Contact number/i)[0] ?? "")?.replace(/[,\s]+$/, "").replace(/,(\S)/g, ", $1") ?? null) : null;44 const pa = parseAddress(addrStr);4546 const mwText = [strap, power, summaryTitle, desc].filter(Boolean).join(" \n ");47 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);48 const code = extractCode(h1);49 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, " "));50 const countryIso2 = pa?.countryIso2 ?? countryFromText(addrStr) ?? countryFromText(h1) ?? (region === "india" ? "IN" : null);51 const status = statusFromText(`${h1} ${strap ?? ""}`);5253 const data: Record<string, unknown> = {54 name: h1, code, aliases: code ? [`NTT ${code}`] : [],55 address: pa?.address ?? addrStr, city: pa?.city ?? (pa?.address ? null : cityGuess), regionName: pa?.region ?? null, postalCode: pa?.postal ?? null, countryIso2,56 itCapacityMw, coolingType: coolingFrom(cooling), renewableClaim: renewableFrom(power),57 carriers: [], cloudProviders: cloudsIn(connectivity), ixps: ixpsIn(connectivity),58 status, description: desc ?? strap,59 externalIds: { ntt_slug: slug },60 };61 const methods: Record<string, string> = {62 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",63 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",64 status: "regex:status_v1", description: "meta:description", externalIds: "url:slug",65 };66 return [record("facility", `ntt:${slug}`, url, data, methods, 0.9)];67 },68};69