Two-column slide showing positive/negative signal and filter chips and a ranked list before and after.

Rules & personalisation

A generic "similar items" list becomes a recommendation for this user by blending signals: positive signals lift candidates, negative signals demote them, and rules filter the result after fusion. Scoring stays in the signals and the fuses — rules only ever drop candidates.


Piece Purpose
AddSignal(name, …) Lift candidates — a positive source, scaled by Weight(...)
AddNegativeSignal(name, …) Demote candidates — scored like a signal, then combined by FuseFinal(...)
AddRule(name, r => r.Filter(…)) Drop candidates — a hard filter, never a score change

Personalise: lift products the current user has bought before. A positive signal with a small weight nudges familiar brands up without dominating.

.AddSignal("PreviouslyPurchased", s => s
    .Weight(0.3f)
    .From(ctx => ctx.Graph.Query()
        .StartAt(CurrentUser)
        .Out(N.Order.Type,   E.Placed)
        .Out(N.Product.Type, E.Contains)))

Demote: push down items the user returned. A negative signal is scored exactly like a positive one, then subtracted from the positive score by FuseFinal (default Fusion.Subtract).

.AddNegativeSignal("Returned", s => s
    .From(ctx => ctx.Graph.Query()
        .StartAt(CurrentUser)
        .Out(N.Return.Type,  E.Returned)
        .Out(N.Product.Type, E.Contains)))
// Optional: scale the score down by how strong the negative is, instead of a flat subtraction.
.FuseFinal(Fusion.Discount)

A negative signal only touches candidates a positive signal already surfaced — it demotes, it never introduces new items. With a subtractive final fuse it shows up as a negative entry in that item's score breakdown, so you can still see why it dropped.

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(Node.GetUID(N.Category.Type, input.Category))
        .AsUIDEnumerable());
})

r.Enabled(false) makes any rule conditional on a request parameter — one scenario, many behaviours.

Similarity engine — negative signals & rules