Python 33.5%
JavaScript 30.1%
TypeScript 25%
CSS 10%
Shell 1.4%
1# KA ID v2 — personnalisation Groupe KA (module satellite)23Standard 2026-08-26. Le hub **groupe-ka.com** est le feature store du groupe :4journal d'interactions, favoris, recherches sauvegardées + alertes, éléments5masqués, **profil de préférences appris** (décroissance 30 j, signaux6négatifs, corrections utilisateur sur `/mon-ka`). Chaque satellite le7consomme par le canal s2s HMAC existant (`KA_SSO_SECRET`).89## Fichiers canoniques1011- `kaid.py` — client backend (FastAPI) : `track()`, `fetch_prefs()`,12 `rerank()`, `build_router()`. Copié dans le paquet de l'app par13 `./sync-kaid.sh`.14- `ka-id.js` — SDK navigateur : clics positionnés, dwell de fiche,15 visites de retour, `KAID.hide()`, `KAID.saveSearch()`. Copié dans16 `frontend/public/` (+ `dist/`).1718## Endpoints hub (s2s, sig = HMAC(secret, "client.ka_id.ts"))1920- `POST /api/sso/events` — lot d'événements `{type, entity_id?, query?,21 filters?, features?, dwell_ms?, position?}` (max 50)22- `GET /api/sso/prefs` — `{personalization, profile: {app, global}, hidden}`23- `GET/POST /api/sso/saved-searches`, `POST /api/sso/hide`2425## Intégration type (app FastAPI)2627```python28# web.py29from . import kaid30kaid.init("job-ka")31app.include_router(kaid.build_router(lambda req: current_user(req)))3233# endpoint listing — APRÈS le tri de base, seulement pour le tri par défaut :34user = current_user(request)35kaid.track(user, "search", query=q, filters={"city": city, ...})36if sort == "recent": # jamais sur un tri explicite37 items, personalized = kaid.rerank(38 items, user,39 features_of=lambda it: {"city": it.get("city"), "price": it.get("price"), ...},40 active_dims={d for d in ("city",) if city}, # intention de session41 )4243# endpoint fiche :44kaid.track(user, "detail_view", entity_type="job", entity_id=uid,45 features=_features_of(row))4647# favoris (accounts.py) :48kaid.track(user, "favorite", entity_id=uid, features=_features_of(row))49```5051```html52<!-- index.html, avant le bundle -->53<script>window.KAID_CONF={card:"a[href^='/emploi/']",detail:"^/emploi/(.+)$",entity:"job"};</script>54<script src="/ka-id.js" defer></script>55```5657## Règles58591. **Jamais obligatoire** : sans session KA ID, zéro appel, zéro changement.602. **Fail-open** : hub injoignable ou profil vide → classement de base.613. **Pertinence d'abord** : `rerank()` reclasse APRÈS le tri de base62 (blend 0,35) et uniquement le tri par défaut — un tri explicite63 (prix, salaire…) n'est jamais altéré.644. **Session > long terme** : passer les dimensions filtrées par la requête65 dans `active_dims` — elles sont exclues du score personnel.665. **Badge parcimonieux** : `item["ka_reco"]` n'apparaît que si le score67 personnel ≥ 0,62, plafonné à ~1 carte sur 8. Frontend : petite68 étiquette « Recommandé pour vous » + raisons (`MATCH_LOCATION`,69 `MATCH_PRICE_RANGE`, `MATCH_<DIM>`…).706. **features riches** : envoyer les mêmes clés dans `track(features=…)` et71 `features_of()` — c'est le vocabulaire du profil (city, price, brand,72 category…). Valeurs texte OU nombres (les nombres apprennent des plages).737. Le masquage (« Pas pour moi ») retire l'annonce des résultats de TOUTES74 les sessions futures et compte comme signal négatif fort.7576## Confidentialité7778Réglages sur groupe-ka.com/mon-ka : Personnalisation / Historique /79Recommandations (ON par défaut, opt-out), effacement de l'historique,80réinitialisation des recommandations, export JSON (Loi 25).81`prefs.personalization=false` ⇒ `rerank()` ne fait que l'exclusion des82masqués ; Historique OFF ⇒ le hub jette les événements reçus.83