"""Workday public career sites — the JSON search endpoint the site's own frontend calls (spec §13 mode C, provenance stored): `POST https://{tenant}.{wdN}.myworkdayjobs.com/wday/cxs/{tenant}/{site}/jobs` body {"appliedFacets":{},"limit":20,"offset":N,"searchText":""} → {total, jobPostings:[{title, externalPath, locationsText, postedOn, bulletFields:[reqId]}]}. Paginated by offset, bounded to `MAX_JOBS_WD` postings. Verified 2026-09-12 against nvidia.wd5 / NVIDIAExternalCareerSite (fixture = first page).""" from __future__ import annotations import re from collections.abc import Mapping from typing import Any from companyatlas.connectors._ats_base import AtsConnector from companyatlas.connectors._util import load_json, merged_result from companyatlas.fetch import Fetcher, FetchResult from companyatlas.sdk.connector import ConnectorContext, ConnectorMeta, register from companyatlas.sdk.models import ExtractedJob from companyatlas.taxonomy import FetchMode, Surface PAGE_LIMIT = 20 MAX_JOBS_WD = 400 API_RE = re.compile(r"https?://([a-z0-9-]+)\.(wd\d+)\.myworkdayjobs\.com/wday/cxs/([a-z0-9-]+)/([A-Za-z0-9_-]+)/jobs", re.IGNORECASE) REQ_RE = re.compile(r"_([A-Z]{1,4}[-_]?\d{3,}[A-Z0-9-]*)$") @register class WorkdayConnector(AtsConnector): vendor = "workday" meta = ConnectorMeta(connector_id="workday-v1", name="Workday career site (public JSON)", version="1", category=Surface.JOBS_BOARD, fetch_mode=FetchMode.JSON, default_interval_s=12 * 3600, url_pattern=r"myworkdayjobs\.com/wday/cxs/", pattern_required=True, priority=50, accept="application/json", description="Public Workday CXS job search endpoint (POST, paginated)") def token(self, sensor: Mapping[str, Any]) -> str | None: m = API_RE.search(str(sensor.get("url") or "")) return f"{m.group(1)}/{m.group(4)}" if m else None async def fetch(self, ctx: ConnectorContext, sensor: Mapping[str, Any], fetcher: Fetcher) -> FetchResult: url = str(sensor["url"]) postings: list[dict[str, Any]] = [] first: FetchResult | None = None total = 0 offset = 0 pages = 0 while True: body = {"appliedFacets": {}, "limit": PAGE_LIMIT, "offset": offset, "searchText": ""} res = await fetcher.post_json(url, body, headers={"Content-Type": "application/json"}) pages += 1 data = load_json(res) if first is None: first = res total = int(data.get("total") or 0) if isinstance(data, dict) else 0 page = (data.get("jobPostings") if isinstance(data, dict) else None) or [] postings.extend(p for p in page if isinstance(p, dict)) offset += len(page) if not page or offset >= total or offset >= MAX_JOBS_WD: break assert first is not None return merged_result(first, {"total": total, "jobPostings": postings, "truncated": total > len(postings)}, pages=pages) def parse_jobs(self, data: Any, sensor: Mapping[str, Any]) -> list[ExtractedJob]: m = API_RE.search(str(sensor.get("url") or "")) base = f"https://{m.group(1)}.{m.group(2)}.myworkdayjobs.com/{m.group(4)}" if m else "" out: list[ExtractedJob] = [] for j in (data.get("jobPostings") if isinstance(data, dict) else []) or []: if not isinstance(j, dict) or not j.get("title"): continue path = str(j.get("externalPath") or "") bullets = [b for b in (j.get("bulletFields") or []) if isinstance(b, str)] req = bullets[0] if bullets else None if not req: mm = REQ_RE.search(path) req = mm.group(1) if mm else None out.append(ExtractedJob(title=str(j["title"]), url=(base + path) if base and path else None, external_id=req or path or None, location_text=j.get("locationsText") or None, raw={"postedOn": j.get("postedOn"), "bulletFields": bullets[:3]})) return out