1
KPIs for e-commerce startups
Dr. Martin Loetzsch, Project A

@martin_loetzsch
2
http://commerce-reporting.com
3
KPIs?
4
KPIs = Aggregations over individual entities
5
▪
▪
▪ All time orders?
▪ Revenue October 1st?
▪ Sales by product?
▪ Allowed query operations
▪ Aggregations (count, distinct-count, sum, avg)
▪ Filtering
▪ Grouping
item
id
order
id
has
voucher
price day product
1 1 20 09-30 Cat
2 1 10 09-30 Dog
3 2 2 20 09-30 Cat
4 3 30 09-30 Cow
5 4 4 10 10-01 Dog
6 4 4 30 10-01 Cow
# Sold items: count(item_id)
# Orders: distinct-count(order_id)
# Orders with vouchers: distinct-count(has_voucher)
Revenue: sum(price)
Avg basket value [Revenue] / [# Orders]
SELECT count(distinct order_id) FROM order_item;
SELECT sum(price) FROM order_item WHERE day = ’10-01';
SELECT count(item_id) FROM order_item GROUP BY product;
Dimensional modelling
6
▪ Move redundant categorial data to “dimension” tables
order_item
item_id
order_id
has_voucher
price
day_fk
product_fk
day
day_id
day_name
month_id
month_name
product
product_id
product_name
item
id
order
id
has
voucher
price
day produc
t
1 1 20 930 1
2 1 10 930 2
3 2 2 20 930 1
4 3 30 930 3
5 4 4 10 1001 2
6 4 4 30 1001 3
day
id
day
name
month
id
month
name
930 09-30 9 Sep
1001 10-01 10 Oct
product
id
product
name
1 Cat
2 Dog
3 Cow
Analysis entities
7
orders
users
products
price 

histories
emails
clicks
…
…
operation

events
Customers
8
Customer metrics & attributes
9
▪ Customer
▪ Name, phone, email, gender, age, location, company
▪ Take data protection seriously!
▪ Categorization
▪ Customer type/ group / segment / cluster (B2B vs B2C,
frequent buyer, etc)
▪ Profession, Industry, Company size
▪ Newsletter subscription status, marketing opt-in
▪ Time
▪ Registration date, First touchpoint date, First order date,
Last order date, Last touchpoint date
▪ Time since last order, time since X, time from X to Y
▪ Purchase behavior
▪ # Orders lifetime, Revenue lifetime
▪ # Orders first 6 months, Revenue first 6 months
▪ # Orders last 6 months, Revenue last 6 months
▪ # Orders predicted, Revenue predicted
▪ Revenue group
▪ First product, Last product, Most bought product, 2nd most
bought product, 3rd most bought product
▪ First category, Last category, Most bought category, 2nd
most bought category, 3rd most bought category
▪ Order frequency & recency
▪ # Paused subscriptions, last NPS
Customer metrics & attributes II
10
▪ Marketing / On-site behavior:
▪ # Visits, Avg. number of page views per visit
▪ First touchpoint channel / partner / campaign etc.
▪ First page, Last page, First product, Last product
▪ First viewed product / category, Last viewed product /
category, Most frequently viewed product / category
▪ # Visits since last order
▪ Sales
▪ Lifecycle status, Funnel status
▪ Sales agent, account manager
▪ # Sales contacts
▪ Time X to Y

▪ Operations
▪ # Cancelled orders, # Returned orders, % Cancelled orders,
etc
▪ # Unpaid orders, Open balance
▪ Fraud status, scores
▪ Average NPS, last complaint
11
12
Orders (order items)
13
Order item metrics & attributes
14
▪ Time
▪ Order date, Payment date, Accounting date, Customer
registration date
▪ Time since X

▪ Who
▪ All customer attributes
▪ Order rank

▪ What
▪ All product attributes
▪ Vouchers
▪ Subscription information
▪ Operations
▪ Payment method & provider
▪ Status (payment & logistics), Cancellation reason, return
reason.
▪ Actual delivery time, promised delivery time,
▪ NPS
▪ Counting
▪ # Orders, # Items, # Unique products
▪ # First orders, # Second orders, # Second or subsequent
orders
▪ # B2B orders
▪ # Items returned, # Orders completely returned, # Orders
only partially returned
15
16
17
Margin structure a.k.a. Margensalat
18
Margin structure a.k.a. Margensalat
18
net shipping 

revenue
net product 

revenue
net

revenue
++
Margin structure a.k.a. Margensalat
18
net shipping 

revenue
net product 

revenue
net

revenue
++
VAT
gross
revenue
+
+
Margin structure a.k.a. Margensalat
18
net shipping 

revenue
net product 

revenue
net

revenue
++
VAT
gross
revenue
+
+
net discount 

amount
net revenue
after discounts
+
-
Margin structure a.k.a. Margensalat
18
net shipping 

revenue
net product 

revenue
net

revenue
++
VAT
gross
revenue
+
+
net discount 

amount
net revenue
after discounts
+
-
CM 1
+
purchase

cost
..
product
cost
+ +
-
Margin structure a.k.a. Margensalat
18
net shipping 

revenue
net product 

revenue
net

revenue
++
VAT
gross
revenue
+
+
net discount 

amount
net revenue
after discounts
+
-
CM 1
+
purchase

cost
..
product
cost
+ +
-
payment

cost
fulfillment

cost
cost
of sales
CM 2a
+
+ +
-
Margin structure a.k.a. Margensalat
18
net shipping 

revenue
net product 

revenue
net

revenue
++
VAT
gross
revenue
+
+
net discount 

amount
net revenue
after discounts
+
-
CM 1
+
purchase

cost
..
product
cost
+ +
-
payment

cost
fulfillment

cost
cost
of sales
CM 2a
+
+ +
-
return
cost
CM 2b+ -
Margin structure a.k.a. Margensalat
18
net shipping 

revenue
net product 

revenue
net

revenue
++
VAT
gross
revenue
+
+
net discount 

amount
net revenue
after discounts
+
-
CM 1
+
purchase

cost
..
product
cost
+ +
-
payment

cost
fulfillment

cost
cost
of sales
CM 2a
+
+ +
-
return
cost
CM 2b+ -
marketing
cost
CM3+ -
Touchpoints, Operations, Products, Sales
19
Consistency
20
Consistency: Conceptual challenge
21
▪ Similar sounding things in different domains
▪ If they are the same, call them the same
▪ If a non-trivial transformation happened between
cubes, then clearly mark it
▪ Find the right domain for a concept and then transfer
orders
users
clicks
# orders
# orders
# orders
Consistency: Technical challenge
22
▪ Single source of truth ▪ Avoid duplicating business logic
application
databases
json files
csv files
apis
reporting
crm
marketing
…
search
pricing
DWH
SELECT sum(total_price) AS revenue
FROM os_data.order
WHERE status IN ('pending', 'accepted', 'completed',
'proposal_for_change');
SELECT CASE WHEN (o.status <> 'started'
AND o.payment_status = 'authorised'
AND o.order_type <> 'backend')
THEN o.order_id END AS processed_order_fk
FROM os_data.order;
SELECT (last_status = 'pending') :: INTEGER
AS is_unprocessed
FROM os_data.order;
Consistent dashboards
23
▪ Contribution margin 3a ▪ Use schemas between reporting and database
▪ Mondrian
▪ LookerML
▪ your own
▪ Or: Pre-compute metrics in database
SELECT
order_item_id,
((((((COALESCE(item_net_price, 0) :: REAL
+ COALESCE(net_shipping_revenue, 0) :: REAL)
- ((COALESCE(item_net_purchase_price, 0) :: REAL
+ COALESCE(alcohol_tax, 0) :: REAL)
+ COALESCE(import_tax, 0) :: REAL))
- (COALESCE(net_fulfillment_costs, 0) :: REAL
+ COALESCE(net_payment_costs, 0) :: REAL))
- COALESCE(net_return_costs, 0) :: REAL)
- ((COALESCE(item_net_price, 0) :: REAL
+ COALESCE(net_shipping_revenue, 0) :: REAL)
- ((((COALESCE(item_net_price, 0) :: REAL
+ COALESCE(item_tax_amount, 0) :: REAL)
+ COALESCE(gross_shipping_revenue, 0) :: REAL)
- COALESCE(voucher_gross_amount, 0) :: REAL)
* (1 - ((COALESCE(item_tax_amount, 0) :: REAL
+ (COALESCE(gross_shipping_revenue, 0) :: REAL
- COALESCE(net_shipping_revenue, 0) :: REAL))
/ NULLIF(((COALESCE(item_net_price, 0) :: REAL
+ COALESCE(item_tax_amount, 0) :: REAL)
+ COALESCE(gross_shipping_revenue, 0) :: REAL), 0))))))
- COALESCE(goodie_cost_per_item, 0) :: REAL) :: DOUBLE PRECISION
AS "Contribution margin 3a"
FROM dim.sales_fact;
Correctness
24
▪ Do tests
SELECT util.assert_equal(
'The number of orders in the data table should equal the number of orders in the dim table',
'SELECT COUNT(DISTINCT order_id)
FROM os_data.sold_item
WHERE order_item_price > 0;',
'SELECT COUNT(order_id)
FROM os_dim.order;');
SELECT util.assert_almost_equal(
'The sum of prices in the dim table should equal the sum of prices in the data table',
'SELECT sum(order_item_price * order_item_quantity)
FROM os_data.sold_item
WHERE order_item_price > 0
AND coalesce( spryker_order_id, '''') NOT LIKE ''CH%'';',
'SELECT sum(net_product_value + tax_amount_product)
FROM os_dim.order
JOIN os_dim.sales_country ON sales_country_id = sales_country_fk
WHERE sales_country_name <> ''CH'';', 1);
SELECT util.assert_equal(
'The number of orders in the data table should equal the number of orders in the dim table',
http://commerce-reporting.com
25
Work with us
26
▪ https://www.project-a.com/en/career/jobs
▪ Director of Business Intelligence
▪ Produkt Manager Business Intelligence
▪ Data Engineer / Data Scientist
27
Thank you
@martin_loetzsch

KPIs for e-commerce startups

  • 1.
    1 KPIs for e-commercestartups Dr. Martin Loetzsch, Project A
 @martin_loetzsch
  • 2.
  • 3.
  • 4.
  • 5.
    KPIs = Aggregationsover individual entities 5 ▪ ▪ ▪ All time orders? ▪ Revenue October 1st? ▪ Sales by product? ▪ Allowed query operations ▪ Aggregations (count, distinct-count, sum, avg) ▪ Filtering ▪ Grouping item id order id has voucher price day product 1 1 20 09-30 Cat 2 1 10 09-30 Dog 3 2 2 20 09-30 Cat 4 3 30 09-30 Cow 5 4 4 10 10-01 Dog 6 4 4 30 10-01 Cow # Sold items: count(item_id) # Orders: distinct-count(order_id) # Orders with vouchers: distinct-count(has_voucher) Revenue: sum(price) Avg basket value [Revenue] / [# Orders] SELECT count(distinct order_id) FROM order_item; SELECT sum(price) FROM order_item WHERE day = ’10-01'; SELECT count(item_id) FROM order_item GROUP BY product;
  • 6.
    Dimensional modelling 6 ▪ Moveredundant categorial data to “dimension” tables order_item item_id order_id has_voucher price day_fk product_fk day day_id day_name month_id month_name product product_id product_name item id order id has voucher price day produc t 1 1 20 930 1 2 1 10 930 2 3 2 2 20 930 1 4 3 30 930 3 5 4 4 10 1001 2 6 4 4 30 1001 3 day id day name month id month name 930 09-30 9 Sep 1001 10-01 10 Oct product id product name 1 Cat 2 Dog 3 Cow
  • 7.
  • 8.
  • 9.
    Customer metrics &attributes 9 ▪ Customer ▪ Name, phone, email, gender, age, location, company ▪ Take data protection seriously! ▪ Categorization ▪ Customer type/ group / segment / cluster (B2B vs B2C, frequent buyer, etc) ▪ Profession, Industry, Company size ▪ Newsletter subscription status, marketing opt-in ▪ Time ▪ Registration date, First touchpoint date, First order date, Last order date, Last touchpoint date ▪ Time since last order, time since X, time from X to Y ▪ Purchase behavior ▪ # Orders lifetime, Revenue lifetime ▪ # Orders first 6 months, Revenue first 6 months ▪ # Orders last 6 months, Revenue last 6 months ▪ # Orders predicted, Revenue predicted ▪ Revenue group ▪ First product, Last product, Most bought product, 2nd most bought product, 3rd most bought product ▪ First category, Last category, Most bought category, 2nd most bought category, 3rd most bought category ▪ Order frequency & recency ▪ # Paused subscriptions, last NPS
  • 10.
    Customer metrics &attributes II 10 ▪ Marketing / On-site behavior: ▪ # Visits, Avg. number of page views per visit ▪ First touchpoint channel / partner / campaign etc. ▪ First page, Last page, First product, Last product ▪ First viewed product / category, Last viewed product / category, Most frequently viewed product / category ▪ # Visits since last order ▪ Sales ▪ Lifecycle status, Funnel status ▪ Sales agent, account manager ▪ # Sales contacts ▪ Time X to Y
 ▪ Operations ▪ # Cancelled orders, # Returned orders, % Cancelled orders, etc ▪ # Unpaid orders, Open balance ▪ Fraud status, scores ▪ Average NPS, last complaint
  • 11.
  • 12.
  • 13.
  • 14.
    Order item metrics& attributes 14 ▪ Time ▪ Order date, Payment date, Accounting date, Customer registration date ▪ Time since X
 ▪ Who ▪ All customer attributes ▪ Order rank
 ▪ What ▪ All product attributes ▪ Vouchers ▪ Subscription information ▪ Operations ▪ Payment method & provider ▪ Status (payment & logistics), Cancellation reason, return reason. ▪ Actual delivery time, promised delivery time, ▪ NPS ▪ Counting ▪ # Orders, # Items, # Unique products ▪ # First orders, # Second orders, # Second or subsequent orders ▪ # B2B orders ▪ # Items returned, # Orders completely returned, # Orders only partially returned
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
    Margin structure a.k.a.Margensalat 18 net shipping 
 revenue net product 
 revenue net
 revenue ++
  • 20.
    Margin structure a.k.a.Margensalat 18 net shipping 
 revenue net product 
 revenue net
 revenue ++ VAT gross revenue + +
  • 21.
    Margin structure a.k.a.Margensalat 18 net shipping 
 revenue net product 
 revenue net
 revenue ++ VAT gross revenue + + net discount 
 amount net revenue after discounts + -
  • 22.
    Margin structure a.k.a.Margensalat 18 net shipping 
 revenue net product 
 revenue net
 revenue ++ VAT gross revenue + + net discount 
 amount net revenue after discounts + - CM 1 + purchase
 cost .. product cost + + -
  • 23.
    Margin structure a.k.a.Margensalat 18 net shipping 
 revenue net product 
 revenue net
 revenue ++ VAT gross revenue + + net discount 
 amount net revenue after discounts + - CM 1 + purchase
 cost .. product cost + + - payment
 cost fulfillment
 cost cost of sales CM 2a + + + -
  • 24.
    Margin structure a.k.a.Margensalat 18 net shipping 
 revenue net product 
 revenue net
 revenue ++ VAT gross revenue + + net discount 
 amount net revenue after discounts + - CM 1 + purchase
 cost .. product cost + + - payment
 cost fulfillment
 cost cost of sales CM 2a + + + - return cost CM 2b+ -
  • 25.
    Margin structure a.k.a.Margensalat 18 net shipping 
 revenue net product 
 revenue net
 revenue ++ VAT gross revenue + + net discount 
 amount net revenue after discounts + - CM 1 + purchase
 cost .. product cost + + - payment
 cost fulfillment
 cost cost of sales CM 2a + + + - return cost CM 2b+ - marketing cost CM3+ -
  • 26.
  • 27.
  • 28.
    Consistency: Conceptual challenge 21 ▪Similar sounding things in different domains ▪ If they are the same, call them the same ▪ If a non-trivial transformation happened between cubes, then clearly mark it ▪ Find the right domain for a concept and then transfer orders users clicks # orders # orders # orders
  • 29.
    Consistency: Technical challenge 22 ▪Single source of truth ▪ Avoid duplicating business logic application databases json files csv files apis reporting crm marketing … search pricing DWH SELECT sum(total_price) AS revenue FROM os_data.order WHERE status IN ('pending', 'accepted', 'completed', 'proposal_for_change'); SELECT CASE WHEN (o.status <> 'started' AND o.payment_status = 'authorised' AND o.order_type <> 'backend') THEN o.order_id END AS processed_order_fk FROM os_data.order; SELECT (last_status = 'pending') :: INTEGER AS is_unprocessed FROM os_data.order;
  • 30.
    Consistent dashboards 23 ▪ Contributionmargin 3a ▪ Use schemas between reporting and database ▪ Mondrian ▪ LookerML ▪ your own ▪ Or: Pre-compute metrics in database SELECT order_item_id, ((((((COALESCE(item_net_price, 0) :: REAL + COALESCE(net_shipping_revenue, 0) :: REAL) - ((COALESCE(item_net_purchase_price, 0) :: REAL + COALESCE(alcohol_tax, 0) :: REAL) + COALESCE(import_tax, 0) :: REAL)) - (COALESCE(net_fulfillment_costs, 0) :: REAL + COALESCE(net_payment_costs, 0) :: REAL)) - COALESCE(net_return_costs, 0) :: REAL) - ((COALESCE(item_net_price, 0) :: REAL + COALESCE(net_shipping_revenue, 0) :: REAL) - ((((COALESCE(item_net_price, 0) :: REAL + COALESCE(item_tax_amount, 0) :: REAL) + COALESCE(gross_shipping_revenue, 0) :: REAL) - COALESCE(voucher_gross_amount, 0) :: REAL) * (1 - ((COALESCE(item_tax_amount, 0) :: REAL + (COALESCE(gross_shipping_revenue, 0) :: REAL - COALESCE(net_shipping_revenue, 0) :: REAL)) / NULLIF(((COALESCE(item_net_price, 0) :: REAL + COALESCE(item_tax_amount, 0) :: REAL) + COALESCE(gross_shipping_revenue, 0) :: REAL), 0)))))) - COALESCE(goodie_cost_per_item, 0) :: REAL) :: DOUBLE PRECISION AS "Contribution margin 3a" FROM dim.sales_fact;
  • 31.
    Correctness 24 ▪ Do tests SELECTutil.assert_equal( 'The number of orders in the data table should equal the number of orders in the dim table', 'SELECT COUNT(DISTINCT order_id) FROM os_data.sold_item WHERE order_item_price > 0;', 'SELECT COUNT(order_id) FROM os_dim.order;'); SELECT util.assert_almost_equal( 'The sum of prices in the dim table should equal the sum of prices in the data table', 'SELECT sum(order_item_price * order_item_quantity) FROM os_data.sold_item WHERE order_item_price > 0 AND coalesce( spryker_order_id, '''') NOT LIKE ''CH%'';', 'SELECT sum(net_product_value + tax_amount_product) FROM os_dim.order JOIN os_dim.sales_country ON sales_country_id = sales_country_fk WHERE sales_country_name <> ''CH'';', 1); SELECT util.assert_equal( 'The number of orders in the data table should equal the number of orders in the dim table',
  • 32.
  • 33.
    Work with us 26 ▪https://www.project-a.com/en/career/jobs ▪ Director of Business Intelligence ▪ Produkt Manager Business Intelligence ▪ Data Engineer / Data Scientist
  • 34.