# ============================================================================= # Job·Ka — Groupe KA # Auteur : Simon-Pierre Boucher # Contact : contact@spboucher.ai # Fichier : jobka/connectors/teamtailor.py # Rôle : Classe de plateforme Teamtailor (.teamtailor.com) — sitemap # public + JSON-LD JobPosting des pages détail /jobs/- # Créé : 2026-08-25 Modifié : 2026-08-25 # ============================================================================= """Plateforme Teamtailor (sites carrières .teamtailor.com). - liste : GET https://.teamtailor.com/sitemap.xml -> URLs /jobs/- (la page /jobs est rendue en JS) - détail : GET /jobs/- -> JSON-LD JobPosting (lieu, dates, description, salaire). Cache BD + budget ; filtre Québec au détail, offre différée si le budget est épuisé sans cache. """ from __future__ import annotations import os import re from ..schema import JobPosting, is_quebec_location from . import _jsonld from .base import BaseConnector MAX_DETAILS = int(os.environ.get("JOBKA_TEAMTAILOR_DETAIL_LIMIT", "60")) _JOB_URL_RE = re.compile( r"\s*(https?://[a-z0-9-]+\.teamtailor\.com/jobs/(\d+)-([^<\s]+))\s*" r"", re.I) class TeamtailorConnector(BaseConnector): """Base Teamtailor — sous-classes : définir source_id, EMPLOYER, ORG.""" ats = "teamtailor" request_delay = 1.0 EMPLOYER = "" ORG = "" # .teamtailor.com quebec_only = True def _fetch_detail(self, url: str) -> dict: html = self.get(url).text node = _jsonld.extract_jobposting(html) return _jsonld.jobposting_fields(node) if node else {} def fetch(self) -> list[JobPosting]: xml = self.get( f"https://{self.ORG}.teamtailor.com/sitemap.xml").text out: list[JobPosting] = [] details_used = 0 seen: set[str] = set() for url, eid, slug in _JOB_URL_RE.findall(xml): if eid in seen: continue seen.add(eid) if details_used >= MAX_DETAILS: d = self.stale_detail(eid) if not d: continue # différée : pas de détail frais ni en cache else: fresh = [False] def _fn(u=url, fresh=fresh): fresh[0] = True return self._fetch_detail(u) d = self.detail(eid, slug, _fn) if fresh[0]: details_used += 1 city = d.get("city") or "" region = (d.get("region_code") or "").upper() if self.quebec_only and not ( region in ("QC", "QUÉBEC", "QUEBEC") or is_quebec_location(f"{city} {region}")): continue job = JobPosting( source=self.source_id, external_id=eid, url=url, employer=self.EMPLOYER, title=d.get("title") or slug.replace("-", " "), city=city, ats=self.ats, ) _jsonld.apply_fields(job, d) out.append(job) return out