
Explaining & access control
A recommendation you can't explain — or that leaks data the user shouldn't see — is a liability. The engine handles both.
Explain: SimilarityResult carries a per-signal breakdown.
var hits = result.Scores
.OrderByDescending(kv => kv.Value)
.Take(topK)
.Select(kv =>
{
Graph.TryGetReadOnlyContent<Product>(kv.Key, out var node);
var perSignal = result.Signals
.Where(s => s.Value.ContainsKey(kv.Key))
.ToDictionary(s => s.Key, s => (double)s.Value[kv.Key]);
return new ScoredProduct(node?.GetKey(), node?.Name, kv.Value, perSignal);
});
SimilarityResult field |
Contents |
|---|---|
Scores |
The final fused-and-ruled score per UID — your page-1 list |
Signals |
Each signal's raw score per UID — the "why" |
Rules |
Score map after each rule, when EnableExplanations(true) |
Timings |
Per-stage wall-clock, when TrackTimings(true) |
Surface Signals to the user when explain = true — "recommended because: same manufacturer, shared tags".
Access control: signals read the admin-level graph.
result.Scores can contain UIDs the caller is not allowed to see. The engine does not enforce per-user access on results — you must. Two options:
// Option A — scope inside a signal:
.AddSignal("x", s => s.From(ctx => ctx.Graph.Query(userUID) /* … */))
// Option B — filter the whole result (simplest, do this by default):
.FilterAsUser(CurrentUser)
Forgetting FilterAsUser (or a per-signal Query(userUID)) is the most common recommendation bug — it silently recommends items across permission boundaries.