SPB Git

spb/job-ka

Public

Nouvelle famille wpjobmanager (WordPress + WP Job Manager) : flux job_feed (contenu complet en une requête) + JSON-LD des pages poste (date limite, lieu). Ouvre la plateforme carrieresante.gouv.qc.ca de Santé Québec (un site par établissement, en déploiement) : CHU de Québec (60), CHUM (25), CISSS Abitibi-Témiscamingue (141), Chaudière-Appalaches (112), Laval (149), Lanaudière (124), Laurentides (130) + CISSS de l'Outaouais via viensvivretespassions.ca (101). Fixture chum. + connecteur cssdd (CSS des Découvreurs, famille DigitalRecruiters existante).

Simon-Pierre Boucher committed (Aug 19, 2026) parent 89f9713 Browse files

15 changed files +20,001 −0

added jobka/connectors/chu_quebec.py +17 −0
@@ -0,0 +1,17 @@
1+# =============================================================================
2+# Job·Ka — Groupe KA
3+# Auteur : Simon-Pierre Boucher
4+# Contact : contact@spboucher.ai
5+# Fichier : jobka/connectors/chu_quebec.py
6+# Rôle : Connecteur CHU de Québec — Université Laval (plateforme
7+# carrieresante.gouv.qc.ca, WordPress + WP Job Manager)
8+# Créé : 2026-08-19 Modifié : 2026-08-19
9+# =============================================================================
10+from .wpjobmanager import WPJobManagerConnector
11+
12+
13+class CHUQuebecConnector(WPJobManagerConnector):
14+ source_id = "chu_quebec"
15+ EMPLOYER = "CHU de Québec — Université Laval"
16+ BASE = "https://chudequebec.carrieresante.gouv.qc.ca"
17+ DEFAULT_CITY = "Québec"
added jobka/connectors/chum.py +17 −0
@@ -0,0 +1,17 @@
1+# =============================================================================
2+# Job·Ka — Groupe KA
3+# Auteur : Simon-Pierre Boucher
4+# Contact : contact@spboucher.ai
5+# Fichier : jobka/connectors/chum.py
6+# Rôle : Connecteur CHUM (Centre hospitalier de l'Université de Montréal)
7+# — plateforme carrieresante.gouv.qc.ca (WP Job Manager)
8+# Créé : 2026-08-19 Modifié : 2026-08-19
9+# =============================================================================
10+from .wpjobmanager import WPJobManagerConnector
11+
12+
13+class CHUMConnector(WPJobManagerConnector):
14+ source_id = "chum"
15+ EMPLOYER = "CHUM"
16+ BASE = "https://chum.carrieresante.gouv.qc.ca"
17+ DEFAULT_CITY = "Montréal"
added jobka/connectors/cisss_abitibi.py +17 −0
@@ -0,0 +1,17 @@
1+# =============================================================================
2+# Job·Ka — Groupe KA
3+# Auteur : Simon-Pierre Boucher
4+# Contact : contact@spboucher.ai
5+# Fichier : jobka/connectors/cisss_abitibi.py
6+# Rôle : Connecteur CISSS de l'Abitibi-Témiscamingue — plateforme
7+# carrieresante.gouv.qc.ca (WP Job Manager)
8+# Créé : 2026-08-19 Modifié : 2026-08-19
9+# =============================================================================
10+from .wpjobmanager import WPJobManagerConnector
11+
12+
13+class CISSSAbitibiConnector(WPJobManagerConnector):
14+ source_id = "cisss_abitibi"
15+ EMPLOYER = "CISSS de l'Abitibi-Témiscamingue"
16+ BASE = "https://cisssat.carrieresante.gouv.qc.ca"
17+ DEFAULT_CITY = "Rouyn-Noranda"
added jobka/connectors/cisss_chaudiere_appalaches.py +17 −0
@@ -0,0 +1,17 @@
1+# =============================================================================
2+# Job·Ka — Groupe KA
3+# Auteur : Simon-Pierre Boucher
4+# Contact : contact@spboucher.ai
5+# Fichier : jobka/connectors/cisss_chaudiere_appalaches.py
6+# Rôle : Connecteur CISSS de Chaudière-Appalaches — plateforme
7+# carrieresante.gouv.qc.ca (WP Job Manager)
8+# Créé : 2026-08-19 Modifié : 2026-08-19
9+# =============================================================================
10+from .wpjobmanager import WPJobManagerConnector
11+
12+
13+class CISSSChaudiereAppalachesConnector(WPJobManagerConnector):
14+ source_id = "cisss_chaudiere_appalaches"
15+ EMPLOYER = "CISSS de Chaudière-Appalaches"
16+ BASE = "https://cisssca.carrieresante.gouv.qc.ca"
17+ DEFAULT_CITY = "Lévis"
added jobka/connectors/cisss_lanaudiere.py +17 −0
@@ -0,0 +1,17 @@
1+# =============================================================================
2+# Job·Ka — Groupe KA
3+# Auteur : Simon-Pierre Boucher
4+# Contact : contact@spboucher.ai
5+# Fichier : jobka/connectors/cisss_lanaudiere.py
6+# Rôle : Connecteur CISSS de Lanaudière — plateforme carrieresante.gouv.qc.ca
7+# (WP Job Manager)
8+# Créé : 2026-08-19 Modifié : 2026-08-19
9+# =============================================================================
10+from .wpjobmanager import WPJobManagerConnector
11+
12+
13+class CISSSLanaudiereConnector(WPJobManagerConnector):
14+ source_id = "cisss_lanaudiere"
15+ EMPLOYER = "CISSS de Lanaudière"
16+ BASE = "https://lanaudiere.carrieresante.gouv.qc.ca"
17+ DEFAULT_CITY = "Joliette"
added jobka/connectors/cisss_laurentides.py +17 −0
@@ -0,0 +1,17 @@
1+# =============================================================================
2+# Job·Ka — Groupe KA
3+# Auteur : Simon-Pierre Boucher
4+# Contact : contact@spboucher.ai
5+# Fichier : jobka/connectors/cisss_laurentides.py
6+# Rôle : Connecteur CISSS des Laurentides — plateforme
7+# carrieresante.gouv.qc.ca (WP Job Manager)
8+# Créé : 2026-08-19 Modifié : 2026-08-19
9+# =============================================================================
10+from .wpjobmanager import WPJobManagerConnector
11+
12+
13+class CISSSLaurentidesConnector(WPJobManagerConnector):
14+ source_id = "cisss_laurentides"
15+ EMPLOYER = "CISSS des Laurentides"
16+ BASE = "https://laurentides.carrieresante.gouv.qc.ca"
17+ DEFAULT_CITY = "Saint-Jérôme"
added jobka/connectors/cisss_laval.py +17 −0
@@ -0,0 +1,17 @@
1+# =============================================================================
2+# Job·Ka — Groupe KA
3+# Auteur : Simon-Pierre Boucher
4+# Contact : contact@spboucher.ai
5+# Fichier : jobka/connectors/cisss_laval.py
6+# Rôle : Connecteur CISSS de Laval — plateforme carrieresante.gouv.qc.ca
7+# (WP Job Manager)
8+# Créé : 2026-08-19 Modifié : 2026-08-19
9+# =============================================================================
10+from .wpjobmanager import WPJobManagerConnector
11+
12+
13+class CISSSLavalConnector(WPJobManagerConnector):
14+ source_id = "cisss_laval"
15+ EMPLOYER = "CISSS de Laval"
16+ BASE = "https://cissslaval.carrieresante.gouv.qc.ca"
17+ DEFAULT_CITY = "Laval"
added jobka/connectors/cisss_outaouais.py +17 −0
@@ -0,0 +1,17 @@
1+# =============================================================================
2+# Job·Ka — Groupe KA
3+# Auteur : Simon-Pierre Boucher
4+# Contact : contact@spboucher.ai
5+# Fichier : jobka/connectors/cisss_outaouais.py
6+# Rôle : Connecteur CISSS de l'Outaouais — site de recrutement
7+# viensvivretespassions.ca (WordPress + WP Job Manager)
8+# Créé : 2026-08-19 Modifié : 2026-08-19
9+# =============================================================================
10+from .wpjobmanager import WPJobManagerConnector
11+
12+
13+class CISSSOutaouaisConnector(WPJobManagerConnector):
14+ source_id = "cisss_outaouais"
15+ EMPLOYER = "CISSS de l'Outaouais"
16+ BASE = "https://viensvivretespassions.ca"
17+ DEFAULT_CITY = "Gatineau"
added jobka/connectors/cssdd.py +16 −0
@@ -0,0 +1,16 @@
1+# =============================================================================
2+# Job·Ka — Groupe KA
3+# Auteur : Simon-Pierre Boucher
4+# Contact : contact@spboucher.ai
5+# Fichier : jobka/connectors/cssdd.py
6+# Rôle : Connecteur Centre de services scolaire des Découvreurs (Québec)
7+# — Cegid Digital Recruiters (emplois.cssdd.gouv.qc.ca)
8+# Créé : 2026-08-19 Modifié : 2026-08-19
9+# =============================================================================
10+from .digitalrecruiters import DigitalRecruitersConnector
11+
12+
13+class CSSDDConnector(DigitalRecruitersConnector):
14+ source_id = "cssdd"
15+ EMPLOYER = "Centre de services scolaire des Découvreurs"
16+ BASE = "https://emplois.cssdd.gouv.qc.ca"
added jobka/connectors/wpjobmanager.py +125 −0
@@ -0,0 +1,125 @@
1+# =============================================================================
2+# Job·Ka — Groupe KA
3+# Auteur : Simon-Pierre Boucher
4+# Contact : contact@spboucher.ai
5+# Fichier : jobka/connectors/wpjobmanager.py
6+# Rôle : Classe de plateforme WordPress + WP Job Manager — flux job_feed
7+# (contenu complet) + JSON-LD des pages poste (lieu, date limite).
8+# Utilisée par la plateforme « carrieresante.gouv.qc.ca » du réseau
9+# de la santé (un site WordPress par établissement Santé Québec).
10+# Créé : 2026-08-19 Modifié : 2026-08-19
11+# =============================================================================
12+"""Plateforme WordPress / WP Job Manager.
13+
14+- liste : GET <BASE>/?feed=job_feed&posts_per_page=500
15+ -> RSS : <item> title / link (/poste/<slug>/) / pubDate /
16+ guid (?post_type=job_listing&p=<id>) / content:encoded (HTML
17+ complet de l'offre) — tout en une requête.
18+- détail : la page /poste/<slug>/ embarque un JSON-LD schema.org/JobPosting
19+ (validThrough, jobLocation, employmentType) — cache BD + budget,
20+ uniquement pour compléter lieu et date limite.
21+"""
22+from __future__ import annotations
23+
24+import html as _html
25+import os
26+import re
27+
28+from ..schema import JobPosting, clean_html
29+from . import _jsonld
30+from .base import BaseConnector
31+
32+MAX_DETAILS = int(os.environ.get("JOBKA_WPJM_DETAIL_LIMIT", "60"))
33+
34+_ITEM_RE = re.compile(r"<item>(.*?)</item>", re.S | re.I)
35+_TAG_RE = {t: re.compile(rf"<{t}>(.*?)</{t}>", re.S | re.I)
36+ for t in ("title", "link", "pubDate", "guid")}
37+_CONTENT_RE = re.compile(
38+ r"<content:encoded><!\[CDATA\[(.*?)\]\]></content:encoded>", re.S | re.I)
39+_PID_RE = re.compile(r"[?&](?:amp;)?p=(\d+)")
40+
41+
42+class WPJobManagerConnector(BaseConnector):
43+ """Base WP Job Manager — sous-classes : définir source_id, EMPLOYER,
44+ BASE (racine du site WordPress) et DEFAULT_CITY au besoin."""
45+
46+ ats = "wpjobmanager"
47+ request_delay = 1.0
48+
49+ EMPLOYER = ""
50+ BASE = ""
51+ DEFAULT_CITY = ""
52+ PER_PAGE = 500
53+
54+ def _cdata(self, s: str) -> str:
55+ s = (s or "").strip()
56+ if s.startswith("<![CDATA["):
57+ s = s[9:]
58+ if s.endswith("]]>"):
59+ s = s[:-3]
60+ return _html.unescape(s.strip())
61+
62+ def _fetch_detail(self, url: str) -> dict:
63+ html = self.get(url).text
64+ node = _jsonld.extract_jobposting(html)
65+ return _jsonld.jobposting_fields(node) if node else {}
66+
67+ def fetch(self) -> list[JobPosting]:
68+ xml = self.get(f"{self.BASE}/",
69+ params={"feed": "job_feed",
70+ "posts_per_page": str(self.PER_PAGE)}).text
71+ out: list[JobPosting] = []
72+ details_used = 0
73+ seen: set[str] = set()
74+ for raw in _ITEM_RE.findall(xml):
75+ def g(tag: str, raw=raw) -> str:
76+ m = _TAG_RE[tag].search(raw)
77+ return self._cdata(m.group(1)) if m else ""
78+ link, guid = g("link"), g("guid")
79+ m_id = _PID_RE.search(_html.unescape(guid))
80+ eid = m_id.group(1) if m_id else ""
81+ title = g("title")
82+ if not link or not title:
83+ continue
84+ if not eid:
85+ m_slug = re.search(r"/poste/([^/]+)/?$", link)
86+ eid = m_slug.group(1) if m_slug else ""
87+ if not eid or eid in seen:
88+ continue
89+ seen.add(eid)
90+ m_c = _CONTENT_RE.search(raw)
91+ job = JobPosting(
92+ source=self.source_id, external_id=eid, url=link,
93+ employer=self.EMPLOYER, title=title,
94+ description=clean_html(_html.unescape(m_c.group(1)))
95+ if m_c else "",
96+ city=self.DEFAULT_CITY,
97+ date_posted=g("pubDate") or None,
98+ ats=self.ats,
99+ )
100+ key = f"{title}|{job.date_posted or ''}"
101+ if details_used < MAX_DETAILS:
102+ fresh = [False]
103+
104+ def _fn(u=link, fresh=fresh):
105+ fresh[0] = True
106+ return self._fetch_detail(u)
107+
108+ fields = self.detail(eid, key, _fn)
109+ if fresh[0]:
110+ details_used += 1
111+ else:
112+ fields = self.stale_detail(eid)
113+ if fields:
114+ if fields.get("date_deadline"):
115+ job.date_deadline = fields["date_deadline"]
116+ if fields.get("city"):
117+ job.city = fields["city"]
118+ if fields.get("postal_code"):
119+ job.postal_code = fields["postal_code"]
120+ if fields.get("employment_label"):
121+ job.details["employment_label"] = fields["employment_label"]
122+ if fields.get("lat") is not None:
123+ job.lat, job.lng = fields["lat"], fields["lng"]
124+ out.append(job)
125+ return out
added tests/fixtures/chum/03ada5dc2047d09bb50f.html +4162 −0
Large diff (4,162 lines) is not loaded by default to keep the page fast.Load diff
added tests/fixtures/chum/11c79adc26615ea860b9.html +4178 −0
Large diff (4,178 lines) is not loaded by default to keep the page fast.Load diff
added tests/fixtures/chum/171380ec67cdfd7bdb17.html +4135 −0
Large diff (4,135 lines) is not loaded by default to keep the page fast.Load diff
added tests/fixtures/chum/1f471636b44482e7722b.html +4137 −0
Large diff (4,137 lines) is not loaded by default to keep the page fast.Load diff
added tests/fixtures/chum/210bc7e9274ae521ae25.html +3112 −0
Large diff (3,112 lines) is not loaded by default to keep the page fast.Load diff

Diff truncated — file too large.

Menu

Theme