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_anchorpattern, 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_ideventtimestamp
u1purchase2024-01-01
u1browse2024-01-02
u2browse2024-01-01
u2browse2024-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_ideventtimestamp
u1purchase2024-01-01
u1browse2024-01-02
u1churned2024-01-02
u2browse2024-01-01
u2churned2024-01-01
u2browse2024-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

ParameterTypeDescription
namestrName of the synthetic event to create.
source_eventstr or list of str, optionalAn 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.
sqlstr, optionalDuckDB 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.
churndict, optionalCreates a churn event after a period of inactivity.
anchorstr or dict, optionalMark 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_colstr, optionalPath 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.