recommend

package
v1.18.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 13, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package recommend serves the recommendation models the hermod-ml worker trains (ml/worker/hermod_ml/recommend.py) in-process: it parses a version's artifact once and answers "which items next for this person" and "which items are like this one" from it, without a call to the worker.

The artifact is little-endian throughout:

"HREC"            4 bytes, magic
format version    uint32, FormatVersion
header length H   uint32
header            H bytes of JSON: algorithm, users, items, factors,
                  neighbors, seen, popular
user factors      float32 [users x factors]      when factors > 0
item factors      float32 [items x factors]      when factors > 0
neighbor items    uint32  [items x neighbors]    when neighbors > 0
neighbor scores   float32 [items x neighbors]    when neighbors > 0
seen offsets      uint32  [users + 1]
seen items        uint32  [seen], ascending per person
popular items     uint32  [popular], most popular first
popular scores    float32 [popular]

Format 2 is a recommendation scenario's (ml/worker/hermod_ml/scenarios.py): the same sections, then what serving the scenario needs. Its header adds scenario, as_of, period_days, customer_neighbors, the names of the customer and item features, customer_attrs and item_attrs (one value or null per customer or item), salesmen, weights, rules, global_gap, global_visit_gap, has_visits and has_margin. Its algorithm may be "none", with neither factors nor neighbors, for a scenario that needs no products. After popular scores come:

customer neighbors     uint32  [users x customer_neighbors] when > 0
customer nbr scores    float32 [users x customer_neighbors] when > 0
seen last day          uint32  [seen], days since 1970-01-01, or empty
seen gap               float32 [seen], median days between orders, or NaN
customer features      float32 [users x customer features], NaN unknown
item features          float32 [items x item features], NaN unknown

An artifact comes from the worker's model store, so it is parsed as input that may be wrong: every count is checked against the bytes that back it before anything is allocated for it, every index against what it indexes, and every float for being finite. A bad artifact is refused with ErrInvalid; nothing in it can make Parse or a later call panic or allocate more than the artifact's own size allows.

Index

Constants

View Source
const (
	FormatVersion         = 1
	FormatVersionScenario = 2
	MaxArtifactBytes      = 128 << 20
	MaxFactors            = 1024
	MaxNeighbors          = 1024
	MaxPopular            = 1000
	MaxCustomerNeighbors  = 1024
	MaxFeatures           = 64
)

Limits of an artifact. MaxArtifactBytes matches the worker's own limit: a scenario's artifact carries per-customer features and neighbours besides the model, so it is 128 MiB, twice format 1's original 64.

View Source
const (
	AlgorithmALS            = "als"
	AlgorithmItemSimilarity = "item_similarity"
	AlgorithmNone           = "none"
)

The algorithms an artifact may hold. AlgorithmNone is a format 2 scenario's that needs no products (a visit plan or reorder dates without a product column).

View Source
const (
	DefaultK = 10
	MaxK     = 100
)

How many items a call returns: DefaultK when it does not say, at most MaxK.

View Source
const (
	RuleFilter    = "filter"
	RuleExclude   = "exclude"
	RuleBoost     = "boost"
	RuleDiversity = "diversity"
	RuleCapacity  = "capacity"
	RulePin       = "pin"
	RuleBlock     = "block"
)

The kinds of Rule.

View Source
const (
	OpEq  = "eq"
	OpIn  = "in"
	OpNeq = "neq"
	OpNin = "nin"
	OpGt  = "gt"
	OpLt  = "lt"
)

The comparisons a filter or boost makes.

View Source
const (
	FromSalesman  = "salesman"
	FromTerritory = "territory"
)

The request fields a rule's value may be bound to.

View Source
const (
	MaxRules       = 50
	MaxRuleIDs     = 1000
	MaxRuleValues  = 1000
	MaxExcludeDays = 3650
	MaxPerValue    = 100
)

Bounds of a rule.

View Source
const (
	// MaxPlan bounds a visit plan: its items, and visits per day.
	MaxPlan = 500
	// MaxBatch bounds the customers one call may ask for.
	MaxBatch = 1000
	// DefaultVisitsPerDay is a salesman's capacity when nothing says.
	DefaultVisitsPerDay = 8
)

Bounds of a scenario call.

View Source
const (
	ReasonReorderDue       = "reorder_due"
	ReasonChurnRisk        = "churn_risk"
	ReasonHighValue        = "high_value"
	ReasonWhitespace       = "whitespace"
	ReasonNotVisited       = "not_visited"
	ReasonSimilarCustomers = "similar_customers"
	ReasonBoughtTogether   = "bought_together"
	ReasonBoosted          = "boosted"
	ReasonPinned           = "pinned"
)

