Forma·Ka
Toutes les formations du Québec. Un seul endroit.
Agrégateur indépendant de formations dans la province de Québec — cours en ligne, cours universitaires et collégiaux, séminaires, ateliers, conférences, certifications et bootcamps — chaque formation avec sa fiche détaillée standardisée et un lien direct vers la page originale de l'établissement. Toujours à jour, automatiquement.
Auteur : Simon-Pierre Boucher — contact@spboucher.ai
Sommaire
- Visite guidée en 10 captures
- Pourquoi Forma-Ka ?
- Fonctionnalités
- Architecture
- Le pipeline d'ingestion en détail
- Les backends de fetch (chaîne résiliente)
- Le schéma
Formation - API & routes
- Données & sources agrégées
- Ajouter un connecteur
- Démarrage rapide
- Tests
- Déploiement (production)
- Dépôt & développement remote-first
- Confidentialité
- Écosystème Groupe Ka
- Contact
Visite guidée en 10 captures
Captures du site en production (www.forma-ka.com),
prises le 2026-08-28 en 1440 × 900 — plus une vue mobile 390 px. Les fichiers
sont dans
docs/screenshots/ (voir aussi
manifest.txt pour la correspondance
capture → URL).
1 · Accueil — le moteur de recherche

La page d'accueil (/) donne le ton « editorial sharp » : gros titres Space
Grotesk, surlignage ambré #ffd54d, ombres décalées, ticker en continu des
types de formations. On y trouve les compteurs en direct (4 932 formations
actives · 135 gratuites · 3 556 en ligne · 18 établissements), la barre de
recherche (sujet, sigle, compétence…), les filtres Domaine / Mode / Ville /
Plus de filtres et les pastilles de filtre rapide par type (Cours
universitaire, Formation continue, Cours collégial, Atelier, Conférence,
Séminaire, Webinaire, Gratuites…). En bas, la bannière de témoins rappelle la
philosophie : aucun traceur publicitaire, préférences en localStorage.
2 · Statistiques — portrait en direct de l'offre québécoise

La page /stats dresse le portrait en direct des formations actives, tous
établissements confondus : compteurs globaux (prix moyen affiché 858 $,
durée moyenne 14 h), répartition par type de formation (2 268 cours
universitaires, 2 058 formations continues, 118 cours collégiaux, 112 ateliers,
104 cours en ligne, 98 conférences, 80 séminaires, 41 webinaires, 27
programmes, 22 certifications, 4 bootcamps) et journal des synchronisations
récentes par source (trouvées / ajoutées / modifiées / retirées + état OK).
3 · Sources agrégées — le registre des établissements

La page /sources expose le registre data/sources.json enrichi en direct :
chaque établissement avec son type d'offre, son statut de connecteur
(CONNECTÉ), le nombre de formations actives et l'horodatage de la dernière
synchronisation. C'est la vitrine de transparence de Forma-Ka : on voit d'où
viennent les données et quand elles ont été rafraîchies.
4 · Confidentialité — « Votre vie privée, simplement. »

La page /confidentialite détaille l'approche minimaliste : aucun compte
utilisateur, aucun pixel publicitaire, aucun témoin tiers. Le seul stockage est
le localStorage du navigateur (choix de consentement, filtres de recherche),
avec un bouton « Modifier mes choix de témoins » accessible en tout temps.
5 · Fiche formation — le détail d'abord (Isarta · Data Studio)

Une fiche /formation/:uid (ici isarta:data-studio) montre la philosophie
« détail d'abord » : fil d'Ariane par catégorie, plan de la formation
complet (11 points), bloc Admission / clientèle visée, colonne des
thèmes et des formations similaires (avec type, mode, durée, prix et
source), puis le contenu original signé par la source. Chaque fiche renvoie
vers la page originale de l'établissement.
6 · Fiche formation — les recommandations « Formations similaires »

