Where tonight sits on the track
Session 1 drew the estate map and wrote the registry. Tonight is the spine of domain 4, orders, fees and settlement, and the middle link of the first chain: the mapping from each platform's ids to Serai's own. The stand-in is the public Olist dataset, real Brazilian marketplace orders, anonymised. The three-way split and the export column names are constructed for teaching; the orders, the prices, the shipping fees and the sellers are real. Session 3 then asks which of these columns can still join a buyer across platforms, and the answer depends on everything you build tonight.
★ What you walk out with tonight. A per-platform column map that turns any of three exports into one conformed schema, a shop mapping table and a SKU mapping table with a weekly unmapped-rows check, and two GMV numbers from the same 110,197 lines, each with a name that says exactly what it counts.
Three exports, three dialects
6 min livePut one order line from each marketplace side by side and it is the same fact three times: an order, a listing, a shop, a price, a shipping fee. Nothing lines up. The order id is called three different things, the price column means a slightly different thing each time, and one export has no address at all. The job is not to stack the three files. It is to translate each one into a language Serai owns.
LiveSame order, three column names, three masking levels3 min▶
| What it is | Platform A | Platform B | Platform C | Conformed |
|---|---|---|---|---|
| The order | ordersn | order_number | order_no | platform_order_id |
| The listing | item_id | sku_id | listing_id | platform_listing_id |
| The shop | shop_id | seller_code | shop_code | platform_shop_id |
| Item price | item_price | paid_price | sale_price | item_price |
| Shipping | shipping_fee | shipping_amount | shipping_fee | shipping |
| Buyer address | zip and city | none | none in this export | buyer_zip, buyer_city |
The names are constructed, chosen to look the way marketplace exports tend to look, and the masking follows the reported pattern from session 1: every marketplace hides the buyer's name and phone on orders it fulfils, and each keeps a different amount of the address. On the session 8 bench, Platform C keeps phone and address only on seller-shipped orders; tonight's export leaves those out, so only A carries an address.
In a real estate the ids differ in format as well as name: each platform issues its own order numbers, its own listing ids and its own shop ids, and none of them means anything on the other two. In the Olist file every id is the same 32-character hex string whichever platform it lands on, which is exactly why you have to imagine the difference, and why Serai owns a mapping table instead of trusting the ids to agree.
LiveWhy conform, instead of stacking the three files2 min▶
A plain union of three exports gives you a frame with every column from every file: ordersn, order_number and order_no side by side, each two-thirds empty. Every query then has to know which column to read for which platform, and every analyst re-learns the dialects on their own. The dialect knowledge leaks into a hundred notebooks and drifts in each one.
Conforming puts that knowledge in exactly one place: a column map per platform, read by one function. Downstream code reads eight columns and never learns that Platform B calls the price paid_price. When a marketplace renames a field next quarter, one line changes and nothing else notices.
Self-studyThe silent drifts: type, currency, timezone4 min▶
Renaming is the loud part and the easy part. Three drifts do the real damage because nothing errors:
- Type. When the conform step fills
buyer_zipwith empty values for B and C, pandas turns the whole column intofloat64. The build-along prints it. A zip prefix is a label, not a number; as a float it loses its leading zeros and joins to nothing. Cast it to a string with the zero padding back, on purpose, in the conform step. - Currency. Olist is all Brazilian reais, so the build-along never meets this. Serai (constructed) runs stores in four markets, so its conformed schema needs a
currencycolumn next to every amount, and any cross-market total needs a reporting currency and a dated rate. Adding two currencies without one is the most common wrong number in a regional deck. - Timezone. Olist's timestamps carry no zone. Marketplace exports are often in the market's local time, and four markets can mean more than one offset. Conform to UTC and keep the local time as a second column; session 4 makes one clock a hard rule for the live channel.
Write each of these into the column map as a rule, not a comment. A comment is a hope.
Olist is itself an enabler. Olist describes its business as connecting small businesses across Brazil to marketplace channels "with a single contract". Its public file has one seller id per seller and no marketplace column at all: Olist issues the ids, so its own estate arrives already conformed. An enabler that sits on top of three marketplaces it does not run gets no such gift. The id comes from the platform, and the conforming is Serai's job. Source: Olist, "Brazilian E-Commerce Public Dataset by Olist", Kaggle, CC BY-NC-SA 4.0.
The platform column and the two mapping tables
6 min liveOnce the rows speak one language, two small things make them comparable. A platform column says where each row came from. Two mapping tables say which of Serai's stores and which of Serai's SKUs each platform id belongs to. Neither is interesting on its own, and together they are what turns three piles of orders into one store view and one SKU view.
LivePlatform is a column, not a folder2 min▶
The first draft of every multi-marketplace warehouse has three folders, platform_a/, platform_b/, platform_c/, with a copy of every table in each. Every comparison is then a three-way join, every new metric is written three times, and the three copies drift within a month.
Make platform the first column of the conformed schema instead. A comparison becomes a groupby("platform"). A new marketplace becomes a new value and a new entry in the column map, not a new folder of tables. Session 1 put the platform in a column on the estate map; tonight it becomes a column in the data.
LiveTwo mapping tables, owned, with an owner3 min▶
A mapping table has one row per platform id and one column that says which Serai id it belongs to. It is Serai's own asset, obtainability word owned, and it is the only reason a cross-platform view exists at all.
| Table | Key | Maps to | Owner | On the full Olist file |
|---|---|---|---|---|
| Shop mapping | platform + platform_shop_id | store_id | Store ops | 6,911 rows to 2,970 stores: 1,665 on all three platforms, 611 on two, 694 on one |
| SKU mapping | platform + platform_listing_id | sku | Catalogue | 47,148 rows to 32,216 SKUs: 4,176 on all three, 6,580 on two, 21,460 on one |
The 1,665 is the number to say out loud. Those are the stores whose three platforms can be read on one screen, which is the first thing a brand asks an enabler for. The master ids in the build-along are "ST" and "SK" plus the first eight characters of the Olist id: a readable convention for teaching. In a real estate the master id is issued by the catalogue tool and never derived from a platform's id, because the platform can re-issue its id and yours must not move.
Self-studyThe weekly unmapped-rows check3 min▶
A mapping table decays the moment a brand launches a new listing on one platform and nobody adds the row. The line still arrives, joins to nothing, and falls out of the SKU view without an error. The fix is a scheduled check with a named owner: left-join every conformed line to both tables, count the lines with no master id, and send the count, by platform and by brand, to the owner every week.
On the full Olist file the count is zero for both tables, because the build-along builds the maps from the same lines it checks. That zero is the check working, not the check being unnecessary. The session 8 bench plants 219 lines on listings missing from Serai's SKU table (constructed), and they are the whole gap between a SKU match rate of 1.0 and 0.9748. Owning the key means maintaining it. When there is no key to own at all, as with buyers, the matching methods live in learn-entity-resolution, and session 3 asks whether you can use them.
Constructed: the Monday a brand's best seller vanished. In this constructed Serai story, a skincare brand re-lists its best seller on Platform B to fix a photo, and the marketplace issues a new listing id. Orders keep flowing. The SKU view shows the product's Platform B sales falling to zero, and the brand's account lead spends a morning drafting an explanation for a collapse that never happened. The weekly unmapped-rows check would have flagged one new listing id with rising lines and no SKU against it, and store ops would have added one row. Every detail of this story is constructed; the mechanism is the one every mapping table has.
One schema, one GMV
4 min liveWith one schema you can finally add up the money. And the first sum raises the first definition question: does GMV include what the buyer paid for shipping? On the 110,197 delivered Olist lines the two answers are R$ 15,419,773.75 and R$ 13,221,498.11. Both are correct. Only one of them can be the number in the board pack.
LiveTwo GMVs from one table2 min▶
| Measure, delivered lines | Value | What it counts |
|---|---|---|
| GMV with shipping | R$ 15,419,773.75 | item_price + shipping, every delivered line |
| GMV without shipping | R$ 13,221,498.11 | item_price only |
| Shipping share | 14.26 percent | shipping / GMV with shipping |
| Platform A, items only | R$ 6,050,000.43 | 43,750 orders in the constructed split |
| Platform B, items only | R$ 4,528,552.65 | 33,360 orders |
| Platform C, items only | R$ 2,642,945.03 | 19,368 orders |
The per-platform numbers are an artefact of the seeded split, so they tell you nothing about any real marketplace; what they show is that one conformed schema answers the per-platform question with one line of code. The 14.26 percent is real Olist behaviour: on these orders, one real in every seven the buyer paid went to shipping.
Self-studyWhere the definition lives, and the enabler twist2 min▶
Tonight is about building the schema that makes the definition computable. The definition itself belongs in one place, the metric registry that session 7 writes as code, with its name, its formula, its owner and a test that fails if the number drifts. The general GMV equation and its variants are taught in learn-ecommerce-metrics, session a1; link to it rather than re-deriving it.
The enabler twist is that "GMV" has an owner question on top of the formula question. Whose GMV is it: the brand's, run through Serai's stores? Placed or settled? With shipping or without? Serai's GMV-linked share (constructed, 2 percent) is paid on one of these, and a contract that says "GMV" without saying which is a dispute waiting for its quarter. Tonight's filter to delivered orders is already one choice; session 7 lines up the others.
A listed marketplace writes its GMV definition down. Sea Limited's annual report for fiscal 2019 defines Shopee's GMV as the value of orders on the marketplace and says its calculation "includes shipping and other charges". It also counts each confirmed order regardless of whether the transaction settles or the item is returned. So the headline GMV of a listed marketplace is the larger of tonight's two numbers, and it counts orders tonight's delivered filter would drop. Neither choice is wrong; the lesson is that Sea wrote its choice down. Source: Sea Limited, Annual Report on Form 20-F for the fiscal year 2019, "Conventions" definitions.
Split, then conform, 110,197 real lines
★ 22 min · everyone buildsYou will make the mess before you clean it: split the real delivered orders into three constructed exports, then conform them back and prove nothing was lost. Every number below is what the code prints on the public dataset, run on pandas 2.1 and numpy 1.26. The seed fixes the split, so your counts should match to the row; if they do not, check the file version first.
Load the three tables and keep delivered orders. The no-login route is kagglehub; run it from a scratch folder, not inside a repo. The customer table rides along because it holds the address columns that Platform A will keep and B will lose.
Split the orders across three constructed platforms, with a seed. The split is at order grain, so every line of an order lands on the same platform, as it would in life. The 45 / 35 / 20 weights are constructed; the seed makes your split identical to this one.
Rename each platform's rows into its own dialect. This is the mess the marketplaces hand you. B and C drop the address columns, which is the masking level of tonight's export.
Conform with one column map per platform, and fix the type drift on purpose. The map is the only place the dialects are written down. reindex adds any schema column a platform does not send as an honest empty value, and that is exactly what turns buyer_zip into a float. Cast it back to a zero-padded string in the same step.
Build the two mapping tables. One row per platform id, one master id per row. The "ST" and "SK" prefixes plus the first eight characters of the Olist id are a teaching convention; in a real estate the catalogue tool issues the master id. Olist uses one seller id across platforms, so here the fan is by construction; in a real estate each platform's id would differ and a person would have mapped it.
Join, and count what did not map. Use a left join so an unmapped line stays in the frame with an empty master id instead of silently disappearing. This is the check store ops and catalogue run every week.
Compute GMV both ways, and by platform. Print both, with names that say what they count. The per-platform split reflects the constructed weights, not any real marketplace.
Keep COLUMN_MAP next to the session 1 registry, in the same file. The registry says what each table is; the column map says how each platform spells it. Session 3 reads the conformed frame to test join keys, and session 7 reads it to compute the registered GMV. One file, imported everywhere, or the dialects start leaking back into notebooks.
Before session 3
30 min- Add a fourth constructed platform, D, with its own column names and a
currencycolumn. Extend COLUMN_MAP and SCHEMA so the conform step still prints 110,197 lines when you re-split four ways. - Delete 1 percent of the rows from
sku_mapat random, with a seed, and re-run step 6. Report the unmapped count by platform, and write the one-line message you would send the catalogue owner. - Compute GMV a third way: all order statuses rather than delivered only. Write the three definitions as three one-line sentences, each starting with the word it counts.
- In an estate you work on, find where the mapping from a platform's product id to your own lives. If the answer is a spreadsheet, write down who last edited it and when.
Three questions
Clear all three and Analyst session 2 gets stamped in your passport.
1 · Three exports call the shipping fee shipping_fee, shipping_amount and shipping_fee. Why conform instead of concatenating the three files as they are?
The union is not wrong, it is expensive: the dialects leak into every notebook and drift in each. The column map puts them in one place, and the platform column is what the conformed schema keeps so the three can still be compared.
2 · On the full file the shop mapping has 6,911 rows and 2,970 stores, 1,665 of them on all three platforms. What makes a cross-platform store view possible?
In a real estate each platform issues its own shop id and none agrees with the others. Olist's ids agree only because Olist issued them. The mapping table is the asset that turns three ids into one store, and without its owner and its weekly check it decays.
3 · The same 110,197 delivered lines give R$ 15,419,773.75 or R$ 13,221,498.11. What is the right move?
Both numbers are correct; the 14.26 percent gap is a definition, not an error. Sea's choice shows a listed marketplace writing its definition down, not that one answer is right. The registry in session 7 is where the choice lives.
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 2 cheat sheet · pin this
You have one schema and two mapping tables that make store and SKU views exact. Session 3 turns to the one thing the mapping tables cannot map: the buyer. It tests every join key the three exports leave, from masked names to the zip that only Platform A kept, and measures at which grain each one holds.