import type { Parser, ExtractedRecord, RawDocument } from "@dci/connectors";
import { countryFromText, slugify } from "@dci/core";
import { loadHtml, txt, parenthetical, resolvePlace, regionRecord, type Place } from "./shared.js";
import { normKey } from "./city-coords.js";
/**
* Oracle Cloud Infrastructure — https://www.oracle.com/cloud/public-cloud-regions/
* Server-rendered HTML (needs a browser identity: the bot UA gets 403). Sections (h2): Commercial,
* Sovereign (EU Sovereign Cloud), Government, Multicloud (Oracle Database@AWS/Azure/Google — NOT Oracle
* regions, skipped). Each table:
US East (Ashburn) | … Live|Coming soon|Planned | .
* Quirks seen live: a few rows lack geo-data, one code is duplicated ("Spain Central 2"), and two rows have
* swapped codes (ap-batam-1 / ap-hyderabad-1). The parser trusts the display name and falls back to a slug
* code when the geo-data code contradicts the city in the name.
*/
const PREFIX_ISO: Array<[RegExp, string]> = [
[/^(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"],
[/^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"],
[/^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"],
[/^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"],
];
/** OCI codes legitimately named after a different town than the one displayed (uk-cardiff-1 is "UK West (Newport)"). */
const CODE_CITY_ALIASES: Record = { "uk-cardiff-1": ["newp"], "sa-vinhedo-1": ["vinh"], "ap-chuncheon-1": ["chun"] };
function statusOf(s: string): "operational" | "announced" {
return /^live$/i.test(s.trim()) ? "operational" : "announced";
}
export const oracleRegionsParser: Parser = {
name: "cloud_oracle_regions_v1",
version: "1.0.0",
pageTypes: ["cloud_region"],
parse(doc: RawDocument, ctx): ExtractedRecord[] {
const $ = loadHtml(doc);
const out: ExtractedRecord[] = [];
const seenCodes = new Set();
// Walk section headers (h2 inside the accordion list) → tables that follow within the same
$("li").each((_, li) => {
const section = txt($(li).find("h2").first());
if (!/^(Commercial|Sovereign|Government)$/i.test(section)) return;
const realm = section.toLowerCase();
$(li).find("table").each((__, table) => {
const area = txt($(table).find("thead th").first());
$(table).find("tbody tr").each((___, tr) => {
const tds = $(tr).find("td");
if (tds.length < 2) return;
const first = tds.first();
const name = txt(first);
if (!name || /^Oracle (AI )?Database@/i.test(name)) return;
const statusCell = tds.toArray().map((c) => txt($(c))).find((t) => /^(Live|Coming soon|Planned)$/i.test(t)) ?? "";
const geoCode = (first.attr("geo-data") ?? "").trim();
const label = parenthetical(name);
const iso = PREFIX_ISO.find(([re]) => re.test(name))?.[1] ?? (realm === "sovereign" ? null : countryFromText(name));
let place: Place = resolvePlace(label, iso);
if (!place.countryIso2) place.countryIso2 = iso ?? (label ? countryFromText(label) : null) ?? countryFromText(name);
// Code: trust geo-data unless missing, duplicated, or contradicting the named city (site typo)
let code = geoCode;
const cityToken = label ? normKey(label).replace(/\s+/g, "").slice(0, 4) : null;
const codeBody = code.replace(/-/g, "");
const contradicts = Boolean(code && cityToken && !codeBody.includes(cityToken) && !(CODE_CITY_ALIASES[code] ?? []).includes(cityToken));
if (!code || seenCodes.has(code) || contradicts) code = slugify(name);
seenCodes.add(code);
const externalIds: Record = { oci_realm: realm, oci_area: area };
if (geoCode) externalIds.oci_geo_data = geoCode;
if (label) externalIds.published_location = label;
const status = statusOf(statusCell);
out.push(
regionRecord({
connectorId: ctx.connectorId,
keyPrefix: "oracle:region",
providerName: "Oracle Cloud",
code,
name,
place,
availabilityZones: null,
status: realm === "government" && status === "operational" ? "restricted" : status,
isSovereign: realm === "sovereign",
externalIds,
url: doc.finalUrl,
methods: { code: geoCode && code === geoCode ? "html:td@geo-data" : "slug:name", name: "html:td", status: "html:td.Status", city: "curated-city-table" },
certainty: code === geoCode ? 0.95 : 0.85,
}),
);
});
});
});
ctx.log("info", `oracle: ${out.length} regions (${out.filter((r) => r.data.status === "announced").length} announced, ${out.filter((r) => r.data.isSovereign).length} sovereign)`);
return out;
},
};