/** Operators2 — Americas facility parsers (US/CA/LATAM colocation + wholesale operators). */ import type { ExtractedRecord } from "@dci/connectors"; import { extractGeo } from "@dci/connectors"; import { countryFromText } from "@dci/core"; import { usStateCode } from "../operators1/shared.js"; import { Rec, all, applyLdAddress, applyLdGeo, breadcrumbLeaf, capacityMw, certifications, countryFromCity, countryOf, describe, facilityParser, first, ha, itMw, key, latinNumbers, ldWithAddress, mw, pue, safeCountryText, seg, splitStreetCity, sqm, statusOf, tier, titleCase, usAddress } from "./shared.js"; /** DataBank — /data-centers//[/]/ ; " ()" heading followed by the street address; stats in .c-figure-stats / .c-info-table__stats. Campus pages (code before name) yield nothing. */ facilityParser("databank_facility_v1", (p) => { const head = p.text.match(/([A-Z][^\n(]{3,80}?)\s*\n\s*\(([A-Z]{2,5}\s?\d{1,2})\)\s*\n\s*(\d[^\n]*?)\s+-\s+([^,\n]+),\s+([A-Z]{2})\s+(\d{5})/); const uk = head ? null : p.text.match(/([A-Z][^\n(]{3,80}?)\s*\n\s*\(([A-Z]{2,5}\s?\d{1,2})\)\s*\n\s*([^\n]{6,120}?)\s+-\s+([A-Za-z ]+),?\s+([A-Z]{1,2}\d[\dA-Z]? ?\d[A-Z]{2})\b/); const m = head ?? uk; if (!m) return []; // metro / campus index page const code = m[2]!.replace(/\s+/g, ""); const r = new Rec(); r.set("name", `${m[1]!.trim().replace(/^Explore\s+/i, "")} (${code})`, "regex:heading_code").set("code", code, "regex:heading_code"); if (head) r.set("address", head[3], "regex:address_line").set("city", head[4], "regex:address_line").set("regionName", head[5], "regex:address_line").set("postalCode", head[6], "regex:address_line").set("countryIso2", "US", "regex:address_line"); else if (uk) r.set("address", uk[3], "regex:address_line_uk").set("city", uk[4], "regex:address_line_uk").set("postalCode", uk[5], "regex:address_line_uk").set("countryIso2", "GB", "regex:address_line_uk"); p.$(".c-figure-stats li, .c-info-table__stats li").each((_, li) => { const strong = p.$(li).find("strong").first().text().trim(); const label = p.$(li).text().replace(strong, "").trim(); if (/IT Square Feet/i.test(label)) r.set("buildingSqm", sqm(`${strong} sq ft`), "selector:.c-figure-stats+sqft"); else if (/Critical IT Load/i.test(label)) r.set("itCapacityMw", mw(strong), "selector:.c-info-table__stats+mw"); else if (/Energy Source/i.test(label) && /renewable/i.test(strong)) r.set("renewableClaim", `${strong} energy source`, "selector:.c-info-table__stats"); else if (/Cooling Design/i.test(label)) r.set("coolingType", `${strong} cooling design`, "selector:.c-info-table__stats"); }); if (!r.has("itCapacityMw")) r.set("itCapacityMw", itMw(p.text), "regex:it_mw_v1"); r.set("certifications", certifications(p.text), "regex:certifications_v1").set("description", describe(p), "meta:description"); return [r.done(key("databank", code), p.url)]; }); /** * EdgeConneX — metro pages; one JSON-LD LocalBusiness per facility (address only). Without JSON-LD, one metro-level record * (city / state from the BreadcrumbList leaf "Richmond, VA", else from the h1 minus its "Data Center" suffix; facility codes as aliases). */ facilityParser("edgeconnex_facility_v1", (p) => { const out: ExtractedRecord[] = []; const metro = seg(p.url).pop() ?? ""; for (const o of ldWithAddress(p.html, "LocalBusiness")) { const a = o.address; if (!a.streetAddress) continue; const r = new Rec(); r.set("name", `EdgeConneX ${String(a.addressLocality ?? titleCase(metro))} – ${String(a.streetAddress)}`, "json-ld:LocalBusiness.name+street"); applyLdAddress(r, a); applyLdGeo(r, o); r.set("description", describe(p), "meta:description").set("certifications", certifications(p.text), "regex:certifications_v1"); out.push(r.done(key("edgeconnex", metro, String(a.streetAddress)), p.url, 0.8)); } if (out.length) return out; const h1 = p.h1 ?? ""; if (!h1 || !/edgeconnex/i.test(p.title ?? "")) return []; // h1 "Richmond Data Center" is the facility name; the breadcrumb leaf "Richmond, VA" is the location line const crumb = breadcrumbLeaf(p.html); const locLine = crumb && crumb.length <= 60 ? crumb : h1.replace(/\s+Data\s+Cent(?:er|re)s?$/i, ""); const locMethod = crumb && crumb.length <= 60 ? "json-ld:BreadcrumbList" : "selector:h1"; const loc = locLine.match(/^([A-Z][^,]+?)(?:,\s*([A-Z][^,]+))?$/); if (!loc) return []; const r = new Rec(); r.set("name", `EdgeConneX ${h1}`, "selector:h1").set("city", loc[1], locMethod); const region = loc[2]?.trim() ?? null; const c = region && usStateCode(region) ? { iso: "US", method: "lookup:us-state" } : countryOf(region, h1, p.desc, p.text.slice(0, 1500)); if (c) r.set("countryIso2", c.iso, c.method); if (region && c?.iso === "US") r.set("regionName", region, locMethod); const codes = [...new Set([...p.text.matchAll(/\b([A-Z]{3}\d{2})\b/g)].map((m) => m[1]!))]; r.set("aliases", codes, "regex:facility_codes"); const single = codes.length === 1 ? p.text.match(new RegExp(`${codes[0]}:\\s*(\\d[\\d.]*\\+?\\s*MW\\+?)`)) : null; const st = statusOf(p.text, 2500); if (st) r.set("status", st, "regex:status_v1"); if (single) r.set(st && st !== "operational" ? "plannedPowerMw" : "itCapacityMw", mw(single[1]), "regex:code_mw_row"); r.set("tier", tier(p.text), "regex:tier_v1").set("certifications", certifications(p.text), "regex:certifications_v1").set("description", describe(p), "meta:description"); return [r.done(key("edgeconnex", metro), p.url, 0.65)]; }); /** TierPoint — /data-centers/// ; JSON-LD LocalBusiness (#location) with address + geo. */ facilityParser("tierpoint_facility_v1", (p) => { const o = ldWithAddress(p.html, "LocalBusiness").find((x) => String(x["@id"] ?? "").includes("#location")) ?? ldWithAddress(p.html, "LocalBusiness")[0]; if (!o) return []; const r = new Rec(); r.set("name", (o.name as string) ?? p.h1, "json-ld:LocalBusiness.name"); applyLdAddress(r, o.address); applyLdGeo(r, o); r.set("description", (o.description as string) ?? describe(p), "json-ld:LocalBusiness.description"); r.set("itCapacityMw", itMw(p.text), "regex:it_mw_v1").set("buildingSqm", sqm(first(p.text, /([\d,]+\+?\s*(?:total\s+)?square feet)/i)), "regex:sqft_v1"); r.set("pue", pue(p.text), "regex:pue_v1").set("tier", tier(p.text), "regex:tier_v1").set("certifications", certifications(p.text), "regex:certifications_v1"); const st = statusOf(`${r.data.description ?? ""}\n${p.text}`); if (st) r.set("status", st, "regex:status_v1"); const slug = seg(p.url).slice(-2).join("-"); return [r.done(key("tierpoint", slug), p.url, 0.9)]; }); /** Evocative — /data-centers/--data-center-/ ; address in Google Maps embed, MW in meta description. */ facilityParser("evocative_facility_v1", (p) => { const slug = seg(p.url).pop() ?? ""; const m = slug.match(/^(.+?)-data-center-([a-z]{3}\d+)$/); if (!m) return []; const code = m[2]!.toUpperCase(); const loc = m[1]!.split("-"); const region = loc.length > 1 && loc[loc.length - 1]!.length === 2 ? loc.pop()!.toUpperCase() : null; const r = new Rec(); r.set("code", code, "url:slug").set("name", `Evocative ${titleCase(loc.join("-"))} ${code}`, "url:slug").set("city", titleCase(loc.join("-")), "url:slug").set("regionName", region, "url:slug").set("countryIso2", "US", "default:operator"); const q = p.html.match(/maps\/embed\/v1\/place\?[^"']*?\bq=([^&"']+)/); if (q) { const addr = usAddress(decodeURIComponent(q[1]!.replace(/\+/g, " "))); if (addr) r.set("address", addr.street, "link:google-maps-embed").set("city", addr.city, "link:google-maps-embed").set("regionName", addr.region, "link:google-maps-embed").set("postalCode", addr.postal, "link:google-maps-embed"); } const desc = p.desc ?? ""; r.set("itCapacityMw", itMw(desc) ?? itMw(p.text), "regex:it_mw_v1").set("buildingSqm", sqm(first(desc, /([\d,]+\s*(?:sq\.?\s*ft|sqft|square feet))/i)), "meta:description+sqft"); r.set("tier", tier(p.text), "regex:tier_v1").set("certifications", certifications(p.text), "regex:certifications_v1").set("description", describe(p), "meta:description"); return [r.done(key("evocative", code), p.url)]; }); /** Serverfarm — /--data-center/ ; data-lat/lng map marker, spec list "IT 100MW", carriers. */ facilityParser("serverfarm_facility_v1", (p) => { const h1 = p.h1 ?? p.title ?? ""; if (!/data center/i.test(h1)) return []; const r = new Rec(); const parts = h1.split(/\s[–-]\s/); const code = parts.length > 1 && /^[A-Z]{2,4}\d/.test(parts[0]!.trim()) ? parts[0]!.trim() : null; const cityPart = (parts[parts.length - 1] ?? h1).replace(/\s*data center.*$/i, "").trim(); r.set("name", `Serverfarm ${h1.replace(/\s*data center\s*$/i, "")}`, "selector:h1").set("code", code, "selector:h1").set("city", cityPart, "selector:h1"); const c = countryOf(cityPart) ?? countryOf(p.text.slice(0, 3000)); if (c) r.set("countryIso2", c.iso, c.method); const g = extractGeo(p.html); if (g && g.method === "attr:data-lat") r.set("lat", g.lat, g.method).set("lng", g.lng, g.method).set("geoPrecision", "exact", g.method); r.set("itCapacityMw", mw(first(p.text, /\bIT\s+(\d[\d.,]*\+?\s*MW\+?)/)) ?? itMw(p.text), "regex:it_mw_serverfarm").set("pue", pue(p.text), "regex:pue_v1"); const carriers = first(p.text, /Carriers\s*\n?\s*([A-Z][^\n]{5,300}?)(?:\n|$)/); if (carriers && /,/.test(carriers)) r.set("carriers", carriers.split(/,\s*/).map((s) => s.trim()).filter((s) => s.length > 1 && s.length < 40), "regex:carriers_list"); r.set("certifications", certifications(p.text), "regex:certifications_v1").set("description", describe(p), "meta:description"); return [r.done(key("serverfarm", code ?? cityPart), p.url)]; }); /** Sabey — /locations/ ; campus stats (MW capacity, PUE, sqft), city from prose. */ facilityParser("sabey_facility_v1", (p) => { const name = (p.title ?? "").split(/\s*[|–-]\s*/)[0]?.trim(); if (!name || !/^SDC\b/i.test(name)) return []; const r = new Rec(); r.set("name", `Sabey ${name}`, "html:title").set("countryIso2", "US", "default:operator"); const loc = p.text.match(/(?:campus|data center) in ([A-Z][a-zA-Z.]+(?: [A-Z][a-zA-Z.]+)?), ([A-Z][a-z]+(?: [A-Z][a-z]+)?|[A-Z]{2})\b/); if (loc) r.set("city", loc[1], "regex:in_city_state").set("regionName", loc[2], "regex:in_city_state"); r.set("totalPowerMw", mw(first(p.text, /(\d[\d.,]*)\s*\n?\s*MW Capacity/i)?.concat(" MW")) ?? capacityMw(p.text), "regex:mw_capacity_stat"); r.set("pue", pue(p.text), "regex:pue_v1").set("buildingSqm", sqm(first(p.text, /([\d,]+\s*square feet)/i)), "regex:sqft_v1").set("certifications", certifications(p.text), "regex:certifications_v1").set("description", describe(p), "meta:description"); const st = statusOf(p.text); if (st) r.set("status", st, "regex:status_v1"); return [r.done(key("sabey", seg(p.url).pop()), p.url)]; }); /** Stream Data Centers — /locations// ; "
Product Type: … Status: …" + hero stats (MW, SF, acres). */ facilityParser("stream_facility_v1", (p) => { const tag = p.text.match(/\n\s*(?:Product Type|Status):/); if (!tag || tag.index == null) return []; // metro page const before = p.text.slice(0, tag.index).split("\n").map((l) => l.trim()).filter(Boolean); const locLine = before[before.length - 1] ?? ""; const tail = p.text.slice(tag.index, tag.index + 400); const ptype = first(tail, /Product Type:\s*([^\n]+)/) ?? ""; const status = first(tail, /Status:\s*([^\n]+)/) ?? ""; const r = new Rec(); const code = p.h1 ?? p.title ?? ""; r.set("name", `Stream ${code}`, "selector:h1").set("code", /^[A-Z]{3,5}\d?$/.test(code) ? code : null, "selector:h1").set("countryIso2", "US", "default:operator"); const addr = locLine.match(/^(\d[^\n,]*?)\s*,?\s*([A-Z][a-zA-Z.'\- ]+?),\s*([A-Z][a-z]+(?: [A-Z][a-z]+)?|[A-Z]{2})\s*(\d{5})?$/); const csz = locLine.match(/^([A-Z][a-zA-Z.'\- ]+?),\s*([A-Z][a-z]+(?: [A-Z][a-z]+)?|[A-Z]{2})\s*(\d{5})?$/); if (addr && /\d{5}$/.test(locLine)) { const sc = splitStreetCity(`${addr[1]} ${addr[2]}`.trim()); if (sc) r.set("address", sc.street, "regex:address_line").set("city", sc.city, "regex:address_line"); else r.set("city", addr[2], "regex:address_line"); r.set("regionName", addr[3], "regex:address_line").set("postalCode", addr[4] ?? null, "regex:address_line"); } else if (csz) r.set("city", csz[1], "regex:city_state").set("regionName", csz[2], "regex:city_state").set("postalCode", csz[3] ?? null, "regex:city_state"); else { const cs = locLine.match(/([A-Z][A-Za-z.'\- ]+?),\s*([A-Z][a-z]+(?: [A-Z][a-z]+)?)$/); if (cs) r.set("city", cs[1], "regex:city_state").set("regionName", cs[2], "regex:city_state"); } if (status) r.set("status", /leased|operational|in service|complete|sold|stabili[sz]ed/i.test(status) ? "operational" : /construction/i.test(status) ? "under_construction" : /planned|future|available|development|land/i.test(status) ? "announced" : null, "regex:status_label"); if (/hyperscale/i.test(ptype)) r.set("facilityType", "hyperscale", "regex:product_type"); else if (/wholesale|build-to-suit/i.test(ptype)) r.set("facilityType", "wholesale", "regex:product_type"); else if (/private|enterprise/i.test(ptype)) r.set("facilityType", "enterprise", "regex:product_type"); r.set("itCapacityMw", itMw(p.text), "regex:it_mw_v1"); if (!r.has("itCapacityMw")) r.set("totalPowerMw", mw(first(p.text, /^\s*(\d[\d.]*\+?\s*MW\+?)\s*\n\s*(?:Campus|Power|Capacity)/m)), "regex:hero_mw_stat"); r.set("buildingSqm", sqm(first(p.text, /([\d,]+\s*(?:Square Feet|SF))\s*\n?\s*(?:Data Center|Building|$)/im) ?? first(p.text, /([\d,]+\s*Square Feet)/i)), "regex:sqft_v1").set("siteAreaHa", ha(first(p.text, /([\d.]+\s*Acres)/i)), "regex:acres_v1"); r.set("certifications", certifications(p.text), "regex:certifications_v1").set("description", describe(p), "meta:description"); return [r.done(key("stream", seg(p.url).pop()), p.url)]; }); /** Skybox — /locations/ ; stat header "CITY 30 MW 141,000 SF", JSON-LD Organization description. */ facilityParser("skybox_facility_v1", (p) => { const seg1 = (p.title ?? "").split("|").map((s) => s.trim()).find((s) => s && !/^Skybox Datacenters$/i.test(s)); if (!seg1) return []; const name = /^Skybox/i.test(seg1) ? seg1 : `Skybox ${seg1}`; const r = new Rec(); r.set("name", name.replace(/™/g, ""), "html:title").set("countryIso2", "US", "default:operator"); const h1 = (p.h1 ?? "").replace(/™/g, ""); const dev = /PowerCampus|Build to Suit|Development/i.test(h1); const hero = p.text.match(/^\s*([A-Za-z™ ]{2,40})\n\s*(\d[\d.,]*\+?\s*MW\+?)\n(?:\s*([\d,]+\s*SF)\n)?(?:\s*([\d,.]+\s*Acres)\n)?/m); const cityRaw = h1.replace(/^PowerCampus\s*/i, "").trim() || hero?.[1]?.trim() || ""; r.set("city", cityRaw ? titleCase(cityRaw.toLowerCase()) : null, "selector:h1"); if (hero) { r.set(dev ? "plannedPowerMw" : "totalPowerMw", mw(hero[2]), "regex:hero_stats").set("buildingSqm", sqm(hero[3] ?? null), "regex:hero_stats").set("siteAreaHa", ha(hero[4] ?? null), "regex:hero_stats"); } const state = first(`${p.desc ?? ""} ${p.text.slice(0, 1500)}`, /\b(Texas|Georgia|Illinois|California|Virginia|Arizona|Ohio|Oregon|Nevada|Washington|Colorado|Florida|Utah|Iowa)\b/); if (state) r.set("regionName", state, "regex:state_name"); if (!r.has("siteAreaHa")) r.set("siteAreaHa", ha(first(p.text, /((?:\d+|[a-z-]+)\s*-?\s*acre)/i)?.replace(/^(twenty-five|twenty|ten|fifteen|thirty|forty|fifty)\b/i, (w) => ({ ten: "10", fifteen: "15", twenty: "20", "twenty-five": "25", thirty: "30", forty: "40", fifty: "50" })[w.toLowerCase()] ?? w)), "regex:acres_v1"); r.set("certifications", certifications(p.text), "regex:certifications_v1").set("description", describe(p), "meta:description"); const st = statusOf(p.text, 1500); if (st) r.set("status", st, "regex:status_v1"); else if (dev) r.set("status", "announced", "regex:powercampus_development"); if (dev) r.set("facilityType", "hyperscale", "regex:powercampus"); return [r.done(key("skybox", seg(p.url).pop()), p.url)]; }); /** Hivelocity — /products/colocation// ; address block, code, sqft, carriers, certifications. */ facilityParser("hivelocity_facility_v1", (p) => { const h1 = p.h1 ?? ""; if (!/colocation|data center/i.test(h1)) return []; const r = new Rec(); const code = first(p.text, /\b([A-Z]{3}\d)\s+Colo/); r.set("name", `Hivelocity ${h1.replace(/\s*colocation\s*data\s*center.*$/i, "").trim()}${code ? ` (${code})` : ""}`, "selector:h1").set("code", code, "regex:code_colo"); const addr = usAddress(p.text.slice(0, 4000)); if (addr) r.set("address", addr.street, "regex:us_address_v1").set("city", addr.city, "regex:us_address_v1").set("regionName", addr.region, "regex:us_address_v1").set("postalCode", addr.postal, "regex:us_address_v1").set("countryIso2", /^\d{5}/.test(addr.postal) ? "US" : "CA", "regex:us_address_v1"); else { const c = countryOf(p.text.slice(0, 3000)); if (c) r.set("countryIso2", c.iso, c.method); } r.set("buildingSqm", sqm(first(p.text, /([\d,]+\s*square feet)/i)), "regex:sqft_v1"); const carriers = first(p.text, /Carrier Neutral\s*[—-]\s*([^\n]{5,300}?)(?:\s+Peering|\n)/i); if (carriers) r.set("carriers", carriers.split(/,\s*/).map((s) => s.replace(/\(.*?\)/g, "").trim()).filter((s) => s.length > 1 && s.length < 40), "regex:carriers_list"); r.set("certifications", certifications(p.text), "regex:certifications_v1").set("description", describe(p), "meta:description"); return [r.done(key("hivelocity", code ?? seg(p.url).pop()), p.url)]; }); /** Element Critical — /data-centers// ; "Location , , , " spec table. */ facilityParser("elementcritical_facility_v1", (p) => { const loc = p.text.match(/Location\s*\n?\s*(\d[^\n]*?),\s*([A-Z][a-zA-Z .]+?),\s*([A-Z][a-zA-Z ]+?),?\s*(\d{5})\b/); if (!loc) return []; // metro page const r = new Rec(); const name = (p.h1 ?? p.title ?? "").replace(/\s*data center services\s*$/i, "").replace(/\s*[-|].*$/, "").trim(); r.set("name", `Element Critical ${name}`, "selector:h1").set("address", loc[1], "regex:location_row").set("city", loc[2], "regex:location_row").set("regionName", loc[3], "regex:location_row").set("postalCode", loc[4], "regex:location_row").set("countryIso2", "US", "regex:location_row"); r.set("buildingSqm", sqm(first(p.text, /Property Size\s*\n?\s*([\d,]+\s*Sq\.?\s*Ft\.?)/i)), "regex:property_size").set("totalPowerMw", mw(first(p.text, /Power\s*\n?\s*(\d[\d.]*\+?\s*MW\+?)/)) ?? capacityMw(p.text), "regex:power_row"); r.set("tier", tier(p.text), "regex:tier_v1").set("certifications", certifications(p.text), "regex:certifications_v1").set("description", describe(p), "meta:description"); return [r.done(key("elementcritical", loc[1]), p.url)]; }); /** 365 Data Centers — /-data-center/ ; address at the top of the page, MW of power, sqft. */ facilityParser("threesixtyfive_facility_v1", (p) => { const addr = usAddress(p.text.slice(0, 3000)) ?? usAddress(p.desc ?? ""); if (!addr) return []; const r = new Rec(); const name = (p.title ?? "").split(/\s*[-|]\s*/)[0]?.trim() || titleCase(seg(p.url).pop() ?? ""); r.set("name", `365 Data Centers ${name.replace(/\s*data center$/i, "")}`, "html:title").set("address", addr.street, "regex:us_address_v1").set("city", addr.city, "regex:us_address_v1").set("regionName", addr.region, "regex:us_address_v1").set("postalCode", addr.postal, "regex:us_address_v1").set("countryIso2", "US", "regex:us_address_v1"); r.set("totalPowerMw", mw(first(p.text, /(\d[\d.]*\+?\s*MW\+?) of (?:critical\s+)?power/i)) ?? capacityMw(p.text), "regex:mw_of_power").set("buildingSqm", sqm(first(p.text, /([\d,]+\s*sq\.?\s*ft)\s+(?:data center|facility)/i)), "regex:sqft_v1"); r.set("certifications", certifications(p.text), "regex:certifications_v1").set("description", describe(p), "meta:description"); return [r.done(key("365dc", seg(p.url).pop()), p.url)]; }); /** Netrality — /data-center// ; h1 "City, State Data Center ", carrier-hotel positioning. */ facilityParser("netrality_facility_v1", (p) => { const h1 = p.h1 ?? ""; const m = h1.match(/^(.+?),\s*([A-Za-z ]+?)\s+Data Center\s+(.+)$/); if (!m) return []; const r = new Rec(); r.set("name", `Netrality ${m[3]!.trim()} – ${m[1]!.trim()}`, "selector:h1").set("address", m[3], "selector:h1").set("city", m[1], "selector:h1").set("regionName", m[2], "selector:h1").set("countryIso2", "US", "default:operator"); const sf = p.text.match(/([\d,]+)(k)?\s*Sq\.?\s*Ft\.?/i); if (sf) { let n = Number(sf[1]!.replace(/,/g, "")); if (sf[2] && n < 1000) n *= 1000; r.set("buildingSqm", sqm(`${n} sq ft`), "regex:sqft_k_v1"); } if (/carrier hotel/i.test(p.text.slice(0, 3000))) r.set("facilityType", "carrier_hotel", "regex:carrier_hotel"); r.set("certifications", certifications(p.text), "regex:certifications_v1").set("description", describe(p), "meta:description"); return [r.done(key("netrality", seg(p.url).pop()), p.url, 0.75)]; }); /** CloudHQ — /campus// ; published "lat, lng" line, total critical IT load, SF, RFS timing. */ facilityParser("cloudhq_campus_v1", (p) => { const h1 = p.h1 ?? ""; if (!/campus/i.test(h1)) return []; const r = new Rec(); r.set("name", `CloudHQ ${h1}`, "selector:h1").set("code", first(h1, /^([A-Z]{3})\b/), "selector:h1"); const ll = p.text.match(/^\s*(-?\d{1,2}\.\d{3,}),\s*(-?\d{1,3}\.\d{3,})\s*$/m); if (ll) r.set("lat", Number(ll[1]), "text:coordinates_line").set("lng", Number(ll[2]), "text:coordinates_line").set("geoPrecision", "parcel", "text:coordinates_line"); r.set("itCapacityMw", mw(first(p.text, /(\d[\d.,]*\s*MWs?)\s+Total Critical IT Load/i)) ?? itMw(p.text), "regex:total_critical_it").set("buildingSqm", sqm(first(p.text, /([\d,]+\s*SF)\b/)), "regex:sf_v1"); const st = statusOf(p.text, 1500); if (st) r.set("status", st, "regex:status_v1"); const cityGuess = first(p.text, /(?:of|in|near) ([A-Z][a-zA-Z]+(?: [A-Z][a-zA-Z]+)?)(?:’s|'s)? (?:Schiphol|airport|metro|area|CBD)/); const c = countryOf(p.text.slice(0, 4000), cityGuess); if (c) r.set("countryIso2", c.iso, c.method); r.set("city", cityGuess, "regex:near_city"); r.set("facilityType", "hyperscale", "default:operator").set("description", describe(p), "meta:description"); return [r.done(key("cloudhq", seg(p.url).pop()), p.url, 0.8)]; }); /** ODATA — /en/blog/data-center/dc-/ ; IT power, floor area, Google Maps link with coordinates, Tier III. */ facilityParser("odata_facility_v1", (p) => { const code = first(`${p.title ?? ""} ${p.h1 ?? ""} ${p.text.slice(0, 200)}`, /\bDC[\s-]?([A-Z]{2}\d{2})\b/); if (!code) return []; const r = new Rec(); r.set("name", `ODATA ${code}`, "html:title").set("code", code, "html:title"); const t = latinNumbers(p.text); r.set("itCapacityMw", mw(first(t, /IT power\s*\n?\s*(\d[\d.,]*\+?\s*MW\+?)/i)) ?? itMw(t), "regex:it_power_row").set("buildingSqm", sqm(first(t, /Floor area\s*\n?\s*([\d.,]+\s*m²)/i)), "regex:floor_area_row"); const g = extractGeo(p.html); if (g && g.method === "link:google-maps") r.set("lat", g.lat, g.method).set("lng", g.lng, g.method).set("geoPrecision", "exact", g.method); const loc = p.text.match(/Located in (?:the )?(?:[A-Z][\w ]+? area in )?([A-Z][^,.\n(]{2,40}?)\s*[,.(]/); if (loc) r.set("city", loc[1], "regex:located_in"); const c = countryOf(safeCountryText(p.text.slice(0, 5000)), loc?.[1]); const prefix: Record = { SP: "BR", RJ: "BR", QR: "MX", ST: "CL", BG: "CO" }; if (c) r.set("countryIso2", c.iso, c.method); else r.set("countryIso2", prefix[code.slice(0, 2)] ?? null, "lookup:odata-code-prefix"); r.set("tier", tier(p.text), "regex:tier_v1").set("certifications", certifications(p.text), "regex:certifications_v1").set("facilityType", "wholesale", "default:operator").set("description", describe(p), "meta:description"); return [r.done(key("odata", code), p.url)]; }); /** Ark Data Centers (arkdna.com, formerly Involta) — /data-centers/// metro pages listing 1–2 facilities. */ facilityParser("arkdna_facility_v1", (p) => { const parts = seg(p.url); if (parts.length < 3 || parts[0] !== "data-centers") return []; const r = new Rec(); const h1 = p.h1 ?? titleCase(parts[2]!); r.set("name", `Ark ${h1.replace(/\s*data centers?$/i, "")}`, "selector:h1").set("city", titleCase(parts[2]!), "url:segment").set("regionName", titleCase(parts[1]!), "url:segment").set("countryIso2", "US", "default:operator"); r.set("buildingSqm", sqm(first(`${p.desc ?? ""}\n${p.text}`, /([\d,]+\+?\s*square feet)/i)), "regex:sqft_v1").set("tier", tier(p.text), "regex:tier_v1").set("certifications", certifications(p.text), "regex:certifications_v1").set("description", describe(p), "meta:description"); return [r.done(key("arkdna", parts[1], parts[2]), p.url, 0.7)]; }); export const AMERICAS_PARSERS = ["databank_facility_v1", "edgeconnex_facility_v1", "tierpoint_facility_v1", "evocative_facility_v1", "serverfarm_facility_v1", "sabey_facility_v1", "stream_facility_v1", "skybox_facility_v1", "hivelocity_facility_v1", "elementcritical_facility_v1", "threesixtyfive_facility_v1", "netrality_facility_v1", "cloudhq_campus_v1", "odata_facility_v1", "arkdna_facility_v1"]; void all; void countryFromCity; void countryFromText; void usAddress;