SPB Git forge

spb/job-ka

Public
227commits 1branches 0releases
37.7 MBsize
maindefault branch
28 min agolast push
HTML 82.1% Python 14.6% TypeScript 1.9% CSS 1% JavaScript 0.5%

docs: refonte du README (pastilles + captures d écran à jour)

Simon-Pierre Boucher committed 1 mo ago (Aug 24, 2026) parent c45c753

3 changed files +90 −264

modified README.md +90 −264
@@ -5,304 +5,130 @@ Auteur : Simon-Pierre Boucher
5 5 Contact : contact@spboucher.ai
6 6 Fichier : README.md
7 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 12 # Job·Ka
15 13
16 −### Tous les emplois des employeurs québécois. Un seul endroit.
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 −---
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.**
29 15
30 −## Sommaire
16 +[![Site](https://img.shields.io/website?url=https%3A%2F%2Fwww.job-ka.com&style=flat-square&label=www.job-ka.com)](https://www.job-ka.com)
17 +![Nœud](https://img.shields.io/badge/n%C5%93ud-M3U96a-1f6feb?style=flat-square)
18 +![Port](https://img.shields.io/badge/port-8096-0b7285?style=flat-square)
19 +![PM2](https://img.shields.io/badge/process-PM2-2b037a?style=flat-square)
20 +![Python](https://img.shields.io/badge/Python-FastAPI-3776ab?style=flat-square&logo=python&logoColor=white)
21 +![React](https://img.shields.io/badge/React-18%20%2B%20Vite-61dafb?style=flat-square&logo=react&logoColor=black)
22 +![SQLite](https://img.shields.io/badge/SQLite-diff%20engine-141814?style=flat-square&logo=sqlite)
23 +![MapLibre](https://img.shields.io/badge/carte-MapLibre-396cb2?style=flat-square)
24 +![Groupe KA](https://img.shields.io/badge/Groupe-KA-b7f000?style=flat-square)
31 25
32 −- [Pourquoi Job·Ka ?](#pourquoi-job-ka-)
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)
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.
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
52 −offres mortes. L'offre originale — celle de la page carrière de l'employeur —
53 −est la seule source fiable : à jour, complète, sans frais de mise en avant.
32 +<p align="center">
33 + <img src="docs/screenshots/job-ka-desktop.png" width="640" alt="Accueil — desktop">
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
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.
37 +## Fonctionnalités
60 38
61 −Ce n'est pas un job board qui vend de la visibilité, c'est un index fidèle de
62 −ce que les employeurs québécois affichent réellement — **avec les salaires en
63 −clair quand ils existent** (normalisés en $/h et $/an), parce que la
64 −transparence salariale est le différenciateur clé.
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.
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.
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.
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 −
86 −| Couche | Rôle | Fichiers |
87 −|---|---|---|
88 −| Connecteurs | 1 employeur = 1 module ; plateformes ATS partagées | `jobka/connectors/` |
89 −| Schéma | `JobPosting` + `finalize()` (normalisation commune) | `jobka/schema.py`, `jobka/normalize.py` |
90 −| Persistance | diff par `content_hash`, cycle de vie, anti-dérive | `jobka/db.py` |
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
53 +Sources (17 plateformes ATS + sites maison + 3 portails)
54 + → connecteurs auto-découverts (jobka/connectors/)
55 + → finalize() — normalisation commune (schema.py, normalize.py)
56 + → SQLite data/jobka.db — diff par content_hash, cycle de vie, anti-dérive (db.py)
57 + → géocodage (geocode.py) + déduplication (dedup.py)
58 + → API FastAPI (web.py) + frontend React/Vite servi en statique (frontend/dist)
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) |
210 −| `run.py watch [minutes]` | boucle : sync + géocodage + dédup (défaut 60 min) |
211 −| `run.py serve [port]` | API + frontend (défaut 8096) |
212 −| `run.py geocode [n]` | géocode les offres sans coordonnées |
213 −| `run.py record <source ...>` | enregistre les fixtures de test d'une source |
74 +| `jobka/` | paquet Python : schéma `JobPosting`, normalisation, DB/diff, ingestion, dédup, géocodage, web/API, stats, PDF, auth KA ID |
75 +| `jobka/connectors/` | base commune + 17 plateformes ATS + ~970 connecteurs (1 fichier = 1 employeur) + portails |
76 +| `frontend/` | React 18 + Vite + TS (carte MapLibre) — `dist/` non versionné |
77 +| `data/` | `sources.json` (registre descriptif, ~970 entrées) + `jobka.db` (non versionnée) |
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 |
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)
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**.
239 87
240 −```python
241 −# jobka/connectors/mon_employeur.py (+ en-tête d'auteur, règle nº 1)
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
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).
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.
256 90
257 91 ```bash
258 −.venv/bin/python -m pytest # tout
259 −.venv/bin/python -m pytest tests/test_normalize.py # normalisation seule
92 +ssh M3U96a
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 ;
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.
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`).
266 102
267 −## Production
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 −```
103 +## Déploiement
274 104
275 −**Philosophie d'exploitation : on ne pousse que le code — le serveur maintient
276 −ses données lui-même.** `data/*.db`, `frontend/dist/` et `.env` ne sont jamais
277 −versionnés. Détails : `docs/deploiement.md`.
278 −
279 −## Principes
105 +| | |
106 +|---|---|
107 +| **Nœud** | **M3U96a** (cluster MacLustr) — `~/apps/job-ka` |
108 +| **Port** | **8096** |
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
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.
112 +Détails : `docs/deploiement.md`.
291 113
292 114 ## Écosystème Groupe KA
293 115
294 −Job·Ka partage la philosophie et l'architecture des plateformes sœurs :
295 −[Lou·Ka](https://www.lou-ka.com) (logements), Immo·Ka, Vrai-Prix, ValoPlex,
296 −Auto·Ka, Fabri·Ka, Food·Ka.
116 +- [groupe-ka.com](https://www.groupe-ka.com) — portail
117 +- [lou-ka.com](https://www.lou-ka.com) — logements à louer
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">
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.
132 +© Groupe KA — Simon-Pierre Boucher · [contact@spboucher.ai](mailto:contact@spboucher.ai)
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.