[ka6] fix connecteur groupemaurice: 502 Bad Gateway transitoire du frontal Dayforce avortait tout le sync (stale 5,3 h, un seul POST search en échec). Garde-fou dans DayforceConnector: le POST de recherche retente une fois après pause sur 5xx, puis lève (sync en échec, jamais un faux 0) — même pattern que njoyn 33dba44 / digitalrecruiters 81cf8e2. Sync isolé re-testé: 108 offres trouvées (= médiane 108).
1 changed file +29 −7
modified
jobka/connectors/dayforce.py
+29 −7
@@ -5,7 +5,7 @@ | ||
| 5 | 5 | # Fichier : jobka/connectors/dayforce.py |
| 6 | 6 | # Rôle : Classe de plateforme Dayforce HCM (jobs.dayforcehcm.com) — API JSON |
| 7 | 7 | # publique du frontal Next.js, un employeur = une sous-classe |
| 8 | −# Créé : 2026-08-22 Modifié : 2026-08-22 | |
| 8 | +# Créé : 2026-08-22 Modifié : 2026-08-26 | |
| 9 | 9 | # ============================================================================= |
| 10 | 10 | """Plateforme Dayforce HCM (ex-Ceridian) — jobs.dayforcehcm.com. |
| 11 | 11 | |
@@ -33,6 +33,9 @@ from __future__ import annotations | ||
| 33 | 33 | |
| 34 | 34 | import os |
| 35 | 35 | import re |
| 36 | +import time | |
| 37 | + | |
| 38 | +import requests | |
| 36 | 39 | |
| 37 | 40 | from ..schema import JobPosting, clean_html, is_quebec_location |
| 38 | 41 | from .base import BaseConnector |
@@ -89,6 +92,30 @@ class DayforceConnector(BaseConnector): | ||
| 89 | 92 | "jobDescriptionFooter")) |
| 90 | 93 | return {"attributes": attrs, "description": clean_html(desc)} |
| 91 | 94 | |
| 95 | + # -- recherche (garde-fou 5xx transitoire) ------------------------------ | |
| 96 | + def _search_page(self, search_url: str, csrf: str, start: int) -> dict: | |
| 97 | + """POST de recherche avec garde-fou : le frontal Dayforce renvoie | |
| 98 | + parfois un 5xx passager (502 Bad Gateway observé le 2026-08-26 sur | |
| 99 | + groupemaurice) qui avortait tout le sync — on retente une fois après | |
| 100 | + pause, puis on lève (sync en échec, jamais un faux 0).""" | |
| 101 | + for attempt in range(2): | |
| 102 | + try: | |
| 103 | + return self.post( | |
| 104 | + search_url, | |
| 105 | + json={"clientNamespace": self.NS, | |
| 106 | + "jobBoardCode": self.BOARD, | |
| 107 | + "cultureCode": self.CULTURE, | |
| 108 | + "paginationStart": start}, | |
| 109 | + headers={"X-CSRF-TOKEN": csrf, | |
| 110 | + "Accept": "application/json"}).json() | |
| 111 | + except requests.HTTPError as e: | |
| 112 | + status = getattr(e.response, "status_code", 0) or 0 | |
| 113 | + if attempt == 0 and status >= 500: | |
| 114 | + time.sleep(10) | |
| 115 | + continue | |
| 116 | + raise | |
| 117 | + raise RuntimeError("unreachable") | |
| 118 | + | |
| 92 | 119 | # -- contrat ------------------------------------------------------------ |
| 93 | 120 | def fetch(self) -> list[JobPosting]: |
| 94 | 121 | csrf = (self.get(f"{BASE}/api/auth/csrf").json() or {}).get( |
@@ -99,12 +126,7 @@ class DayforceConnector(BaseConnector): | ||
| 99 | 126 | details_used = 0 |
| 100 | 127 | start = 0 |
| 101 | 128 | for _ in range(self.max_pages): |
| 102 | − data = self.post( | |
| 103 | − search_url, | |
| 104 | − json={"clientNamespace": self.NS, "jobBoardCode": self.BOARD, | |
| 105 | − "cultureCode": self.CULTURE, "paginationStart": start}, | |
| 106 | − headers={"X-CSRF-TOKEN": csrf, | |
| 107 | − "Accept": "application/json"}).json() | |
| 129 | + data = self._search_page(search_url, csrf, start) | |
| 108 | 130 | postings = data.get("jobPostings") or [] |
| 109 | 131 | if not postings: |
| 110 | 132 | break |
| 111 | 133 | |