
Rules & personalisation
Rules run after fusion. They are where a generic "similar items" list becomes a recommendation for this user.
| Rule | Purpose |
|---|---|
r.Filter((ctx, cands) => keep) |
Hard filter — only the returned UIDs survive |
r.BoostByRank(ctx => rankedUIDs, weight, topK, contributesAs) |
Soft boost — adds an RRF-style contribution to candidates in the ranked list |
r.TransformFusedScore((ctx, uid, score) => newScore) |
Arbitrary post-processing — time decay, normalisation, score floors |
Personalise: lift products the current user has bought before.
.AddRule("BoostPurchases", r => r
.BoostByRank(
ctx => ctx.Graph.Query()
.StartAt(CurrentUser)
.Out(N.Order.Type, E.Placed)
.Out(N.Product.Type, E.Contains)
.AsUIDEnumerable(),
weight: 0.3f, topK: 50,
contributesAs: "PreviouslyPurchased"))
Constrain: keep only the requested category — conditionally.
.AddRule("CategoryFilter", r =>
{
r.Enabled(!string.IsNullOrEmpty(input.Category)); // skip when no category asked
r.Filter((ctx, candidates) => ctx.Graph.Query()
.StartAt(candidates)
.IsRelatedTo(N.Category.Type, input.Category)
.AsUIDEnumerable());
})
Freshness: penalise stale items with a time-decay transform.
.AddRule("Recency", r => r.TransformFusedScore((ctx, uid, score) =>
{
ctx.Graph.TryGetReadOnlyContent<Product>(uid, out var p);
var ageDays = (DateTimeOffset.UtcNow - (p?.Released ?? default)).TotalDays;
return score * (float)Math.Exp(-ageDays / 365.0); // half-life ~ a year
}))
r.Enabled(false) makes any rule conditional on a request parameter — one scenario, many behaviours.