learn-ship-platform-diagnosis-with-phoebe / Analyst session 4 of 5
Learn Ship Platform Diagnosis with Phoebe · Analyst track · Session 4 of 5

Merchants and stores

The platform's customers are merchants: 140 of them, 59 already gone, running 342 stores. Today measures who was acquired when and where, who survived, which stores actually sell, how much of each server rides on five merchants, and how each onboarding vintage matured. Two traps run through every chart: survivorship, because a revenue line drawn from the merchants still here flatters, and censoring, because the ones still here have not told you how long they will stay. Kaplan-Meier handles the second; the first is handled by putting every merchant in the denominator, alive or not.

🟠 Analyst track Data analysts · pandas + lifelines Notebook: a4_merchants_and_stores.ipynb 45 min
0-3 · Welcome 3-18 · Acquisition and survival 18-40 · Activity, concentration, leading signals 40-45 · Q&A
Part 0

Where this session sits

a2 followed the margin and named the mechanism behind the 2024 break; a3 walked the product hierarchy and the baskets. Today climbs one level up the hierarchy: platform > server > merchant > store. A merchant is the unit the platform sells to, so merchant acquisition and merchant churn are the platform's own growth accounting. a5 will then go down to the customers those merchants brought and find that the retention drop shows up in customers of surviving and churned merchants alike - which is why today's survival numbers matter: they are one of the two decisions a5 has to tell apart. Everything runs through analysis/lib.py with dedupe=True, in notebooks/a4_merchants_and_stores.ipynb.

Live - presented in session Self-study - read after class Sources covered
★ What you walk out with today An acquisition chart that shows the key servers were the 2021-2023 growth engine and then stopped acquiring. A growth-accounting chart where, from mid-2024, S07 and S08 churn more merchants than they add. A Kaplan-Meier curve that keeps the survivors honest and a hazard that nearly doubled, 1.26 to 2.39 percent per month, after the June 2024 merchant-success cut. A dormancy band that says the store count nobody should trust. And two leading indicators that were visible in a merchant's first quarter, years before the churn.
Part 1 · covers acquisition, growth accounting and survival

Acquisition, activity, survival 15 min live

Three questions, in the order a merchant lives them: were they acquired, are they transacting, have they left. Each has a trap. Acquisition counts look like health when they are only presence. Activity counts need a definition, and it has to be the same one every month. Survival is where most teams go wrong: they either chart the merchants who are still here, which flatters, or they drop the ones still here from a lifetime average, which understates. The merchants who have not left yet are not missing data. They are censored, and the estimator has to know it.

Censoring - the merchants still here have not told you how long they will stay 2020 2021 2022 2023 2024 2025 2026 today 2026-09 M1 M2 M3 M4 M5 M6 Dropping these understates survival. Keeping them as alive overstates it. X = churn observed. A bar that reaches today is censored: still active, lifetime unknown. KM uses both.
🔍 Click to zoom - three lifetimes are known, three are not yet. A lifetime average that drops or ignores the second kind is wrong in a known direction.
Live Merchants onboarded per year 3 min▶

The merchant dimension carries onboard_month, server_id and, for 59 of 140, a churn_month. Group the onboardings by year and by server group and the platform's growth story is one bar chart long.

★ Onboardings by year, key servers against the restm = dims["merchant"].copy() m["onboard"] = pd.PeriodIndex(m.onboard_month, freq="M") m["year"] = m.onboard.dt.year m["is_key"] = m.server_id.isin(lib.KEY_SERVERS) # S07, S08 grp = np.where(m.is_key, "S07 + S08", "other servers") acq = m.assign(grp=grp).groupby(["year", "grp"]).size().unstack(fill_value=0)
Grouped bar chart of merchants onboarded per year 2020 to 2026. S07 and S08 onboarded 16, 18 and 11 merchants in 2021, 2022 and 2023 and none in any other year. Other servers onboarded between 22 and 8 per year through 2025, and 2 in 2026.
🔍 Click to zoom - the amber bars exist for three years only. The key servers were the growth engine, then acquisition on them stopped outright.

S07 and S08 launched in 2021 and took 16, 18 and 11 merchants in their first three years. Since 2024: zero. The other servers slowed too, from 22 in 2020 to 8 in 2025. The a1 coverage heatmap showed sparse onboarding months for 2024-2026 and called it presence, not health. This is the chart that makes the judgement: it is not a broken table, it is a stalled funnel. What the data cannot say is how many merchants were pitched or trialled before onboarding - there is no acquisition funnel table at all, and that goes on the c3 collection list.

