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é dansfrontend/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
<!-- index.html, avant le bundle -->
<script>window.KAID_CONF={card:"a[href^='/emploi/']",detail:"^/emploi/(.+)$",entity:"job"};</script>
<script src="/ka-id.js" defer></script>Règles
- Jamais obligatoire : sans session KA ID, zéro appel, zéro changement.
- Fail-open : hub injoignable ou profil vide → classement de base.
- 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é. - Session > long terme : passer les dimensions filtrées par la requête
dans
active_dims— elles sont exclues du score personnel. - 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_<DIM>…). - features riches : envoyer les mêmes clés dans
track(features=…)etfeatures_of()— c'est le vocabulaire du profil (city, price, brand, category…). Valeurs texte OU nombres (les nombres apprennent des plages). - 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.