SPB Git forge

spb/job-ka

Public
229commits 1branches 0releases
38.1 MBsize
maindefault branch
6 h agolast push
HTML 82.1% Python 14.6% TypeScript 1.9% CSS 1% JavaScript 0.5%
3.6 KB · 85 lines python
Raw Blame History
1# =============================================================================2# Job·Ka — Groupe KA3# Auteur  : Simon-Pierre Boucher4# Contact : contact@spboucher.ai5# Fichier : jobka/connectors/workable.py6# Rôle    : Classe de plateforme Workable (widget public v1) — un employeur =7#           une sous-classe de ~8 lignes (ORG, EMPLOYER)8# Créé    : 2026-08-17   Modifié : 2026-08-179# =============================================================================10"""Plateforme Workable.1112API publique : GET https://apply.workable.com/api/v1/widget/accounts/<org>13                   ?details=true14-> {name, jobs:[{title, shortcode, employment_type("Full-time"…),15   telecommuting(bool), department, url, published_on("2026-06-19"),16   country, city, state, locations:[{country, countryCode, city, region}],17   education, experience, description(HTML)}]}.1819Une seule requête liste TOUT (descriptions incluses). Pas de salaire dans le20widget v1 — finalize() tente l'extraction depuis la description.21"""22from __future__ import annotations2324from ..schema import JobPosting, clean_html, is_quebec_location25from .base import BaseConnector262728class WorkableConnector(BaseConnector):29    """Base Workable — sous-classes : définir source_id, EMPLOYER, ORG."""3031    ats = "workable"32    request_delay = 1.03334    EMPLOYER = ""35    ORG = ""36    quebec_only = True3738    @staticmethod39    def _locations(item: dict) -> list[str]:40        locs = [f"{item.get('city') or ''}, {item.get('state') or ''}"]41        locs += [f"{l.get('city') or ''}, {l.get('region') or ''}"42                 for l in item.get("locations") or []43                 if (l.get("countryCode") or "").upper() in ("CA", "")]44        return [l.strip(", ") for l in locs if l.strip(", ")]4546    def _keep(self, item: dict) -> bool:47        if not self.quebec_only:48            return True49        return any(is_quebec_location(l) for l in self._locations(item))5051    def fetch(self) -> list[JobPosting]:52        data = self.get("https://apply.workable.com/api/v1/widget/accounts/"53                        f"{self.ORG}", params={"details": "true"}).json()54        out: list[JobPosting] = []55        for item in data.get("jobs") or []:56            if not self._keep(item):57                continue58            locs = self._locations(item)59            job = JobPosting(60                source=self.source_id,61                external_id=str(item.get("shortcode") or ""),62                url=item.get("url") or item.get("shortlink") or "",63                employer=self.EMPLOYER or data.get("name") or "",64                title=item.get("title") or "",65                description=clean_html(item.get("description") or ""),66                location_label=locs[0] if locs else "",67                work_mode="teletravail" if item.get("telecommuting") else None,68                date_posted=item.get("published_on") or item.get("created_at"),69                apply_url=item.get("application_url") or "",70                ats=self.ats,71            )72            job.details["employment_label"] = item.get("employment_type") or ""73            if item.get("department"):74                job.details["team"] = item["department"]75            if item.get("function"):76                job.details["function"] = item["function"]77            if item.get("industry"):78                job.details["industry"] = item["industry"]79            if item.get("experience"):80                job.requirements["experience"] = item["experience"]81            if item.get("education"):82                job.requirements["education"] = item["education"]83            out.append(job)84        return out85