Live Active merchants and growth accounting 5 min▶

A merchant is active in a month if any of its stores transacted that month. That is a definition you write down once and never vary, because an active count with a moving definition is two charts pretending to be one. The growth-accounting identity for merchants reads the same as for customers: actives this quarter = new + retained, and churned sits below the axis.

Line chart of active merchants per month, 2020 to 2026, for other servers and for S07 plus S08. The key-server line climbs through 2023, then declines after the dashed June 2024 marker while the other-server line stays roughly level.
🔍 Click to zoom - active merchants per month, transacted that month. The amber line turns down after the dashed June 2024 line.
★ New and churned merchants per quarter - one rule for "churned"act = txc.groupby(["month", "merchant_id"]).size().reset_index()[["month", "merchant_id"]] first = act.groupby("merchant_id").month.min() last = act.groupby("merchant_id").month.max() rows = [] for grp_name, flag in [("S07 + S08", True), ("other servers", False)]: ids = m[m.is_key == flag].merchant_id for qq in pd.period_range("2020Q1", "2026Q2", freq="Q"): f = first.reindex(ids).dropna(); l = last.reindex(ids).dropna() new = (f.dt.asfreq("Q") == qq).sum() churned = (l.dt.asfreq("Q") == qq - 1).sum() # last order in the PRIOR quarter = churned THIS quarter rows.append((qq, grp_name, new, -churned)) ga = pd.DataFrame(rows, columns=["quarter", "grp", "new", "churned"])

The churn rule matters more than it looks. A merchant whose last order fell in 2024Q2 is counted as churned in 2024Q3, the first quarter with no order. Count it in the quarter of the last order instead and every churn moves one quarter earlier, which puts the merchant exodus before the June cut instead of after it. The rule is a sentence in the chart subtitle, and the growth-accounting range stops at 2026Q2 because the quarter still running cannot yet tell a churned merchant from a quiet one.

Two panels of quarterly bars, 2020 to 2026. New merchants above the axis, churned merchants below, with a dotted cumulative net line. On S07 plus S08 the new bars stop after 2023 and the churned bars grow from mid-2024, so the net line turns down. On other servers new and churned roughly balance.
🔍 Click to zoom - from mid-2024 the key servers churn more merchants than they add, and nothing new arrives to replace them.
Live Kaplan-Meier, because the survivors are still here 7 min▶

81 of 140 merchants have no churn month. Their lifetimes are not known; they are at least as long as the time since onboarding. The Kaplan-Meier estimator uses exactly that information: at each month, it asks what share of the merchants still at risk left, and multiplies the survival down. A merchant still here contributes to the denominator up to today and then stops, without pretending to have left or to be immortal. In lifelines the duration is months from onboarding to churn month or to today, and event is 1 only when the churn was observed.

★ Survival by server group, and the monthly hazard before and after June 2024from lifelines import KaplanMeierFitter as_of_m = pd.Period(lib.AS_OF, "M") m["end"] = np.where(m.churn_month != "", m.churn_month, str(as_of_m)) m["dur"] = (pd.PeriodIndex(m.end, freq="M") - m.onboard).map(lambda x: x.n).clip(lower=1) m["event"] = (m.churn_month != "").astype(int) # 0 = still here, censored at today kmf = KaplanMeierFitter() for flag, lab in [(False, "other servers"), (True, "S07 + S08")]: d = m[m.is_key == flag] kmf.fit(d.dur, d.event, label=f"{lab} (n={len(d)})").plot_survival_function(ax=ax, ci_alpha=.12) # hazard: every merchant-month at risk on the key servers, split at 2024-06 mm = [(str(r.onboard + k), 1 if (r.event and k == r.dur - 1) else 0) for r in m[m.is_key].itertuples() for k in range(r.dur)] hz = pd.DataFrame(mm, columns=["month", "churn"]); hz["month"] = pd.PeriodIndex(hz.month, freq="M") hz["period"] = np.where(hz.month >= pd.Period("2024-06", "M"), "Jun 2024 onward", "before Jun 2024") haz = hz.groupby("period").churn.agg(["sum", "count"]) haz["monthly hazard %"] = haz["sum"] / haz["count"] * 100
Left: Kaplan-Meier survival curves since onboarding for other servers and for S07 plus S08, with confidence bands; the key-server curve falls faster. Right: monthly churn hazard for key-server merchants, 1.26 percent per month before June 2024 and 2.39 percent per month from June 2024 onward.
🔍 Click to zoom - the hazard is the honest number: of the merchants at risk in a given month, how many left. It nearly doubled the month the success team was cut.

