SPB Git

spb/forma-ka Public

Python 65.1% TypeScript 17.9% CSS 16.4% HTML 0.5%

Fix connectors: isarta unique slug ids, uqam price parsing, versalys session list format; README badge 4,900+

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 43 min ago (Aug 12, 2026) parent 1897456

Showing 4 changed files with +26 and −5

modified README.md +1 −1
@@ -14,7 +14,7 @@
14 14
15 15 ![Sources](https://img.shields.io/badge/sources-18-1d3f66?style=flat-square)
16 16 ![Connecteurs](https://img.shields.io/badge/active_connectors-18-1d3f66?style=flat-square)
17 ![Formations](https://img.shields.io/badge/aggregated_programs-4%2C400%2B-1d3f66?style=flat-square)
17 +![Formations](https://img.shields.io/badge/aggregated_programs-4%2C900%2B-1d3f66?style=flat-square)
18 18 ![Gratuites](https://img.shields.io/badge/free_programs-130%2B-1d3f66?style=flat-square)
19 19 ![Couverture](https://img.shields.io/badge/coverage-all_of_Qu%C3%A9bec-1d3f66?style=flat-square)
20 20
modified formaka/connectors/isarta.py +1 −1
@@ -27,7 +27,7 @@ BASE = "https://formations.isarta.com"
27 27 LIST_URL = f"{BASE}/"
28 28
29 29 _DURATION_RE = re.compile(r"\b(\d{1,2})\s*h\s*(\d{2})?\b")
30 _ID_RE = re.compile(r"/cours/[^/]+/(\d+)")
30 +_ID_RE = re.compile(r"/cours/([^/]+)") # slug unique (le nombre final = id de catégorie, non unique)
31 31
32 32 # type de contenu Isarta -> type de formation Forma-Ka
33 33 _TYPE_MAP = {"training": "Formation continue", "conference": "Conférence",
modified formaka/connectors/uqam_perfectionnement.py +18 −2
@@ -119,8 +119,24 @@ class UqamPerfectionnementConnector(BaseConnector):
119 119 payload["price_label"] = label
120 120 # produit à prix variable (« 26,09 $ – 490 $ ») : le premier
121 121 # montant est un acompte — le vrai tarif est le plus élevé
122 amounts = [float(a.replace(",", "").replace(" ", ""))
123 for a in re.findall(r"\$\s*([\d, ]+(?:\.\d{2})?)", label)]
122 + # montants en format anglais (« $490 ») ou français (« 490 $ »,
123 + # « 490,00 $ ») — ignorer les captures vides/sans chiffre
124 + amounts = []
125 + for m_ in re.finditer(
126 + r"\$\s*([\d, ]*\d(?:\.\d{2})?)|(\d[\d ,]*(?:[.,]\d{2})?)\s*\$",
127 + label):
128 + raw = (m_.group(1) or m_.group(2) or "")
129 + raw = raw.replace(" ", "").replace(" ", "").replace(" ", "")
130 + if "," in raw and "." in raw:
131 + raw = raw.replace(",", "")
132 + elif re.search(r",\d{2}$", raw):
133 + raw = raw.replace(",", ".")
134 + else:
135 + raw = raw.replace(",", "")
136 + try:
137 + amounts.append(float(raw))
138 + except ValueError:
139 + continue
124 140 if amounts:
125 141 payload["price"] = max(amounts)
126 142
modified formaka/connectors/versalys.py +6 −1
@@ -140,7 +140,12 @@ class VersalysConnector(BaseConnector):
140 140 slots = json.loads(unquote_plus(inp["value"])) if inp else {}
141 141 except (ValueError, KeyError):
142 142 slots = {}
143 for slot in slots.values():
143 + # le JSON est tantôt un objet {id: slot}, tantôt un tableau [slot]
144 + slot_list = (slots.values() if isinstance(slots, dict)
145 + else slots if isinstance(slots, list) else [])
146 + for slot in slot_list:
147 + if not isinstance(slot, dict):
148 + continue
144 149 start = str(slot.get("startDate", ""))[:10]
145 150 if re.match(r"20\d{2}-\d{2}-\d{2}", start):
146 151 sessions.append(start)
147 152