spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1import type { Parser, ExtractedRecord, RawDocument } from "@dci/connectors";2import { countryFromText, cleanText, parseMoney, slugify } from "@dci/core";3import { loadHtml, txt, resolvePlace, type Place } from "./shared.js";4import { countryFromStateName, stateDisplayName } from "./city-coords.js";56/**7 * Meta data centers — https://datacenters.atmeta.com/all-locations/ (WordPress, server-rendered cards):8 * <article class="card …"> eyebrow = state/country, title = town, excerpt = "$1 billion+ investment | 2010 break ground |9 * 1,000 skilled trade workers … | 350+ operational jobs …", CTA = info-sheet PDF.10 * The page states investment and the break-ground year but NOT the operating status. Status is therefore:11 * break-ground year > current year → announced ; ≥ current year − 1 → under_construction ; older → unknown12 * (recorded in the description and left for reconciliation with other sources — never assumed operational).13 */14export const metaDatacentersParser: Parser = {15 name: "cloud_meta_datacenters_v1",16 version: "1.0.0",17 parse(doc: RawDocument, ctx): ExtractedRecord[] {18 const $ = loadHtml(doc);19 const out: ExtractedRecord[] = [];20 const year = new Date().getUTCFullYear();21 const seen = new Set<string>();22 $("article.card").each((_, card) => {23 const title = txt($(card).find(".card__title").first());24 const eyebrow = txt($(card).find(".card__eyebrow-text").first());25 if (!title) return;26 const excerptHtml = $(card).find(".card__excerpt").first().html() ?? "";27 const excerpt = cleanText(excerptHtml.replace(/<br\s*\/?>/gi, " | ").replace(/<[^>]+>/g, "")) ?? "";28 const key = `meta:dc:${slugify(`${title}-${eyebrow}`)}`;29 if (seen.has(key)) return;30 seen.add(key);31 const regionIso = countryFromStateName(eyebrow);32 const iso = regionIso ?? countryFromText(eyebrow) ?? null;33 let place: Place = resolvePlace(title, iso, regionIso ? eyebrow : null);34 if (!place.city) place.city = title;35 if (!place.countryIso2) place.countryIso2 = iso;36 if (regionIso && !place.regionName) place.regionName = stateDisplayName(eyebrow);37 const breakGround = excerpt.match(/(\d{4})\s*break ground/i)?.[1] ?? null;38 const bgYear = breakGround ? Number(breakGround) : null;39 const status = bgYear == null ? "unknown" : bgYear > year ? "announced" : bgYear >= year - 1 ? "under_construction" : "unknown";40 const investText = excerpt.split("|")[0]?.trim() ?? null;41 const money = parseMoney(investText ?? "");42 const sheet = $(card).find("a[href*='info-sheet']").first().attr("href")?.trim() ?? null;43 const fb = $(card).find(".card__follow-us-cta").first().attr("href")?.trim() ?? null;44 const externalIds: Record<string, string | number> = { meta_location: `${title}, ${eyebrow}` };45 if (sheet) externalIds.meta_info_sheet = sheet;46 if (fb) externalIds.facebook_page = fb;47 if (investText) externalIds.published_investment = investText;48 if (money) { externalIds.investment_amount = money.amount; externalIds.investment_currency = money.currency; }49 out.push({50 kind: "facility",51 key,52 url: doc.finalUrl,53 pageType: "facility_index",54 certainty: 0.9,55 methods: { name: "html:.card__title", regionName: "html:.card__eyebrow-text", constructionStartedOn: "regex:break ground", status: "heuristic:break-ground-year", lat: "curated-city-table" },56 data: {57 name: `Meta ${title} Data Center`,58 operatorName: "Meta",59 city: place.city,60 regionName: place.regionName,61 countryIso2: place.countryIso2,62 lat: place.lat,63 lng: place.lng,64 geoPrecision: place.geoPrecision ?? "city",65 status,66 facilityType: "hyperscale",67 isHyperscale: true,68 constructionStartedOn: breakGround,69 website: sheet ?? doc.finalUrl,70 description: `Meta data center site (${eyebrow}). Published: ${excerpt}${status === "unknown" ? " Operating status not stated on the page." : ""}`,71 externalIds,72 },73 });74 });75 ctx.log("info", `meta: ${out.length} data center sites`);76 return out;77 },78};79