Add Events
Insert synthetic events derived from existing events or a SQL query.
Exactly one of source_event, sql, churn, or anchor must be
provided. The new event rows are appended to the eventstream; original
rows are kept.
A synthetic event shares its source row's timestamp and sorts before
it, so it marks the moment the source event opens. The exception is a
churn event, which closes a stretch of inactivity and so sorts after
the last active event (and always before path_end).
Usage
stream.add_events("session_start", source_event=["login", "app_open"])
stream.add_events("churned", churn={"inactivity_days": 30})
stream.add_events("churned", churn={"inactivity_days": 30, "active_events": ["purchase"]})
# the cart that actually led to checkout, not every cart
stream.add_events(
"checkout_cart",
anchor={"pattern": "cart->[^cart]*->shipping_details", "at": "start"},
)
stream.step_matrix(path_pattern="checkout_cart")
# every purchase a path made, marked three events ahead of time
stream.add_events(
"pre_purchase",
anchor={"pattern": "purchase", "occurrence": "all", "offset": -3},
)
How it works
New events are marked synthetic and inserted at the same timestamp as the row
that produced them, ordered immediately before it: a marker names the moment
its source event opens. Nothing is removed. The one exception is churn, which
closes a stretch of inactivity rather than opening anything — it carries its own
churn event type and sorts after the last active event.
source_event
One synthetic event per occurrence of any listed source event — not one per path. This is the usual way to give several equivalent events a single name you can anchor on later.
Before:
- u1:
login → browse → login → app_open - u2:
browse → app_open
stream.add_events("session_start", source_event=["login", "app_open"])
After — u1 gets three session_start events, because it has three matching
source events, each sitting just before the event it marks:
- u1:
session_start → login → browse → session_start → login → session_start → app_open - u2:
browse → session_start → app_open
anchor
source_event marks an event name, wherever it occurs. anchor marks a
position — the one place in the path a
path pattern resolves to — and gives it a name. That is what
makes the position usable everywhere else: a pattern can describe "the cart
that checkout actually followed", but only an event name can be centred on by
step_matrix, counted by a funnel, or filtered on.
The spec is the one truncate_paths takes for start_anchor — pattern,
at, occurrence, offset, offset_side — and one anchor per call, not a
list.
Before — u1 visits the cart twice, and only the second visit leads to checkout:
- u1:
catalog → cart → catalog → cart → shipping_details - u2:
cart → catalog
stream.add_events(
"checkout_cart",
anchor={"pattern": "cart->[^cart]*->shipping_details", "at": "start"},
)
After — one marker, on the cart the pattern picked out. u2 never matches the pattern, so it gets no event at all:
- u1:
catalog → cart → catalog → checkout_cart → cart → shipping_details - u2:
cart → catalog
By default the anchor resolves once per path (occurrence="first"); pass
occurrence="all" to mark every position it can occupy. offset moves the
marker by a number of events or by a duration — {"pattern": "purchase", "occurrence": "all", "offset": -3} marks every purchase three events ahead of
time.
churn
Marks the point where a path goes quiet: a churn event is inserted after
the last activity that is followed by a gap of at least inactivity_days.
Reaching the end of the observation window counts as such a gap, so paths that
simply stop also get the event.
Before — u1 stops after 2 January, while u2 comes back after two months:
| user_id | event | timestamp |
|---|---|---|
| u1 | purchase | 2024-01-01 |
| u1 | browse | 2024-01-02 |
| u2 | browse | 2024-01-01 |
| u2 | browse | 2024-03-01 |
stream.add_events("churned", churn={"inactivity_days": 30})
After — u1 churns at the end of its own activity, u2 churns mid-path, at the event that preceded the long gap:
| user_id | event | timestamp |
|---|---|---|
| u1 | purchase | 2024-01-01 |
| u1 | browse | 2024-01-02 |
| u1 | churned | 2024-01-02 |
| u2 | browse | 2024-01-01 |
| u2 | churned | 2024-01-01 |
| u2 | browse | 2024-03-01 |
Pass active_events to count only meaningful actions as activity — with
churn={"inactivity_days": 30, "active_events": ["purchase"]} a user who keeps
browsing but stops buying still churns.
Churn markers are the one kind of added event that is not synthetic: filter
them with event_type == "churn".
sql
For anything the three modes above don't cover, sql takes a DuckDB SELECT
over the eventstream table alias returning rows in the eventstream schema;
each returned row becomes one synthetic event.
Parameters
| Parameter | Type | Description |
|---|---|---|
name | str | Name of the synthetic event to create. |
source_event | str or list of str, optional | An existing event name, or several. Every occurrence of any of them gets a synthetic event at the same timestamp — a path with three matching events gets three synthetic events. |
sql | str, optional | DuckDB SQL SELECT statement that reads from the eventstream table alias and returns rows in the eventstream schema. Each returned row is added as a new synthetic event. |
churn | dict, optional | Creates a churn event after a period of inactivity. |
anchor | str or dict, optional | Mark a position in each path rather than an event name: an event name, or an anchor spec — the same form truncate_paths takes for start_anchor, and the same keys (pattern, at, occurrence, offset, offset_side); see Path Patterns. One anchor per call, not a list. By default the anchor resolves once per path (occurrence="first"); pass occurrence="all" to mark every position the anchor can occupy. Paths where it resolves nowhere get no event. This is what makes a position addressable by every other tool: a pattern can say "the cart that was followed by checkout with no cart in between", but only an event name can be centred on by step_matrix, counted by a funnel, or filtered on. |
path_col | str, optional | Path ID column the anchor is resolved within; defaults to schema.path_col. Ignored by the other modes. |
Churn keys
inactivity_days(int or float, required) — gap in days after which a churn event is inserted.active_events(list of str, optional) — only these events count as activity; defaults to all events.