SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
6 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
5.9 KB · 100 lines typescript
Raw Blame History
1import type { Parser, ExtractedRecord, RawDocument } from "@dci/connectors";2import { resolvePlace, regionRecord, type Place } from "./shared.js";34/**5 * Microsoft Azure — https://datacenters.microsoft.com/globe/data/geo/regions.json6 * Public JSON behind the "Azure global infrastructure" globe. One object per region:7 *   id, displayName, location (city or state), geographyId, latitude/longitude (region marker, NOT a8 *   facility), typeId (regionaz | aregionaz | region | regionaaz), isOpen, yearOpen, monthOpen,9 *   availabilityZonesId (available | soon | nearest | nearestSoon), availableTo, announcementLink.10 */11interface AzureRegion {12  id: string;13  displayName: string;14  location?: string;15  geographyId?: string;16  continent?: string;17  latitude?: number;18  longitude?: number;19  typeId?: string;20  isOpen?: boolean;21  yearOpen?: number | null;22  monthOpen?: string | null;23  availabilityZonesId?: string;24  availabilityZonesNearestRegionIds?: string[];25  availableTo?: string;26  announcementLink?: string;27  dataResidency?: string;28  hasGroundStation?: boolean;29}3031/** Azure geographyId → ISO-2 (fallback when `location` is a state/area that the city table cannot resolve). */32const GEOGRAPHY_ISO: Record<string, string> = {33  africa: "ZA", australia: "AU", austria: "AT", belgium: "BE", brazil: "BR", canada: "CA", chile: "CL", china: "CN", denmark: "DK", finland: "FI", france: "FR", germany: "DE", greece: "GR", india: "IN", indonesia: "ID", israel: "IL", italy: "IT", japan: "JP", korea: "KR", malaysia: "MY", mexico: "MX", newzealand: "NZ", norway: "NO", poland: "PL", qatar: "QA", saudiarabia: "SA", spain: "ES", sweden: "SE", switzerland: "CH", taiwan: "TW", unitedarabemirates: "AE", unitedkingdom: "GB", unitedstates: "US",34};35/** Regions whose `location` is ambiguous without a country (e.g. "Hong Kong" under geography asiapacific). */36const LOCATION_ISO_OVERRIDES: Record<string, string> = { eastasia: "HK", southeastasia: "SG", northeurope: "IE", westeurope: "NL", chinanorth3: "CN" };3738/** Sub-national areas Microsoft names that are neither a city nor a US state. */39const AREA_LOCATIONS: Record<string, { regionName: string; iso: string }> = { Hebei: { regionName: "Hebei", iso: "CN" } };4041const MONTHS: Record<string, string> = { january: "01", february: "02", march: "03", april: "04", may: "05", june: "06", july: "07", august: "08", september: "09", october: "10", november: "11", december: "12" };4243export const azureRegionsParser: Parser = {44  name: "cloud_azure_regions_v1",45  version: "1.0.0",46  pageTypes: ["cloud_region"],47  parse(doc: RawDocument, ctx): ExtractedRecord[] {48    let list: AzureRegion[];49    try { const parsed = JSON.parse(doc.text) as unknown; list = Array.isArray(parsed) ? (parsed as AzureRegion[]) : ((parsed as { regions?: AzureRegion[] }).regions ?? []); } catch (e) { ctx.log("warn", `azure regions.json not JSON: ${(e as Error).message}`); return []; }50    const out: ExtractedRecord[] = [];51    for (const r of list) {52      if (!r?.id || !r.displayName) continue;53      const isoHint = LOCATION_ISO_OVERRIDES[r.id] ?? (r.geographyId ? GEOGRAPHY_ISO[r.geographyId] : undefined) ?? null;54      const area = r.location ? AREA_LOCATIONS[r.location] : undefined;55      let place: Place = area ? { city: null, regionName: area.regionName, countryIso2: area.iso, lat: null, lng: null, geoPrecision: null } : resolvePlace(r.location ?? null, isoHint);56      if (!place.countryIso2 && isoHint) place.countryIso2 = isoHint;57      // Curated city coordinates first. When Microsoft only names a sub-national area (a US state, Hebei, …) fall58      // back to their own region marker flagged "approximate". When they only name a country ("Netherlands",59      // "Ireland", "Israel") no marker is emitted: their country markers can sit across a border or offshore.60      let geoSource: string | undefined;61      if (place.lat == null && place.regionName && !place.city && typeof r.latitude === "number" && typeof r.longitude === "number") {62        place = { ...place, lat: r.latitude, lng: r.longitude, geoPrecision: "approximate" };63        geoSource = "provider-marker:azure";64      }65      const open = r.isOpen === true;66      const restricted = /business presence in China|government|by invitation/i.test(r.availableTo ?? "");67      const status = !open ? "announced" : restricted ? "restricted" : "operational";68      const launchedOn = open && r.yearOpen ? (r.monthOpen && MONTHS[r.monthOpen.toLowerCase()] ? `${r.yearOpen}-${MONTHS[r.monthOpen.toLowerCase()]}` : String(r.yearOpen)) : null;69      const azState = r.availabilityZonesId ?? "";70      const externalIds: Record<string, string | number> = { azure_region_id: r.id, azure_type: r.typeId ?? "", azure_geography: r.geographyId ?? "", availability_zones_state: azState };71      if (r.availableTo) externalIds.available_to = r.availableTo;72      if (r.announcementLink) externalIds.announcement_url = r.announcementLink;73      if (r.dataResidency) externalIds.data_residency = r.dataResidency;74      if (r.location) externalIds.published_location = r.location;75      out.push(76        regionRecord({77          connectorId: ctx.connectorId,78          keyPrefix: "azure:region",79          providerName: "Microsoft Azure",80          code: r.id,81          name: r.displayName,82          place,83          // Microsoft publishes whether AZs are available, not how many; leave the count null when unknown.84          availabilityZones: null,85          launchedOn,86          status,87          isSovereign: false,88          externalIds,89          geoSource,90          url: doc.finalUrl,91          methods: { code: "json:id", name: "json:displayName", city: "json:location+curated-city-table", status: "json:isOpen", launchedOn: "json:yearOpen", ...(geoSource ? { lat: "json:latitude(region marker)" } : {}) },92          certainty: 0.98,93        }),94      );95    }96    ctx.log("info", `azure: ${out.length} regions (${out.filter((r) => r.data.status === "announced").length} announced)`);97    return out;98  },99};100