Add Events
Insert synthetic events derived from existing events or a SQL query.
Exactly one of source_events, sql, or churn must be provided.
The new event rows are appended to the eventstream; original rows are kept.
Usage
stream.add_events("session_start", source_events=["login", "app_open"])
stream.add_events("churned", churn={"inactivity_days": 30})
stream.add_events("churned", churn={"inactivity_days": 30, "active_events": ["purchase"]})
How it works
New events are marked synthetic and inserted at the same timestamp as the row
that produced them, ordered immediately after it. Nothing is removed.
source_events
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_events=["login", "app_open"])
After — u1 gets three session_start events, because it has three matching
source events:
- u1:
login → session_start → browse → login → session_start → app_open → session_start - u2:
browse → app_open → session_start
churn
Marks the point where a path goes quiet: a synthetic 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.
sql
For anything the two 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_events | list of str, optional | List of existing event names. Every occurrence of any of them gets a synthetic event at the same timestamp, ordered right after the source row — 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. |
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.