Le cœur d'une fiche vu de près : à gauche le plan de la formation point par point et le bloc Admission / clientèle visée ; à droite la colonne de recommandations Formations similaires, calculée par l'API (même catégorie, même source, thèmes proches) avec pour chacune son type, son mode, sa durée et son prix — un vrai moteur de découverte entre les 4 900+ fiches.
7 · Accueil — la grille de résultats

L'accueil, défilé jusqu'à la grille : cartes de formation en 3 colonnes avec badge de type (Formation continue, Atelier…), établissement, titre, sigle, mode (présentiel / en ligne) et durée, date de début (« Débute le 3 septembre 2026 ») et traitement franc du prix — « Prix non affiché » assumé (prix optionnel), « Gratuit » mis en valeur, sinon le montant.
8 · Accueil — cartes illustrées & pagination (411 pages)

Plus bas sur l'accueil : les cartes reprennent les visuels originaux des établissements (Institut de leadership, Événements Les Affaires…) avec prix affiché (395 $, 545 $, 945 $), mode et ville. En pied de grille, la pagination — « Page 1 de 411 — 4 930 formations » à 12 fiches par page — puis le pied de page sombre avec la mission de l'agrégateur.
9 · Fiche formation en mobile (390 px)

La même fiche Data Studio sur téléphone : menu hamburger, ticker conservé, fil d'Ariane et plan de la formation qui se replient proprement sur une colonne — le socle mobile du Groupe Ka (zones tactiles, safe-area, PWA installable) appliqué à Forma-Ka.
10 · Fiche événement — prix optionnel, dates offertes (CRHA · 5 à 7 estival)

