docs: refonte du README (pastilles + captures d écran à jour)
3 changed files +90 −264
modified
README.md
+90 −264
@@ -5,304 +5,130 @@ Auteur : Simon-Pierre Boucher | |||||
| 5 | Contact : contact@spboucher.ai | 5 | Contact : contact@spboucher.ai | ||
| 6 | Fichier : README.md | 6 | Fichier : README.md | ||
| 7 | Rôle : Documentation principale du projet | 7 | Rôle : Documentation principale du projet | ||
| 8 | −Créé : 2026-08-17 Modifié : 2026-08-18 | 8 | +Créé : 2026-08-17 Modifié : 2026-08-24 | ||
| 9 | ============================================================================= | 9 | ============================================================================= | ||
| 10 | --> | 10 | --> | ||
| 11 | 11 | ||||
| 12 | −<div align="center"> | ||||
| 13 | − | ||||
| 14 | # Job·Ka | 12 | # Job·Ka | ||
| 15 | 13 | ||||
| 16 | −### Tous les emplois des employeurs québécois. Un seul endroit. | 14 | +**Tous les emplois des employeurs québécois, collectés directement sur leurs pages carrières — un seul endroit, zéro boîte noire.** | ||
| 17 | − | ||||
| 18 | −**[www.job-ka.com](https://www.job-ka.com)** | ||||
| 19 | − | ||||
| 20 | −*Job·Ka collecte les offres directement sur les pages carrières des employeurs | ||||
| 21 | −du Québec — 217 employeurs branchés sur 17 plateformes ATS, complétés par | ||||
| 22 | −3 portails publics (l'offre directe gagne toujours). Chaque offre est | ||||
| 23 | −traçable à sa source, les salaires sont normalisés en $/h et $/an, et la | ||||
| 24 | −candidature se fait chez l'employeur. Zéro boîte noire, zéro intermédiaire.* | ||||
| 25 | − | ||||
| 26 | −</div> | ||||
| 27 | − | ||||
| 28 | −--- | ||||
| 29 | 15 | ||||
| 30 | −## Sommaire | 16 | +[](https://www.job-ka.com) | ||
| 17 | + | ||||
| 18 | + | ||||
| 19 | + | ||||
| 20 | + | ||||
| 21 | + | ||||
| 22 | + | ||||
| 23 | + | ||||
| 24 | + | ||||
| 31 | 25 | ||||
| 32 | −- [Pourquoi Job·Ka ?](#pourquoi-job-ka-) | 26 | +Les grands sites d'emploi mélangent annonces payantes, doublons repostés et offres mortes. L'offre originale — celle de la page carrière de l'employeur — est la seule source fiable. **Job·Ka** se branche **directement sur les systèmes de recrutement (ATS)** des employeurs québécois : **968 connecteurs employeurs directs** sur **17 plateformes ATS** (Workday, Lever, Greenhouse, SmartRecruiters, BambooHR, Breezy, Recruitee, Ashby, Workable, Taleo, Njoyn, UltiPro, iCIMS, SuccessFactors, ADP WFN, Digital Recruiters, Workland), complétés par **3 portails publics** (Guichet-Emplois, Jobillico, Espresso-Jobs) — l'offre directe gagne toujours. | ||
| 33 | −- [L'architecture en 30 secondes](#larchitecture-en-30-secondes) | ||||
| 34 | −- [Le pipeline en détail](#le-pipeline-en-détail) | ||||
| 35 | −- [Connecteurs](#connecteurs) | ||||
| 36 | −- [Structure du dépôt](#structure-du-dépôt) | ||||
| 37 | −- [Démarrage rapide](#démarrage-rapide) | ||||
| 38 | −- [CLI `run.py`](#cli-runpy) | ||||
| 39 | −- [Variables d'environnement](#variables-denvironnement) | ||||
| 40 | −- [API](#api) | ||||
| 41 | −- [Ajouter un employeur (≈ 10 lignes)](#ajouter-un-employeur--10-lignes) | ||||
| 42 | −- [Tests](#tests) | ||||
| 43 | −- [Production](#production) | ||||
| 44 | −- [Principes](#principes) | ||||
| 45 | −- [Écosystème Groupe KA](#écosystème-groupe-ka) | ||||
| 46 | 27 | ||||
| 47 | −--- | 28 | +Résultat en base : plus de **21 000 offres actives** de plus de **1 600 employeurs** (≈960 sources), dont ~**19 800 offres directes** et près de **6 900 avec salaire affiché** — salaires **normalisés en $/h et $/an**, jamais inventés. Chaque offre est traçable à sa source et la candidature se fait chez l'employeur. Pour qui ? Les chercheurs d'emploi québécois qui veulent l'inventaire réel, à jour et transparent, des postes affichés dans la province — resynchronisé jour et nuit. | ||
| 48 | 29 | ||||
| 49 | −## Pourquoi Job·Ka ? | 30 | +## Captures d'écran | ||
| 50 | 31 | ||||
| 51 | −Les grands sites d'emploi mélangent annonces payantes, doublons repostés et | 32 | +<p align="center"> | ||
| 52 | −offres mortes. L'offre originale — celle de la page carrière de l'employeur — | 33 | + <img src="docs/screenshots/job-ka-desktop.png" width="640" alt="Accueil — desktop"> | ||
| 53 | −est la seule source fiable : à jour, complète, sans frais de mise en avant. | 34 | + <img src="docs/screenshots/job-ka-mobile.png" width="200" alt="Accueil — mobile"> | ||
| 35 | +</p> | ||||
| 54 | 36 | ||||
| 55 | −> Job·Ka se branche directement sur les systèmes de recrutement (ATS) des | 37 | +## Fonctionnalités | ||
| 56 | −> employeurs — 17 plateformes : Workday, Lever, Greenhouse, SmartRecruiters, | ||||
| 57 | −> BambooHR, Breezy, Recruitee, Ashby, Workable, Taleo, Njoyn, UltiPro, | ||||
| 58 | −> iCIMS, SuccessFactors, ADP WFN, Digital Recruiters, Workland. Un | ||||
| 59 | −> connecteur = un employeur. Le tout resynchronisé jour et nuit. | ||||
| 60 | 38 | ||||
| 61 | −Ce n'est pas un job board qui vend de la visibilité, c'est un index fidèle de | 39 | +- **Recherche filtrée** — texte, ville, catégorie (16 catégories : TI, Santé, Finance, Ingénierie…), mode de travail (dont télétravail), type d'emploi, **salaire minimum / « salaire affiché »**, tri, pagination. | ||
| 62 | −ce que les employeurs québécois affichent réellement — **avec les salaires en | 40 | +- **Connecteurs auto-découverts** — toute sous-classe de `BaseConnector` avec un `source_id` est enregistrée par `pkgutil` ; brancher un nouvel employeur sur une plateforme ATS = **~10 lignes**. Politesse : throttling par source, User-Agent identifiable `JobKaBot/1.0`, budgets de requêtes, cache des pages détail. | ||
| 63 | −clair quand ils existent** (normalisés en $/h et $/an), parce que la | 41 | +- **Diff engine anti-dérive** — comparaison par `content_hash` à chaque sync ; une offre absente 2 syncs passe `active=0` (jamais de DELETE) ; si une source retourne ≤ 25 % de sa médiane historique, alerte et retraits suspendus. | ||
| 64 | −transparence salariale est le différenciateur clé. | 42 | +- **Salaires normalisés, jamais inventés** — `parse_salary()` exige un signe monétaire ou une unité explicite, bornes de plausibilité, conversion $/h ↔ $/an, historique des changements (`salary_log`). | ||
| 43 | +- **Géocodage sans invention** — Nominatim + repli officiel Adresses Québec (MERN), cache persistant, validation par bounding box provinciale ; carte des offres (MapLibre, sans jeton) via `GET /api/jobs.geojson`. | ||||
| 44 | +- **Déduplication inter-sources direct-prioritaire** — union-find avec second signal concordant ; en cas de doublon, l'offre de la page carrière (badge « Offre directe ») devient la canonique, les copies portail sont masquées (`dup_of`), jamais supprimées. | ||||
| 45 | +- **Dépôt direct employeurs** — `/employeurs` (`POST /api/employeurs/offres`), modération manuelle. | ||||
| 46 | +- **Page /stats** — tableau de bord analytique + rapports PDF Groupe-KA (kit kacharts/kapdf) ; **favoris « Mon univers Ka »** (KA ID, cœur sur cartes et fiche, page `/favoris`). | ||||
| 47 | +- **Conformité documentée** — `docs/CONFORMITE.md` (base d'accès par famille de sources, attribution, zéro donnée personnelle de candidat) + une fiche générée par connecteur dans `docs/connecteurs/`. | ||||
| 48 | +- **Tests hors ligne** — fixtures HTTP enregistrées et rejouées (pytest), invariants (uid uniques, URLs absolues, salaires plausibles). | ||||
| 65 | 49 | ||||
| 66 | −## L'architecture en 30 secondes | 50 | +## Architecture | ||
| 67 | 51 | ||||
| 68 | −```mermaid | ||||
| 69 | −flowchart LR | ||||
| 70 | − subgraph Sources | ||||
| 71 | − W["Workday"] --> C | ||||
| 72 | − L["Lever"] --> C | ||||
| 73 | − G["Greenhouse"] --> C | ||||
| 74 | − S["SmartRecruiters"] --> C | ||||
| 75 | − X["Sites maison<br/>(HTML / Scrapfly)"] --> C | ||||
| 76 | − end | ||||
| 77 | − C["Connecteurs<br/>(auto-découverts)"] --> N["finalize()<br/>normalisation"] | ||||
| 78 | − N --> D[("SQLite<br/>data/jobka.db")] | ||||
| 79 | − W2["⏱ Watcher horaire<br/>(PM2)"] -.-> C | ||||
| 80 | − D --> A["FastAPI<br/>/api/*"] | ||||
| 81 | − A --> F["React + Vite<br/>frontend/dist"] | ||||
| 82 | − D --> GEO["Géocodage<br/>Nominatim + Adresses Québec"] | ||||
| 83 | − GEO --> D | ||||
| 84 | ``` | 52 | ``` | ||
| 85 | − | 53 | +Sources (17 plateformes ATS + sites maison + 3 portails) | ||
| 86 | −| Couche | Rôle | Fichiers | | 54 | + → connecteurs auto-découverts (jobka/connectors/) | ||
| 87 | −|---|---|---| | 55 | + → finalize() — normalisation commune (schema.py, normalize.py) | ||
| 88 | −| Connecteurs | 1 employeur = 1 module ; plateformes ATS partagées | `jobka/connectors/` | | 56 | + → SQLite data/jobka.db — diff par content_hash, cycle de vie, anti-dérive (db.py) | ||
| 89 | −| Schéma | `JobPosting` + `finalize()` (normalisation commune) | `jobka/schema.py`, `jobka/normalize.py` | | 57 | + → géocodage (geocode.py) + déduplication (dedup.py) | ||
| 90 | −| Persistance | diff par `content_hash`, cycle de vie, anti-dérive | `jobka/db.py` | | 58 | + → API FastAPI (web.py) + frontend React/Vite servi en statique (frontend/dist) | ||
| 91 | −| Pipeline | ingestion, boucle `watch`, géocodage, dédup | `jobka/ingest.py`, `geocode.py`, `dedup.py` | | ||||
| 92 | −| API + Web | FastAPI + frontend Vite servi en statique | `jobka/web.py`, `frontend/` | | ||||
| 93 | −| Tests | fixtures HTTP enregistrées, rejeu hors ligne | `jobka/fixtures.py`, `tests/` | | ||||
| 94 | − | ||||
| 95 | −## Le pipeline en détail | ||||
| 96 | − | ||||
| 97 | −### Connecteurs auto-découverts | ||||
| 98 | −Toute sous-classe de `BaseConnector` avec un `source_id` est enregistrée par | ||||
| 99 | −`pkgutil` — aucun registre à maintenir. Les 17 plateformes ATS sont des | ||||
| 100 | −classes de base : brancher un nouvel employeur = ~10 lignes. Politesse : | ||||
| 101 | −throttling par source (`request_delay`), User-Agent identifiable | ||||
| 102 | −`JobKaBot/1.0`, budgets de requêtes, cache BD des pages détail. | ||||
| 103 | − | ||||
| 104 | −### Diff engine avec garde-fou anti-dérive | ||||
| 105 | −Chaque sync compare le `content_hash` de chaque offre : ajout, mise à jour, ou | ||||
| 106 | −disparition. Une offre absente 2 syncs consécutifs passe `active=0` (jamais de | ||||
| 107 | −DELETE). Si une source retourne ≤ 25 % de sa médiane historique, alerte et | ||||
| 108 | −**retraits suspendus** — un connecteur cassé ne vide pas la base. Les offres | ||||
| 109 | −dont la `date_limite` est dépassée sont retirées même si encore affichées. | ||||
| 110 | − | ||||
| 111 | −### Salaires normalisés — jamais inventés | ||||
| 112 | −`parse_salary()` exige un signe monétaire ou une unité explicite, applique des | ||||
| 113 | −bornes de plausibilité par unité, et convertit en $/h et $/an (40 h × 52 sem.). | ||||
| 114 | −Une offre sans salaire reste sans salaire. L'historique des changements est | ||||
| 115 | −conservé (`salary_log`). | ||||
| 116 | − | ||||
| 117 | −### Géocodage sans invention | ||||
| 118 | −Nominatim (1 req/1,1 s) + repli officiel Adresses Québec (MERN, lots de 200), | ||||
| 119 | −cache persistant par lieu normalisé, validation par bounding box provinciale. | ||||
| 120 | −Sans adresse : centroïde de ville. Introuvable : `geocode_failed=1`, jamais de | ||||
| 121 | −coordonnées bidon. | ||||
| 122 | − | ||||
| 123 | −### Déduplication inter-sources | ||||
| 124 | −Blocage par clé exacte `titre normalisé | employeur | ville` + union-find avec | ||||
| 125 | −exigence d'un second signal concordant (type, salaire ±4 %, date ±7 j). Le | ||||
| 126 | −canonique est la source la plus autoritaire (page carrière avant agrégat) ; | ||||
| 127 | −les doublons sont masqués (`dup_of`), jamais supprimés. | ||||
| 128 | − | ||||
| 129 | −## Connecteurs | ||||
| 130 | − | ||||
| 131 | −**217 employeurs directs** connectés via **17 plateformes ATS** (Workday, | ||||
| 132 | −Lever, Greenhouse, SmartRecruiters, BambooHR, Breezy, Recruitee, Ashby, | ||||
| 133 | −Workable, Taleo, Njoyn, UltiPro, iCIMS, SuccessFactors, ADP WFN, Digital | ||||
| 134 | −Recruiters, Workland) + **3 portails agrégateurs** en complément | ||||
| 135 | −(Guichet-Emplois — Crawl-delay 5 s respecté —, Jobillico via sitemaps, | ||||
| 136 | −Espresso-Jobs) + un **canal de dépôt direct** pour les employeurs | ||||
| 137 | −(`/employeurs`, modération manuelle). | ||||
| 138 | − | ||||
| 139 | −- Les offres issues des pages carrières portent le badge **« Offre | ||||
| 140 | − directe »** (`is_direct`) ; la candidature se fait chez l'employeur. | ||||
| 141 | −- En cas de doublon entre une offre directe et sa copie portail, **l'offre | ||||
| 142 | − directe gagne la canonique** (dédup direct-prioritaire, `dup_of`). | ||||
| 143 | −- **Documentation par connecteur** (générée, standard Groupe-KA) : | ||||
| 144 | − [`docs/connecteurs/INDEX.md`](docs/connecteurs/INDEX.md) — une fiche par | ||||
| 145 | − plateforme ATS, par employeur, par portail et pour le dépôt direct. | ||||
| 146 | − Régénérer : `.venv/bin/python scripts/gen_connector_docs.py`. | ||||
| 147 | −- **Registre de conformité** : [`docs/CONFORMITE.md`](docs/CONFORMITE.md) | ||||
| 148 | − (base d'accès par famille, attribution, zéro donnée personnelle de | ||||
| 149 | − candidat ; Québec Emploi écarté — auth clicSÉQUR). | ||||
| 150 | − | ||||
| 151 | −## Structure du dépôt | ||||
| 152 | − | ||||
| 153 | −``` | ||||
| 154 | −job-ka/ | ||||
| 155 | −├── run.py # point d'entrée : sync | watch | serve | geocode | record | ||||
| 156 | −├── requirements.txt # fastapi, uvicorn, requests, beautifulsoup4 | ||||
| 157 | −├── jobka/ | ||||
| 158 | −│ ├── schema.py # dataclass JobPosting + finalize() | ||||
| 159 | −│ ├── normalize.py # salaires, dates, mode/type, lieux, catégories | ||||
| 160 | −│ ├── db.py # SQLite : diff, cycle de vie, anti-dérive | ||||
| 161 | −│ ├── ingest.py # run() / watch() — orchestration | ||||
| 162 | −│ ├── dedup.py # déduplication inter-sources (union-find) | ||||
| 163 | −│ ├── geocode.py # Nominatim + Adresses Québec + cache | ||||
| 164 | −│ ├── web.py # API FastAPI + /health + frontend statique | ||||
| 165 | −│ ├── fixtures.py # enregistrement/rejeu HTTP pour les tests | ||||
| 166 | −│ └── connectors/ | ||||
| 167 | −│ ├── base.py # BaseConnector (throttling, Scrapfly, cache détail) | ||||
| 168 | −│ ├── workday.py # plateforme Workday (facette Québec auto-découverte) | ||||
| 169 | −│ ├── <ats>.py # 16 autres plateformes ATS partagées | ||||
| 170 | −│ ├── guichet_emplois.py # + jobillico.py, espresso_jobs.py : portails | ||||
| 171 | −│ └── <employeur>.py # 1 fichier = 1 employeur (sous-classe ~10 lignes) | ||||
| 172 | −├── data/sources.json # registre descriptif des sources (221 entrées) | ||||
| 173 | −├── frontend/ # React 18 + Vite + TS (carte MapLibre, sans jeton) | ||||
| 174 | −├── tests/ # pytest : normalisation + rejeu des connecteurs | ||||
| 175 | −├── scripts/check_headers.py # hook pre-commit : en-tête d'auteur obligatoire | ||||
| 176 | −├── scripts/gen_connector_docs.py # génère docs/connecteurs/ (fiches + INDEX) | ||||
| 177 | −└── docs/ # connecteurs/, CONFORMITE.md, déploiement | ||||
| 178 | −``` | ||||
| 179 | − | ||||
| 180 | −## Démarrage rapide | ||||
| 181 | − | ||||
| 182 | −```bash | ||||
| 183 | −git clone <repo> job-ka && cd job-ka | ||||
| 184 | − | ||||
| 185 | −# Backend | ||||
| 186 | −python3 -m venv .venv | ||||
| 187 | −.venv/bin/pip install -r requirements.txt | ||||
| 188 | − | ||||
| 189 | −# Frontend | ||||
| 190 | −cd frontend && npm install && npm run build && cd .. | ||||
| 191 | − | ||||
| 192 | −# Secrets (Scrapfly pour les sites anti-bot — facultatif au départ) | ||||
| 193 | −cp .env.example .env # puis remplir SCRAPFLY_API_KEY | ||||
| 194 | − | ||||
| 195 | −# Amorçage : première synchronisation + géocodage | ||||
| 196 | −.venv/bin/python run.py sync | ||||
| 197 | −.venv/bin/python run.py geocode | ||||
| 198 | − | ||||
| 199 | −# Service (API + frontend) — http://localhost:8096 | ||||
| 200 | −.venv/bin/python run.py serve 8096 | ||||
| 201 | ``` | 59 | ``` | ||
| 202 | 60 | ||||
| 203 | −En dev frontend : `npm run dev` dans `frontend/` (proxy `/api` → :8096). | 61 | +- **Backend** : Python + **FastAPI** (uvicorn) — `/api/jobs`, `/api/jobs.geojson`, `/api/jobs/{uid}`, `/api/facets`, `/api/sources`, `/api/stats`, `/health`, SSO/favoris KA ID. | ||
| 62 | +- **Base de données** : **SQLite** (`data/jobka.db`, non versionnée) — offres, historique salarial, cache de géocodage, journal des syncs. | ||||
| 63 | +- **Frontend** : **React 18 + Vite + TypeScript**, carte **MapLibre GL** (sans jeton), design system Groupe KA ; build servi en statique par FastAPI. | ||||
| 64 | +- **Anti-bot** : Scrapfly pour certaines pages carrières maison (`SCRAPFLY_API_KEY`). | ||||
| 65 | +- **Processus PM2** : | ||||
| 66 | + - **`job-ka-web`** — `run.py serve 8096` (API + frontend) ; | ||||
| 67 | + - **`job-ka-sync`** — `run.py watch 60` (resync horaire + géocodage + dédup, jour et nuit) ; | ||||
| 68 | + - **`job-ka-ngrok`** — tunnel ngrok vers www.job-ka.com. | ||||
| 204 | 69 | ||||
| 205 | −## CLI `run.py` | 70 | +## Structure du repo | ||
| 206 | 71 | ||||
| 207 | −| Commande | Rôle | | 72 | +| Répertoire / fichier | Rôle | | ||
| 208 | |---|---| | 73 | |---|---| | ||
| 209 | −| `run.py sync [source ...]` | synchronise les offres (toutes les sources ou celles données) | | 74 | +| `jobka/` | paquet Python : schéma `JobPosting`, normalisation, DB/diff, ingestion, dédup, géocodage, web/API, stats, PDF, auth KA ID | | ||
| 210 | −| `run.py watch [minutes]` | boucle : sync + géocodage + dédup (défaut 60 min) | | 75 | +| `jobka/connectors/` | base commune + 17 plateformes ATS + ~970 connecteurs (1 fichier = 1 employeur) + portails | | ||
| 211 | −| `run.py serve [port]` | API + frontend (défaut 8096) | | 76 | +| `frontend/` | React 18 + Vite + TS (carte MapLibre) — `dist/` non versionné | | ||
| 212 | −| `run.py geocode [n]` | géocode les offres sans coordonnées | | 77 | +| `data/` | `sources.json` (registre descriptif, ~970 entrées) + `jobka.db` (non versionnée) | | ||
| 213 | −| `run.py record <source ...>` | enregistre les fixtures de test d'une source | | 78 | +| `tests/` | pytest : normalisation + rejeu hors ligne des fixtures connecteurs | | ||
| 79 | +| `scripts/` | `check_headers.py` (en-tête d'auteur obligatoire), `gen_connector_docs.py` (fiches connecteurs) | | ||||
| 80 | +| `docs/` | `CONFORMITE.md`, `connecteurs/`, `deploiement.md`, captures d'écran | | ||||
| 81 | +| `run.py` | CLI : `sync` · `watch` · `serve` · `geocode` · `record` | | ||||
| 82 | +| `requirements.txt` | fastapi, uvicorn, requests, beautifulsoup4, fpdf2 | | ||||
| 214 | 83 | ||||
| 215 | −## Variables d'environnement | 84 | +## Développement (remote-first) | ||
| 216 | 85 | ||||
| 217 | −| Variable | Rôle | | 86 | +⚠️ **La source de vérité est le repo git sur le nœud M3U96a** (`~/apps/job-ka`) — on n'édite **jamais** les copies laptop. Toute modification se fait sur le nœud via SSH : édition, tests, build frontend au besoin, redémarrage PM2, puis commit + push **depuis le nœud**. | ||
| 218 | −|---|---| | ||||
| 219 | −| `SCRAPFLY_API_KEY` | rendu JS / anti-bot pour les pages carrières maison | | ||||
| 220 | −| `JOBKA_WORKDAY_DETAIL_LIMIT` | plafond de pages détail Workday par sync (défaut 300) | | ||||
| 221 | −| `JOBKA_SR_DETAIL_LIMIT` | plafond de pages détail SmartRecruiters (défaut 300) | | ||||
| 222 | −| `JOBKA_<PLATEFORME>_*` | budgets par plateforme/portail (pages, détails, fenêtres) — défauts et rôle documentés dans [`docs/connecteurs/`](docs/connecteurs/INDEX.md) | | ||||
| 223 | − | ||||
| 224 | −## API | ||||
| 225 | − | ||||
| 226 | −| Route | Rôle | | ||||
| 227 | −|---|---| | ||||
| 228 | −| `GET /health` | healthcheck (BD + fraîcheur des syncs) | | ||||
| 229 | −| `GET /api/jobs` | recherche : `q, city, category, work_mode, employment_type, salary_min, with_salary, sort, limit, offset` | | ||||
| 230 | −| `GET /api/jobs.geojson` | offres géolocalisées (carte), `bbox` + mêmes filtres | | ||||
| 231 | −| `GET /api/jobs/{uid}` | fiche complète + historique salarial | | ||||
| 232 | −| `GET /api/facets` | valeurs distinctes pour les filtres | | ||||
| 233 | −| `GET /api/sources` | registre des employeurs + fraîcheur par source | | ||||
| 234 | −| `GET /api/stats` | totaux, catégories, villes, dernières syncs | | ||||
| 235 | −| `POST /api/sync` | déclenche une synchronisation (webhook) | | ||||
| 236 | −| `POST /api/employeurs/offres` | dépôt direct d'une offre par un employeur (modération manuelle) | | ||||
| 237 | − | ||||
| 238 | −## Ajouter un employeur (≈ 10 lignes) | ||||
| 239 | 87 | ||||
| 240 | −```python | 88 | +- Remote **`origin` = spbgit** (git perso, https://git.spboucher.ai) — sur M3U96a, l'origin est le chemin local **`/Users/simon-pierreboucher/srv/git/job-ka.git`** (bare repo). | ||
| 241 | −# jobka/connectors/mon_employeur.py (+ en-tête d'auteur, règle nº 1) | 89 | +- On ne pousse **que le code** : `data/*.db`, `frontend/dist/` et `.env` ne sont jamais versionnés — le serveur maintient ses données lui-même. | ||
| 242 | −from .workday import WorkdayConnector | ||||
| 243 | − | ||||
| 244 | −class MonEmployeurConnector(WorkdayConnector): | ||||
| 245 | − source_id = "mon_employeur" | ||||
| 246 | − EMPLOYER = "Mon Employeur inc." | ||||
| 247 | − TENANT = "monemployeur" # monemployeur.wd3.myworkdayjobs.com | ||||
| 248 | − HOST = "wd3" | ||||
| 249 | − SITE = "Careers" | ||||
| 250 | −``` | ||||
| 251 | − | ||||
| 252 | −Puis : ajouter l'entrée dans `data/sources.json`, `run.py sync mon_employeur` | ||||
| 253 | −pour valider, `run.py record mon_employeur` pour figer les fixtures de test. | ||||
| 254 | − | ||||
| 255 | −## Tests | ||||
| 256 | 90 | ||||
| 257 | ```bash | 91 | ```bash | ||
| 258 | −.venv/bin/python -m pytest # tout | 92 | +ssh M3U96a | ||
| 259 | −.venv/bin/python -m pytest tests/test_normalize.py # normalisation seule | 93 | +cd ~/apps/job-ka | ||
| 94 | +.venv/bin/python -m pytest # tests hors ligne | ||||
| 95 | +.venv/bin/python run.py sync <source> # valider un connecteur | ||||
| 96 | +cd frontend && npm run build && cd .. # si le frontend a changé | ||||
| 97 | +pm2 restart job-ka-web # après changement | ||||
| 98 | +git add <fichiers> && git commit -m "…" && git push origin main | ||||
| 260 | ``` | 99 | ``` | ||
| 261 | 100 | ||||
| 262 | −- `test_normalize.py` — salaires, dates, modes, types, lieux, catégories ; | 101 | +Variables d'environnement : `SCRAPFLY_API_KEY`, `JOBKA_WORKDAY_DETAIL_LIMIT`, `JOBKA_SR_DETAIL_LIMIT` et autres budgets `JOBKA_<PLATEFORME>_*` (documentés dans `docs/connecteurs/INDEX.md`). | ||
| 263 | −- `test_connectors.py` — rejeu hors ligne des fixtures : instantané exact + | ||||
| 264 | − invariants (uid uniques, URLs absolues, salaires plausibles, valeurs jamais | ||||
| 265 | − inventées) + `finalize()` sûr. | ||||
| 266 | 102 | ||||
| 267 | −## Production | 103 | +## Déploiement | ||
| 268 | − | ||||
| 269 | −``` | ||||
| 270 | −job-ka-web .venv/bin/python run.py serve 8096 # API + frontend | ||||
| 271 | −job-ka-sync .venv/bin/python run.py watch 60 # resync horaire, jour et nuit | ||||
| 272 | −job-ka-ngrok ngrok http --url=www.job-ka.com 8096 # tunnel public | ||||
| 273 | −``` | ||||
| 274 | 104 | ||||
| 275 | −**Philosophie d'exploitation : on ne pousse que le code — le serveur maintient | 105 | +| | | | ||
| 276 | −ses données lui-même.** `data/*.db`, `frontend/dist/` et `.env` ne sont jamais | 106 | +|---|---| | ||
| 277 | −versionnés. Détails : `docs/deploiement.md`. | 107 | +| **Nœud** | **M3U96a** (cluster MacLustr) — `~/apps/job-ka` | | ||
| 278 | − | 108 | +| **Port** | **8096** | | ||
| 279 | −## Principes | 109 | +| **Processus PM2** | `job-ka-web` (API + frontend) + `job-ka-sync` (watcher horaire) + `job-ka-ngrok` (tunnel) | | ||
| 110 | +| **Domaine** | [www.job-ka.com](https://www.job-ka.com) via tunnel ngrok | | ||||
| 280 | 111 | ||||
| 281 | −1. **Politesse** — throttling par source, robots.txt respecté, User-Agent | 112 | +Détails : `docs/deploiement.md`. | ||
| 282 | − identifiable, budgets de requêtes, caches. | ||||
| 283 | −2. **Fidélité** — les champs reflètent la source ; une donnée absente reste | ||||
| 284 | − absente, jamais de valeur inventée (salaire, date, coordonnées). | ||||
| 285 | −3. **Traçabilité** — chaque offre garde son URL originale ; la candidature se | ||||
| 286 | − fait chez l'employeur. Zéro boîte noire. | ||||
| 287 | −4. **Robustesse** — une source cassée ne bloque pas les autres et ne vide pas | ||||
| 288 | − la base (délai de grâce + anti-dérive). | ||||
| 289 | −5. **Transparence salariale** — salaires numériques normalisés en $/h et | ||||
| 290 | − $/an ; filtre « salaire affiché » de premier ordre. | ||||
| 291 | 113 | ||||
| 292 | ## Écosystème Groupe KA | 114 | ## Écosystème Groupe KA | ||
| 293 | 115 | ||||
| 294 | −Job·Ka partage la philosophie et l'architecture des plateformes sœurs : | 116 | +- [groupe-ka.com](https://www.groupe-ka.com) — portail | ||
| 295 | −[Lou·Ka](https://www.lou-ka.com) (logements), Immo·Ka, Vrai-Prix, ValoPlex, | 117 | +- [lou-ka.com](https://www.lou-ka.com) — logements à louer | ||
| 296 | −Auto·Ka, Fabri·Ka, Food·Ka. | 118 | +- [immo-ka.com](https://www.immo-ka.com) — propriétés à vendre | ||
| 119 | +- [vrai-prix.com](https://www.vrai-prix.com) — estimation immobilière | ||||
| 120 | +- [auto-ka.com](https://www.auto-ka.com) — véhicules | ||||
| 121 | +- [fabri-ka.com](https://www.fabri-ka.com) — produits québécois | ||||
| 122 | +- [food-ka.com](https://www.food-ka.com) — épicerie / alimentation | ||||
| 123 | +- [resto-ka.com](https://www.resto-ka.com) — restaurants | ||||
| 124 | +- [sorti-ka.com](https://www.sorti-ka.com) — sorties et événements | ||||
| 125 | +- [job-ka.com](https://www.job-ka.com) — emplois | ||||
| 126 | +- [crea-ka.com](https://www.crea-ka.com) — créateurs | ||||
| 127 | +- [trouve-ka.com](https://www.trouve-ka.com) — petites annonces | ||||
| 128 | +- [api-ka.com](https://www.api-ka.com) — API de données | ||||
| 297 | 129 | ||||
| 298 | --- | 130 | --- | ||
| 299 | 131 | ||||
| 300 | −<div align="center"> | 132 | +© Groupe KA — Simon-Pierre Boucher · [contact@spboucher.ai](mailto:contact@spboucher.ai) | ||
| 301 | − | ||||
| 302 | −## Auteur | ||||
| 303 | − | ||||
| 304 | −**Simon-Pierre Boucher** — [contact@spboucher.ai](mailto:contact@spboucher.ai) | ||||
| 305 | − | ||||
| 306 | −© 2026 Groupe KA — Simon-Pierre Boucher. Tous droits réservés. | ||||
| 307 | 133 | ||||
| 308 | −</div> | 134 | +*Ce dépôt vit sur **spbgit** (git perso — https://git.spboucher.ai).* | ||
added
docs/screenshots/job-ka-desktop.png
+0 −0
Binary file not shown.
added
docs/screenshots/job-ka-mobile.png
+0 −0
Binary file not shown.