The right panel is 956 merchant-months at risk before June 2024 with 12 exits, against 670 merchant-months after with 16. The hazard is not a survival rate and not a churn count; it is the chance a merchant at risk leaves in a given month, and it lets a period with fewer merchants be compared with a period that had more. 1.26 to 2.39 percent per month is the merchant-side echo of the decision a2 priced on the margin side. The store dormancy in Part 2 is the earlier, quieter version of the same signal.

Real world A subscription-software company reported average customer lifetime as the mean tenure of customers who had cancelled. The number fell every quarter and the board concluded churn was accelerating. It was not: the company was growing, so recent cancellers were disproportionately young accounts, and the long-tenured customers who had never cancelled were simply not in the average. A Kaplan-Meier curve on the same data was flat. The estimator was invented in 1958 for exactly this problem - patients still alive at the end of a trial - and it applies unchanged to merchants still trading.
Self-study Why a revenue chart of surviving merchants flatters 3 min▶

Take the merchants active today and plot their revenue back to 2020. The line rises beautifully, because every merchant in it is, by construction, one that made it. The 59 that left are not on the chart, and neither is the revenue they took with them. This is survivorship bias, and it is the default shape of any "our customers" chart built from a current customer list. Three habits prevent it:

  • Build merchant-level series from the transaction fact, never from a filtered dimension. A merchant who churned in 2023 still has 2021 and 2022 revenue, and it belongs on the 2021 and 2022 chart.
  • Put the whole vintage in the denominator. The vintage curves in Part 2 divide by every merchant onboarded that year, alive or not, so a churn shows as a decline in the curve instead of a disappearance from it.
  • When someone shows a per-merchant average rising, ask how many merchants are in the average this year against last year. A shrinking denominator of survivors makes any per-unit metric climb.
Part 2 · covers dormancy, concentration and vintages

Stores that exist versus stores that sell 12 min live

A dimension table records that a store was created. It does not record that anyone stopped using it, because closing a record is a chore nobody owns. About 65 percent of Printwell's dead stores are still open on the books. So the "number of stores" any dashboard shows is a count of rows nobody deleted, and the gap between that and the stores that transacted recently is the earliest merchant-churn signal in the data.

Stores that exist versus stores that sell - the lines part in 2024 and never rejoin merchant-success cut, Jun 2024 stores that existdim_store rows, never closed stores active - an order in the last 90 days dormancy 2020 2021 2022 2023 2024 2025 2026 Existence is a row nobody deleted; activity is an order with a date. Count the second, report the gap.
🔍 Click to zoom - the pale line is what the store dashboard shows. The navy line is what is happening. The band is the difference, and it is a leading indicator.
Live Define active 4 min▶

Active store: at least one order in the trailing 90 days. Ninety days because the median customer takes 118 days between first and second order (a5), so a store with no order in a quarter is quiet, not merely between customers. The definition is applied month by month against the stores whose dimension row says they exist that month, and the dormant share is one minus active over existing.

★ Existing versus active stores, month by monthst["open"] = pd.PeriodIndex(st.open_month, freq="M") st["close"] = pd.PeriodIndex(st.close_month.replace("", str(as_of_m)), freq="M") # blank = still open on the books last_order = txc.groupby(["store_id", "month"]).size().reset_index()[["store_id", "month"]] rows = [] for per in pd.period_range("2020-06", as_of_m - 1, freq="M"): existing = st[(st.open <= per) & (st.close >= per)] recent = set(last_order[(last_order.month <= per) & (last_order.month >= per - 2)].store_id) # trailing 3 months for flag in [True, False]: e = existing[existing.is_key == flag] rows.append((per, "S07 + S08" if flag else "other servers", len(e), e.store_id.isin(recent).sum())) dorm = pd.DataFrame(rows, columns=["month", "grp", "existing", "active"]) dorm["dormant %"] = (1 - dorm.active / dorm.existing) * 100
Two panels, S07 plus S08 and other servers, 2020 to 2026. In each, a pale line of existing stores and a coloured line of stores active in the last 90 days, with the gap shaded. On the key servers the shaded dormancy band widens through 2025 and 2026; on other servers it stays thin.
🔍 Click to zoom - the shaded band is dormancy plus dead stores still on the books. On the key servers it widens; elsewhere it does not.