The reason codes of a Pick.

View Source
const (
	ScenarioVisits   = "visits"
	ScenarioProducts = "products"
	ScenarioReorder  = "reorder"
	ScenarioAudience = "audience"
	ScenarioSimilar  = "similar"
)

The scenarios a format 2 artifact answers. A format 1 artifact answers ScenarioProducts and ScenarioSimilar.

View Source
const (
	SignalReorderDue  = "reorder_due"
	SignalChurnRisk   = "churn_risk"
	SignalValue       = "value"
	SignalOpportunity = "opportunity"
	SignalNeglect     = "neglect"
)

The visit signals, which the weights name.

View Source
const ReasonPopular = "popular"

ReasonPopular marks an item recommended because many people have it, not because of who is asking: for a person or item the model does not know, or to fill a list the model's own scores leave short.

Variables

View Source
var DefaultWeights = map[string]float64{
	SignalReorderDue: 0.35, SignalChurnRisk: 0.2, SignalValue: 0.2, SignalOpportunity: 0.1, SignalNeglect: 0.15,
}

DefaultWeights are a visit plan's weights when its training names none.

View Source
var ErrBadQuery = errors.New("invalid recommendation request")

ErrBadQuery marks a scenario call that is wrong in itself or for the model's scenario.

View Source
var ErrBadRule = errors.New("invalid rule")

ErrBadRule marks a rule that is wrong in itself, or for its scenario.

View Source
var ErrInvalid = errors.New("not a valid recommendation artifact")

ErrInvalid is returned by Parse for bytes that are not a well-formed recommendation artifact. The error says what is wrong.

View Source
var ErrLoosens = errors.New("a request rule may only tighten the model's rules")

ErrLoosens marks a request rule that would loosen a rule the model was trained with. A request may only tighten them.

Scenarios lists every scenario, in the order the worker documents them.

Functions

func CheckTightens

func CheckTightens(defaults, request []Rule) error

CheckTightens refuses request rules that would loosen the defaults, as far as that can be told from the rules alone: a higher capacity or working days the defaults do not allow, a higher diversity cap on the same field, or a pin of what a default blocks. A pin of what a default filter or exclude drops is refused when the call is answered, where the items are known. Filters, excludes, blocks and boosts are added to the defaults, so they can only narrow or reorder what the defaults allow.

func ClampK

func ClampK(k int) int

ClampK is the k a call uses: DefaultK for zero or less, at most MaxK.

func ID

func ID(v any) (string, bool)

ID is the text a person or item id is matched on, as the worker turned the column's values into text (hermod_ml.datasets.to_text): a string as it is, true or false, and a whole number without ".0". It reports false for anything that cannot be an id: nothing, an empty string, an object or list, or a number that is not finite.

func ValidateRules

func ValidateRules(scenario string, rules []Rule) error

ValidateRules reports the first rule that is wrong in itself or for the scenario, and refuses more than MaxRules.

Types

type Item

type Item struct {
	Item   string  `json:"item"`
	Score  float64 `json:"score"`
	Reason string  `json:"reason,omitempty"`
}

Item is one recommended item. Score is the model's: a dot product for als, a sum of similarities for item_similarity, a cosine similarity for similar items, and the share of the most popular item's count for a popular one.

type Model

type Model struct {
	// contains filtered or unexported fields
}

Model is a parsed artifact. It is immutable, so one Model serves every caller at once.

func Parse

func Parse(raw []byte) (*Model, error)

Parse reads and checks an artifact.

func (*Model) Algorithm

func (m *Model) Algorithm() string

Algorithm is the algorithm that trained the model.

func (*Model) Answer

func (m *Model) Answer(q Query) ([]Result, error)

Answer answers a call of the model's scenario: one Result per subject.

  • visits: for a salesman, their customers to visit, ranked and planned across the working days of the period.
  • products: for a customer (or a batch of customers), products to offer; for a product, products like it.
  • reorder: for a customer, a batch of them or a salesman's customers, when each will order again (DueDate), most overdue first.
  • audience: for a product, the customers likely to want it.
  • similar: for a product, products like it.

func (*Model) AsOf

func (m *Model) AsOf() time.Time

AsOf is the day of the latest transaction the model learned from; zero for a format 1 model.

func (*Model) CustomerIDs

func (m *Model) CustomerIDs() []string

Customer and product ids, for building every result of a model.

func (*Model) DefaultRules

func (m *Model) DefaultRules() []Rule

DefaultRules are the rules trained with the model, which every call obeys.

func (*Model) ForUser

