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, slugify } from "@dci/core";3import { loadHtml, txt, parenthetical, resolvePlace, regionRecord, type Place } from "./shared.js";4import { normKey } from "./city-coords.js";56/**7 * Oracle Cloud Infrastructure — https://www.oracle.com/cloud/public-cloud-regions/8 * Server-rendered HTML (needs a browser identity: the bot UA gets 403). Sections (h2): Commercial,9 * Sovereign (EU Sovereign Cloud), Government, Multicloud (Oracle Database@AWS/Azure/Google — NOT Oracle10 * regions, skipped). Each table: <td geo-data="us-ashburn-1">US East (Ashburn)</td> … <td>Live|Coming soon|Planned</td>.11 * Quirks seen live: a few rows lack geo-data, one code is duplicated ("Spain Central 2"), and two rows have12 * swapped codes (ap-batam-1 / ap-hyderabad-1). The parser trusts the display name and falls back to a slug13 * code when the geo-data code contradicts the city in the name.14 */15const PREFIX_ISO: Array<[RegExp, string]> = [16 [/^(US|United States)\b/i, "US"], [/^Canada\b/i, "CA"], [/^Mexico\b/i, "MX"], [/^Brazil\b/i, "BR"], [/^Chile\b/i, "CL"], [/^Colombia\b/i, "CO"],17 [/^France\b/i, "FR"], [/^Germany\b/i, "DE"], [/^Italy\b/i, "IT"], [/^Netherlands\b/i, "NL"], [/^Serbia\b/i, "RS"], [/^Spain\b/i, "ES"], [/^Sweden\b/i, "SE"], [/^Switzerland\b/i, "CH"], [/^UK\b/i, "GB"],18 [/^Israel\b/i, "IL"], [/^Kenya\b/i, "KE"], [/^Morocco\b/i, "MA"], [/^Saudi Arabia\b/i, "SA"], [/^South Africa\b/i, "ZA"], [/^UAE\b/i, "AE"],19 [/^Australia/i, "AU"], [/^India\b/i, "IN"], [/^Indonesia\b/i, "ID"], [/^Japan\b/i, "JP"], [/^Malaysia\b/i, "MY"], [/^Singapore\b/i, "SG"], [/^South Korea\b/i, "KR"],20];2122/** OCI codes legitimately named after a different town than the one displayed (uk-cardiff-1 is "UK West (Newport)"). */23const CODE_CITY_ALIASES: Record<string, string[]> = { "uk-cardiff-1": ["newp"], "sa-vinhedo-1": ["vinh"], "ap-chuncheon-1": ["chun"] };2425function statusOf(s: string): "operational" | "announced" {26 return /^live$/i.test(s.trim()) ? "operational" : "announced";27}2829export const oracleRegionsParser: Parser = {30 name: "cloud_oracle_regions_v1",31 version: "1.0.0",32 pageTypes: ["cloud_region"],33 parse(doc: RawDocument, ctx): ExtractedRecord[] {34 const $ = loadHtml(doc);35 const out: ExtractedRecord[] = [];36 const seenCodes = new Set<string>();37 // Walk section headers (h2 inside the accordion list) → tables that follow within the same <li>38 $("li").each((_, li) => {39 const section = txt($(li).find("h2").first());40 if (!/^(Commercial|Sovereign|Government)$/i.test(section)) return;41 const realm = section.toLowerCase();42 $(li).find("table").each((__, table) => {43 const area = txt($(table).find("thead th").first());44 $(table).find("tbody tr").each((___, tr) => {45 const tds = $(tr).find("td");46 if (tds.length < 2) return;47 const first = tds.first();48 const name = txt(first);49 if (!name || /^Oracle (AI )?Database@/i.test(name)) return;50 const statusCell = tds.toArray().map((c) => txt($(c))).find((t) => /^(Live|Coming soon|Planned)$/i.test(t)) ?? "";51 const geoCode = (first.attr("geo-data") ?? "").trim();52 const label = parenthetical(name);53 const iso = PREFIX_ISO.find(([re]) => re.test(name))?.[1] ?? (realm === "sovereign" ? null : countryFromText(name));54 let place: Place = resolvePlace(label, iso);55 if (!place.countryIso2) place.countryIso2 = iso ?? (label ? countryFromText(label) : null) ?? countryFromText(name);56 // Code: trust geo-data unless missing, duplicated, or contradicting the named city (site typo)57 let code = geoCode;58 const cityToken = label ? normKey(label).replace(/\s+/g, "").slice(0, 4) : null;59 const codeBody = code.replace(/-/g, "");60 const contradicts = Boolean(code && cityToken && !codeBody.includes(cityToken) && !(CODE_CITY_ALIASES[code] ?? []).includes(cityToken));61 if (!code || seenCodes.has(code) || contradicts) code = slugify(name);62 seenCodes.add(code);63 const externalIds: Record<string, string | number> = { oci_realm: realm, oci_area: area };64 if (geoCode) externalIds.oci_geo_data = geoCode;65 if (label) externalIds.published_location = label;66 const status = statusOf(statusCell);67 out.push(68 regionRecord({69 connectorId: ctx.connectorId,70 keyPrefix: "oracle:region",71 providerName: "Oracle Cloud",72 code,73 name,74 place,75 availabilityZones: null,76 status: realm === "government" && status === "operational" ? "restricted" : status,77 isSovereign: realm === "sovereign",78 externalIds,79 url: doc.finalUrl,80 methods: { code: geoCode && code === geoCode ? "html:td@geo-data" : "slug:name", name: "html:td", status: "html:td.Status", city: "curated-city-table" },81 certainty: code === geoCode ? 0.95 : 0.85,82 }),83 );84 });85 });86 });87 ctx.log("info", `oracle: ${out.length} regions (${out.filter((r) => r.data.status === "announced").length} announced, ${out.filter((r) => r.data.isSovereign).length} sovereign)`);88 return out;89 },90};91