Dormant share on the key servers: 8.7 percent in December 2023, 4.1 in December 2024, 8.8 in December 2025 and 10.2 by August 2026, against 6.6, 4.9, 3.7 and 6.2 on the other servers. The December 2024 dip on both groups is seasonal - a quarter with a December in it has few quiet stores. The trend after it is not. A store that stops selling is a merchant that is disengaging, months before the merchant's last order becomes a churn in the growth accounting.

Report existing, active and the gap Three numbers, every month, never just one. "342 stores" is true and useless. "342 on the books, N active, dormancy X percent and rising on S07 and S08" is a sentence a merchant-success lead can act on this week. The cost of not closing records is not tidiness; it is that the only trustworthy store count has to be recomputed from orders every time.
Live Concentration: who carries each server 4 min▶

Revenue per server hides how many merchants it rests on. The Herfindahl index (sum of squared revenue shares, scaled to 10,000) and the plainer top-5 merchant share say the same thing in two registers: high concentration is fragility, because one merchant leaving becomes a server-level event.

★ HHI and top-5 share of merchant revenue, per server per yearmr = txc.groupby(["server_id", "year", "merchant_id"]).revenue.sum().reset_index() def hhi(g): s = g.revenue / g.revenue.sum() return pd.Series({"hhi": (s ** 2).sum() * 10000, "top5 share %": s.sort_values(ascending=False).head(5).sum() * 100, "merchants": len(s)}) conc = mr.groupby(["server_id", "year"]).apply(hhi).reset_index() conc.round(1).to_csv(lib.REPORTS / "a4_concentration.csv", index=False) piv = conc[conc.year == 2025].set_index("server_id")
Bar chart of top-5 merchant share of server revenue in 2025, one bar per server, key servers in amber. Small servers sit near 100 percent with under ten merchants each; S03, the largest, is lowest at 73 percent across 19 merchants.
🔍 Click to zoom - the smaller the server, the more one merchant matters. S03 is the only server whose top five carry under three-quarters of revenue.

In 2025, S03 spreads its revenue across 19 merchants and its top five carry 73.1 percent; S01 has 7 merchants and its top five carry 98.2 percent. A server with five merchants is a portfolio of five bets. The full table by year is in reports/a4_concentration.csv; watch the HHI trend, because concentration that rises while merchant count is flat means the smaller merchants are fading, which is the dormancy story told in a different unit.

Self-study Vintage curves 4 min▶

A vintage is every merchant onboarded in the same year. Revenue per merchant of the vintage, by months since onboarding, with all of the vintage in the denominator - alive or not - is the merchant version of a cohort curve. Churn shows as decline, not disappearance. The 2021 and 2022 vintages carried the platform; 2023 onward matured lower. Read alongside the acquisition chart, this says the key servers not only stopped acquiring, the last merchants they did acquire earned less per head than the ones before.

Line chart of monthly revenue per merchant of each onboarding vintage, 2020 to 2024, over 36 months since onboarding. The 2021 and 2022 vintages climb highest; the 2023 and 2024 vintages sit lower at every month of age.
🔍 Click to zoom - each line divides by every merchant of the vintage, so a merchant who left pulls the line down instead of vanishing from it.

Segment and vertical tell you where a merchant-month earns most: an Enterprise merchant-month averages 7,376 against 2,027 for Mid and 462 for SMB, and sports clubs (3,661) out-earn schools (1,241) three to one. This is the cut that turns "acquire more merchants" into "acquire merchants in the segments and verticals where a merchant-month is worth most", which is the c3 acquisition recommendation.

Two bar charts of average revenue per merchant-month. Left by segment: SMB 462, Mid 2,027, Enterprise 7,376. Right by vertical, sorted: school lowest at 1,241, sports club highest at 3,661.
🔍 Click to zoom - a merchant-month is not a merchant-month. Segment is a 16x range; vertical is a 3x range.
Part 3 · covers leading indicators and merchant health signals

The churn was visible in month three 8 min live

Everything so far measures the funeral: the churn month, the hazard, the dormancy that precedes it by a few months. A merchant-success team cannot act on a funeral. It can act on a list of merchants who look, today, like the ones who later left. So the question is whether a merchant's first quarter predicts its fate - and on this data, it does.

Live Leading indicators 8 min▶

