Repository Public
1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2// KA ID v2 — profil de préférences pour le reranking personnalisé.3// Les plateformes lisent ici le profil appris de leur membre (leur univers +4// affinités transversales) et la liste de ses éléments masqués. Elles mettent5// ce résultat en cache court (60-120 s) et retombent sur le classement de6// base à la moindre erreur (fail-open).7// GET ?client_id&ka_id&ts&sig8// → { ok, personalization, profile: { app, global } | null, hidden: […] }9import { NextRequest, NextResponse } from "next/server";10import { findUserByKaId } from "@/lib/db";11import { verifyClientSig } from "@/lib/sso";12import { privacyOf, hiddenOf, mfRecsOf } from "@/lib/kaid-data";13import { getProfile } from "@/lib/personal";1415export async function GET(req: NextRequest) {16 const q = req.nextUrl.searchParams;17 const clientId = q.get("client_id") ?? "";18 const kaId = q.get("ka_id") ?? "";19 if (!verifyClientSig(clientId, kaId, q.get("ts") ?? "", q.get("sig") ?? ""))20 return NextResponse.json({ error: "signature invalide" }, { status: 401 });21 const user = findUserByKaId(kaId);22 if (!user)23 return NextResponse.json({ error: "membre introuvable" }, { status: 404 });2425 const privacy = privacyOf(user.id);26 const hidden = hiddenOf(user.id, clientId).map((h) => h.item_id);27 if (!privacy.personalization)28 return NextResponse.json({29 ok: true, personalization: false, profile: null, hidden,30 });3132 const full = getProfile(user.id);33 // recommandations du modèle ALS (batch quotidien) fusionnées au profil34 const mf = mfRecsOf(user.id, clientId);35 let appBlock = full.apps[clientId] ?? null;36 if (mf.length)37 appBlock = { ...(appBlock ?? { n: 0, dims: {}, ranges: {} }), mf };38 return NextResponse.json({39 ok: true,40 personalization: true,41 profile: {42 app: appBlock,43 global: full.global,44 computed_at: full.computed_at,45 },46 hidden,47 });48}49