Spaces:
Sleeping
Sleeping
| """ | |
| A collection of functions to calculate key health and valuation metrics | |
| for a SaaS / micro-SaaS business based on a provided set of formulas. | |
| """ | |
| import gradio as gr | |
| def calculate_arr(mrr: float) -> float: | |
| """ | |
| Calculates Annual Recurring Revenue (ARR) from Monthly Recurring Revenue (MRR). | |
| (Formula 1) | |
| Args: | |
| mrr: Monthly Recurring Revenue. | |
| Returns: | |
| The calculated Annual Recurring Revenue. | |
| """ | |
| return mrr * 12 | |
| def calculate_valuation_revenue(arr: float, multiple: float) -> float: | |
| """ | |
| Calculates a simple revenue-based valuation. | |
| (Formula 2) | |
| Args: | |
| arr: Annual Recurring Revenue. | |
| multiple: The valuation multiple (e.g., 3.0 for 3x). | |
| Returns: | |
| The calculated revenue-based valuation. | |
| """ | |
| return arr * multiple | |
| def calculate_sde( | |
| revenue: float, owner_comp: float, cogs: float, op_ex: float | |
| ) -> float: | |
| """ | |
| Calculates Seller's Discretionary Earnings (SDE). | |
| (Formula 3) | |
| Args: | |
| revenue: Total revenue for the period. | |
| owner_comp: Owner compensation to be added back. | |
| cogs: Cost of Goods Sold. | |
| op_ex: Operating Expenses. | |
| Returns: | |
| The calculated Seller's Discretionary Earnings. | |
| """ | |
| return (revenue + owner_comp) - (cogs + op_ex) | |
| def calculate_valuation_sde(sde: float, multiple: float) -> float: | |
| """ | |
| Calculates an SDE-based valuation. | |
| (Formula 3) | |
| Args: | |
| sde: Seller's Discretionary Earnings. | |
| multiple: The SDE valuation multiple. | |
| Returns: | |
| The calculated SDE-based valuation. | |
| """ | |
| return sde * multiple | |
| def calculate_valuation_ebitda(ebitda: float, multiple: float) -> float: | |
| """ | |
| Calculates an EBITDA-based valuation. | |
| (Formula 4) | |
| Args: | |
| ebitda: Earnings Before Interest, Taxes, Depreciation, and Amortization. | |
| multiple: The EBITDA valuation multiple. | |
| Returns: | |
| The calculated EBITDA-based valuation. | |
| """ | |
| return ebitda * multiple | |
| def calculate_customer_churn_rate( | |
| churned_customers: int, start_customers: int | |
| ) -> float: | |
| """ | |
| Calculates the customer churn rate for a period. | |
| (Formula 5) | |
| Args: | |
| churned_customers: Number of customers lost during the period. | |
| start_customers: Number of customers at the start of the period. | |
| Returns: | |
| The customer churn rate as a decimal (e.g., 0.05 for 5%). | |
| """ | |
| if start_customers == 0: | |
| return 0.0 | |
| return churned_customers / start_customers | |
| def calculate_gross_revenue_churn_rate( | |
| churned_revenue: float, mrr_start: float | |
| ) -> float: | |
| """ | |
| Calculates the gross revenue churn rate for a period. | |
| (Formula 6) | |
| Args: | |
| churned_revenue: MRR lost from cancellations and downgrades. | |
| mrr_start: MRR at the start of the period. | |
| Returns: | |
| The gross revenue churn rate as a decimal. | |
| """ | |
| if mrr_start == 0: | |
| return 0.0 | |
| return churned_revenue / mrr_start | |
| def calculate_net_revenue_churn_rate( | |
| churned_revenue: float, expansion_revenue: float, mrr_start: float | |
| ) -> float: | |
| """ | |
| Calculates the net revenue churn rate for a period. Can be negative. | |
| (Formula 7) | |
| Args: | |
| churned_revenue: MRR lost from cancellations and downgrades. | |
| expansion_revenue: MRR gained from upgrades and upsells. | |
| mrr_start: MRR at the start of the period. | |
| Returns: | |
| The net revenue churn rate as a decimal. | |
| """ | |
| if mrr_start == 0: | |
| return 0.0 | |
| return (churned_revenue - expansion_revenue) / mrr_start | |
| def calculate_nrr( | |
| mrr_start: float, expansion_revenue: float, churn_revenue: float | |
| ) -> float: | |
| """ | |
| Calculates Net Revenue Retention (NRR). | |
| Note: 'churn_revenue' here includes all lost revenue (cancellations + contractions). | |
| (Formula 8) | |
| Args: | |
| mrr_start: MRR at the start of the period. | |
| expansion_revenue: MRR gained from existing customers. | |
| churn_revenue: MRR lost from existing customers. | |
| Returns: | |
| The Net Revenue Retention rate as a decimal (e.g., 1.05 for 105%). | |
| """ | |
| if mrr_start == 0: | |
| return 0.0 | |
| return (mrr_start + expansion_revenue - churn_revenue) / mrr_start | |
| def calculate_cac(sm_spend: float, new_customers: int) -> float: | |
| """ | |
| Calculates Customer Acquisition Cost (CAC). | |
| (Formula 9) | |
| Args: | |
| sm_spend: Total sales and marketing spend for the period. | |
| new_customers: Number of new customers acquired in the period. | |
| Returns: | |
| The average Customer Acquisition Cost. | |
| """ | |
| if new_customers == 0: | |
| # Returning infinity is mathematically correct for 0 new customers | |
| # with non-zero spend, representing an infinitely high cost. | |
| return float("inf") if sm_spend > 0 else 0.0 | |
| return sm_spend / new_customers | |
| def calculate_arpa(mrr: float, active_accounts: int) -> float: | |
| """ | |
| Calculates Average Revenue Per Account (ARPA). | |
| (Formula 10) | |
| Args: | |
| mrr: Monthly Recurring Revenue. | |
| active_accounts: Number of active accounts. | |
| Returns: | |
| The Average Revenue Per Account. | |
| """ | |
| if active_accounts == 0: | |
| return 0.0 | |
| return mrr / active_accounts | |
| def calculate_customer_lifetime(customer_churn_rate: float) -> float: | |
| """ | |
| Calculates the approximate customer lifetime in periods. | |
| (Formula 11) | |
| Args: | |
| customer_churn_rate: The per-period customer churn rate as a decimal. | |
| Returns: | |
| The average customer lifetime in number of periods. | |
| """ | |
| if customer_churn_rate <= 0: | |
| # A churn rate of 0 implies infinite lifetime. | |
| return float("inf") | |
| return 1 / customer_churn_rate | |
| def calculate_ltv(arpa: float, gross_margin: float, revenue_churn_rate: float) -> float: | |
| """ | |
| Calculates the margin-adjusted Customer Lifetime Value (LTV). | |
| (Formula 12) | |
| Args: | |
| arpa: Average Revenue Per Account. | |
| gross_margin: The gross margin as a decimal (e.g., 0.80 for 80%). | |
| revenue_churn_rate: The revenue churn rate as a decimal. | |
| Returns: | |
| The margin-adjusted Customer Lifetime Value. | |
| """ | |
| if revenue_churn_rate <= 0: | |
| # A churn rate of 0 implies infinite LTV. | |
| return float("inf") | |
| return (arpa * gross_margin) / revenue_churn_rate | |
| def calculate_ltv_cac_ratio(ltv: float, cac: float) -> float: | |
| """ | |
| Calculates the LTV to CAC ratio. | |
| (Formula 13) | |
| Args: | |
| ltv: Customer Lifetime Value. | |
| cac: Customer Acquisition Cost. | |
| Returns: | |
| The LTV to CAC ratio. | |
| """ | |
| if cac == 0: | |
| # An LTV with zero acquisition cost is technically an infinite return. | |
| return float("inf") if ltv > 0 else 0.0 | |
| return ltv / cac | |
| def calculate_cac_payback_period(cac: float, arpa: float, gross_margin: float) -> float: | |
| """ | |
| Calculates the CAC payback period in months (assuming ARPA is monthly). | |
| (Formula 14) | |
| Args: | |
| cac: Customer Acquisition Cost. | |
| arpa: Average Revenue Per Account (monthly). | |
| gross_margin: Gross margin as a decimal. | |
| Returns: | |
| The number of months to pay back CAC. | |
| """ | |
| monthly_gross_profit_per_customer = arpa * gross_margin | |
| if monthly_gross_profit_per_customer <= 0: | |
| # If there's no gross profit, CAC is never paid back. | |
| return float("inf") | |
| return cac / monthly_gross_profit_per_customer | |
| def calculate_gross_margin(revenue: float, cogs: float) -> float: | |
| """ | |
| Calculates the gross margin. | |
| (Formula 15) | |
| Args: | |
| revenue: Total revenue. | |
| cogs: Cost of Goods Sold. | |
| Returns: | |
| The gross margin as a decimal. | |
| """ | |
| if revenue == 0: | |
| return 0.0 | |
| return (revenue - cogs) / revenue | |
| def calculate_yoy_growth(arr_current: float, arr_prior: float) -> float: | |
| """ | |
| Calculates Year-over-Year (YoY) growth rate. | |
| (Formula 16) | |
| Args: | |
| arr_current: ARR for the current year. | |
| arr_prior: ARR for the prior year. | |
| Returns: | |
| The YoY growth rate as a decimal. | |
| """ | |
| if arr_prior == 0: | |
| return float("inf") if arr_current > 0 else 0.0 | |
| return (arr_current - arr_prior) / arr_prior | |
| def calculate_rule_of_40(growth_rate: float, ebitda_margin: float) -> float: | |
| """ | |
| Calculates the Rule of 40 score. | |
| (Formula 17) | |
| Args: | |
| growth_rate: The growth rate as a percentage (e.g., 30 for 30%). | |
| ebitda_margin: The EBITDA margin as a percentage (e.g., 15 for 15%). | |
| Returns: | |
| The Rule of 40 score. | |
| """ | |
| return growth_rate + ebitda_margin | |
| with gr.Blocks(title="SaaS Metrics Calculator") as demo: | |
| gr.Markdown("# SaaS Metrics Calculator") | |
| gr.Markdown( | |
| "Calculate various key health and valuation metrics for your SaaS business. Each metric is in its own section below." | |
| ) | |
| with gr.Accordion("Annual Recurring Revenue (ARR)", open=False): | |
| mrr_input = gr.Number(label="Monthly Recurring Revenue (MRR)") | |
| arr_output = gr.Number(label="Annual Recurring Revenue (ARR)") | |
| btn = gr.Button("Calculate") | |
| btn.click(calculate_arr, inputs=[mrr_input], outputs=[arr_output]) | |
| with gr.Accordion("Revenue-Based Valuation", open=False): | |
| arr_input = gr.Number(label="Annual Recurring Revenue (ARR)") | |
| multiple_input = gr.Number(label="Valuation Multiple (e.g., 3.0 for 3x)") | |
| valuation_output = gr.Number(label="Revenue-Based Valuation") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_valuation_revenue, | |
| inputs=[arr_input, multiple_input], | |
| outputs=[valuation_output], | |
| ) | |
| with gr.Accordion("Seller's Discretionary Earnings (SDE)", open=False): | |
| revenue_input = gr.Number(label="Total Revenue") | |
| owner_comp_input = gr.Number(label="Owner Compensation") | |
| cogs_input = gr.Number(label="Cost of Goods Sold (COGS)") | |
| op_ex_input = gr.Number(label="Operating Expenses") | |
| sde_output = gr.Number(label="Seller's Discretionary Earnings (SDE)") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_sde, | |
| inputs=[revenue_input, owner_comp_input, cogs_input, op_ex_input], | |
| outputs=[sde_output], | |
| ) | |
| with gr.Accordion("SDE-Based Valuation", open=False): | |
| sde_input = gr.Number(label="Seller's Discretionary Earnings (SDE)") | |
| multiple_input = gr.Number(label="SDE Valuation Multiple") | |
| valuation_output = gr.Number(label="SDE-Based Valuation") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_valuation_sde, | |
| inputs=[sde_input, multiple_input], | |
| outputs=[valuation_output], | |
| ) | |
| with gr.Accordion("EBITDA-Based Valuation", open=False): | |
| ebitda_input = gr.Number(label="EBITDA") | |
| multiple_input = gr.Number(label="EBITDA Valuation Multiple") | |
| valuation_output = gr.Number(label="EBITDA-Based Valuation") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_valuation_ebitda, | |
| inputs=[ebitda_input, multiple_input], | |
| outputs=[valuation_output], | |
| ) | |
| with gr.Accordion("Customer Churn Rate", open=False): | |
| churned_customers_input = gr.Number(label="Churned Customers") | |
| start_customers_input = gr.Number(label="Starting Customers") | |
| churn_rate_output = gr.Number(label="Customer Churn Rate (decimal)") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_customer_churn_rate, | |
| inputs=[churned_customers_input, start_customers_input], | |
| outputs=[churn_rate_output], | |
| ) | |
| with gr.Accordion("Gross Revenue Churn Rate", open=False): | |
| churned_revenue_input = gr.Number(label="Churned Revenue (MRR lost)") | |
| mrr_start_input = gr.Number(label="Starting MRR") | |
| churn_rate_output = gr.Number(label="Gross Revenue Churn Rate (decimal)") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_gross_revenue_churn_rate, | |
| inputs=[churned_revenue_input, mrr_start_input], | |
| outputs=[churn_rate_output], | |
| ) | |
| with gr.Accordion("Net Revenue Churn Rate", open=False): | |
| churned_revenue_input = gr.Number(label="Churned Revenue (MRR lost)") | |
| expansion_revenue_input = gr.Number(label="Expansion Revenue (MRR gained)") | |
| mrr_start_input = gr.Number(label="Starting MRR") | |
| churn_rate_output = gr.Number(label="Net Revenue Churn Rate (decimal)") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_net_revenue_churn_rate, | |
| inputs=[churned_revenue_input, expansion_revenue_input, mrr_start_input], | |
| outputs=[churn_rate_output], | |
| ) | |
| with gr.Accordion("Net Revenue Retention (NRR)", open=False): | |
| mrr_start_input = gr.Number(label="Starting MRR") | |
| expansion_revenue_input = gr.Number(label="Expansion Revenue") | |
| churn_revenue_input = gr.Number(label="Churn Revenue (MRR lost)") | |
| nrr_output = gr.Number(label="Net Revenue Retention (decimal)") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_nrr, | |
| inputs=[mrr_start_input, expansion_revenue_input, churn_revenue_input], | |
| outputs=[nrr_output], | |
| ) | |
| with gr.Accordion("Customer Acquisition Cost (CAC)", open=False): | |
| sm_spend_input = gr.Number(label="Sales & Marketing Spend") | |
| new_customers_input = gr.Number(label="New Customers") | |
| cac_output = gr.Number(label="Customer Acquisition Cost (CAC)") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_cac, | |
| inputs=[sm_spend_input, new_customers_input], | |
| outputs=[cac_output], | |
| ) | |
| with gr.Accordion("Average Revenue Per Account (ARPA)", open=False): | |
| mrr_input = gr.Number(label="Monthly Recurring Revenue (MRR)") | |
| active_accounts_input = gr.Number(label="Active Accounts") | |
| arpa_output = gr.Number(label="Average Revenue Per Account (ARPA)") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_arpa, | |
| inputs=[mrr_input, active_accounts_input], | |
| outputs=[arpa_output], | |
| ) | |
| with gr.Accordion("Customer Lifetime", open=False): | |
| customer_churn_rate_input = gr.Number(label="Customer Churn Rate (decimal)") | |
| lifetime_output = gr.Number(label="Customer Lifetime (periods)") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_customer_lifetime, | |
| inputs=[customer_churn_rate_input], | |
| outputs=[lifetime_output], | |
| ) | |
| with gr.Accordion("Customer Lifetime Value (LTV)", open=False): | |
| arpa_input = gr.Number(label="ARPA") | |
| gross_margin_input = gr.Number(label="Gross Margin (decimal)") | |
| revenue_churn_rate_input = gr.Number(label="Revenue Churn Rate (decimal)") | |
| ltv_output = gr.Number(label="Customer Lifetime Value (LTV)") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_ltv, | |
| inputs=[arpa_input, gross_margin_input, revenue_churn_rate_input], | |
| outputs=[ltv_output], | |
| ) | |
| with gr.Accordion("LTV to CAC Ratio", open=False): | |
| ltv_input = gr.Number(label="LTV") | |
| cac_input = gr.Number(label="CAC") | |
| ratio_output = gr.Number(label="LTV to CAC Ratio") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_ltv_cac_ratio, | |
| inputs=[ltv_input, cac_input], | |
| outputs=[ratio_output], | |
| ) | |
| with gr.Accordion("CAC Payback Period", open=False): | |
| cac_input = gr.Number(label="CAC") | |
| arpa_input = gr.Number(label="ARPA (monthly)") | |
| gross_margin_input = gr.Number(label="Gross Margin (decimal)") | |
| payback_output = gr.Number(label="CAC Payback Period (months)") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_cac_payback_period, | |
| inputs=[cac_input, arpa_input, gross_margin_input], | |
| outputs=[payback_output], | |
| ) | |
| with gr.Accordion("Gross Margin", open=False): | |
| revenue_input = gr.Number(label="Total Revenue") | |
| cogs_input = gr.Number(label="Cost of Goods Sold (COGS)") | |
| margin_output = gr.Number(label="Gross Margin (decimal)") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_gross_margin, | |
| inputs=[revenue_input, cogs_input], | |
| outputs=[margin_output], | |
| ) | |
| with gr.Accordion("Year-over-Year (YoY) Growth", open=False): | |
| arr_current_input = gr.Number(label="Current ARR") | |
| arr_prior_input = gr.Number(label="Prior ARR") | |
| growth_output = gr.Number(label="YoY Growth Rate (decimal)") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_yoy_growth, | |
| inputs=[arr_current_input, arr_prior_input], | |
| outputs=[growth_output], | |
| ) | |
| with gr.Accordion("Rule of 40 Score", open=False): | |
| growth_rate_input = gr.Number( | |
| label="Growth Rate (percentage, e.g., 30 for 30%)" | |
| ) | |
| ebitda_margin_input = gr.Number( | |
| label="EBITDA Margin (percentage, e.g., 15 for 15%)" | |
| ) | |
| rule_output = gr.Number(label="Rule of 40 Score") | |
| btn = gr.Button("Calculate") | |
| btn.click( | |
| calculate_rule_of_40, | |
| inputs=[growth_rate_input, ebitda_margin_input], | |
| outputs=[rule_output], | |
| ) | |
| demo.launch() | |