Curiosity
A result's per-signal score breakdown beside a FilterAsUser boundary dropping results the user cannot see.

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.

Similarity engine — access control