Properties and Filtering

Sometimes your app knows things that Tamber doesn't – like the fact that a restaurant is closed and should not be recommended, or that the user is looking for articles in the Sports category. To bridge this knowledge-gap, Tamber gives you the ability to set items' properties, and provides an expressive logical filtering tool.

Tamber's filtering tool allows you to form complex logical phrases that operate on item properties and tags. Filters can be applied when requesting discoveries or when creating an engine (to control which event the engine learns from, allowing for specialized engines that use subsets of your data).

Sections
Setup Item Properties
Filter Recommendations
Tag and Array-Property Filtering
Engine Filtering

Setup Item Properties

First, let's set our item's properties. For this guide, we will be setting up recommendations for a restaurant discovery app with both restaurant and chef items. The chefs will have properties for type and last_post_time, and the restaurants will have properties for type, open_time, close_time, cuisine, and price_tier.

var tamber = require('tamber')('project_key');

const item = await tamber.item.update({ // update automatically creates novel items
	id: "item_wmt4fn6o4zlk",
	updates: {
		add: {
			properties: {
				"type":	 "restaurant",
				"open_time": 7,
				"close_time": 22
			}
		}
	}
});

const item_2 = await tamber.item.update({
	id: "item_bmnfmjbaiwyx",
	updates: {
		add: {
			properties: {
				"type": "chef",
				"last_post_time": 1592301489
			}
		}
	}
});
package main

import (
	tamber "github.com/tamber/tamber-go"
	"github.com/tamber/tamber-go/item"
	"fmt"
)

func main() {
	tamber.DefaultProjectKey = "project_key"

	i, info, err := item.Update(&tamber.ItemParams{
		Id: "item_wmt4fn6o4zlk",
		Updates: tamber.ItemUpdates{
			Add: tamber.ItemFeatures{
				Properties: map[string]interface{}{
					"type":	 "restaurant",
					"open_time": 7,
					"close_time": 22,
				},
			},
		},
	})

	i, info, err = item.Update(&tamber.ItemParams{
		Id: "item_bmnfmjbaiwyx",
		Updates: tamber.ItemUpdates{
			Add: tamber.ItemFeatures{
				Properties: map[string]interface{}{
					"type":				"chef",
					"last_post_time":	1592301489,
				},
			},
		},
	})
}
require 'tamber'
Tamber.project_key = 'project_key'

Tamber::Item.update(
  :id => 'item_wmt4fn6o4zlk',
  :updates => {
    :add => {
      :properties => {
        'type' => 'restaurant',
        'open_time' => 7,
        'close_time'=> 22
      }
    }
  }
)

Tamber::Item.update(
  :id => 'item_bmnfmjbaiwyx',
  :updates => {
    :add => {
      :properties => {
        'type' => 'chef',
        'last_post_time' => 1592301489
      }
    }
  }
)
$ curl https://api.tamber.com/v1/item/update \
	-u project_key: \
	-d id=item_wmt4fn6o4zlk \
	-d updates='{"add":{"properties":{"type":"restaurant","open_time":7,"close_time":22}}}'

$ curl https://api.tamber.com/v1/item/update \
	-u project_key: \
	-d id=item_bmnfmjbaiwyx \
	-d updates='{"add":{"properties":{"type":"chef","last_post_time":1592301489}}}'
Tamber tamber = new Tamber("project_key", "");

HashMap<String, Object> itemParams = new HashMap<String, Object>();
HashMap<String, Object> updates = new HashMap<String, Object>();
HashMap<String, Object> add = new HashMap<String, Object>();
HashMap<String, Object> addProperties = new HashMap<String, Object>();
JSONObject resp = new JSONObject();

addProperties.put("type", "restaurant");
addProperties.put("open_time", 7);
addProperties.put("close_time", 22);
add.put("properties", addProperties);
updates.put("add", add);

itemParams.put("id", "item_wmt4fn6o4zlk");
itemParams.put("updates", updates);
try {
    resp = tamber.item.update(itemParams);
} catch (TamberException e) {
    System.out.println(String.format("%s=%s", e.getClass().getName(), e.getMessage()));
}
addProperties.clear();
addProperties.put("type", "chef");
addProperties.put("last_post_time", 1592301489);
add.put("properties", addProperties);
updates.put("add", add);

