Skip to main content
Signals implementation
Real-time personalization

Train and serve a purchase propensity model with Signals

Build a point-in-time correct training dataset from Signals attributes, train a model on it, and score live sessions with the same attributes.

Progress0%

Define the model features as attributes

Open in Colab

The features for your model are Signals attributes. You'll define them once in this step, and use the same published definition for training and serving.

You can create the definition with the Signals Python SDK, or by describing it to the Snowplow Assistant in Console or to your own AI assistant connected to the Snowplow MCP server. The rest of the tutorial is in Python either way, because it builds the dataset and trains the model in code.

Decide on the features​

The model predicts whether the current session will end in a purchase, so the features describe the current session. You'll calculate them against the domain_sessionid attribute key. The dataset builder requires this key when it generates session anchors.

This starter set counts the main shopping actions and measures how long the session has lasted:

AttributeDescriptionAggregation
page_viewsPage views in the sessioncounter
product_viewsProduct detail views in the sessioncounter
add_to_cartsCart additions in the sessioncounter
remove_from_cartsCart removals in the sessioncounter
checkout_stepsCheckout steps reached in the sessioncounter
minutes_in_sessionMinutes since the first event in the sessiontime_since_first

All the ecommerce actions are the same snowplow_ecommerce_action event, so each counter filters on the event's type property. Signals matches events by exact version, so check which version of snowplow_ecommerce_action your tracker sends, for example in the event_version column of atomic.events. With the sample data, use 1-0-2: the sample table and the shopper script both use it.

Choose features that exist at prediction time​

A feature is only useful if it has a value when you score. Two kinds of attribute cause problems:

  • Attributes that only change after the outcome, such as a count of transaction events in the session: the dataset builder only places anchors before a session's first purchase, so this column would always be empty in training
  • Attributes whose meaning depends on exactly when you read them, such as time_since_last on all events: in training it measures the gap before the anchor event, but if you score right after an event it's close to zero every time

Counts and session duration don't have these problems, which is why the starter set uses them. Once the pipeline works, try adding attributes such as the number of distinct products viewed or the value of items added to the cart, and keep the ones that improve the evaluation metrics in Train the model.

Connect to Signals​

You'll use the Signals Python SDK from the next step onwards, whichever way you create the definition. The Signals client takes four values:

See Connect to Signals for details. Set the values as environment variables, then create the client:

python
import os

from snowplow_signals import Signals

sp_signals = Signals(
api_url=os.environ["SP_API_URL"], # includes https://
api_key=os.environ["SP_API_KEY"],
api_key_id=os.environ["SP_API_KEY_ID"],
org_id=os.environ["SP_ORG_ID"],
)

Create and publish the attribute group​

Create an attribute group called purchase_propensity with the six attributes, and a service called purchase_propensity_service to retrieve them from your /predict endpoint later. Keep these names: the code in the rest of the tutorial uses them.

Define the attributes. A helper function builds the five ecommerce counters, which differ only in name and action type:

python
from snowplow_signals import (
Attribute,
Criteria,
Criterion,
Event,
EventProperty,
PageView,
)

# UPDATE THIS if your tracker sends a different version of the event
ecommerce_action = Event(
vendor="com.snowplowanalytics.snowplow.ecommerce",
name="snowplow_ecommerce_action",
version="1-0-2",
)


def ecommerce_counter(name: str, action_type: str) -> Attribute:
return Attribute(
name=name,
type="int32",
events=[ecommerce_action],
aggregation="counter",
criteria=Criteria(
all=[
Criterion.eq(
EventProperty(
vendor="com.snowplowanalytics.snowplow.ecommerce",
name="snowplow_ecommerce_action",
major_version=1,
path="type",
),
action_type,
)
]
),
)


attributes = [
Attribute(
name="page_views",
type="int32",
events=[PageView()],
aggregation="counter",
),
ecommerce_counter("product_views", "product_view"),
ecommerce_counter("add_to_carts", "add_to_cart"),
ecommerce_counter("remove_from_carts", "remove_from_cart"),
ecommerce_counter("checkout_steps", "checkout_step"),
Attribute(
name="minutes_in_session",
type="double",
events=[],
aggregation="time_since_first",
time_unit="min",
),
]

Group the attributes and create the service. Use your own email address for owner.

python
from snowplow_signals import Service, StreamAttributeGroup, domain_sessionid

propensity_group = StreamAttributeGroup(
name="purchase_propensity",
version=1,
owner="your_email@example.com", # UPDATE THIS
attribute_key=domain_sessionid,
attributes=attributes,
)

propensity_service = Service(
name="purchase_propensity_service",
owner="your_email@example.com", # UPDATE THIS
attribute_groups=[propensity_group],
)

Before publishing, you can test what the group calculates on the last hour of events in your warehouse:

python
sp_signals.test(attribute_group=propensity_group)

Publish the attribute group and service:

python
published = sp_signals.publish([propensity_group, propensity_service])
print(f"{len(published)} objects published")

From this point, Signals calculates the attributes in real time for every new session on your site. Stream attribute groups only process events received after publishing.

Publishing doesn't affect the training dataset. The dataset builder computes historical values directly from atomic.events, so you don't have to wait for Signals to accumulate data before you train.

Check the published group​

In Snowplow Console, go to Signals > Attribute groups. You should see purchase_propensity at version 1 with a published status. Open it and check the six attributes against the table above.

The next steps load this published definition from Signals by name, rather than reusing the object in your notebook. That way the dataset is always built from the definition Signals serves, however you created it.

Troubleshooting​

  • The test returns no rows, or the ecommerce counters are always empty: check that ecommerce_action uses the same version as the event_version your tracker sends, and that there were ecommerce events in the last hour.
  • UnsupportedProtocol or ConnectError when creating the client: SP_API_URL must be the full URL, including https://.