Yehor commited on
Commit
dd059ea
·
verified ·
1 Parent(s): 9ed79a3

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +533 -0
app.py ADDED
@@ -0,0 +1,533 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ A collection of functions to calculate key health and valuation metrics
3
+ for a SaaS / micro-SaaS business based on a provided set of formulas.
4
+ """
5
+
6
+ import gradio as gr
7
+
8
+
9
+ def calculate_arr(mrr: float) -> float:
10
+ """
11
+ Calculates Annual Recurring Revenue (ARR) from Monthly Recurring Revenue (MRR).
12
+ (Formula 1)
13
+
14
+ Args:
15
+ mrr: Monthly Recurring Revenue.
16
+
17
+ Returns:
18
+ The calculated Annual Recurring Revenue.
19
+ """
20
+ return mrr * 12
21
+
22
+
23
+ def calculate_valuation_revenue(arr: float, multiple: float) -> float:
24
+ """
25
+ Calculates a simple revenue-based valuation.
26
+ (Formula 2)
27
+
28
+ Args:
29
+ arr: Annual Recurring Revenue.
30
+ multiple: The valuation multiple (e.g., 3.0 for 3x).
31
+
32
+ Returns:
33
+ The calculated revenue-based valuation.
34
+ """
35
+ return arr * multiple
36
+
37
+
38
+ def calculate_sde(
39
+ revenue: float, owner_comp: float, cogs: float, op_ex: float
40
+ ) -> float:
41
+ """
42
+ Calculates Seller's Discretionary Earnings (SDE).
43
+ (Formula 3)
44
+
45
+ Args:
46
+ revenue: Total revenue for the period.
47
+ owner_comp: Owner compensation to be added back.
48
+ cogs: Cost of Goods Sold.
49
+ op_ex: Operating Expenses.
50
+
51
+ Returns:
52
+ The calculated Seller's Discretionary Earnings.
53
+ """
54
+ return (revenue + owner_comp) - (cogs + op_ex)
55
+
56
+
57
+ def calculate_valuation_sde(sde: float, multiple: float) -> float:
58
+ """
59
+ Calculates an SDE-based valuation.
60
+ (Formula 3)
61
+
62
+ Args:
63
+ sde: Seller's Discretionary Earnings.
64
+ multiple: The SDE valuation multiple.
65
+
66
+ Returns:
67
+ The calculated SDE-based valuation.
68
+ """
69
+ return sde * multiple
70
+
71
+
72
+ def calculate_valuation_ebitda(ebitda: float, multiple: float) -> float:
73
+ """
74
+ Calculates an EBITDA-based valuation.
75
+ (Formula 4)
76
+
77
+ Args:
78
+ ebitda: Earnings Before Interest, Taxes, Depreciation, and Amortization.
79
+ multiple: The EBITDA valuation multiple.
80
+
81
+ Returns:
82
+ The calculated EBITDA-based valuation.
83
+ """
84
+ return ebitda * multiple
85
+
86
+
87
+ def calculate_customer_churn_rate(
88
+ churned_customers: int, start_customers: int
89
+ ) -> float:
90
+ """
91
+ Calculates the customer churn rate for a period.
92
+ (Formula 5)
93
+
94
+ Args:
95
+ churned_customers: Number of customers lost during the period.
96
+ start_customers: Number of customers at the start of the period.
97
+
98
+ Returns:
99
+ The customer churn rate as a decimal (e.g., 0.05 for 5%).
100
+ """
101
+ if start_customers == 0:
102
+ return 0.0
103
+ return churned_customers / start_customers
104
+
105
+
106
+ def calculate_gross_revenue_churn_rate(
107
+ churned_revenue: float, mrr_start: float
108
+ ) -> float:
109
+ """
110
+ Calculates the gross revenue churn rate for a period.
111
+ (Formula 6)
112
+
113
+ Args:
114
+ churned_revenue: MRR lost from cancellations and downgrades.
115
+ mrr_start: MRR at the start of the period.
116
+
117
+ Returns:
118
+ The gross revenue churn rate as a decimal.
119
+ """
120
+ if mrr_start == 0:
121
+ return 0.0
122
+ return churned_revenue / mrr_start
123
+
124
+
125
+ def calculate_net_revenue_churn_rate(
126
+ churned_revenue: float, expansion_revenue: float, mrr_start: float
127
+ ) -> float:
128
+ """
129
+ Calculates the net revenue churn rate for a period. Can be negative.
130
+ (Formula 7)
131
+
132
+ Args:
133
+ churned_revenue: MRR lost from cancellations and downgrades.
134
+ expansion_revenue: MRR gained from upgrades and upsells.
135
+ mrr_start: MRR at the start of the period.
136
+
137
+ Returns:
138
+ The net revenue churn rate as a decimal.
139
+ """
140
+ if mrr_start == 0:
141
+ return 0.0
142
+ return (churned_revenue - expansion_revenue) / mrr_start
143
+
144
+
145
+ def calculate_nrr(
146
+ mrr_start: float, expansion_revenue: float, churn_revenue: float
147
+ ) -> float:
148
+ """
149
+ Calculates Net Revenue Retention (NRR).
150
+ Note: 'churn_revenue' here includes all lost revenue (cancellations + contractions).
151
+ (Formula 8)
152
+
153
+ Args:
154
+ mrr_start: MRR at the start of the period.
155
+ expansion_revenue: MRR gained from existing customers.
156
+ churn_revenue: MRR lost from existing customers.
157
+
158
+ Returns:
159
+ The Net Revenue Retention rate as a decimal (e.g., 1.05 for 105%).
160
+ """
161
+ if mrr_start == 0:
162
+ return 0.0
163
+ return (mrr_start + expansion_revenue - churn_revenue) / mrr_start
164
+
165
+
166
+ def calculate_cac(sm_spend: float, new_customers: int) -> float:
167
+ """
168
+ Calculates Customer Acquisition Cost (CAC).
169
+ (Formula 9)
170
+
171
+ Args:
172
+ sm_spend: Total sales and marketing spend for the period.
173
+ new_customers: Number of new customers acquired in the period.
174
+
175
+ Returns:
176
+ The average Customer Acquisition Cost.
177
+ """
178
+ if new_customers == 0:
179
+ # Returning infinity is mathematically correct for 0 new customers
180
+ # with non-zero spend, representing an infinitely high cost.
181
+ return float("inf") if sm_spend > 0 else 0.0
182
+ return sm_spend / new_customers
183
+
184
+
185
+ def calculate_arpa(mrr: float, active_accounts: int) -> float:
186
+ """
187
+ Calculates Average Revenue Per Account (ARPA).
188
+ (Formula 10)
189
+
190
+ Args:
191
+ mrr: Monthly Recurring Revenue.
192
+ active_accounts: Number of active accounts.
193
+
194
+ Returns:
195
+ The Average Revenue Per Account.
196
+ """
197
+ if active_accounts == 0:
198
+ return 0.0
199
+ return mrr / active_accounts
200
+
201
+
202
+ def calculate_customer_lifetime(customer_churn_rate: float) -> float:
203
+ """
204
+ Calculates the approximate customer lifetime in periods.
205
+ (Formula 11)
206
+
207
+ Args:
208
+ customer_churn_rate: The per-period customer churn rate as a decimal.
209
+
210
+ Returns:
211
+ The average customer lifetime in number of periods.
212
+ """
213
+ if customer_churn_rate <= 0:
214
+ # A churn rate of 0 implies infinite lifetime.
215
+ return float("inf")
216
+ return 1 / customer_churn_rate
217
+
218
+
219
+ def calculate_ltv(arpa: float, gross_margin: float, revenue_churn_rate: float) -> float:
220
+ """
221
+ Calculates the margin-adjusted Customer Lifetime Value (LTV).
222
+ (Formula 12)
223
+
224
+ Args:
225
+ arpa: Average Revenue Per Account.
226
+ gross_margin: The gross margin as a decimal (e.g., 0.80 for 80%).
227
+ revenue_churn_rate: The revenue churn rate as a decimal.
228
+
229
+ Returns:
230
+ The margin-adjusted Customer Lifetime Value.
231
+ """
232
+ if revenue_churn_rate <= 0:
233
+ # A churn rate of 0 implies infinite LTV.
234
+ return float("inf")
235
+ return (arpa * gross_margin) / revenue_churn_rate
236
+
237
+
238
+ def calculate_ltv_cac_ratio(ltv: float, cac: float) -> float:
239
+ """
240
+ Calculates the LTV to CAC ratio.
241
+ (Formula 13)
242
+
243
+ Args:
244
+ ltv: Customer Lifetime Value.
245
+ cac: Customer Acquisition Cost.
246
+
247
+ Returns:
248
+ The LTV to CAC ratio.
249
+ """
250
+ if cac == 0:
251
+ # An LTV with zero acquisition cost is technically an infinite return.
252
+ return float("inf") if ltv > 0 else 0.0
253
+ return ltv / cac
254
+
255
+
256
+ def calculate_cac_payback_period(cac: float, arpa: float, gross_margin: float) -> float:
257
+ """
258
+ Calculates the CAC payback period in months (assuming ARPA is monthly).
259
+ (Formula 14)
260
+
261
+ Args:
262
+ cac: Customer Acquisition Cost.
263
+ arpa: Average Revenue Per Account (monthly).
264
+ gross_margin: Gross margin as a decimal.
265
+
266
+ Returns:
267
+ The number of months to pay back CAC.
268
+ """
269
+ monthly_gross_profit_per_customer = arpa * gross_margin
270
+ if monthly_gross_profit_per_customer <= 0:
271
+ # If there's no gross profit, CAC is never paid back.
272
+ return float("inf")
273
+ return cac / monthly_gross_profit_per_customer
274
+
275
+
276
+ def calculate_gross_margin(revenue: float, cogs: float) -> float:
277
+ """
278
+ Calculates the gross margin.
279
+ (Formula 15)
280
+
281
+ Args:
282
+ revenue: Total revenue.
283
+ cogs: Cost of Goods Sold.
284
+
285
+ Returns:
286
+ The gross margin as a decimal.
287
+ """
288
+ if revenue == 0:
289
+ return 0.0
290
+ return (revenue - cogs) / revenue
291
+
292
+
293
+ def calculate_yoy_growth(arr_current: float, arr_prior: float) -> float:
294
+ """
295
+ Calculates Year-over-Year (YoY) growth rate.
296
+ (Formula 16)
297
+
298
+ Args:
299
+ arr_current: ARR for the current year.
300
+ arr_prior: ARR for the prior year.
301
+
302
+ Returns:
303
+ The YoY growth rate as a decimal.
304
+ """
305
+ if arr_prior == 0:
306
+ return float("inf") if arr_current > 0 else 0.0
307
+ return (arr_current - arr_prior) / arr_prior
308
+
309
+
310
+ def calculate_rule_of_40(growth_rate: float, ebitda_margin: float) -> float:
311
+ """
312
+ Calculates the Rule of 40 score.
313
+ (Formula 17)
314
+
315
+ Args:
316
+ growth_rate: The growth rate as a percentage (e.g., 30 for 30%).
317
+ ebitda_margin: The EBITDA margin as a percentage (e.g., 15 for 15%).
318
+
319
+ Returns:
320
+ The Rule of 40 score.
321
+ """
322
+ return growth_rate + ebitda_margin
323
+
324
+
325
+ with gr.Blocks(title="SaaS Metrics Calculator") as demo:
326
+ gr.Markdown("# SaaS Metrics Calculator")
327
+ gr.Markdown(
328
+ "Calculate various key health and valuation metrics for your SaaS business. Each metric is in its own section below."
329
+ )
330
+
331
+ with gr.Accordion("Annual Recurring Revenue (ARR)", open=False):
332
+ mrr_input = gr.Number(label="Monthly Recurring Revenue (MRR)")
333
+ arr_output = gr.Number(label="Annual Recurring Revenue (ARR)")
334
+ btn = gr.Button("Calculate")
335
+ btn.click(calculate_arr, inputs=[mrr_input], outputs=[arr_output])
336
+
337
+ with gr.Accordion("Revenue-Based Valuation", open=False):
338
+ arr_input = gr.Number(label="Annual Recurring Revenue (ARR)")
339
+ multiple_input = gr.Number(label="Valuation Multiple (e.g., 3.0 for 3x)")
340
+ valuation_output = gr.Number(label="Revenue-Based Valuation")
341
+ btn = gr.Button("Calculate")
342
+ btn.click(
343
+ calculate_valuation_revenue,
344
+ inputs=[arr_input, multiple_input],
345
+ outputs=[valuation_output],
346
+ )
347
+
348
+ with gr.Accordion("Seller's Discretionary Earnings (SDE)", open=False):
349
+ revenue_input = gr.Number(label="Total Revenue")
350
+ owner_comp_input = gr.Number(label="Owner Compensation")
351
+ cogs_input = gr.Number(label="Cost of Goods Sold (COGS)")
352
+ op_ex_input = gr.Number(label="Operating Expenses")
353
+ sde_output = gr.Number(label="Seller's Discretionary Earnings (SDE)")
354
+ btn = gr.Button("Calculate")
355
+ btn.click(
356
+ calculate_sde,
357
+ inputs=[revenue_input, owner_comp_input, cogs_input, op_ex_input],
358
+ outputs=[sde_output],
359
+ )
360
+
361
+ with gr.Accordion("SDE-Based Valuation", open=False):
362
+ sde_input = gr.Number(label="Seller's Discretionary Earnings (SDE)")
363
+ multiple_input = gr.Number(label="SDE Valuation Multiple")
364
+ valuation_output = gr.Number(label="SDE-Based Valuation")
365
+ btn = gr.Button("Calculate")
366
+ btn.click(
367
+ calculate_valuation_sde,
368
+ inputs=[sde_input, multiple_input],
369
+ outputs=[valuation_output],
370
+ )
371
+
372
+ with gr.Accordion("EBITDA-Based Valuation", open=False):
373
+ ebitda_input = gr.Number(label="EBITDA")
374
+ multiple_input = gr.Number(label="EBITDA Valuation Multiple")
375
+ valuation_output = gr.Number(label="EBITDA-Based Valuation")
376
+ btn = gr.Button("Calculate")
377
+ btn.click(
378
+ calculate_valuation_ebitda,
379
+ inputs=[ebitda_input, multiple_input],
380
+ outputs=[valuation_output],
381
+ )
382
+
383
+ with gr.Accordion("Customer Churn Rate", open=False):
384
+ churned_customers_input = gr.Number(label="Churned Customers")
385
+ start_customers_input = gr.Number(label="Starting Customers")
386
+ churn_rate_output = gr.Number(label="Customer Churn Rate (decimal)")
387
+ btn = gr.Button("Calculate")
388
+ btn.click(
389
+ calculate_customer_churn_rate,
390
+ inputs=[churned_customers_input, start_customers_input],
391
+ outputs=[churn_rate_output],
392
+ )
393
+
394
+ with gr.Accordion("Gross Revenue Churn Rate", open=False):
395
+ churned_revenue_input = gr.Number(label="Churned Revenue (MRR lost)")
396
+ mrr_start_input = gr.Number(label="Starting MRR")
397
+ churn_rate_output = gr.Number(label="Gross Revenue Churn Rate (decimal)")
398
+ btn = gr.Button("Calculate")
399
+ btn.click(
400
+ calculate_gross_revenue_churn_rate,
401
+ inputs=[churned_revenue_input, mrr_start_input],
402
+ outputs=[churn_rate_output],
403
+ )
404
+
405
+ with gr.Accordion("Net Revenue Churn Rate", open=False):
406
+ churned_revenue_input = gr.Number(label="Churned Revenue (MRR lost)")
407
+ expansion_revenue_input = gr.Number(label="Expansion Revenue (MRR gained)")
408
+ mrr_start_input = gr.Number(label="Starting MRR")
409
+ churn_rate_output = gr.Number(label="Net Revenue Churn Rate (decimal)")
410
+ btn = gr.Button("Calculate")
411
+ btn.click(
412
+ calculate_net_revenue_churn_rate,
413
+ inputs=[churned_revenue_input, expansion_revenue_input, mrr_start_input],
414
+ outputs=[churn_rate_output],
415
+ )
416
+
417
+ with gr.Accordion("Net Revenue Retention (NRR)", open=False):
418
+ mrr_start_input = gr.Number(label="Starting MRR")
419
+ expansion_revenue_input = gr.Number(label="Expansion Revenue")
420
+ churn_revenue_input = gr.Number(label="Churn Revenue (MRR lost)")
421
+ nrr_output = gr.Number(label="Net Revenue Retention (decimal)")
422
+ btn = gr.Button("Calculate")
423
+ btn.click(
424
+ calculate_nrr,
425
+ inputs=[mrr_start_input, expansion_revenue_input, churn_revenue_input],
426
+ outputs=[nrr_output],
427
+ )
428
+
429
+ with gr.Accordion("Customer Acquisition Cost (CAC)", open=False):
430
+ sm_spend_input = gr.Number(label="Sales & Marketing Spend")
431
+ new_customers_input = gr.Number(label="New Customers")
432
+ cac_output = gr.Number(label="Customer Acquisition Cost (CAC)")
433
+ btn = gr.Button("Calculate")
434
+ btn.click(
435
+ calculate_cac,
436
+ inputs=[sm_spend_input, new_customers_input],
437
+ outputs=[cac_output],
438
+ )
439
+
440
+ with gr.Accordion("Average Revenue Per Account (ARPA)", open=False):
441
+ mrr_input = gr.Number(label="Monthly Recurring Revenue (MRR)")
442
+ active_accounts_input = gr.Number(label="Active Accounts")
443
+ arpa_output = gr.Number(label="Average Revenue Per Account (ARPA)")
444
+ btn = gr.Button("Calculate")
445
+ btn.click(
446
+ calculate_arpa,
447
+ inputs=[mrr_input, active_accounts_input],
448
+ outputs=[arpa_output],
449
+ )
450
+
451
+ with gr.Accordion("Customer Lifetime", open=False):
452
+ customer_churn_rate_input = gr.Number(label="Customer Churn Rate (decimal)")
453
+ lifetime_output = gr.Number(label="Customer Lifetime (periods)")
454
+ btn = gr.Button("Calculate")
455
+ btn.click(
456
+ calculate_customer_lifetime,
457
+ inputs=[customer_churn_rate_input],
458
+ outputs=[lifetime_output],
459
+ )
460
+
461
+ with gr.Accordion("Customer Lifetime Value (LTV)", open=False):
462
+ arpa_input = gr.Number(label="ARPA")
463
+ gross_margin_input = gr.Number(label="Gross Margin (decimal)")
464
+ revenue_churn_rate_input = gr.Number(label="Revenue Churn Rate (decimal)")
465
+ ltv_output = gr.Number(label="Customer Lifetime Value (LTV)")
466
+ btn = gr.Button("Calculate")
467
+ btn.click(
468
+ calculate_ltv,
469
+ inputs=[arpa_input, gross_margin_input, revenue_churn_rate_input],
470
+ outputs=[ltv_output],
471
+ )
472
+
473
+ with gr.Accordion("LTV to CAC Ratio", open=False):
474
+ ltv_input = gr.Number(label="LTV")
475
+ cac_input = gr.Number(label="CAC")
476
+ ratio_output = gr.Number(label="LTV to CAC Ratio")
477
+ btn = gr.Button("Calculate")
478
+ btn.click(
479
+ calculate_ltv_cac_ratio,
480
+ inputs=[ltv_input, cac_input],
481
+ outputs=[ratio_output],
482
+ )
483
+
484
+ with gr.Accordion("CAC Payback Period", open=False):
485
+ cac_input = gr.Number(label="CAC")
486
+ arpa_input = gr.Number(label="ARPA (monthly)")
487
+ gross_margin_input = gr.Number(label="Gross Margin (decimal)")
488
+ payback_output = gr.Number(label="CAC Payback Period (months)")
489
+ btn = gr.Button("Calculate")
490
+ btn.click(
491
+ calculate_cac_payback_period,
492
+ inputs=[cac_input, arpa_input, gross_margin_input],
493
+ outputs=[payback_output],
494
+ )
495
+
496
+ with gr.Accordion("Gross Margin", open=False):
497
+ revenue_input = gr.Number(label="Total Revenue")
498
+ cogs_input = gr.Number(label="Cost of Goods Sold (COGS)")
499
+ margin_output = gr.Number(label="Gross Margin (decimal)")
500
+ btn = gr.Button("Calculate")
501
+ btn.click(
502
+ calculate_gross_margin,
503
+ inputs=[revenue_input, cogs_input],
504
+ outputs=[margin_output],
505
+ )
506
+
507
+ with gr.Accordion("Year-over-Year (YoY) Growth", open=False):
508
+ arr_current_input = gr.Number(label="Current ARR")
509
+ arr_prior_input = gr.Number(label="Prior ARR")
510
+ growth_output = gr.Number(label="YoY Growth Rate (decimal)")
511
+ btn = gr.Button("Calculate")
512
+ btn.click(
513
+ calculate_yoy_growth,
514
+ inputs=[arr_current_input, arr_prior_input],
515
+ outputs=[growth_output],
516
+ )
517
+
518
+ with gr.Accordion("Rule of 40 Score", open=False):
519
+ growth_rate_input = gr.Number(
520
+ label="Growth Rate (percentage, e.g., 30 for 30%)"
521
+ )
522
+ ebitda_margin_input = gr.Number(
523
+ label="EBITDA Margin (percentage, e.g., 15 for 15%)"
524
+ )
525
+ rule_output = gr.Number(label="Rule of 40 Score")
526
+ btn = gr.Button("Calculate")
527
+ btn.click(
528
+ calculate_rule_of_40,
529
+ inputs=[growth_rate_input, ebitda_margin_input],
530
+ outputs=[rule_output],
531
+ )
532
+
533
+ demo.launch()