Data AnalyticsIndustry analytics playbooks

Cloud kitchen margins: what an aggregator order really leaves you

PK
Pankit Kumar
Sr. Data Scientist at Parexel (a Goldman Sachs–backed company) · 27 September 2026 · 5 min read
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).

The short answer

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:

TermWhy it mattersWhere it lives in the code
Commission %The largest variable cost after foodcommission_pct
Commission baseMenu price or post-discount price — can change margin on promo orders more than the rate itselfcommission_on_discounted_price
Who funds the discountAggregator-funded discounts do not reduce your revenue; restaurant-funded ones dorestaurant_funded_discount
Payment and delivery costsUsually inside the commission on aggregators, separate on your own sitepayment_fee_pct, delivery_cost_per_order
TaxesTax treatment of food, commission and delivery depends on current law and your registrationNot 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

python
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())
text
 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.

PK
Pankit Kumar
Lead Instructor, NeuraPath Academy

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
Counselling is free · no obligation

Not sure which programme fits?

Tell us your background and we will map it to the right entry point — including saying so when a cheaper programme is the better fit. A counsellor replies within one working day.