feat(ka-id v2.1): CF SIMILAR_USERS + langue transversale (kaid.py), SDK pilules « Sauvegarder cette recherche »/« Pas pour moi » (ka-id.js), alignés sur ka-ui.git/kaid cbe9c9f
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 changed files +218 −6
modified
creaka/kaid.py
+14 −0
@@ -41,6 +41,7 @@ PREFS_TTL = 90 # secondes de cache du profil | ||
| 41 | 41 | TIMEOUT = 5 # secondes par appel hub |
| 42 | 42 | LOCATION_DIMS = {"city", "region", "sector", "quartier", "ville", |
| 43 | 43 | "location", "neighborhood"} |
| 44 | +LANGUAGE_DIMS = {"language", "langue"} | |
| 44 | 45 | PRICE_DIMS = {"price", "rent", "salary", "salary_year", "price_min"} |
| 45 | 46 | |
| 46 | 47 | # Événements acceptés depuis le navigateur (le reste vient du serveur). |
@@ -243,6 +244,14 @@ def personal_score(feats: dict, app_profile: dict, global_profile: dict, | ||
| 243 | 244 | den += w |
| 244 | 245 | if max(affs) >= 0.6 and "MATCH_LOCATION" not in reasons: |
| 245 | 246 | reasons.append("MATCH_LOCATION") |
| 247 | + if dim in LANGUAGE_DIMS: | |
| 248 | + glang = (global_profile or {}).get("language") or {} | |
| 249 | + gv = glang.get("values") or {} | |
| 250 | + affs = [gv[v] for v in vals if v in gv] | |
| 251 | + if affs and max(affs) > 0: | |
| 252 | + w = 0.4 * float(glang.get("conf") or 0.3) | |
| 253 | + num += w * max(affs) | |
| 254 | + den += w | |
| 246 | 255 | if den < 0.8: |
| 247 | 256 | return None, [] |
| 248 | 257 | score = (num / den + 1.0) / 2.0 |
@@ -283,6 +292,8 @@ def rerank(items: list, user, *, features_of, uid_of=None, | ||
| 283 | 292 | tail = items[max_considered:] |
| 284 | 293 | n = len(head) |
| 285 | 294 | active = active_dims or set() |
| 295 | + # co-favoris des membres semblables (filtrage collaboratif du hub) | |
| 296 | + similar = {str(s) for s in (app_p.get("similar") or [])} | |
| 286 | 297 | scored = [] |
| 287 | 298 | badged = 0 |
| 288 | 299 | for i, it in enumerate(head): |
@@ -292,6 +303,9 @@ def rerank(items: list, user, *, features_of, uid_of=None, | ||
| 292 | 303 | profile.get("global") or {}, active) |
| 293 | 304 | except Exception: |
| 294 | 305 | p, reasons = None, [] |
| 306 | + if similar and str(uid_of(it)) in similar: | |
| 307 | + p = min(1.0, (p if p is not None else 0.55) + 0.25) | |
| 308 | + reasons = (["SIMILAR_USERS"] + reasons)[:4] | |
| 295 | 309 | if p is None: |
| 296 | 310 | final = (1.0 - blend) * base + blend * 0.5 |
| 297 | 311 | else: |
modified
frontend/dist/ka-id.js
+102 −3
@@ -135,9 +135,11 @@ | ||
| 135 | 135 | |
| 136 | 136 | function checkRoute() { |
| 137 | 137 | var uid = detailUid(); |
| 138 | − if (uid === dwell.uid) return; | |
| 139 | − endDwell(false); | |
| 140 | − if (uid) dwell = { uid: uid, t0: Date.now() }; | |
| 138 | + if (uid !== dwell.uid) { | |
| 139 | + endDwell(false); | |
| 140 | + if (uid) dwell = { uid: uid, t0: Date.now() }; | |
| 141 | + } | |
| 142 | + updateUi(); | |
| 141 | 143 | } |
| 142 | 144 | |
| 143 | 145 | function watchRoutes() { |
@@ -158,6 +160,103 @@ | ||
| 158 | 160 | checkRoute(); |
| 159 | 161 | } |
| 160 | 162 | |
| 163 | + /* ---------- interface : sauvegarder la recherche / pas pour moi ---------- */ | |
| 164 | + | |
| 165 | + var INK = "#141814"; | |
| 166 | + var PAPER = "#f5f3ee"; | |
| 167 | + | |
| 168 | + function injectCss() { | |
| 169 | + if (document.getElementById("kaid-ui-css")) return; | |
| 170 | + var st = document.createElement("style"); | |
| 171 | + st.id = "kaid-ui-css"; | |
| 172 | + st.textContent = | |
| 173 | + ".kaid-pill{position:fixed;left:14px;bottom:14px;z-index:560;display:inline-flex;" + | |
| 174 | + "align-items:center;gap:8px;padding:9px 14px;border-radius:999px;border:1.5px solid " + INK + ";" + | |
| 175 | + "background:" + PAPER + ";color:" + INK + ";font:600 12.5px/1 system-ui,sans-serif;" + | |
| 176 | + "box-shadow:0 2px 10px rgba(20,24,20,.18);cursor:pointer;max-width:78vw}" + | |
| 177 | + ".kaid-pill b{font-weight:700}" + | |
| 178 | + ".kaid-pill .kaid-x{margin-left:2px;opacity:.55;font-weight:700;cursor:pointer}" + | |
| 179 | + ".kaid-pill a{color:inherit;text-decoration:underline}" + | |
| 180 | + "@media (max-width:768px){.kaid-pill{bottom:calc(84px + env(safe-area-inset-bottom))}}"; | |
| 181 | + document.head.appendChild(st); | |
| 182 | + } | |
| 183 | + | |
| 184 | + function removePill() { | |
| 185 | + var el = document.getElementById("kaid-pill"); | |
| 186 | + if (el) el.remove(); | |
| 187 | + } | |
| 188 | + | |
| 189 | + function pillDismissed() { | |
| 190 | + try { | |
| 191 | + return Date.now() - Number(localStorage.getItem("kaid_pill_off") || 0) < 6048e5; | |
| 192 | + } catch (err) { return false; } | |
| 193 | + } | |
| 194 | + | |
| 195 | + function showSavePill() { | |
| 196 | + if (pillDismissed() || document.getElementById("kaid-pill")) return; | |
| 197 | + injectCss(); | |
| 198 | + var el = document.createElement("div"); | |
| 199 | + el.id = "kaid-pill"; | |
| 200 | + el.className = "kaid-pill"; | |
| 201 | + el.innerHTML = "<span>☆ <b>Sauvegarder cette recherche</b></span>" + | |
| 202 | + "<span class='kaid-x' role='button' aria-label='Ne plus proposer'>✕</span>"; | |
| 203 | + el.querySelector(".kaid-x").addEventListener("click", function (ev) { | |
| 204 | + ev.stopPropagation(); | |
| 205 | + try { localStorage.setItem("kaid_pill_off", String(Date.now())); } catch (err) { /* privé */ } | |
| 206 | + removePill(); | |
| 207 | + }); | |
| 208 | + el.addEventListener("click", function () { | |
| 209 | + el.innerHTML = "<span>…</span>"; | |
| 210 | + var filters = {}; | |
| 211 | + new URLSearchParams(location.search).forEach(function (v, k) { | |
| 212 | + if (v) filters[k] = v; | |
| 213 | + }); | |
| 214 | + window.KAID.saveSearch({ alert: true, filters: filters, | |
| 215 | + query: filters.q || null }) | |
| 216 | + .then(function (r) { | |
| 217 | + el.innerHTML = r && r.ok !== false && !r.error | |
| 218 | + ? "<span>✓ Sauvegardée · alerte activée — <a href='https://www.groupe-ka.com/mon-ka'>Mon KA</a></span>" | |
| 219 | + : "<span>Connexion KA ID requise</span>"; | |
| 220 | + setTimeout(removePill, 6000); | |
| 221 | + }) | |
| 222 | + .catch(function () { removePill(); }); | |
| 223 | + }); | |
| 224 | + document.body.appendChild(el); | |
| 225 | + } | |
| 226 | + | |
| 227 | + function showHideLink(uid) { | |
| 228 | + if (document.getElementById("kaid-pill")) return; | |
| 229 | + injectCss(); | |
| 230 | + var el = document.createElement("div"); | |
| 231 | + el.id = "kaid-pill"; | |
| 232 | + el.className = "kaid-pill"; | |
| 233 | + el.innerHTML = "<span>✕ Pas pour moi</span>" + | |
| 234 | + "<span class='kaid-x' role='button' aria-label='Fermer'>✕</span>"; | |
| 235 | + el.querySelector(".kaid-x").addEventListener("click", function (ev) { | |
| 236 | + ev.stopPropagation(); | |
| 237 | + try { localStorage.setItem("kaid_pill_off", String(Date.now())); } catch (err) { /* privé */ } | |
| 238 | + removePill(); | |
| 239 | + }); | |
| 240 | + el.addEventListener("click", function () { | |
| 241 | + el.innerHTML = "<span>…</span>"; | |
| 242 | + window.KAID.hide(uid).then(function () { | |
| 243 | + el.innerHTML = "<span>Masquée — elle ne réapparaîtra plus dans vos résultats.</span>"; | |
| 244 | + setTimeout(removePill, 5000); | |
| 245 | + }).catch(function () { removePill(); }); | |
| 246 | + }); | |
| 247 | + document.body.appendChild(el); | |
| 248 | + } | |
| 249 | + | |
| 250 | + function updateUi() { | |
| 251 | + if (!connected || CONF.ui === false) return; | |
| 252 | + removePill(); | |
| 253 | + if (pillDismissed()) return; | |
| 254 | + var uid = detailUid(); | |
| 255 | + if (uid) showHideLink(uid); | |
| 256 | + else if (location.search.length > 1 && CONF.savePill !== false) | |
| 257 | + showSavePill(); | |
| 258 | + } | |
| 259 | + | |
| 161 | 260 | /* ---------- visite de retour ---------- */ |
| 162 | 261 | |
| 163 | 262 | function returnVisit() { |
modified
frontend/src/ka-id.js
+102 −3
@@ -135,9 +135,11 @@ | ||
| 135 | 135 | |
| 136 | 136 | function checkRoute() { |
| 137 | 137 | var uid = detailUid(); |
| 138 | − if (uid === dwell.uid) return; | |
| 139 | − endDwell(false); | |
| 140 | − if (uid) dwell = { uid: uid, t0: Date.now() }; | |
| 138 | + if (uid !== dwell.uid) { | |
| 139 | + endDwell(false); | |
| 140 | + if (uid) dwell = { uid: uid, t0: Date.now() }; | |
| 141 | + } | |
| 142 | + updateUi(); | |
| 141 | 143 | } |
| 142 | 144 | |
| 143 | 145 | function watchRoutes() { |
@@ -158,6 +160,103 @@ | ||
| 158 | 160 | checkRoute(); |
| 159 | 161 | } |
| 160 | 162 | |
| 163 | + /* ---------- interface : sauvegarder la recherche / pas pour moi ---------- */ | |
| 164 | + | |
| 165 | + var INK = "#141814"; | |
| 166 | + var PAPER = "#f5f3ee"; | |
| 167 | + | |
| 168 | + function injectCss() { | |
| 169 | + if (document.getElementById("kaid-ui-css")) return; | |
| 170 | + var st = document.createElement("style"); | |
| 171 | + st.id = "kaid-ui-css"; | |
| 172 | + st.textContent = | |
| 173 | + ".kaid-pill{position:fixed;left:14px;bottom:14px;z-index:560;display:inline-flex;" + | |
| 174 | + "align-items:center;gap:8px;padding:9px 14px;border-radius:999px;border:1.5px solid " + INK + ";" + | |
| 175 | + "background:" + PAPER + ";color:" + INK + ";font:600 12.5px/1 system-ui,sans-serif;" + | |
| 176 | + "box-shadow:0 2px 10px rgba(20,24,20,.18);cursor:pointer;max-width:78vw}" + | |
| 177 | + ".kaid-pill b{font-weight:700}" + | |
| 178 | + ".kaid-pill .kaid-x{margin-left:2px;opacity:.55;font-weight:700;cursor:pointer}" + | |
| 179 | + ".kaid-pill a{color:inherit;text-decoration:underline}" + | |
| 180 | + "@media (max-width:768px){.kaid-pill{bottom:calc(84px + env(safe-area-inset-bottom))}}"; | |
| 181 | + document.head.appendChild(st); | |
| 182 | + } | |
| 183 | + | |
| 184 | + function removePill() { | |
| 185 | + var el = document.getElementById("kaid-pill"); | |
| 186 | + if (el) el.remove(); | |
| 187 | + } | |
| 188 | + | |
| 189 | + function pillDismissed() { | |
| 190 | + try { | |
| 191 | + return Date.now() - Number(localStorage.getItem("kaid_pill_off") || 0) < 6048e5; | |
| 192 | + } catch (err) { return false; } | |
| 193 | + } | |
| 194 | + | |
| 195 | + function showSavePill() { | |
| 196 | + if (pillDismissed() || document.getElementById("kaid-pill")) return; | |
| 197 | + injectCss(); | |
| 198 | + var el = document.createElement("div"); | |
| 199 | + el.id = "kaid-pill"; | |
| 200 | + el.className = "kaid-pill"; | |
| 201 | + el.innerHTML = "<span>☆ <b>Sauvegarder cette recherche</b></span>" + | |
| 202 | + "<span class='kaid-x' role='button' aria-label='Ne plus proposer'>✕</span>"; | |
| 203 | + el.querySelector(".kaid-x").addEventListener("click", function (ev) { | |
| 204 | + ev.stopPropagation(); | |
| 205 | + try { localStorage.setItem("kaid_pill_off", String(Date.now())); } catch (err) { /* privé */ } | |
| 206 | + removePill(); | |
| 207 | + }); | |
| 208 | + el.addEventListener("click", function () { | |
| 209 | + el.innerHTML = "<span>…</span>"; | |
| 210 | + var filters = {}; | |
| 211 | + new URLSearchParams(location.search).forEach(function (v, k) { | |
| 212 | + if (v) filters[k] = v; | |
| 213 | + }); | |
| 214 | + window.KAID.saveSearch({ alert: true, filters: filters, | |
| 215 | + query: filters.q || null }) | |
| 216 | + .then(function (r) { | |
| 217 | + el.innerHTML = r && r.ok !== false && !r.error | |
| 218 | + ? "<span>✓ Sauvegardée · alerte activée — <a href='https://www.groupe-ka.com/mon-ka'>Mon KA</a></span>" | |
| 219 | + : "<span>Connexion KA ID requise</span>"; | |
| 220 | + setTimeout(removePill, 6000); | |
| 221 | + }) | |
| 222 | + .catch(function () { removePill(); }); | |
| 223 | + }); | |
| 224 | + document.body.appendChild(el); | |
| 225 | + } | |
| 226 | + | |
| 227 | + function showHideLink(uid) { | |
| 228 | + if (document.getElementById("kaid-pill")) return; | |
| 229 | + injectCss(); | |
| 230 | + var el = document.createElement("div"); | |
| 231 | + el.id = "kaid-pill"; | |
| 232 | + el.className = "kaid-pill"; | |
| 233 | + el.innerHTML = "<span>✕ Pas pour moi</span>" + | |
| 234 | + "<span class='kaid-x' role='button' aria-label='Fermer'>✕</span>"; | |
| 235 | + el.querySelector(".kaid-x").addEventListener("click", function (ev) { | |
| 236 | + ev.stopPropagation(); | |
| 237 | + try { localStorage.setItem("kaid_pill_off", String(Date.now())); } catch (err) { /* privé */ } | |
| 238 | + removePill(); | |
| 239 | + }); | |
| 240 | + el.addEventListener("click", function () { | |
| 241 | + el.innerHTML = "<span>…</span>"; | |
| 242 | + window.KAID.hide(uid).then(function () { | |
| 243 | + el.innerHTML = "<span>Masquée — elle ne réapparaîtra plus dans vos résultats.</span>"; | |
| 244 | + setTimeout(removePill, 5000); | |
| 245 | + }).catch(function () { removePill(); }); | |
| 246 | + }); | |
| 247 | + document.body.appendChild(el); | |
| 248 | + } | |
| 249 | + | |
| 250 | + function updateUi() { | |
| 251 | + if (!connected || CONF.ui === false) return; | |
| 252 | + removePill(); | |
| 253 | + if (pillDismissed()) return; | |
| 254 | + var uid = detailUid(); | |
| 255 | + if (uid) showHideLink(uid); | |
| 256 | + else if (location.search.length > 1 && CONF.savePill !== false) | |
| 257 | + showSavePill(); | |
| 258 | + } | |
| 259 | + | |
| 161 | 260 | /* ---------- visite de retour ---------- */ |
| 162 | 261 | |
| 163 | 262 | function returnVisit() { |
| 164 | 263 | |