func (m *Model) ForUser(user string, k int, excludeSeen bool) (items []Item, known bool)

ForUser is a person's top k items, best first, leaving out what they already have when excludeSeen is set. A person the model does not know gets the popular items, and known reports false. A list the model's own scores leave short is filled with popular items the person does not have.

func (*Model) Items

func (m *Model) Items() int

Items is how many items the model can recommend: its catalogue.

func (*Model) PeriodDays

func (m *Model) PeriodDays() int

PeriodDays is the model's planning or evaluation period in days; 7 for a format 1 model.

func (*Model) Popular

func (m *Model) Popular(k int) []Item

Popular is the k most popular items.

func (*Model) ProductIDs

func (m *Model) ProductIDs() []string

func (*Model) Salesmen

func (m *Model) Salesmen() []string

Salesmen lists the salesmen the model knows, from its salesmen table and its customers' salesman column, sorted.

func (*Model) Scenario

func (m *Model) Scenario() string

Scenario is the question the model answers: one of Scenarios. A format 1 model answers ScenarioProducts (and similar items).

func (*Model) Similar

func (m *Model) Similar(item string, k int) (items []Item, known bool)

Similar is the k items most like item, best first. An item the model does not know gets the popular items, and known reports false; a list its neighbors leave short is filled with popular items.

func (*Model) Users

func (m *Model) Users() int

Users is how many people the model knows.

type Pick

type Pick struct {
	ID      string         `json:"id"`
	Score   float64        `json:"score"`
	Rank    int            `json:"rank"`
	Reasons []Reason       `json:"reasons"`
	Fields  map[string]any `json:"fields"`
	DueDate string         `json:"due_date,omitempty"`
}

Pick is one result: a customer or a product, its score (higher is stronger; scores compare within one list only), its rank from 1, why, and what the model knows of it. DueDate is set by the reorder scenario.

type PlanDay

type PlanDay struct {
	Date  string `json:"date"`
	Items []Pick `json:"items"`
}

PlanDay is one working day of a visit plan.

type Query

type Query struct {
	Customer, Product, Salesman, Territory string
	Customers                              []string
	From, To                               time.Time
	Today                                  time.Time
	K                                      int
	Rules                                  []Rule
	ExcludeSeen                            bool
}

Query is one scenario call. Exactly one subject is set: Customer, Customers, Product or Salesman, as the scenario allows. Territory, when set, is what a rule's from_request "territory" binds to; otherwise the territory most of the salesman's customers are in. From and To bound a visit plan (both days included); zero means the model's period from Today. K is how many results, 0 for the default. Rules are the request's, added to the model's own.

type Reason

type Reason struct {
	Code string `json:"code"`
	Text string `json:"text"`
}

Reason says in words, with numbers, why a result is listed.

type Result

type Result struct {
	For   string    `json:"for"`
	Items []Pick    `json:"items"`
	Plan  []PlanDay `json:"plan,omitempty"`
}

Result answers one subject of a call: For is the customer, product or salesman asked about.

type Rule

type Rule struct {
	Kind        string   `json:"kind"`
	Field       string   `json:"field,omitempty"`
	Op          string   `json:"op,omitempty"`
	Value       any      `json:"value,omitempty"`
	FromRequest string   `json:"from_request,omitempty"`
	Days        int      `json:"days,omitempty"`
	Weight      float64  `json:"weight,omitempty"`
	MaxPer      int      `json:"max_per,omitempty"`
	PerDay      int      `json:"per_day,omitempty"`
	IDs         []string `json:"ids,omitempty"`
}

Rule is one business rule, applied when serving: on the model as a default for every call, or on one request, where it is added to the defaults and may only tighten them.

  • filter keeps only what matches Field Op Value.
  • exclude drops what was interacted with (bought; visited, for a visit plan) in the last Days days, or ever when Days is 0.
  • boost adds Weight x the normalised Field to the score, or a flat Weight to what matches Field Op Value or is listed in IDs.
  • diversity keeps at most MaxPer results per value of Field.
  • capacity (visits only) plans at most PerDay visits a day, on the weekdays Value lists ("mon", ...) when it lists any.
  • pin always includes IDs; block never does.

Field is "customers.<name>" or "products.<name>": an attribute the training mapped (salesman, territory, segment, blocked, name, category, active) or a feature (monetary, price, margin, ...), or id. FromRequest binds Value to the request's salesman or territory instead.

type SalesmanInfo

type SalesmanInfo struct {
	VisitsPerDay *float64 `json:"visits_per_day"`
	WorkingDays  []string `json:"working_days"`
}

SalesmanInfo is a salesman's capacity as the salesmen table gave it; a nil or empty field was not given.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL