To start, imagine that each “feature” (for lack of a better name) has an id, a friendly name (for description), and a default value.

Feature ::= feature id, feature friendly name, default value 

Imagine that these participate in expression trees that represent weights.

We can start out with linear models:

FeatureTree ::= Number
  | Feature * Number + FeatureTree

By expanding the structure of a feature tree, we can construct complex features, complex models, etc.

Events can be weighted by these feature trees:

WeightedEvent<X>
{
 X data;
 FeatureTree weight;
}

We provide a set of operations for ranking sets of events, explaning preferences, and gathering end-user feebdack:

//
list<WeightedEvent<X>> rank(set<WeightedEvent<X>>, context)

string explainPreference(WeightedEvent<X> option, set<WeightedEvent<X>> allOptions, context)

void dislike(WeightedEvent<X>, penalty, context)
void like(WeightedEvent<X>, context)

void prefer(WeightedEvent<X> better, WeightedEvent<X> worse, context)

Some requirements:


Maybe you just declare features at the module level.

feature dayOfWeekMismatch;

Is the similarity computation itself a feature?

function dateMatch(date: Date, evidence: PartialDate): Feature {
  let total: ExpFeature = 0;
  if (evidence.dayOfWeek !== date.dayOfWeek) {
    total += dayOfWeekMismatch;
  }
  ...
}

The result of this thing is a quantity that depends on the feature pool and also something about the concrete data (the date argument). We can make this trivially incremental, by just re-running the dateMatch function, or we could use symbolic execution to do something fancier.