[ka6] fix connecteur altusgroup: external_id régional → ID de réquisition (hook _external_id dans la plateforme workday, défaut inchangé) — le tenant altusgroup.wd3 préfixe bulletFields par la région quand il y en a une (['Ontario','R0013639'] vs ['R0013942'] seul), si bien que bullets[0] devenait l'external_id : l'offre QC historique était en base sous external_id='Quebec', et le sync du 2026-08-26 trouvait 2 offres pour 1 seule stockée (collision d'ID entre postes d'une même région). L'extraction est désormais isolée dans un hook _external_id() de WorkdayConnector (comportement par défaut strictement identique pour les autres tenants) et surchargée dans altusgroup.py : premier bullet au format réquisition R+chiffres, sinon suffixe du chemin. Vérifié live : 36 postes scannés → 36 IDs uniques au format R00xxxxx, 0 poste non conforme. Volume 0 légitime AUJOURD'HUI : 7 postes Canada, tous Toronto — aucune offre QC depuis la dépublication de l'offre montréalaise le 2026-08-26 (médiane historique 0.0). Le statut stale vient de là (la supervision date le dernier succès au dernier sync found>0, 2026-08-26 14:19) ; l'unique échec récent (2026-09-12 03:04, « Workday non-JSON HTTP 200 text/html ») est la fenêtre de maintenance hebdomadaire Workday du vendredi/samedi nuit (28 occurrences multi-sources cette nuit, mêmes plages 02h30–03h15 les 2026-08-29 et 2026-09-05), transitoire et déjà retryée. Sync isolé rejoué : found=0 en 2,4 s, statut ok. 101 tests pytest verts. pm2 restart job-ka-sync OK, site 200.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 changed files +28 −4
modified
jobka/connectors/altusgroup.py
+17 −1
@@ -5,8 +5,10 @@ | ||
| 5 | 5 | # Fichier : jobka/connectors/altusgroup.py |
| 6 | 6 | # Rôle : Connecteur Groupe Altus — workday (tenant altusgroup.wd3, site AltusGroup) |
| 7 | 7 | # [généré par scripts/gen_connectors.py, flux vérifié le 2026-08-25] |
| 8 | −# Créé : 2026-08-25 Modifié : 2026-08-25 | |
| 8 | +# Créé : 2026-08-25 Modifié : 2026-09-12 | |
| 9 | 9 | # ============================================================================= |
| 10 | +import re | |
| 11 | + | |
| 10 | 12 | from .workday import WorkdayConnector |
| 11 | 13 | |
| 12 | 14 | |
@@ -16,3 +18,17 @@ class AltusgroupConnector(WorkdayConnector): | ||
| 16 | 18 | TENANT = 'altusgroup' |
| 17 | 19 | HOST = 'wd3' |
| 18 | 20 | SITE = 'AltusGroup' |
| 21 | + | |
| 22 | + # Ce tenant préfixe bulletFields par la région quand il y en a une : | |
| 23 | + # ["Ontario", "R0013639"] ou ["R0013942"] seul. bullets[0] donnait donc | |
| 24 | + # « Quebec » comme external_id (collision entre postes d'une même région, | |
| 25 | + # constatée le 2026-08-26 : 2 offres trouvées, 1 seule en base). On prend | |
| 26 | + # le bullet au format réquisition (R + chiffres), sinon le suffixe du | |
| 27 | + # chemin (qui peut porter un suffixe de variante, ex. R0013801-1). | |
| 28 | + _REQ_ID = re.compile(r'^R\d+(?:-\d+)?$') | |
| 29 | + | |
| 30 | + def _external_id(self, item: dict, path: str) -> str: | |
| 31 | + for b in item.get('bulletFields') or []: | |
| 32 | + if self._REQ_ID.match(str(b).strip()): | |
| 33 | + return str(b).strip() | |
| 34 | + return path.rsplit('_', 1)[-1] | |
modified
jobka/connectors/workday.py
+11 −3
@@ -5,7 +5,7 @@ | ||
| 5 | 5 | # Fichier : jobka/connectors/workday.py |
| 6 | 6 | # Rôle : Classe de plateforme Workday (API JSON publique wday/cxs) — un |
| 7 | 7 | # employeur = une sous-classe de ~10 lignes (TENANT, HOST, SITE) |
| 8 | −# Créé : 2026-08-17 Modifié : 2026-09-10 | |
| 8 | +# Créé : 2026-08-17 Modifié : 2026-09-12 | |
| 9 | 9 | # ============================================================================= |
| 10 | 10 | """Plateforme Workday. |
| 11 | 11 | |
@@ -245,6 +245,15 @@ class WorkdayConnector(BaseConnector): | ||
| 245 | 245 | except Exception: |
| 246 | 246 | continue # facette indisponible : on n'invente rien |
| 247 | 247 | |
| 248 | + def _external_id(self, item: dict, path: str) -> str: | |
| 249 | + """Identifiant externe d'une ligne de liste — par défaut le premier | |
| 250 | + `bulletFields` (= req_id chez la plupart des tenants), sinon le | |
| 251 | + suffixe du chemin. Hook surchargeable : certains tenants (ex. | |
| 252 | + altusgroup) préfixent les bullets par la région, ce qui provoque des | |
| 253 | + collisions d'ID si on prend bullets[0] aveuglément.""" | |
| 254 | + bullets = item.get("bulletFields") or [] | |
| 255 | + return str((bullets[0] if bullets else "") or path.rsplit("_", 1)[-1]) | |
| 256 | + | |
| 248 | 257 | def _fetch_detail(self, external_path: str) -> dict: |
| 249 | 258 | data = self._req_json("get", |
| 250 | 259 | f"{self._base}/wday/cxs/{self.TENANT}" |
@@ -272,8 +281,7 @@ class WorkdayConnector(BaseConnector): | ||
| 272 | 281 | if not self._server_filtered and not self._keep(item): |
| 273 | 282 | continue |
| 274 | 283 | path = item.get("externalPath") or "" |
| 275 | − bullets = item.get("bulletFields") or [] | |
| 276 | − eid = (bullets[0] if bullets else "") or path.rsplit("_", 1)[-1] | |
| 284 | + eid = self._external_id(item, path) | |
| 277 | 285 | if not eid or not path: |
| 278 | 286 | continue |
| 279 | 287 | job = JobPosting( |
| 280 | 288 | |