Two readiness signals from the first quarter after onboarding: months from onboarding to first sale, and revenue in the first 90 days. Measured only on merchants onboarded by 2023, so every one of them has had at least two and a half years to churn or not - a 2025 merchant who has not churned yet is not evidence of anything.

★ First-quarter signals against eventual outcome, merchants onboarded by 2023mo = txc.groupby("merchant_id").agg(first_sale=("month", "min")).join( m.set_index("merchant_id")[["onboard", "event", "year", "is_key"]]) mo["months_to_first_sale"] = (mo.first_sale - mo.onboard).map(lambda x: x.n) r90 = txc.merge(m[["merchant_id", "onboard"]], on="merchant_id") r90 = r90[(r90.month - r90.onboard).map(lambda x: x.n) <= 2].groupby("merchant_id").revenue.sum().rename("first_90d_revenue") mo = mo.join(r90).fillna({"first_90d_revenue": 0}) mature = mo[mo.year <= 2023].copy() # everyone has had time to churn mature["outcome"] = np.where(mature.event == 1, "churned", "still active") lead = mature.groupby("outcome").agg(merchants=("event", "size"), months_to_first_sale=("months_to_first_sale", "mean"), first_90d_revenue=("first_90d_revenue", "median")) lead.to_csv(lib.REPORTS / "a4_leading_indicators.csv") # a5 reads this into canon.json
Two bar charts comparing merchants onboarded by 2023 who later churned against those still active. Left: average months from onboarding to first sale, 3.8 for churned versus 1.3 for still active. Right: median revenue in the first 90 days, 0 for churned versus 399 for still active.
🔍 Click to zoom - the merchants who left took three times longer to make a first sale, and half of them sold nothing at all in their first quarter.

54 merchants churned, 63 are still active. The ones who churned took 3.8 months on average to their first sale against 1.3 for the survivors, and their median first-90-day revenue was 0 - more than half of the eventual churners sold nothing in their first quarter. The survivors' median was 399. One merchant onboarded by 2023 never recorded a sale at all, and it churned. A merchant who has not sold by day 90 is not a new merchant; it is a churn in progress, and a success manager can call it this week rather than write it off in 2027.

A list, not a eulogy This is the readiness-indicator method from Mark Roberge's work on scaling sales organisations: find the early behaviours that separate the accounts that stayed from the ones that left, then manage to those behaviours while there is still time. Two cautions travel with it. First, a leading indicator is tested on your own data, never assumed from someone else's - time to first sale predicts here because this platform's merchants either start or do not; a marketplace with a long setup phase might find the opposite. Second, the signal is a screen, not a verdict: it tells the success team whom to call, and the call is where the retention is earned.
Real world A payments provider found that merchants who processed a live transaction within 14 days of signing retained at nearly twice the rate of those who did not, regardless of merchant size. The onboarding team stopped measuring "accounts created" and started measuring "accounts with a first live transaction inside two weeks", and rebuilt the onboarding flow around getting that first transaction to happen. The leading indicator became the operating metric, which is where a leading indicator earns its keep.
Self-study What to collect on merchants 4 min▶

Today's analysis worked with two dimension tables and the order fact. It could have worked with more. Two families of merchant data are missing, and each is a c3 recommendation with an owner and a date.

MissingWhat it would answerOwnerFirst use
Acquisition funnel: merchants pitched, trialled, onboarded, by server and monthWhether 2024's zero onboardings on S07 and S08 is a demand problem or a sales-effort problemMerchant salesFunnel conversion per server per quarter
Merchant health events: platform logins, catalogue edits, campaign launches, support ticketsEarlier and richer readiness indicators than time to first sale; a disengagement signal weeks before dormancyMerchant successWeekly at-risk list
Store status as events (opened, paused, closed), not an overwritable fieldA trustworthy store count without recomputing activity from ordersPlatform dataStore dashboard
Merchant-success coverage: which merchants had a named manager, whenWhether the June 2024 cut removed coverage from the merchants who then churnedMerchant successHazard by coverage status

The last row is the one that would settle the two-decision question a5 opens: the hazard doubled after the team was cut, and a coverage table would say whether the merchants who left were the ones who lost their manager.

Build-along

Rebuild the merchant notebook ★ 20 min · everyone builds

Open notebooks/a4_merchants_and_stores.ipynb, run it once, then rebuild each section against the shared reader. Load with dedupe=True; the a1 duplicate rule is already in lib. The data is seeded, so your hazard reads 1.26 and 2.39 like the page.

Merchant frame. Load dims, build onboard, year, is_key. Print merchants, churned, stores and the tier counts. Cut transactions to complete months only.

