Where this sits in the analyst track
Sessions 1 to 3 dealt with data that arrives whole: orders, lines, stores, SKUs, and the keys that do or do not survive masking. A live channel is different. Its most useful data is a stream of small events, most of them fired by software Serai does not own, and nobody downstream can tell a missing minute from a quiet one. Tonight is the operator side of that problem: the spec as a contract, the owners, the versioning and the gate. Session 5 reads the tables that pass the gate and turns them into Stop, Hold, Love, Buy. Pasar Live, Serai Commerce, the stream and every number on this page are constructed; the pattern is the one any team running a live channel across several brands ends up needing.
★ What you walk out with tonight. A five-event spec with a definition, a grain, a source, an owner and a first-seen version on every field; a minute-table builder that de-duplicates on an idempotency key; and a four-check gate that says "scored" or "rejected" for every stream, with the reason printed. Plus the habit that saves the most arguments on a live channel: nobody reads a stream the gate has not passed.
Five events, one clock
6 min livePasar Live's minute table counts five things: room entry, product card impression, product click, add to cart and order. Every row is keyed by stream_id + minute + sku, and every timestamp sits on one clock: UTC, with the minute the platform marks the room live as minute 0. How that table is modelled and what livestream data science does with it belongs to the live commerce course's session on the stream as data. This session is about the agreement that has to exist before the table can be trusted.
LiveSix fields on every event in the spec2 min▶
| Field | What it says | Why it has to be written |
|---|---|---|
| name | The exact string in the log, product_click | A renamed event is a new event to every query that filters on the old name |
| definition | One sentence a new analyst could compute from | "Impression" means nothing until you say on screen for how long |
| grain | The key a count sits at: stream_id, minute or stream_id, minute, sku | Room entries belong to the room, not a product; summing them per SKU triple-counts |
| source | Who fires it: Serai's overlay, the platform's per-minute totals, the order API | It decides what you can fix yourself and what you can only reconcile |
| owner | One team, never two | It is who gets called when the column is empty at 21:00 |
| first-seen version | The spec version the event appeared in | Lets anyone tell "zero clicks" from "clicks were not tracked yet" |
Why the minute, and not the frame or the whole stream? The frame is finer than any event can be timed across two systems, so it manufactures precision nobody has. The whole stream is too coarse to coach from: the point of the table is to find the minute a host lost the room. A minute is the smallest window in which Serai's overlay, the platform's per-minute totals and the order API can all be made to agree.
LiveWhat Serai logs itself, and what only the platform can tell it2 min▶
Session 1's obtainability words apply event by event. In the constructed Pasar Live set-up, Serai's studio runs its own overlay, the layer that pins product cards on screen and takes the tap, on its own studio clock, synced to UTC. Those events are owned: Serai fires them, stores them raw and can re-count them.
- Owned, from the overlay: product card impressions and product clicks, one row per event with its own id.
- Full via API: orders, from the platform's order API, each with a placed timestamp Serai can put on its own minute.
- Partial, platform per-minute totals: room entries and add to cart. Serai gets a count per minute, not the events behind it.
- Partial, platform totals only: live card impressions, the times the stream's cover appeared in someone's feed. That is the denominator of Stop, and it arrives as one number per stream, after the stream ends.
The line matters because it decides the fix. An owned event that looks wrong can be re-counted from raw. A platform total that looks wrong can only be reconciled against, which is exactly what check 4 in Part 3 does.
Self-studyLate events, duplicate events and the idempotency key3 min▶
Phones on bad networks retry. An overlay that did not hear an acknowledgement sends the same tap again, sometimes seconds later, sometimes after the stream has ended. If the pipeline counts what arrives, a retry storm looks like a surge of interest.
The fix is an idempotency key: an id the event carries from the moment it was fired, so that processing it twice gives the same result as processing it once. De-duplicate on that id, never on the timestamp (two real taps can share a second) and never on the whole row (a retry can carry a different arrival time). A late event is then just an event: it lands in the minute of its own timestamp, not the minute it arrived, and the stream's minute table is rebuilt until the platform's end-of-stream totals are in.
Platform per-minute totals have no event id, so they cannot be de-duplicated by you. Write that on the spec's source field. It is the honest reason those columns are reconciled, not re-counted.
Two impression counts for one stream, constructed. In Pasar Live's first month, Serai's overlay counted product card impressions from the moment a card was pinned; the brand's own report, read from the platform dashboard, counted a card only once it had been on screen for a full second. Same stream, same word, two numbers, and a brand manager asking which one her fee was built on. Nothing was broken. The word had no definition. The fix was one sentence in the spec and a version number on it.
The spec is a contract with owners
6 min liveLeader session 3 put eight teams around one minute-grain table. A table eight teams read is eight teams' numbers, and the spec underneath it is a contract between them: one owner per field, a stated way to change it, a version on every release. Without that, a single rename by one team silently changes the other seven teams' dashboards, and nobody finds out until a Monday meeting disagrees with itself.
LiveEight teams, and the one question each one asks of the spec2 min▶
| Team | Owns in the spec | Reads, and needs to be true |
|---|---|---|
| Tech | Room entry ingestion, card impression, product click | Which events went missing, per stream |
| Data engineering | Add to cart, order, the clock and minute 0 | Where the joins break |
| Analysts | The stage definitions built on top (session 5) | Every column, at the grain it was declared |
| Live ops | Notes on the minute, attached as labels | Room entries, to find the minute the room left |
| Traffic ops | Promo and push timestamps | Entries and orders after each push |
| Commercial | Assortment per stream | Cards, clicks, carts and orders by SKU |
| Brand teams | Room set-up, SKU count | Their own brand's impressions, carts and orders |
| Hosts | The stream itself | Entries, clicks and orders on their own scorecard |
Ownership here means "fires it, or ingests it, and answers for it". Analysts own no event, on purpose: the team that defines the stages should not also be the team that can quietly adjust the counts under them.
LiveChange control: a rename is a breaking change3 min▶
Version the spec the way software versions an interface, because that is what it is. A three-part number, MAJOR.MINOR.PATCH, with a rule for each part:
- MAJOR: an event renamed or removed, a definition changed, a grain changed. Every reader's numbers can move. It needs every owner's sign-off, a date, and a period in which the old and new names are both emitted.
- MINOR: a new event or a new optional field. Nobody's old numbers move. The owner announces it; the
first_seenfield records it. - PATCH: wording that clarifies without changing what is counted.
Every minute table carries the spec version it was built under, as a column. When session 5 compares a stream from March with one from June, the first question is whether they were counted under the same major version. If not, the comparison needs a sentence, and the metric governance session in the ecommerce metrics course covers how that sentence is kept for company metrics. Session 7 applies the same versioning to the metric registry.
Self-studyWhat "done" means for tech, written into the spec3 min▶
A tracking ticket that says "add click event" is done when the code ships. A tracking ticket written against the spec is done when four things are true, and the spec says so in its own words:
- The event fires with the exact name in the spec, carrying an event id, a UTC timestamp, the stream id and, where the grain says so, the SKU.
- It has run on one real stream and the minute table built from it passes all four checks in Part 3.
- The spec version is bumped, with the event's
first_seenset, and every owner has seen the change. - The readers named in the contract have been told what moved, and when.
Item 2 is the one that changes behaviour. It turns "we shipped the event" into "a stream counted with the event has passed the gate", which is the only version of done the other seven teams care about.
The overlay release that zeroed a stage, constructed. Two months into Pasar Live, an overlay release renamed product_click to card_tap, a tidy-up nobody thought of as a change. For two days every host's click rate read zero, commercial pulled a SKU that "nobody clicked", and a brand host was told her Buy stage had collapsed. The event had fired perfectly the whole time, under a name no query was filtering on. After that week, a rename became a MAJOR version with both names emitted for a fortnight, and "done" for tracking work started to mean a stream counted with the change had passed the gate.
Validate before anyone reads it
4 min liveA minute table that has not been checked is a rumour with columns. Four checks run on every stream, in order, before the table is released to the eight teams. A stream that fails any one of them is rejected, not scored with a footnote, and the reason is printed next to it. The checks are cheap; the argument they prevent is not.
LiveThe four checks, and what each one catches3 min▶
| Check | Rule | What it catches |
|---|---|---|
| 1 · Funnel order | Cumulative card impressions >= clicks >= add to cart >= orders, at every minute | Swapped or mislabelled events, a click event double-firing |
| 2 · No gap minutes | Every minute from 0 to the stream's last minute is present | A lost batch, an ingestion job that died for a minute |
| 3 · One clock | Every timestamp in UTC; every minute between 0 and the last | A source still on local time, a stream whose minute 0 came from the wrong system |
| 4 · Reconciles | Serai's counts within a tolerance of the platform's end-of-stream totals | Anything the first three missed that changes a total |
Check 1 is cumulative on purpose. Checked minute by minute, it would reject a perfectly good stream the first time a viewer clicked in minute 17 and ordered in minute 18. Cumulative, it asks the question that must always hold: by this minute, have we counted more orders than clicks?
Self-studyChoosing the tolerance, and why check 4 is not enough on its own2 min▶
The tolerance is a written choice, owned by data engineering, and it goes in the spec next to the clock. Too tight and every stream fails on the platform's own rounding; too loose and a lost minute slips through. The build-along uses 2 percent and shows the second failure happening: dropping one minute of a 60-minute stream moves room entries by 1.99 percent, inside the tolerance, and only orders cross it.
That is the argument for running all four. Reconciliation sees totals; it cannot see where in the stream the difference sits, and a gap in the minute the host lost the room is precisely the gap that matters. The gap check sees position; it cannot see a count that is wrong but present. Neither replaces the other.
Even the platform's totals carry a definition. Sea Limited's Annual Report on Form 20-F for fiscal 2019 defines Shopee's GMV as the value of orders on the marketplace, including shipping and other charges, and counts an order "regardless of whether the transaction is settled" or the item is returned. A listed marketplace chose that deliberately and wrote it down. Check 4 compares Serai's orders with a platform total, so the spec has to say which definition that total uses: placed or paid, with or without cancellations. Reconciling against a number whose definition you have not written is comparing two unknowns.
Write the spec, then test a stream against it
★ 22 min · everyone buildsFive short steps in plain Python with numpy and pandas. The stream is constructed with a fixed seed, default_rng(4), so every output below is exactly what the code prints, run as one session in the order shown (numpy 1.26, pandas 2.1). Keep the seed and you will see the same numbers; the lesson is in the checks, not the counts.
Write the spec as data. A Python dict, one entry per event, the six fields from Part 1 on each, plus the clock as its own owned field. In production this is a YAML file in a repo with owners on the review list; the shape is identical. Everything after this step reads SPEC, never its own copy of the event names.
Generate one constructed 60-minute stream as raw events. Three SKUs, one featured per 20 minutes, a room that ramps up and slowly fades. Every event gets an id at the moment it fires, and 1.5 percent of events are sent twice, the way retries arrive. Room entries sit on sku = "ALL" because their grain is the room.
Build the minute table: de-duplicate on the idempotency key, put every event on the one clock, count at the grain. The columns come from SPEC, in the spec's order, so an event missing from the log still appears as a column of zeros instead of silently vanishing.
Write the gate and run it. The platform's end-of-stream totals are constructed here as the true counts nudged by a small random error, because a real platform's totals never match yours to the unit. Four checks, one verdict, and the evidence printed with it: the gap list and how far each total is from the platform's.
Break it twice, on purpose. A gate you have only ever seen pass is a gate you have not tested. First, lose minute 17, the way a dead ingestion job would. Second, swap two event names, the way a careless overlay release would. Each break should fail a different check, and say which.
Read the first break carefully. Room entries moved by 0.0199 against the platform, under the 0.02 tolerance: on entries alone, reconciliation would have waved the stream through. Orders crossed it at 0.0408, and the gap check named the minute. The swap is louder: 194 clicks relabelled as orders is 2.96 times the platform's 49, and the funnel check fails before reconciliation even gets a say.
Keep the gate next to the spec, and import both. Session 5 computes Stop, Hold, Love, Buy only on streams whose verdict is "scored", and it reads the event names from the same SPEC. A second list of event names in a notebook is the rename from Part 2 waiting to happen.
Before session 5
30 min- Write the spec for one event stream you work with, live or not: five events or fewer, all six fields on each, and one clock with an owner. Mark every event as owned, full via API or partial.
- Draw the contract sheet for it: events down the side, teams across the top, one owner per row. Find the field everyone reads and nobody owns. Most teams have one.
- Add a third break to step 5: put one source on local time, UTC+8, and check which of the four checks fails. If none does, change the clock check until it would.
- Rerun step 4 with a tolerance of 0.01 and then 0.05. Write one sentence on which you would put in the spec, and who should sign it.
Three questions
Clear all three and Analyst session 4 gets stamped in your passport.
1 · Live card impressions, the times the stream's cover appeared in a feed, reach Serai only as one platform total per stream. What does that mean for the spec?
The source field decides the fix. An owned event can be re-counted from raw; a platform total can only be compared with. Writing that down stops a team from "correcting" a number nobody at Serai can see behind.
2 · An overlay release renames product_click to card_tap. Nothing else changes. How should the spec treat it?
The definition did not move, but every query that reads the event did. To the other seven teams a renamed event is a removed one, which is exactly how Pasar Live's click rate read zero for two days in the constructed case.
3 · With minute 17 lost, room entries were 1.99 percent off the platform's total, inside the 2 percent tolerance. What does that show?
Each check sees something the others cannot. Reconciliation tells you a total is off; only the gap check tells you which minute is missing, and the missing minute may be the one where the host lost the room.
What this session teaches, and where it came from
Every fact on this page, and its verification tier, is recorded in the course's source map.
Analyst session 4 cheat sheet · pin this
You have a spec, a minute table and a gate. Session 5 takes the streams that pass it, eight constructed Pasar Live streams across two host types, and writes Stop, Hold, Love, Buy and the North Star as code, with the denominator of every stage stated and why platform hosts win on GMV while brand hosts win on the North Star.