SPB Git forge

spb/forma-ka

Public
6commits 1branches 0releases
4.0 MBsize
maindefault branch
22 days agolast push
Python 66.7% TypeScript 17.1% CSS 15.7% HTML 0.5%
26.9 KB

# Forma·Ka

# Toutes les formations du Québec. Un seul endroit.

www.forma-ka.com

Python FastAPI React SQLite PWA

Sources Connecteurs Formations Gratuites En ligne Couverture

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

  1. Visite guidée en 10 captures
  2. Pourquoi Forma-Ka ?
  3. Fonctionnalités
  4. Architecture
  5. Le pipeline d'ingestion en détail
  6. Les backends de fetch (chaîne résiliente)
  7. Le schéma Formation
  8. API & routes
  9. Données & sources agrégées
  10. Ajouter un connecteur
  11. Démarrage rapide
  12. Tests
  13. Déploiement (production)
  14. Dépôt & développement remote-first
  15. Confidentialité
  16. Écosystème Groupe Ka
  17. 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

Accueil

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

Statistiques

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

Sources

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. »

Confidentialité

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)

Fiche formation Isarta

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 »

Fiche formation — plan et 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

Accueil — 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)

Accueil — cartes illustrées et pagination

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)

Fiche formation en mobile

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)

Fiche formation CRHA

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
Accueil mobile
Fiche mobile
Fiche — détail d'abord
Stats mobile
Stats mobile
Menu 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

text
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

  1. Découverte — le registre des connecteurs est auto-découvrant : chaque module de formaka/connectors/ qui définit une classe héritant de BaseConnector avec un source_id est enrôlé automatiquement.
  2. Collecte — fetch() liste les formations du site (listing HTML, API JSON interne, sitemap…), puis visite les pages de détail au besoin.
  3. 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.
  4. 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.
  5. Diff & grâce — chaque fiche a un content_hash SHA-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).
  6. 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 /stats et /sources.

# Les backends de fetch (chaîne résiliente)

Historiquement, BaseConnector.fetch_html() enchaînait trois backends :

  1. requests direct — sites rendus serveur (rapide, gratuit) ;
  2. Scrapfly (SCRAPFLY_API_KEY) — contournement anti-bot (asp), rendu JavaScript (render_js), géolocalisation canadienne ;
  3. 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 :

  1. Direct — session du connecteur (curl_cffi avec empreinte de navigateur si disponible, sinon requests) ;
  2. Proxy résidentiel — IP résidentielle canadienne propre ;
  3. Scrapfly (ASP) — bypass anti-bot géré + rendu JS optionnel ;
  4. 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

  1. Créer formaka/connectors/<source_id>.py : une classe héritant de BaseConnector, définir source_id et implémenter fetch() -> list[Formation]. Le registre est auto-découvrant — rien d'autre à modifier.
  2. Ajouter l'entrée correspondante dans data/sources.json.
  3. Tester : .venv/bin/python run.py sync <source_id>.
python
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

bash
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

bash
.venv/bin/python -m pytest tests/ -q

# Dé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)
bash
# 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 300

# Dé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.