Skip to content

Commit f3a4cb5

Browse files
committed
design: Events + sequences
The idea and proposal behind groups of events and sequences.
1 parent 4f51e0e commit f3a4cb5

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<Info>
2+
**Status**: Active
3+
**Created**: October 2025
4+
**Last Updated**: November 2025
5+
</Info>
6+
7+
## Summary
8+
9+
Increase the scope and functionality of Polar events to allow for aggregating multiple events and tying them together, ultimately to be able to act on the success or failure of a product flow in a meter.
10+
11+
## Goals
12+
13+
* Make it possible for Polar to give more actionable insights based on how customers are using a product.
14+
* Allow someone to experiment and determine costs and ideal pricing for different parts of a product.
15+
16+
## Events
17+
18+
### Data model
19+
20+
| id | name | external_id`*` | parent_id`*` | ...metadata |
21+
| ------------------ | ------------- | ------------------ | ---------------- | ------------- |
22+
| \<Polar uuid-1> | Start event | $user\ specified$ | `null` | {... anything }|
23+
| \<Polar uuid-2> | Nested event | $user\ specified$ | \<Polar uuid-2> | {... anything }|
24+
| \<Polar uuid-3> | Nested event | $user\ specified$ | \<Polar uuid-3> | {... anything }|
25+
26+
`*`: New field.
27+
28+
By adding an `external_id` to the `events` we gain an idempotency key on ingested events, making it unproblematic to re-ingest the same events multiple times. We can then leverage the `external_id` as the identifier to specify both the id on an event as well as the parent id of an event.
29+
30+
Internally we don't want to store the relationship between two events via an user-specified ID, but we can validate and translate the specified `parent_id` on the ingestion of an event thus ensuring the relationship is stored by Polar IDs.
31+
32+
### Flowchart
33+
34+
```mermaid
35+
sequenceDiagram
36+
participant Parrot
37+
participant Polar SDK
38+
participant Polar API
39+
% participant Events
40+
41+
Parrot->>Polar SDK: withSpan(externalId: 'parrot-internal-id')
42+
Polar SDK->>Polar API: POST /events/ingest
43+
44+
Polar SDK->>Polar SDK: Mark instance as parentEventId = parrot-internal-id
45+
Parrot->>Polar SDK: sendEvent(name, metadata)
46+
Polar SDK->>Polar API: POST /events/ingest {name, metadata, parentEventId = Parrot internal id}
47+
note over Polar SDK, Polar API: Lookup externalId to get Polar ID and set Polar ID on parent_id
48+
```
49+
50+
## Sequences
51+
52+
A subset (or a full) hierarchy of events can be thought of as a sequence of events.
53+
54+
A sequence groups related events via parent-child hierarchies. Each root event that matches a creation criteria creates its own sequence. Descendant events (via `parent_id`) are automatically added to the same sequence. Cost and revenue are aggregated on each sequence.
55+
56+
Sequences that have the same definition (creation criteria) can then be aggregated or compared to each other to be able to answer questions such as:
57+
58+
* How does this sequence compare to the average sequence.
59+
* How does this customer compare to the average customer in terms of
60+
* Usage
61+
* Cost
62+
63+
### Data model
64+
65+
```mermaid
66+
erDiagram
67+
direction LR
68+
EventSequenceDefinition ||--o{ EventSequence : "creates instances"
69+
EventSequence ||--o{ EventToSequence : "contains"
70+
Event ||--o{ EventToSequence : "belongs to"
71+
Event ||--o| Event : "parent_id"
72+
Organization ||--o{ EventSequenceDefinition : "owns"
73+
74+
EventSequenceDefinition {
75+
uuid id PK
76+
string name
77+
Filter creation_criteria
78+
jsonb config
79+
uuid organization_id FK
80+
}
81+
82+
EventSequence {
83+
uuid id PK
84+
uuid event_sequence_definition_id FK
85+
jsonb aggregated_data
86+
string label
87+
timestamp first_event_at
88+
timestamp last_event_at
89+
}
90+
91+
EventToSequence {
92+
uuid event_id PK,FK
93+
uuid event_sequence_id PK,FK
94+
boolean is_root
95+
}
96+
97+
Event {
98+
uuid id PK
99+
string name
100+
uuid parent_id FK
101+
uuid customer_id FK
102+
jsonb user_metadata
103+
timestamp timestamp
104+
}
105+
```
106+
107+
108+
#### Root vs Descendant Events
109+
110+
- **Root events**: Events that match the creation_criteria in EventSequenceDefinition and create a new sequence
111+
- **Descendant events**: Child/grandchild events found via `parent_id` traversal
112+
- Both stored in `EventToSequence`, distinguished by `is_root` flag to easily allow querying the root events for a listing.
113+
114+
#### Sequence Instances
115+
116+
Each root event creates its **own** EventSequence instance:
117+
118+
```
119+
Event A: support_request.created (+ 4 descendants)
120+
→ EventSequence X
121+
122+
Event B: support_request.created (+ 2 descendants)
123+
→ EventSequence Y
124+
125+
Event C: support_request.created (+ 1 descendant)
126+
→ EventSequence Z
127+
128+
Three separate, unrelated support request sequences
129+
```
130+
131+
<OpenQuestion>
132+
The proposal is to let a sequence only have a single outcome defined, and if a hierarchy of events can have multiple outcomes we would prefer the user to set up multiple sequences with the same creation criteria.
133+
134+
This simplifies the creation and understanding of what a single sequence (or sequence definition) is.
135+
136+
It does not solve the comparison between multiple sequences with different definitions.
137+
</OpenQuestion>

0 commit comments

Comments
 (0)