SPB Git forge

spb/job-ka

Public
229commits 1branches 0releases
38.1 MBsize
maindefault branch
5 h agolast push
HTML 82.1% Python 14.6% TypeScript 1.9% CSS 1% JavaScript 0.5%
3.1 KB · 88 lines python
Raw Blame History
1# =============================================================================2# Job·Ka — Groupe KA3# Auteur  : Simon-Pierre Boucher4# Contact : contact@spboucher.ai5# Fichier : jobka/connectors/teamtailor.py6# Rôle    : Classe de plateforme Teamtailor (<org>.teamtailor.com) — sitemap7#           public + JSON-LD JobPosting des pages détail /jobs/<id>-<slug>8# Créé    : 2026-08-25   Modifié : 2026-08-259# =============================================================================10"""Plateforme Teamtailor (sites carrières <org>.teamtailor.com).1112- liste  : GET https://<org>.teamtailor.com/sitemap.xml13           -> URLs /jobs/<id>-<slug> (la page /jobs est rendue en JS)14- détail : GET /jobs/<id>-<slug> -> JSON-LD JobPosting (lieu, dates,15           description, salaire). Cache BD + budget ; filtre Québec au16           détail, offre différée si le budget est épuisé sans cache.17"""18from __future__ import annotations1920import os21import re2223from ..schema import JobPosting, is_quebec_location24from . import _jsonld25from .base import BaseConnector2627MAX_DETAILS = int(os.environ.get("JOBKA_TEAMTAILOR_DETAIL_LIMIT", "60"))2829_JOB_URL_RE = re.compile(30    r"<loc>\s*(https?://[a-z0-9-]+\.teamtailor\.com/jobs/(\d+)-([^<\s]+))\s*"31    r"</loc>", re.I)323334class TeamtailorConnector(BaseConnector):35    """Base Teamtailor — sous-classes : définir source_id, EMPLOYER, ORG."""3637    ats = "teamtailor"38    request_delay = 1.03940    EMPLOYER = ""41    ORG = ""               # <ORG>.teamtailor.com42    quebec_only = True4344    def _fetch_detail(self, url: str) -> dict:45        html = self.get(url).text46        node = _jsonld.extract_jobposting(html)47        return _jsonld.jobposting_fields(node) if node else {}4849    def fetch(self) -> list[JobPosting]:50        xml = self.get(51            f"https://{self.ORG}.teamtailor.com/sitemap.xml").text52        out: list[JobPosting] = []53        details_used = 054        seen: set[str] = set()55        for url, eid, slug in _JOB_URL_RE.findall(xml):56            if eid in seen:57                continue58            seen.add(eid)59            if details_used >= MAX_DETAILS:60                d = self.stale_detail(eid)61                if not d:62                    continue      # différée : pas de détail frais ni en cache63            else:64                fresh = [False]6566                def _fn(u=url, fresh=fresh):67                    fresh[0] = True68                    return self._fetch_detail(u)6970                d = self.detail(eid, slug, _fn)71                if fresh[0]:72                    details_used += 173            city = d.get("city") or ""74            region = (d.get("region_code") or "").upper()75            if self.quebec_only and not (76                    region in ("QC", "QUÉBEC", "QUEBEC")77                    or is_quebec_location(f"{city} {region}")):78                continue79            job = JobPosting(80                source=self.source_id, external_id=eid, url=url,81                employer=self.EMPLOYER,82                title=d.get("title") or slug.replace("-", " "),83                city=city, ats=self.ats,84            )85            _jsonld.apply_fields(job, d)86            out.append(job)87        return out88