Deuxième exemple de fiche (crha:202609035@7Lanaudiere) qui illustre la
souplesse du schéma : un événement de réseautage RH sans prix affiché
(le prix est optionnel dans Forma-Ka), avec ses dates offertes (3 septembre
2026), ses infos pratiques (établissement, sigle, horaire affiché, date de
synchronisation) et ses thèmes régionaux (Comité régionale de Lanaudière).
Galerie mobile & desktop (2026-08-25)
Captures WebP précédentes (mobile 390 × 844 · desktop 1440 × 900), conservées
dans docs/screenshots/mobile/ et
docs/screenshots/desktop/.
![]() Accueil mobile |
![]() Fiche — détail d'abord |
![]() Stats mobile |
![]() Menu mobile |
🗄️ Les toutes premières captures (PNG) sont archivées dans
docs/archive/.
Pourquoi Forma-Ka ?
Chercher une formation au Québec, c'est ouvrir des dizaines de sites — universités, cégeps, firmes de formation, organisateurs d'événements — chacun avec sa navigation et son format. Forma-Ka renverse le problème : un connecteur dédié par établissement visite chaque site, normalise chaque formation dans un schéma unique et détecte les changements en continu.
Les sites de formation n'offrent pas de webhooks. Forma-Ka en reproduit l'équivalent : synchronisation périodique + hachage de contenu → ajouts, mises à jour et retraits détectés automatiquement. Une formation qui disparaît du site source disparaît de Forma-Ka (après une période de grâce de 2 synchronisations).
Philosophie : contrairement à un agrégateur de produits, le prix est optionnel (un cours universitaire n'affiche pas de prix) — ce qui compte, ce sont les détails de chaque formation : description complète, objectifs d'apprentissage, plan de cours, préalables, clientèle visée, durée, crédits/UEC, mode de diffusion, dates offertes.
Fonctionnalités
- Recherche plein texte (sujet, sigle, compétence) combinable avec les filtres domaine (Informatique, Gestion, RH, Marketing…), type (cours universitaire, formation continue, atelier, séminaire, webinaire, certification, bootcamp…), mode (en ligne, présentiel, hybride, asynchrone), ville, langue, niveau, gratuit, prix maximal et date de début ;
- Fiches détaillées standardisées : description, objectifs, plan de cours, préalables, clientèle visée, durée (texte + heures), crédits/UEC, sigle, formateur, dates offertes, prix (optionnel, avec mention « à partir de »), thèmes, formations similaires, lien direct vers la source ;
- Statistiques en direct (
/stats) : portrait global + journal des synchronisations par source ; - Registre des sources (
/sources) : statut du connecteur, volume, dernière synchro ; - Ticker en continu des types de formations en tête de chaque page ;
- PWA installable (manifest + thème clair, accent ambré
#ffd54d), pagination 12 par page, feuillet mobile (« bottom sheet ») pour les filtres ; - Aucun traceur : consentement localStorage, page confidentialité dédiée ;
- Mise à jour automatique toutes les 6 h (watcher PM2) avec détection de dérive des connecteurs.
Architecture
flowchart LR
subgraph Sources["18 établissements de formation"]
S1["ÉTS Formation · TÉLUQ · ULaval<br/>McGill · HEC · UQAM · Technologia<br/>AFI · Cégep à distance · Les Affaires<br/>… un connecteur par site"]
end
subgraph FormaKa["Forma-Ka"]
C["Connecteurs<br/><i>1 adaptateur / site</i>"] --> N["Normalisation<br/><i>schéma Formation unique</i>"]
N --> D[("SQLite<br/>hash + diff + grâce")]
D --> A["FastAPI<br/>/api/formations · /api/facets"]
A --> F["React 18 + Vite<br/>PWA mobile · thème clair ambré"]
end
W["⏱ Watcher périodique<br/>(PM2, 6 h)"] -.-> C
S1 --> C
F --> U["🎓 Apprenant"]
| Couche | Rôle | Fichiers |
|---|---|---|
| Connecteurs | 1 module Python par établissement : HTML rendu serveur, API JSON internes (Shopify products.json, WordPress REST, Gatsby page-data, Destiny One…), JSON-LD schema.org (Course/Event), chaîne anti-bot résiliente pour les sites difficiles |
formaka/connectors/*.py |
| Chaîne résiliente | Escalade automatique direct → proxy résidentiel → anti-bot géré → déblocage premium quand un site se ferme (403/429/503, Cloudflare…) | formaka/connectors/_resilient.py |
| Schéma | Formation standardisée : type, catégorie, mode, description, objectifs, plan, préalables, durée, crédits/UEC, sessions, prix (optionnel) |
formaka/schema.py |
| Normalisation | Couche commune : nettoyage de texte, types/modes/langues canoniques, prix, durées en heures, dates FR → ISO, extraction de détails (niveau, UEC…) | formaka/normalize.py |
| Moteur de diff | Upsert par hash de contenu — nouveau / modifié / disparu (période de grâce de 2 synchros), détection de dérive des connecteurs, cache des fiches | formaka/db.py, formaka/ingest.py |
| API | Filtres type / catégorie / mode / ville / langue / niveau / gratuit / prix / source / recherche, facettes, stats, déclenchement de synchro | formaka/web.py |
| Frontend | Design « editorial sharp » : Space Grotesk, ombres décalées, accent ambré #ffd54d, ticker en continu, feuillet mobile, pagination 12/page, PWA installable |
frontend/ |
Structure du dépôt
forma-ka/
├── run.py # point d'entrée : sync | watch | serve
├── requirements.txt # fastapi, uvicorn, requests, beautifulsoup4
├── formaka/
│ ├── schema.py # dataclass Formation + finalize()
│ ├── normalize.py # normalisation commune (texte, prix, dates…)
│ ├── ingest.py # pipeline sync/watch (diff + grâce + journal)
│ ├── db.py # SQLite : formations, detail_cache, sync_log
│ ├── web.py # FastAPI : /api/* + service du frontend bâti
│ └── connectors/ # 18 connecteurs + base.py + _resilient.py
├── frontend/ # React 18 + Vite + TypeScript (PWA)
│ └── src/pages/ # Home, Formation, Stats, Sources, Privacy
├── data/
│ ├── sources.json # registre des établissements (versionné)
│ └── formaka.db # base SQLite (NON versionnée)
├── docs/screenshots/ # visite guidée (JPG) + galeries webp
└── tests/ # pytest (normalisation)Le pipeline d'ingestion en détail
- Découverte — le registre des connecteurs est auto-découvrant :
chaque module de
formaka/connectors/qui définit une classe héritant deBaseConnectoravec unsource_idest enrôlé automatiquement. - Collecte —
fetch()liste les formations du site (listing HTML, API JSON interne, sitemap…), puis visite les pages de détail au besoin. - Cache des fiches — les pages de détail sont mises en cache dans la base
(
detail_cache) avec une clé hebdomadaire : une page n'est revisitée que si elle est nouvelle, modifiée, ou quand la semaine ISO change. Les synchros intermédiaires sont donc rapides et économes. - Normalisation —
Formation.finalize()applique la couche commune (types/modes/langues canoniques, prix depuis le libellé, durée en heures, dates françaises → ISO, extraction de niveau/UEC/crédits) sans jamais écraser une valeur explicite du connecteur. - Diff & grâce — chaque fiche a un
content_hashSHA-256 ; l'upsert ne touche que ce qui a changé. Une formation absente du site source est retirée après 2 synchros consécutives d'absence (période de grâce contre les listings instables). - Journal & dérive — chaque synchro est journalisée (
sync_log: trouvées / ajoutées / modifiées / retirées + taux de champs nuls) ; une chute anormale du volume ou une explosion des champs vides signale une dérive du connecteur (site remanié) visible sur/statset/sources.
Les backends de fetch (chaîne résiliente)
Historiquement, BaseConnector.fetch_html() enchaînait trois backends :
- requests direct — sites rendus serveur (rapide, gratuit) ;
- Scrapfly (
SCRAPFLY_API_KEY) — contournement anti-bot (asp), rendu JavaScript (render_js), géolocalisation canadienne ; - Firecrawl (
FIRECRAWL_API_KEY) — rendu JS de repli, formats html/markdown.
Cette chaîne est désormais renforcée par la chaîne anti-bot résiliente
commune du Groupe KA (formaka/connectors/_resilient.py) : quand un site
jusque-là ouvert déploie un anti-bot (Cloudflare, Akamai, Incapsula,
PerimeterX) ou renvoie 403/429/503, la requête directe n'échoue plus
silencieusement — elle escalade automatiquement :
- Direct — session du connecteur (
curl_cffiavec empreinte de navigateur si disponible, sinonrequests) ; - Proxy résidentiel — IP résidentielle canadienne propre ;
- Scrapfly (ASP) — bypass anti-bot géré + rendu JS optionnel ;
- Bright Data (Web Unlocker) — déblocage premium, dernier recours.
Le premier backend qui renvoie un 200 non vide gagne ; si tous échouent, la
dernière réponse est renvoyée telle quelle pour que le connecteur journalise
l'échec normalement (aucun blocage silencieux). Les clés d'API vivent dans
.env (jamais versionné).
Le schéma Formation
Chaque connecteur, peu importe le site source, produit des objets Formation
(formaka/schema.py) :
| Groupe | Champs |
|---|---|
| Identité | source, external_id, url → uid = source:external_id |
| Classement | title, training_type, category, tags, level, language |
| Diffusion | mode (en ligne / présentiel / hybride / asynchrone), city, start_date (ISO), sessions[], schedule_label, duration, duration_hours |
| Contenu | description, objectives[], program[] (plan de cours), prerequisites, audience, instructor, code (sigle), images[] |
| Valeur | price (optionnel — None = non affiché, c'est normal !), price_label (texte original), is_free, credits (« 3 crédits », « 1,4 UEC »), credential |
| Technique | details (JSON structuré dérivé), content_hash() (SHA-256 pour la détection de changements) |
API & routes
API REST (FastAPI)
| Point d'accès | Description |
|---|---|
GET /api/formations |
Liste filtrable (training_type, category, mode, city, language, level, source, free, price_max, starts_after, q, sort, limit, offset) |
GET /api/formations/{uid} |
Fiche complète + historique de prix + formations similaires |
GET /api/facets |
Valeurs distinctes pour construire les filtres |
GET /api/sources |
Registre des établissements + état de synchro (active_formations, last_sync) |
GET /api/stats |
Portrait global (totaux, moyennes, répartition par type) + journal des synchros |
POST /api/sync |
Déclenche une synchronisation en arrière-plan |
Routes du frontend (SPA React Router)
| Route | Page |
|---|---|
/ |
Accueil — recherche, filtres, compteurs en direct |
/formation/:uid |
Fiche détaillée (ex. /formation/isarta:data-studio) |
/stats |
Statistiques en direct |
/sources |
Registre des sources agrégées |
/confidentialite |
Politique de confidentialité & témoins |
* |
Page 404 (boussole) |
Le backend sert aussi le frontend bâti (frontend/dist) : /assets en
statique + rattrapage SPA sur toutes les autres routes.
Données & sources agrégées
18 établissements connectés — 4 932 formations actives (relevé du 2026-08-28) :
| Établissement | Offre | Formations | Région |
|---|---|---|---|
| Université Laval — Formation à distance | Cours universitaires à distance, hybrides et comodaux | 1 757 | Québec (à distance / hybride) |
| Technologia | Formation professionnelle — TI, IA, gestion | 590 | Montréal / Québec |
| Université TÉLUQ | Cours universitaires 100 % à distance, tous cycles | 511 | Québec (à distance) |
| AFI par Edgenda | Formation professionnelle — TI, leadership | 358 | Québec / Montréal |
| ÉTS Formation | Formation continue + UEC — technologie, construction, gestion, RH | 296 | Montréal |
| Versalys | Bureautique, TI, langues | 257 | Montréal · Québec · Laval · Brossard |
| McGill School of Continuing Studies | Formation continue (anglais) | 197 | Montréal |
| Isarta Formations | Marketing, communications, RH | 189 | Montréal (virtuel) |
| CRHA — Espace Formation | Formations et événements RH | 180 | Québec (province) — surtout en ligne |
| Événements Les Affaires | Conférences et webinaires d'affaires | 139 | Montréal / Québec / en ligne |
| Cégep à distance | Cours collégiaux à distance | 118 | Québec (en ligne) |
| École des dirigeant(e)s HEC Montréal | Séminaires et certifications pour cadres | 93 | Montréal |
| ITHQ — Ateliers et formations | Ateliers vins/cuisine + hôtellerie | 78 | Montréal (+ province) |
| Formation continue UQAM | Formation continue universitaire — UEC, séminaires | 49 | Montréal |
| École des entrepreneurs du Québec | Formations entrepreneur·e·s (souvent gratuites) | 48 | Montréal + en ligne |
| Institut de leadership | Certifications et programmes en leadership | 37 | Montréal (+ cohortes en ligne) |
| AlphaNumérique | Littératie numérique — gratuit | 29 | Québec (en ligne) |
| Le Wagon Montréal | Bootcamps dev web / data / IA | 4 | Montréal |
Répartition par type : 2 268 cours universitaires · 2 058 formations continues · 118 cours collégiaux · 112 ateliers · 104 cours en ligne · 98 conférences · 80 séminaires · 41 webinaires · 27 programmes · 22 certifications · 4 bootcamps.
Le registre vit dans data/sources.json (versionné) ;
la base SQLite (data/formaka.db) est locale et reconstruite par sync.
Ajouter un connecteur
- Créer
formaka/connectors/<source_id>.py: une classe héritant deBaseConnector, définirsource_idet implémenterfetch() -> list[Formation]. Le registre est auto-découvrant — rien d'autre à modifier. - Ajouter l'entrée correspondante dans
data/sources.json. - Tester :
.venv/bin/python run.py sync <source_id>.
class MonEcoleConnector(BaseConnector):
source_id = "mon_ecole"
def fetch(self) -> list[Formation]:
html = self.fetch_html(LIST_URL) # direct -> chaîne résiliente
...
return [Formation(source=self.source_id, external_id=..., url=...,
title=..., description=..., objectives=[...], ...)]Démarrage rapide
git clone https://git.spboucher.ai/forma-ka.git && cd forma-ka
# Backend
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
# Frontend
cd frontend && npm install && npm run build && cd ..
# Clés des backends de scraping (sites JavaScript / anti-bot) — jamais versionnées
cat > .env <<EOF
FIRECRAWL_API_KEY=fc-votre-cle
SCRAPFLY_API_KEY=scp-live-votre-cle
EOF
# Ingestion, puis service
.venv/bin/python run.py sync # toutes les sources (ou : run.py sync ets_formation teluq)
.venv/bin/python run.py serve 8080 # API + frontend -> http://localhost:8080
.venv/bin/python run.py watch 360 # boucle de synchro (défaut : toutes les 6 h)Tests
.venv/bin/python -m pytest tests/ -qDéploiement (production)
| Élément | Valeur |
|---|---|
| Nœud | M3U96a (Mac Studio, cluster MacLustr) — ~/apps/forma-ka |
| Port | 8110 |
| Processus PM2 | forma-ka (API + frontend, run.py serve 8110) · forma-ka-sync (run.py watch 360 → synchro toutes les 6 h) · forma-ka-ngrok (tunnel) |
| Domaine | www.forma-ka.com via ngrok |
| Base | data/formaka.db (SQLite, locale au nœud) |
| Résilience | PM2 avec redémarrage automatique + pm2 startup (launchd) |
# Sur le nœud (lecture seule — l'état de référence)
pm2 ls | grep forma-ka
curl -s localhost:8110/api/stats | head -c 300Dépôt & développement remote-first
La source de vérité est le dépôt git sur le nœud de déploiement
(M3U96a:~/apps/forma-ka), pas une copie locale. Le remote origin est le
git personnel spbgit (git.spboucher.ai) — dépôt nu
~/srv/git/forma-ka.git hébergé sur M3U96a.
Cycle de travail : éditer sur le nœud via SSH → npm run build (frontend) →
pm2 restart forma-ka → git add/commit/push origin main sur le nœud
(agent forwarding actif).
Ce qui n'est jamais versionné (voir .gitignore) : la base
SQLite et les caches (data/, sauf sources.json déjà suivi), les secrets
(.env*), les environnements (.venv/, node_modules/), les artefacts de
build (frontend/dist/) et les journaux.
Confidentialité
Forma-Ka ne collecte rien qui identifie l'utilisateur : pas de compte, pas
de formulaire d'inscription, pas de pixel publicitaire, pas de témoin tiers,
aucune donnée vendue. Le seul stockage est le localStorage du navigateur
(choix de consentement, filtres, préférences d'affichage), modifiable en tout
temps via « Gérer mes témoins ». Détails : page
/confidentialite.
Écosystème Groupe Ka
Forma-Ka fait partie du Groupe Ka (groupe-ka.com), la famille d'agrégateurs indépendants du Québec — notamment Lou-Ka (logements), Immo-Ka (propriétés), Vrai-Prix (épicerie), Auto-Ka (véhicules), Food-Ka, Resto-Ka, Sorti-Ka (sorties), Job-Ka (emplois), Trouve-Ka (recherche), Créa-Ka (créateurs), Fabri-Ka (produits d'ici) et Ka·Stats (statistiques du Québec). Même ADN partout : connecteurs dédiés, schéma standardisé, diff par hachage, fiches détaillées, respect de la vie privée — et le mécanisme de Forma-Ka est directement adapté de celui de Lou-Ka.
Contact
Simon-Pierre Boucher contact@spboucher.ai · www.forma-ka.com
© 2026 Simon-Pierre Boucher — tous droits réservés.



