import { load } from "cheerio"; import type { ExtractedRecord, Parser, RawDocument } from "@dci/connectors"; import { areaSqm, cleanText, description, parseMw, record, renewableFrom, usStateCode } from "./shared.js"; /** * Switch campus pages (www.switch.com/core|citadel|keep|pyramid/). The body is rendered client-side (the
is * empty in the HTML), so the only server-rendered facts are the ("Switch – The Core Campus, Las Vegas, Nevada") * and the meta description ("Switch's Tier 5 Platinum rated multi-tenant/colocation data center with up to 2.4 million sqft * and 315 MW."). "up to N MW" is the design/full-build figure → plannedPowerMw. No address/coordinates on the page. */ export const switchCampus: Parser = { name: "switch_campus_v1", version: "1.0.0", pageTypes: ["facility_page"], parse(doc: RawDocument): ExtractedRecord[] { const url = doc.finalUrl; const m = url.match(/switch\.com\/(core|citadel|keep|pyramid|rock)\/?$/); if (!m) return []; const slug = m[1]!; const $ = load(doc.text); const title = cleanText($('meta[property="og:title"]').attr("content") ?? $("title").first().text()); if (!title) return []; const t = title.replace(/^Switch\s*[–—-]\s*/, ""); const parts = t.split(",").map((s) => s.trim()).filter(Boolean); // "The Core Campus, Las Vegas, Nevada" → campus / city / state. When the title is a slogan instead // (Pyramid: "GRAND RAPIDS: Largest, Most Advanced Campus …"), fall back to the campus table published on // Switch's own market pages (/las-vegas/, /tahoe-reno/, /atlanta/, /grand-rapids/, /austin/). const TABLE: Record<string, { campus: string; city: string; state: string }> = { core: { campus: "The Core Campus", city: "Las Vegas", state: "NV" }, citadel: { campus: "The Citadel Campus", city: "Tahoe Reno", state: "NV" }, keep: { campus: "The Keep Campus", city: "Atlanta", state: "GA" }, pyramid: { campus: "The Pyramid Campus", city: "Grand Rapids", state: "MI" }, rock: { campus: "The Rock Campus", city: "Austin", state: "TX" } }; const parsedState = usStateCode(parts[2] ?? null); const fromTitle = parts.length >= 3 && parsedState && /campus/i.test(parts[0] ?? ""); const tbl = TABLE[slug]; const campus = fromTitle ? parts[0]! : tbl?.campus ?? `The ${slug[0]!.toUpperCase()}${slug.slice(1)} Campus`; const city = fromTitle ? parts[1]! : tbl?.city ?? null; const regionName = fromTitle ? parsedState : tbl?.state ?? null; const desc = description(doc.text); const tier = desc?.match(/Tier\s?(?:5|IV|4|III|3)(?:\s?(?:Platinum|Gold))?/i)?.[0] ?? null; const upTo = desc?.match(/up to[\s\S]{0,80}?(\d[\d.,]*\s*(?:MW|megawatts?))\b/i)?.[1] ?? null; const anyMw = desc ? parseMw(desc) : null; const plannedPowerMw = upTo ? parseMw(upTo) : null; const totalPowerMw = !upTo && anyMw ? anyMw : null; const buildingSqm = areaSqm(desc); const data: Record<string, unknown> = { name: /^switch/i.test(campus) ? campus : `Switch ${campus}`, code: slug.toUpperCase(), aliases: [campus, `Switch ${slug.toUpperCase()}`], city, regionName, countryIso2: regionName ? "US" : null, tier, plannedPowerMw, totalPowerMw, buildingSqm, renewableClaim: renewableFrom(desc), facilityType: "colocation", description: desc, externalIds: { switch_slug: slug }, }; const src = fromTitle ? "meta:og:title+parse" : "fallback:switch-campus-table"; const methods: Record<string, string> = { name: src, code: "url:slug", city: src, regionName: src, countryIso2: `${src}>state`, tier: "meta:description+regex:tier", plannedPowerMw: "meta:description+regex:up_to_mw", totalPowerMw: "meta:description>mw", buildingSqm: "meta:description>sqm", renewableClaim: "meta:description+regex:renewable_v1", facilityType: "meta:description", description: "meta:description", externalIds: "url:slug", }; return [record("facility", `switch:${slug}`, url, data, methods, 0.7)]; }, };