SPB Git forge

spb/ka-ui

Public
30commits 1branches 0releases
145.7 MBsize
maindefault branch
27 days agolast push
Python 33.5% JavaScript 30.1% TypeScript 25% CSS 10% Shell 1.4%
3.6 KB

# 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
<!-- 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

  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_<DIM>…).
  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.