SPB Git forge
28commits 1branches 0releases
7.7 MBsize
maindefault branch
10 days agolast push
Python 66.3% TypeScript 22.7% JavaScript 8.6% HTML 1.4% CSS 0.7%
4.0 KB · 74 lines python
Raw Blame History
1"""Workday public career sites — the JSON search endpoint the site's own frontend calls (spec §13 mode C, provenance stored):2`POST https://{tenant}.{wdN}.myworkdayjobs.com/wday/cxs/{tenant}/{site}/jobs` body {"appliedFacets":{},"limit":20,"offset":N,"searchText":""}3→ {total, jobPostings:[{title, externalPath, locationsText, postedOn, bulletFields:[reqId]}]}. Paginated by offset, bounded to4`MAX_JOBS_WD` postings. Verified 2026-09-12 against nvidia.wd5 / NVIDIAExternalCareerSite (fixture = first page)."""5from __future__ import annotations67import re8from collections.abc import Mapping9from typing import Any1011from companyatlas.connectors._ats_base import AtsConnector12from companyatlas.connectors._util import load_json, merged_result13from companyatlas.fetch import Fetcher, FetchResult14from companyatlas.sdk.connector import ConnectorContext, ConnectorMeta, register15from companyatlas.sdk.models import ExtractedJob16from companyatlas.taxonomy import FetchMode, Surface1718PAGE_LIMIT = 2019MAX_JOBS_WD = 40020API_RE = re.compile(r"https?://([a-z0-9-]+)\.(wd\d+)\.myworkdayjobs\.com/wday/cxs/([a-z0-9-]+)/([A-Za-z0-9_-]+)/jobs", re.IGNORECASE)21REQ_RE = re.compile(r"_([A-Z]{1,4}[-_]?\d{3,}[A-Z0-9-]*)$")222324@register25class WorkdayConnector(AtsConnector):26    vendor = "workday"27    meta = ConnectorMeta(connector_id="workday-v1", name="Workday career site (public JSON)", version="1", category=Surface.JOBS_BOARD,28                         fetch_mode=FetchMode.JSON, default_interval_s=12 * 3600, url_pattern=r"myworkdayjobs\.com/wday/cxs/", pattern_required=True, priority=50,29                         accept="application/json", description="Public Workday CXS job search endpoint (POST, paginated)")3031    def token(self, sensor: Mapping[str, Any]) -> str | None:32        m = API_RE.search(str(sensor.get("url") or ""))33        return f"{m.group(1)}/{m.group(4)}" if m else None3435    async def fetch(self, ctx: ConnectorContext, sensor: Mapping[str, Any], fetcher: Fetcher) -> FetchResult:36        url = str(sensor["url"])37        postings: list[dict[str, Any]] = []38        first: FetchResult | None = None39        total = 040        offset = 041        pages = 042        while True:43            body = {"appliedFacets": {}, "limit": PAGE_LIMIT, "offset": offset, "searchText": ""}44            res = await fetcher.post_json(url, body, headers={"Content-Type": "application/json"})45            pages += 146            data = load_json(res)47            if first is None:48                first = res49                total = int(data.get("total") or 0) if isinstance(data, dict) else 050            page = (data.get("jobPostings") if isinstance(data, dict) else None) or []51            postings.extend(p for p in page if isinstance(p, dict))52            offset += len(page)53            if not page or offset >= total or offset >= MAX_JOBS_WD:54                break55        assert first is not None56        return merged_result(first, {"total": total, "jobPostings": postings, "truncated": total > len(postings)}, pages=pages)5758    def parse_jobs(self, data: Any, sensor: Mapping[str, Any]) -> list[ExtractedJob]:59        m = API_RE.search(str(sensor.get("url") or ""))60        base = f"https://{m.group(1)}.{m.group(2)}.myworkdayjobs.com/{m.group(4)}" if m else ""61        out: list[ExtractedJob] = []62        for j in (data.get("jobPostings") if isinstance(data, dict) else []) or []:63            if not isinstance(j, dict) or not j.get("title"):64                continue65            path = str(j.get("externalPath") or "")66            bullets = [b for b in (j.get("bulletFields") or []) if isinstance(b, str)]67            req = bullets[0] if bullets else None68            if not req:69                mm = REQ_RE.search(path)70                req = mm.group(1) if mm else None71            out.append(ExtractedJob(title=str(j["title"]), url=(base + path) if base and path else None, external_id=req or path or None,72                                    location_text=j.get("locationsText") or None, raw={"postedOn": j.get("postedOn"), "bulletFields": bullets[:3]}))73        return out74