itemParams.put("id", "item_bmnfmjbaiwyx");
itemParams.put("updates", updates);
try {
    resp = tamber.item.update(itemParams);
} catch (TamberException e) {
    System.out.println(String.format("%s=%s", e.getClass().getName(), e.getMessage()));
}
import tamber

tamber.project_key = 'project_key'
tamber.Item.update(
    id='item_wmt4fn6o4zlk',
    updates={
        'add': {
            'properties': {
                'type':  'restaurant',
                'open_time': 7,
                'close_time': 22
            }
        }
    }
)

tamber.Item.update(
    id='item_bmnfmjbaiwyx',
    updates={
        'add': {
            'properties': {
                'type': 'chef',
                'last_post_time': 1592301489
            }
        }
    }
)

Note that the item update method will automatically create the item if it does not yet exist.

Filter Recommendations

Assuming we already have an engine that we created before setting our items' properties, we can now filter recommendation results. Here is the logical argument we want to use:

(open_time <= clock.now) && (close_time > clock.now)

This argument can be broken down into Operations and Values. In Tamber, all operations are represented as JSON objects with a single key (the function name, ex. and), and an array of objects on which it operates. To translate the above argument into a Tamber JSON filter, we move the Logical Operator and to the outside of the clauses it encapsulates, which become a list of objects.

{
  "and":[
  	(open_time <= clock.now),
  	(close_time > clock.now)
  ]
}

Then we do the same thing with each inner clause, moving the Comparison Operator (in this case lte, and gt) to the outside of the values they compare, which also become a list of objects.

{
  "and":[
  	{"lte":[
	  open_time,
	  clock.now
	]},
	{"gt":[
	  close_time,
	  clock.now
	]}
  ]
}

These first two steps deal with operations, now we will deal with the inner values. You can provide any values you want to an Operator and Tamber will handle it. To get an item's property value, you request the property with a simple operation: {"property": "your_property_name"}. This allows you to compare properties against each other. Note that Tags can be accessed similarly: {"tags": []}.

{
  "and":[
	{"lte":[
	  {"property": "open_time"},
	  clock.now
	]},
	{"gt":[
	  {"property": "close_time"},
	  clock.now
	]}
  ]
}

Now let's get a bit fancier. Here we want to recommend restaurants that are open alongside chefs that have recently posted to the app. The logical argument looks like this:

(open_time <= clock.now && close_time > clock.now && type == "restaurant") || (type == "chef" && last_post_time > clock.last_week)

Translated to a Tamber filter:

{
  "or": [
    {"and":[
	  {"lte":[
	    {"property": "open_time"},
	    clock.now
	  ]},
	  {"gt":[
	    {"property": "close_time"},
	    clock.now
	  ]},
      {"eq":[
	    {"property": "type"},
	    "restaurant"
	  ]}
    ]},
    {"and":[
      {"lte":[
        {"property": "open_time"},
        clock.now
      ]},
      {"gt":[
        {"property": "close_time"},
        clock.now
      ]},
      {"eq":[
        {"property": "type"},
        "chef"
      ]},
      {"gt":[
        {"property": "last_post_time"},
        clock.last_week
      ]}
    ]},
  ]
}

Tag and Array-Property Filtering

Tags can be filtered on by supplying {"tags": []} as an Operation value. When filtering on an array, be it an item's tags or a property that takes an array, you may use special Array Operators, contains and overlaps. contains returns whether or not the first argument contains all elements in the second. overlaps returns whether or not the first arguments contains any of the elements in the second.

If you wanted to select items that were in the "sci-fi" or "fantasy" genres, you could use a filter like this one:

{
  "overlaps":[
	{"property":"genres"},
	["sci-fi", "fantasy"]
  ]
}

Or, if the genres were stored as tags, the filter would like like this:

{
  "overlaps":[
	{"tags":[]},
	["sci-fi", "fantasy"]
  ]
}

Engine Filtering

When you create an engine, you may configure it to only listen for certain behaviors and items. To filter items, we simply active 'Advanced' mode in the engine creator, and enter the JSON string for our filter in the code editor.

Our app has items of type chef and restaurant, but, for the sake of this guide, let's say that when we first setup our recommendation engine we also had a third item type: dish. Our app no longer features dishes, but the events we tracked for items of type dish are still stored in our project. We want our recommendation engine to ignore them completely. To do this, we apply a simple filter:

{"neq":[
  {"property": "type"},
  "dish"
]},