Cloud kitchen margins: what an aggregator order really leaves you
In this article (5 sections)
Related programme: the Data Analytics with Generative AI course (3–4 months; ₹90,000 (inclusive of GST) · EMI from ₹4,000/month).
To find what an aggregator order actually leaves a cloud kitchen, rebuild each order's money flow line by line: menu value, minus the discount you fund, minus commission calculated on the base your contract specifies, minus packaging, payment and delivery costs you bear, minus recipe food cost. The result is contribution per order. Compare it by channel and by discount level, not as one monthly margin, because the loss-making orders hide inside averages that look healthy.
One order, walked through
Order 3 in the synthetic data below: a ₹380 menu value, with a ₹150 discount the kitchen agreed to fund in a promotion. The customer's food value is ₹230. On this hypothetical contract, commission is 22% of the undiscounted ₹380, so ₹83.6. Recipe cost at 32% of menu value is ₹121.6, packaging ₹28. The kitchen keeps −₹3.2 on this order. It made the food, packed it, paid for the ingredients and lost money doing it.
Nothing in the aggregator's weekly payout statement shows that. The payout statement shows the total transferred; the loss is only visible once each order is costed.
The contract terms become parameters
Every figure that comes from an agreement is a parameter, not a constant. Before modelling, pull these from your own aggregator contract and payout statements:
| Term | Why it matters | Where it lives in the code |
|---|---|---|
| Commission % | The largest variable cost after food | commission_pct |
| Commission base | Menu price or post-discount price — can change margin on promo orders more than the rate itself | commission_on_discounted_price |
| Who funds the discount | Aggregator-funded discounts do not reduce your revenue; restaurant-funded ones do | restaurant_funded_discount |
| Payment and delivery costs | Usually inside the commission on aggregators, separate on your own site | payment_fee_pct, delivery_cost_per_order |
| Taxes | Tax treatment of food, commission and delivery depends on current law and your registration | Not modelled — see below |
On tax: this model works on amounts before tax. How GST applies to the food, to the aggregator's commission invoice, and whether input credit is available depends on your registration and current rules, so take those treatments from your tax adviser and add them as further parameters. Do not copy a rate from a blog, including this one.
The model, on eight synthetic orders
import pandas as pd
# Synthetic: one week of orders for one cloud-kitchen brand. All amounts in Rs, before tax.
# Contract terms are parameters -- copy them from YOUR aggregator agreement.
terms = pd.DataFrame({
"channel": ["AggA", "AggB", "Own site"],
"commission_pct": [0.22, 0.18, 0.0], # hypothetical rates
"commission_on_discounted_price": [False, True, False],
"payment_fee_pct": [0.0, 0.0, 0.02], # own site pays its gateway directly
"delivery_cost_per_order": [0, 0, 55], # own site pays a delivery partner
})
orders = pd.DataFrame({
"order_id": range(1, 9),
"channel": ["AggA","AggA","AggA","AggB","AggB","AggB","Own site","Own site"],
"menu_value": [450, 600, 380, 500, 720, 400, 520, 460],
"restaurant_funded_discount": [0, 120, 150, 0, 100, 160, 50, 0],
})
FOOD_COST_PCT = 0.32 # recipe cost as a share of menu value
PACKAGING_PER_ORDER = 28
df = orders.merge(terms, on="channel")
df["net_food_value"] = df["menu_value"] - df["restaurant_funded_discount"]
base = df["menu_value"].where(~df["commission_on_discounted_price"], df["net_food_value"])
df["commission"] = base * df["commission_pct"]
df["payment_fee"] = df["net_food_value"] * df["payment_fee_pct"]
df["food_cost"] = df["menu_value"] * FOOD_COST_PCT
df["contribution"] = (df["net_food_value"] - df["commission"] - df["payment_fee"]
- df["delivery_cost_per_order"] - df["food_cost"] - PACKAGING_PER_ORDER)
df["contribution_pct_of_menu"] = 100 * df["contribution"] / df["menu_value"]
print(df[["order_id","channel","menu_value","restaurant_funded_discount","commission","contribution"]]
.round(0).to_string(index=False))
by_channel = df.groupby("channel").agg(orders=("order_id","size"), menu=("menu_value","sum"),
contribution=("contribution","sum"))
by_channel["per_order"] = (by_channel["contribution"] / by_channel["orders"]).round(0)
by_channel["pct_of_menu"] = (100 * by_channel["contribution"] / by_channel["menu"]).round(1)
by_channel["contribution"] = by_channel["contribution"].round(0)
print(by_channel.to_string())
print("Orders with negative contribution:", df.loc[df["contribution"] < 0, "order_id"].tolist()) order_id channel menu_value restaurant_funded_discount commission contribution
1 AggA 450 0 99.0 179.0
2 AggA 600 120 132.0 128.0
3 AggA 380 150 84.0 -3.0
4 AggB 500 0 90.0 222.0
5 AggB 720 100 112.0 250.0
6 AggB 400 160 43.0 41.0
7 Own site 520 50 0.0 211.0
8 Own site 460 0 0.0 221.0
orders menu contribution per_order pct_of_menu
channel
AggA 3 1430 304.0 101.0 21.2
AggB 3 1620 513.0 171.0 31.7
Own site 2 980 432.0 216.0 44.1
Orders with negative contribution: [3]AggA's 22% commission on the undiscounted price leaves 21.2% of menu value as contribution; AggB's lower rate on the discounted price leaves 31.7%. Order 6 on AggB carries a bigger discount than order 3 (₹160 against ₹150), yet stays positive at ₹41, because AggB takes its commission after the discount. The commission base did more damage than the headline rate.
Four ways the margin analysis misleads
Treating the own website as free. The own-site orders show 44.1%, but the model charges them only a gateway fee and a delivery partner. It carries none of the marketing spend that brought those customers, and two orders is not a sample. Put customer acquisition cost for the own channel into the comparison before arguing to move volume there.
Averaging across discount tiers. A weekly margin of 21% on AggA looks survivable. Split orders by discount band (none, up to 20%, above 20% of menu value) and the loss sits entirely in the deepest band. That is the band to renegotiate or stop funding.
Using the payout file as revenue. Payout statements net off commission, ad spend deducted by the platform, cancellations charged to the kitchen, and sometimes penalties for late preparation. Reconcile each deduction category separately; ad spend deducted at source is a marketing cost, not a commission.
Food cost from a flat percentage. The 32% here is a placeholder. Real recipe costs differ widely by dish — a biryani and a cold coffee do not share a food-cost ratio — so join a recipe cost table by item rather than applying one percentage to the whole menu.
Taking it to the kitchen owner
Owners of small cloud-kitchen brands think in rupees per order and in "which promotions should I stop". Give them exactly that: contribution per order by channel, a list of orders with negative contribution grouped by the promotion that caused them, and the break-even discount for each channel — the largest restaurant-funded discount at which an average order still contributes zero. On this data, the channel table and the single negative order already make the case for renegotiating AggA's commission base before the next promotion calendar is agreed.
Order-level costing with merges, conditional columns and group summaries is what learners build in the pandas weeks of the Data Analyst programme. If you are coming from delivery or dark-store operations, quick-commerce ops to data analyst explains how to turn store-floor experience into interview evidence.
Frequently asked questions
How do I calculate contribution per order for a cloud kitchen?
Start from the menu value, subtract any discount the restaurant funds, the aggregator commission on whatever base your contract specifies, payment and delivery costs you pay yourself, packaging and recipe food cost. What remains is the order's contribution towards rent, salaries and marketing.
Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.
This article is part of our Data Analytics with Generative AI programme — 3–4 months. The full analyst stack — Excel, SQL, Power BI and Python pipelines — then a generative-AI layer you can prove is right.
Explore Data Analytics with Generative AI