# KA ID v2 — personnalisation Groupe KA (module satellite) Standard 2026-08-26. Le hub **groupe-ka.com** est le feature store du groupe : journal d'interactions, favoris, recherches sauvegardées + alertes, éléments masqués, **profil de préférences appris** (décroissance 30 j, signaux négatifs, corrections utilisateur sur `/mon-ka`). Chaque satellite le consomme par le canal s2s HMAC existant (`KA_SSO_SECRET`). ## Fichiers canoniques - `kaid.py` — client backend (FastAPI) : `track()`, `fetch_prefs()`, `rerank()`, `build_router()`. Copié dans le paquet de l'app par `./sync-kaid.sh`. - `ka-id.js` — SDK navigateur : clics positionnés, dwell de fiche, visites de retour, `KAID.hide()`, `KAID.saveSearch()`. Copié dans `frontend/public/` (+ `dist/`). ## Endpoints hub (s2s, sig = HMAC(secret, "client.ka_id.ts")) - `POST /api/sso/events` — lot d'événements `{type, entity_id?, query?, filters?, features?, dwell_ms?, position?}` (max 50) - `GET /api/sso/prefs` — `{personalization, profile: {app, global}, hidden}` - `GET/POST /api/sso/saved-searches`, `POST /api/sso/hide` ## Intégration type (app FastAPI) ```python # web.py from . import kaid kaid.init("job-ka") app.include_router(kaid.build_router(lambda req: current_user(req))) # endpoint listing — APRÈS le tri de base, seulement pour le tri par défaut : user = current_user(request) kaid.track(user, "search", query=q, filters={"city": city, ...}) if sort == "recent": # jamais sur un tri explicite items, personalized = kaid.rerank( items, user, features_of=lambda it: {"city": it.get("city"), "price": it.get("price"), ...}, active_dims={d for d in ("city",) if city}, # intention de session ) # endpoint fiche : kaid.track(user, "detail_view", entity_type="job", entity_id=uid, features=_features_of(row)) # favoris (accounts.py) : kaid.track(user, "favorite", entity_id=uid, features=_features_of(row)) ``` ```html ``` ## Règles 1. **Jamais obligatoire** : sans session KA ID, zéro appel, zéro changement. 2. **Fail-open** : hub injoignable ou profil vide → classement de base. 3. **Pertinence d'abord** : `rerank()` reclasse APRÈS le tri de base (blend 0,35) et uniquement le tri par défaut — un tri explicite (prix, salaire…) n'est jamais altéré. 4. **Session > long terme** : passer les dimensions filtrées par la requête dans `active_dims` — elles sont exclues du score personnel. 5. **Badge parcimonieux** : `item["ka_reco"]` n'apparaît que si le score personnel ≥ 0,62, plafonné à ~1 carte sur 8. Frontend : petite étiquette « Recommandé pour vous » + raisons (`MATCH_LOCATION`, `MATCH_PRICE_RANGE`, `MATCH_`…). 6. **features riches** : envoyer les mêmes clés dans `track(features=…)` et `features_of()` — c'est le vocabulaire du profil (city, price, brand, category…). Valeurs texte OU nombres (les nombres apprennent des plages). 7. Le masquage (« Pas pour moi ») retire l'annonce des résultats de TOUTES les sessions futures et compte comme signal négatif fort. ## Confidentialité Réglages sur groupe-ka.com/mon-ka : Personnalisation / Historique / Recommandations (ON par défaut, opt-out), effacement de l'historique, réinitialisation des recommandations, export JSON (Loi 25). `prefs.personalization=false` ⇒ `rerank()` ne fait que l'exclusion des masqués ; Historique OFF ⇒ le hub jette les événements reçus.