| 211 | and "SIMILAR_USERS" in (reco_cf or {}).get("reasons", []), |
211 | and "SIMILAR_USERS" in (reco_cf or {}).get("reasons", []), |
| 212 | f"similar={similar} pos={pos_cf} reco={reco_cf}") |
212 | f"similar={similar} pos={pos_cf} reco={reco_cf}") |
| 213 | |
213 | |
| |
214 | +# 12. matrix factorization (ALS) : cohorte croisée → le modèle recommande à A |
| |
215 | +# l'annonce aimée par les membres au profil proche, jusqu'au rerank |
| |
216 | +import shutil |
| |
217 | +import subprocess |
| |
218 | +extra_uids = [] |
| |
219 | +for ka, email in [("ka-9900000004", "test-kaid-d@test.local"), |
| |
220 | + ("ka-9900000005", "test-kaid-e@test.local"), |
| |
221 | + ("ka-9900000006", "test-kaid-f@test.local")]: |
| |
222 | + con.execute("DELETE FROM users WHERE email=?", (email,)) |
| |
223 | + con.execute("INSERT INTO users (email, name, ka_id) VALUES (?,?,?)", |
| |
224 | + (email, "Membre Test MF", ka)) |
| |
225 | + extra_uids.append(con.execute("SELECT id FROM users WHERE ka_id=?", (ka,)).fetchone()["id"]) |
| |
226 | +uid_d, uid_e, uid_f = extra_uids |
| |
227 | +fav = "INSERT OR IGNORE INTO favorites (user_id, app, item_id, title) VALUES (?,?,?,?)" |
| |
228 | +for uid, items in [(uid_a, ["e2e:x1", "e2e:x2"]), |
| |
229 | + (uid_d, ["e2e:x1", "e2e:x2", "e2e:x3"]), |
| |
230 | + (uid_e, ["e2e:x2", "e2e:x3", "e2e:x4"]), |
| |
231 | + (uid_f, ["e2e:x1", "e2e:x3"])]: |
| |
232 | + for it in items: |
| |
233 | + con.execute(fav, (uid, "lou-ka", it, f"Annonce {it}")) |
| |
234 | +con.commit() |
| |
235 | +node = shutil.which("node") or "/opt/homebrew/bin/node" |
| |
236 | +run = subprocess.run([node, "scripts/kaid-mf.mjs", "--app", "lou-ka"], |
| |
237 | + cwd=os.path.expanduser("~/apps/groupe-ka"), |
| |
238 | + capture_output=True, text=True, timeout=120) |
| |
239 | +mf_rows = [r["item_id"] for r in con.execute( |
| |
240 | + "SELECT item_id FROM mf_recs WHERE user_id=? AND app='lou-ka' ORDER BY rank", |
| |
241 | + (uid_a,)).fetchall()] |
| |
242 | +st, prefs = call("lou-ka", "GET", "/api/sso/prefs") |
| |
243 | +mf_in_prefs = (prefs.get("profile") or {}).get("app", {}).get("mf") or [] |
| |
244 | +kaid.invalidate_prefs(TA) |
| |
245 | +items_mf = ([{"uid": f"q{i}", "city": "Québec", "unit_type": "5 1/2", |
| |
246 | + "price": 1500} for i in range(6)] |
| |
247 | + + [{"uid": "e2e:x3", "city": "Québec", "unit_type": "5 1/2", |
| |
248 | + "price": 1500}]) |
| |
249 | +out_mf, _ = kaid.rerank( |
| |
250 | + list(items_mf), {"ka_id": TA}, |
| |
251 | + features_of=lambda it: {"city": it["city"], "price": it["price"]}) |
| |
252 | +pos_mf = [o["uid"] for o in out_mf].index("e2e:x3") |
| |
253 | +reco_mf = next((o.get("ka_reco") for o in out_mf if o["uid"] == "e2e:x3"), None) |
| |
254 | +mf_reason_ok = reco_mf is not None and any( |
| |
255 | + r0 in ("COLLABORATIVE_MODEL", "SIMILAR_USERS") for r0 in reco_mf.get("reasons", [])) |
| |
256 | +check("matrix factorization → ALS entraîné, reco x3 propagée + boostée", |
| |
257 | + "e2e:x3" in mf_rows and "e2e:x3" in mf_in_prefs and pos_mf < 6 and mf_reason_ok, |
| |
258 | + f"stdout={run.stdout.strip()[-160:]} mf_rows={mf_rows[:5]} " |
| |
259 | + f"prefs_mf={mf_in_prefs[:5]} pos={pos_mf} reco={reco_mf}") |
| |
260 | + |
| 214 | # --- nettoyage --------------------------------------------------------------- |
261 | # --- nettoyage --------------------------------------------------------------- |
| 215 | −for uid in (uid_a, uid_b, uid_c): |
262 | +for uid in (uid_a, uid_b, uid_c, uid_d, uid_e, uid_f): |
| 216 | for table in ("user_events", "favorites", "saved_searches", "hidden_items", |
263 | for table in ("user_events", "favorites", "saved_searches", "hidden_items", |
| 217 | − "user_prefs", "pref_overrides"): |
264 | + "user_prefs", "pref_overrides", "mf_recs"): |
| 218 | con.execute(f"DELETE FROM {table} WHERE user_id=?", (uid,)) |
265 | con.execute(f"DELETE FROM {table} WHERE user_id=?", (uid,)) |
| 219 | con.execute("DELETE FROM users WHERE email LIKE 'test-kaid-%@test.local'") |
266 | con.execute("DELETE FROM users WHERE email LIKE 'test-kaid-%@test.local'") |
| 220 | con.execute("DELETE FROM favorites WHERE item_id LIKE 'e2e:%'") |
267 | con.execute("DELETE FROM favorites WHERE item_id LIKE 'e2e:%'") |
| |
268 | +con.execute("DELETE FROM mf_recs WHERE item_id LIKE 'e2e:%'") |
| 221 | con.commit() |
269 | con.commit() |
| 222 | left = con.execute("SELECT COUNT(*) c FROM users WHERE ka_id IN (?,?)", (TA, TB)).fetchone()["c"] |
270 | left = con.execute("SELECT COUNT(*) c FROM users WHERE ka_id IN (?,?)", (TA, TB)).fetchone()["c"] |
| 223 | print(f"\nnettoyage : membres de test supprimés (restants={left})") |
271 | print(f"\nnettoyage : membres de test supprimés (restants={left})") |