Acquisition. Onboardings by year and server group as a grouped bar. Write the finding in the title, not the axis label.

Growth accounting. First and last order month per merchant. New this quarter, churned this quarter as last order in the prior quarter. Stop at the last complete quarter.

Survival. Duration to churn or to today, event flag, KaplanMeierFitter per server group. Then the merchant-month table and the hazard split at 2024-06. Save the hazard to reports/.

Dormancy. Existing stores per month from the dimension, active stores from orders in the trailing three months. Two lines, one band, per server group. Print the dormant share at four December-ish checkpoints.

Concentration and vintages. HHI and top-5 share per server per year to reports/. Vintage curves with the full vintage in the denominator.

Leading indicators. Months to first sale and first-90-day revenue by outcome, merchants onboarded by 2023 only. Save the table: a5 folds it into canon.json.

The check that keeps you honest Rerun python3 analysis/nbgen.py notebooks/src/a4_merchants_and_stores.py. A failed run leaves no notebook behind, so a stale passing notebook can never masquerade as a fresh one. And check the censoring the cheap way: count merchants with event == 0 and confirm it equals 140 minus 59. If it does not, some survivor has been silently given a churn date.
Homework

Try it yourself - this week ◐ 45 min

Sources covered

What this session draws on

Merchant analytics is standard survival and portfolio craft rather than a certified syllabus. The working core drawn on here:

✓lifelines documentation - KaplanMeierFitter, durations, event observed, censoringPart 1 · survival by server group
✓Kaplan and Meier (1958), Nonparametric estimation from incomplete observations - right-censoringPart 1 · why survivors stay in the denominator
✓Herfindahl-Hirschman index - sum of squared shares as a concentration measurePart 2 · HHI and top-5 share per server
✓Growth accounting - actives = new + retained + resurrected, churned below the axisPart 1 · the merchant version, per quarter
✓Mark Roberge, The Sales Acceleration Formula and The Science of Scaling - readiness and leading indicators of retentionPart 3 · the method, tested on this data
◐Cox proportional hazards - covariates on the hazardNamed only; the before/after split is the teaching here
Check yourself

Three questions before you go 🎯 ◐ 90 seconds

1 · You compute average merchant lifetime as the mean months from onboarding to churn, over the 59 merchants who churned. What is wrong with the number?

A merchant still trading is not missing; it is censored, and its lifetime is known to be at least its tenure so far. Dropping the censored merchants keeps only the ones who left, and in a growing base those skew young. The Kaplan-Meier estimator keeps every merchant at risk in the denominator up to the month it leaves or today, whichever comes first.

2 · The store dashboard says S07 has more stores than ever. The order data says fewer stores on S07 transacted last quarter than a year ago. Which is right?

Existence is a row nobody deleted; activity is an order with a date. Report existing, active and the gap every month. The dimension is not wrong about existence, it is silent about activity, and about 65 percent of dead stores are still open on the books.

3 · Merchants who later churned took 3.8 months to first sale against 1.3 for survivors. What kind of metric is time to first sale, and what does the success team do with it?

Churn month and hazard describe what already happened; months to first sale is known within a quarter of onboarding and, on this data, separates the merchants who stayed from the ones who left. That makes it a leading indicator - provided it was measured here, on merchants mature enough to have churned, not borrowed from another business's playbook.

Analyst session 4 cheat sheet · pin this

Merchants are the customers140 merchants, 59 churned, 342 stores. Merchant acquisition and churn are the platform's own growth accounting.
Presence is not healthS07 and S08 onboarded 16, 18, 11 merchants in 2021-2023 and zero since. Stalled funnel, not a broken table.
One churn ruleLast order in the prior quarter = churned this quarter. Stop at the last complete quarter. Put the rule in the subtitle.
Censored is not missing81 merchants are still here. Kaplan-Meier keeps them at risk until today; dropping them understates survival.
Hazard, not headcountExits over merchant-months at risk. Key servers: 1.26 percent per month before June 2024, 2.39 after.
Existing versus activeActive = an order in the trailing 90 days. Rows are never closed; report existing, active and the gap every month.
Concentration is fragilityHHI and top-5 share per server per year. S03: 73 percent across 19 merchants. S01: 98 percent across 7.
Measure readiness, not funeralsChurners: 3.8 months to first sale, median 0 in the first 90 days. Survivors: 1.3 and 399. Test it on your own data, then hand the team a list.