# ============================================================================= # Job·Ka — Groupe KA # Auteur : Simon-Pierre Boucher # Contact : contact@spboucher.ai # Fichier : jobka/connectors/greenhouse.py # Rôle : Classe de plateforme Greenhouse (boards-api.greenhouse.io) — un # employeur = une sous-classe de ~8 lignes (BOARD, EMPLOYER) # Créé : 2026-08-17 Modifié : 2026-08-17 # ============================================================================= """Plateforme Greenhouse. API publique : GET https://boards-api.greenhouse.io/v1/boards//jobs avec ?content=true -> {jobs:[{id, title, absolute_url, location:{name}, first_published, updated_at, application_deadline, content(HTML échappé), departments, offices, metadata}]}. Une seule requête liste TOUT (contenu inclus) : pas de page détail. `application_deadline` alimente date_deadline (retrait automatique). """ from __future__ import annotations from ..schema import JobPosting, clean_html, is_quebec_location from .base import BaseConnector class GreenhouseConnector(BaseConnector): """Base Greenhouse — sous-classes : définir source_id, EMPLOYER, BOARD.""" ats = "greenhouse" request_delay = 1.0 EMPLOYER = "" BOARD = "" quebec_only = True def _keep(self, item: dict) -> bool: if not self.quebec_only: return True # strict sur le lieu du POSTE (les bureaux de l'entreprise sont trop # larges : un employeur québécois peut afficher un poste aux États-Unis) return is_quebec_location((item.get("location") or {}).get("name") or "") def fetch(self) -> list[JobPosting]: data = self.get( f"https://boards-api.greenhouse.io/v1/boards/{self.BOARD}/jobs", params={"content": "true"}).json() out: list[JobPosting] = [] for item in data.get("jobs") or []: if not self._keep(item): continue deadline = item.get("application_deadline") or None job = JobPosting( source=self.source_id, external_id=str(item.get("id") or ""), url=item.get("absolute_url") or "", employer=self.EMPLOYER or item.get("company_name") or "", title=item.get("title") or "", description=clean_html(item.get("content") or ""), location_label=(item.get("location") or {}).get("name") or "", date_posted=item.get("first_published") or item.get("updated_at"), date_deadline=deadline, language=(item.get("language") or "")[:2], # « fr » / « en » ats=self.ats, ) deps = [d.get("name") for d in item.get("departments") or [] if d.get("name")] if deps: job.details["team"] = deps[0] if item.get("requisition_id"): job.details["requisition"] = str(item["requisition_id"]) out.append(job) return out