# Forecast formulas

> The forecast turns a company's plan (customers, prices, costs, hires, loans and funding) into monthly revenue, profit, cash and a balance sheet, and from those into runway, break-even and the key ratios investors ask about. This page gives every formula it uses, what each symbol means, when the formula holds and a worked example.

The forecast turns a company's plan (customers, prices, costs, hires, loans and funding) into monthly revenue,
profit, cash and a balance sheet, and from those into runway, break-even and the key ratios investors ask about.
This page gives every formula it uses, what each symbol means, when the formula holds and a worked example.

**How to read this page.** Each formula is shown on its own line, followed by its symbols with their units, the
conditions under which it holds, what it says in plain words and a short example. Implementation notes for
developers (which function, which field) close each section. Amounts are in the workspace's base currency; the
examples use euros.

## Notation

| Symbol | Meaning | Unit |
| --- | --- | --- |
| $t$ | Month index: 0 is the model's start month (`meta.startMonth`), 1 the month after, and so on | month |
| $n$ | Horizon: the number of months forecast (`meta.horizonMonths`) | months |
| $a$ | As-of month: the month "today" falls in, as a month index | month |
| $s_d, e_d$ | First and last month of an item $d$ (a channel, a cost, a hire), as month indexes | month |
| $p(t)$ | A parameter read for month $t$, so a scenario can change it from a given month on ([scenario overrides](scenario-overrides.md)) | as the parameter |
| $\lfloor x \rfloor$ | $x$ rounded down to a whole number | |

A **rate** or **share** is a number between 0 and 1: 0.03 means 3 %. Every month is a whole calendar month; nothing
is split by days, except where payment delays are spread over 30-day months (section 4).

## 1. Horizon and as-of month

$$\begin{gathered} \text{period}_t = \text{startMonth} + t \\[8pt] t = 0, 1, \dots, n-1 \end{gathered}$$

| Item | Rule |
| --- | --- |
| Periods | `startMonth + t` for `t = 0..n-1`; `horizonMonths` is 1..120 in the engine (the database restricts it to 12..120) |
| Month index of a date | Calendar-month difference to `startMonth`; dates are reduced to their month, there is no day proration anywhere |
| As-of month $a$ | `meta.asOfMonth`, or `startMonth` if absent, clamped to $[0, n-1]$. The control plane sets it to the current calendar month clamped to the horizon (`ModelInputBuilder.asOfMonth`) |

**In plain words.** The forecast covers $n$ months from the start month. The as-of month marks "now": the monthly
figures always cover the whole horizon, but the summary (runway, unit economics, minimum cash, funding need) looks
forward from the as-of month.

**Example.** A model starting in January 2026 with a 24-month horizon, looked at in April 2026, has $n = 24$ and
$a = 3$.

*For developers:* `run_forecast`, `_summary`.

## 2. Acquisition funnel

### Leads

$$\begin{gathered} \text{leads}_d(t) = L_d(t)\,\bigl(1 + g_d(t)\bigr)^{\,t - \max(s_d,\,0)} \quad \text{for } s_d \le t \le e_d, \text{ else } 0 \\[8pt] \text{leads}(t) = \sum_d \text{leads}_d(t) \end{gathered}$$

| Symbol | Meaning | Unit |
| --- | --- | --- |
| $d$ | A revenue driver (a sales or marketing channel) | |
| $L_d(t)$ | Leads per month at the channel's start (`leadsPerMonth`) | leads per month |
| $g_d(t)$ | Monthly growth of leads (`monthlyLeadGrowthRate`) | share per month |
| $s_d, e_d$ | The channel's first and last month (default 0 and $n-1$) | month |

**In plain words.** Each channel starts with a number of leads a month and grows them by the same percentage every
month while it runs.

**Valid when.** The channel is active. Growth compounds from the later of the channel's start and the model start.

**Example.** 200 leads a month growing 10 % a month, from month 0: in month 3 there are
$200 \times 1.1^3 = 266.2$ leads.

### New customers

$$\text{new}(t) = \sum_d \text{leads}_d\bigl(t - \ell_d\bigr)\,q_d(t) \quad \text{for } s_d \le t \le e_d + \ell_d$$

| Symbol | Meaning | Unit |
| --- | --- | --- |
| $\ell_d$ | Sales cycle: months from lead to customer (`salesCycleMonths`) | months |
| $q_d(t)$ | Share of leads who become customers (`conversionRate`) | share |
| $\text{new}(t)$ | New paying customers in month $t$ | customers |

**In plain words.** Leads become customers one sales cycle later, at the channel's conversion rate.

**Valid when.** A lead month before the model start ($t - \ell_d < 0$) counts only if
`assumptions.assumeExistingPipeline` is true and the channel was already running (no `startMonth`, or one before
the model start). A channel that starts at the model start has no earlier pipeline, so its first $\ell_d$ months
bring no customers.

**Example.** With a one-month sales cycle and 5 % conversion, month 3 gets $242 \times 0.05 = 12.1$ new customers
from month 2's 242 leads.

### Split over price plans

$$\text{new}_p(t) = \text{new}(t)\,\frac{\text{mix}_p(t)}{\sum_{p'} \text{mix}_{p'}(t)}$$

**In plain words.** New customers are shared out over the price plans in proportion to each plan's
`newCustomerMix`. A channel tied to a product (`productId`) feeds only that product's plans, with the mix
normalised within the product; channels without a product feed the plans of products that have no channel of their
own (all plans when every product has one).

**Valid when.** The mix adds up to more than 0. Otherwise new customers are not assigned to any plan (a warning is
emitted if this happens in month 0). After the plan loop, `S["customers.new"]` is the sum of assigned customers.

## 3. Customers, MRR and revenue (per price plan)

Each plan holds cohorts: groups of customers who joined in the same month, each with its own start, number of
customers, monthly price (ARPA), whether it is annual, and whether it is a legacy cohort of starting customers.

### Net price

$$\text{netPrice}(t) = \text{list}(t)\,\bigl(1 - \delta(t)\bigr)\,\bigl(1 - \delta_{\text{annual}}\bigr)$$

| Symbol | Meaning | Unit |
| --- | --- | --- |
| $\text{list}(t)$ | List price per month (`listPricePerMonth`) | currency per customer per month |
| $\delta(t)$ | Discount (`discountRate`) | share |
| $\delta_{\text{annual}}$ | Annual prepayment discount, only for annual plans (`annualPrepayDiscountRate`) | share |

**In plain words.** What a customer actually pays per month after discounts.

**Valid when.** A new cohort is locked at the net price of its sign-up month. Later price changes affect only new
cohorts, unless a scenario reprices existing customers (`applyToExisting`, see
[scenario overrides](scenario-overrides.md)). `startingCustomers > 0` creates a legacy cohort at $\text{netPrice}(0)$;
legacy cohorts always behave as monthly (churn and billing), even on an annual plan.

**Example.** A €100 list price with a 10 % discount on an annual plan with a 15 % prepayment discount:
$100 \times 0.9 \times 0.85 = 76.50$ euros a month.

### Churn and expansion of a cohort

$$\begin{gathered} \text{monthly: } \text{lost} = C\,\kappa(t), \quad \text{ARPA} \leftarrow \text{ARPA}\,\bigl(1 + x(t)\bigr) \\[8pt] \text{annual, at each 12-month anniversary: } \text{lost} = C\,\bigl(1 - (1-\kappa)^{12}\bigr), \quad \text{ARPA} \leftarrow \text{ARPA}\,(1 + x)^{12} \end{gathered}$$

| Symbol | Meaning | Unit |
| --- | --- | --- |
| $C$ | Customers in the cohort at the start of the month | customers |
| $\kappa(t)$ | Monthly churn rate (`monthlyChurnRate`) | share per month |
| $x(t)$ | Monthly expansion: how much survivors' spending grows (`monthlyExpansionRate`) | share per month |

**In plain words.** Monthly customers can leave every month. Annual customers can only leave at renewal, and then
a year's worth of monthly churn happens at once; survivors grow their spending the same way.

**Valid when.** $0 \le \kappa < 1$. The order within a month, per plan, is: (1) optional repricing of all cohorts by
$\text{netPrice}(t)/\text{netPrice}(t-1)$; (2) churn and expansion of existing cohorts; (3) the new cohort is
added. New customers do not churn in their sign-up month.

**Example.** 100 monthly customers with 3 % churn lose 3 in a month. 100 annual customers with the same churn lose
$100 \times (1 - 0.97^{12}) = 30.6$ at their first renewal and none in between.

### Revenue of a plan

$$\begin{gathered} \mathit{MRR} = \sum_{\text{cohorts}} C \cdot \text{ARPA} \\[8pt] \text{usage} = C_{\text{end}}\,u(t)\,p_u(t) \\[8pt] \text{oneTime} = \text{new}_p\,\text{fee}(t) \\[8pt] \text{refunds} = (\mathit{MRR} + \text{usage} + \text{oneTime})\,r(t) \\[8pt] \text{revenue} = \mathit{MRR} + \text{usage} + \text{oneTime} - \text{refunds} \end{gathered}$$

| Symbol | Meaning | Unit |
| --- | --- | --- |
| $\mathit{MRR}$ | Monthly recurring revenue at the end of the month | currency per month |
| $C_{\text{end}}$ | Customers at the end of the month | customers |
| $u(t)$, $p_u(t)$ | Usage units per customer and price per unit | units; currency per unit |
| $\text{fee}(t)$ | One-time fee per new customer | currency |
| $r(t)$ | Refund rate (`refundRate`) | share |

**In plain words.** Subscription revenue is what every customer pays per month; usage and one-time fees come on top;
refunds are a share of all of it.

**Valid when.** Always. Subscription revenue is recognised monthly (equal to MRR) for annual plans too, even though
they are billed once a year (section 4).

**Example.** 97 customers at €100 give $\mathit{MRR} = 9{,}700$; with 50 usage units each at €0.10 and a 2 %
refund rate, revenue is $(9{,}700 + 485) \times 0.98 = 9{,}981.30$ euros.

Also per plan: $\mathit{MRR}_{\text{new}} = \text{new}_p \cdot \text{netPrice}(t)$,
$\mathit{MRR}_{\text{churned}} = \sum \text{lost} \cdot \text{ARPA}$, $\mathit{MRR}_{\text{expansion}}$ = the
survivors' ARPA uplift, and the revenue at list price $\mathit{MRR} / \bigl((1-\delta)(1-\delta_{\text{annual}})\bigr)$.
Totals: `revenue.subscription` $= \sum \mathit{MRR}$, `revenue.recurring` = subscription + usage, `revenue.discounts`
= list-price revenue − subscription, and

$$\mathit{ARR} = 12 \cdot \mathit{MRR}$$

## 4. Billing, collections and receivables

### What is billed

$$\begin{gathered} \text{billed}_p(t) = \sum_{\text{monthly cohorts}} C \cdot \text{ARPA} \;+\; \sum_{\text{annual cohorts with } (t - \text{start}) \bmod 12 = 0} 12\,C \cdot \text{ARPA} \\[8pt] \text{billings}(t) = \sum_p \bigl(\text{billed}_p(t) + \text{usage} + \text{oneTime} - \text{refunds}\bigr) \end{gathered}$$

**In plain words.** Monthly customers are invoiced every month; annual customers are invoiced a full year in
advance in their sign-up month and on each anniversary.

### When the money arrives

$$\begin{gathered} \text{pay}(t) = (1 - f)\,X(t - m) + f\,X(t - m - 1) \\[8pt] m = \left\lfloor \frac{\text{days}}{30} \right\rfloor \\[8pt] f = \frac{\text{days} \bmod 30}{30} \end{gathered}$$

| Symbol | Meaning | Unit |
| --- | --- | --- |
| $X(t)$ | The amount invoiced (or owed) in month $t$; for $t < 0$ the amount `pre` before the model start | currency |
| $\text{days}$ | Payment delay (`paymentDelayDays`, read once, not per month) | days |
| $m$, $f$ | Whole months of delay, and the fraction of a month left over | months; share |

**In plain words.** With a 45-day payment term, half of a month's invoices are paid one month later and the other
half two months later. Months count as 30 days here.

**Valid when.** Always; with 0 days everything is collected in the month it is billed. Collections use
$X = \text{billings}$ and $\text{pre} = \sum_p \text{startingCustomers} \cdot \text{netPrice}(0)$. The opening
receivable is $\text{pre} \cdot \text{days} / 30$.

**Example.** 45 days: $m = 1$, $f = 0.5$. With €8,000 billed two months ago and €10,000 last month, this month
collects $0.5 \times 10{,}000 + 0.5 \times 8{,}000 = 9{,}000$ euros.

*For developers:* `lagged(X, days, pre)`, `opening_outstanding`.

## 5. Costs

### Cost drivers

$$\begin{gathered} \text{amt}(t) = A_d(t)\,\bigl(1 + g_d(t)\bigr)^{\,t - s_d} \quad \text{for } s_d \le t \le e_d \\[8pt] \text{cost}(t) = \begin{cases} \text{amt} & \text{fixed} \\ \text{amt} \cdot \text{customers}_{\text{end}}(t) & \text{per active customer} \\ \text{amt} \cdot \text{customers}_{\text{new}}(t) & \text{per new customer} \\ \text{amt} \cdot \text{revenue}(t) & \text{share of revenue} \\ \text{amt} \cdot \text{usage units}(t) & \text{per usage unit} \end{cases} \end{gathered}$$

| Symbol | Meaning | Unit |
| --- | --- | --- |
| $A_d(t)$ | The driver's amount (`amount`) | currency, or currency per customer or unit, or share of revenue |
| $g_d(t)$ | Monthly growth of the amount (`monthlyGrowthRate`) | share per month |

**In plain words.** A cost is either a fixed amount a month or an amount per customer, per new customer, per unit
or per euro of revenue, and it can grow every month.

**Valid when.** The driver is active. $s_d$ can be negative when the cost started before the model start, so growth
compounds from the real start. Costs paid to suppliers are delayed like collections:
$\text{lagged}(\text{cost}, \text{paymentDelayDays} \text{ or } \text{payablesDelayDays}, \text{pre})$ with
$\text{pre} = \text{cost}(0)$ if the driver has no start or started before the model start, else 0; opening
payable $= \text{pre} \cdot \text{days}/30$.

**Example.** €1,000 a month growing 2 % a month, six months after it started:
$1{,}000 \times 1.02^6 = 1{,}126.16$ euros.

A driver goes to the line `cogs.<category>` if it is a cost of goods sold (`isCogs`; by default the categories
`ai_api`, `cloud_hosting`, `payment_processing`, `customer_support`, `other_cogs`), else to `opex.<category>`.
Fixed drivers add to `kpi.fixed_costs`; all other behaviours add to `kpi.variable_costs` and are allocated to plans
pro rata to the plan's customers, new customers, revenue or usage units.

### Headcount

$$\text{cost}(t) = W(t)\cdot \text{FTE} \cdot \bigl(1 + \rho(t)\bigr)^{\left\lfloor (t - s)/12 \right\rfloor} \cdot \bigl(1 + o(t)\bigr)$$

| Symbol | Meaning | Unit |
| --- | --- | --- |
| $W(t)$ | Monthly cost of the role (`monthlyCost`) | currency per month |
| $\text{FTE}$ | Full-time equivalent (1 = full time) | |
| $\rho(t)$ | Annual raise (`annualSalaryIncreaseRate`) | share per year |
| $o(t)$ | Employer overhead (`employerOverheadRate`); 0 for contractors | share |

**In plain words.** Salary times FTE, raised once a year on the anniversary of the start month, plus employer costs
for employees.

**Valid when.** For months $\max(s, 0)$ to $\min(e, n-1)$, whole months, end month included. Raises apply to
contractors too. Payroll is paid in the same month and counts as a fixed cost. A hire whose end date is before
their start date is skipped with a warning. The cost is split: $\text{cogs.headcount} \mathrel{+}= \text{cost} \cdot
\text{cogsAllocation}$ and the rest goes to `opex.headcount`.

**Example.** €5,000 a month, full time, 3 % raise, 20 % overhead, in the 14th month after starting:
$5{,}000 \times 1.03^{1} \times 1.2 = 6{,}180$ euros.

## 6. P&L

$$\begin{gathered} \text{GP} = \text{revenue} - \text{COGS} \\[8pt] \text{GM} = \frac{\text{GP}}{\text{revenue}} \\[8pt] \text{OR} = \text{GP} - \text{OPEX} \\[8pt] \text{PTR} = \text{OR} - \text{interest} \\[8pt] \text{tax} = \max\bigl(0,\; \text{PTR} - \text{loss pool}\bigr)\cdot \theta(t) \\[8pt] \text{NI} = \text{PTR} - \text{tax} \end{gathered}$$

| Symbol | Meaning | Unit |
| --- | --- | --- |
| $\text{COGS}$ | Cost of goods sold: all `cogs.<category>` lines plus `cogs.headcount` | currency |
| $\text{OPEX}$ | Operating expenses: all `opex.<category>` lines plus `opex.headcount` | currency |
| $\text{GP}$, $\text{GM}$ | Gross profit; gross margin | currency; share |
| $\text{OR}$ | Operating result ("EBITDA, management view"; there is no depreciation) | currency |
| $\text{PTR}$ | Pre-tax result | currency |
| $\theta(t)$ | Corporate tax rate (`corporateTaxRate`) | share |
| $\text{NI}$ | Net income | currency |

**In plain words.** Revenue minus the direct cost of serving customers is gross profit; minus the running costs of
the company is the operating result; minus interest and tax is net income. Losses are kept in a pool that is set
against later profits first, without limit.

**Valid when.** Tax is a simplified planning estimate, paid in the month it arises. Gross margin is not defined in a
month without revenue. `vatRate` exists in the assumptions but the engine does not use it. There is no separate
marketing model: marketing spend is a cost driver (category `marketing`, often fixed) and feeds
`kpi.sales_marketing_cost` (section 9).

**Example.** Revenue €50,000, COGS €15,000, OPEX €45,000: GP €35,000, GM 70 %, OR −€10,000. With €30,000 of
earlier losses in the pool, a later pre-tax profit of €20,000 pays no tax and leaves €10,000 in the pool; the next
€20,000 is taxed on €10,000, at 30 % that is €3,000.

## 7. Debt

$$\begin{gathered} r = \frac{\text{annualInterestRate}}{12} \\[8pt] \text{interest}_k = B_{k-1}\,r \\[8pt] \text{repayment}_k = \begin{cases} \min\left(\dfrac{\text{principal}}{\text{term} - \text{grace}},\; B_{k-1}\right) & \text{amortizing, } k > \text{grace} \\ B_{k-1} & \text{bullet, at } k = \text{term} \end{cases} \end{gathered}$$

| Symbol | Meaning | Unit |
| --- | --- | --- |
| $k$ | Months since the drawdown ($0$ to `termMonths`) | months |
| $B_k$ | Balance owed after month $k$ ($B_0$ = principal) | currency |
| $r$ | Monthly interest rate | share per month |
| term, grace | Length of the loan and the months without repayment | months |

**In plain words.** The loan is paid out in its first month. Each month after, interest is charged on what is still
owed. An amortizing loan is repaid in equal parts after the grace period; a bullet loan all at once at the end.

**Valid when.** A loan with $\text{term} - \text{grace} \le 0$ is treated as bullet. Interest and repayments are paid
in cash in the same month. For loans drawn before the model start, earlier rows are not shown and the remaining
balance becomes opening debt; opening debt is the sum of every loan's balance at the end of the month before the
model start.

**Example.** €120,000 at 12 % a year for 24 months with 4 months' grace: $r = 1\,\%$, the first month's interest is
€1,200, and from month 5 on €6,000 is repaid each month ($120{,}000 / 20$).

## 8. Cash, funding and balance sheet

$$\begin{gathered} \text{CF}_{\text{op}} = \text{collections} - \text{supplier payments} - \text{payroll} - \text{interest} - \text{tax} \\[8pt] \text{CF}_{\text{fin}} = \text{funding} + \text{drawdowns} - \text{repayments} \\[8pt] \text{cash}_{\text{end}}(t) = \text{cash}_{\text{begin}}(t) + \text{net change}(t) \\[8pt] \text{cash}_{\text{begin}}(0) = \text{opening cash} \\[8pt] \text{net burn} = -\text{CF}_{\text{op}} \\[8pt] \text{gross burn} = \text{supplier payments} + \text{payroll} + \text{interest} + \text{tax} \end{gathered}$$

| Symbol | Meaning | Unit |
| --- | --- | --- |
| $\text{CF}_{\text{op}}$ | Operating cash flow: cash in from customers minus cash out for running the company | currency per month |
| $\text{CF}_{\text{fin}}$ | Financing cash flow: money from investors, grants and loans minus loan repayments | currency per month |
| net change | Inflows minus outflows | currency per month |
| net burn | Cash lost by operations in a month (negative when operations bring cash in) | currency per month |

**In plain words.** Each month's ending cash is the beginning cash plus everything that came in minus everything that
went out. Net burn is how much cash the business itself loses in a month, before any funding.

**Valid when.** Opening cash is, per account, the latest cash position dated before the first day of the month after
`startMonth`, converted with `fxRateToBase` if it is in another currency (left out with a warning if there is no
rate), then summed. Funding events count when `includeInForecast` is true and the status is not `cancelled`, in the
month of their date; events before the model start are left out with a warning, events after the horizon are
ignored. All types (equity, grant, convertible, other) flow into `cash.equity_funding` and paid-in capital.
**Uncommitted funding is funding with status `planned`**; `committed` and `received` count as committed, and

$$\text{cash}_{\text{end, excl. planned}}(t) = \text{cash}_{\text{end}}(t) - \sum_{t' \le t} \text{planned funding}(t')$$

**Example.** Beginning cash €600,000, collections €30,000, supplier payments €20,000, payroll €60,000: operating cash
flow is −€50,000, so net burn is €50,000 and ending cash €550,000.

**Balance sheet (simplified).** Receivables grow by billings − collections; deferred revenue by billings − revenue;
payables by costs − supplier payments. Paid-in capital starts at opening cash + opening receivables − opening
payables − opening debt and adds funding; retained earnings are the cumulative net income. The check
$\text{assets} - \text{liabilities} - \text{equity}$ (`bs.check`) must be zero.

## 9. KPIs and unit economics

Monthly KPI lines (`run_forecast`, section "KPIs"). Each is calculated per month $t$.

### Sales and marketing cost

$$\begin{aligned} \text{S\&M} = {} & \text{opex.marketing} + \text{opex.sales} + \text{cogs.marketing} + \text{cogs.sales} \\ & + \text{headcount cost of the sales and marketing departments} \end{aligned}$$

**In plain words.** Everything spent on winning customers.

### ARPA, churn rate and CAC

$$\begin{gathered} \text{ARPA} = \frac{\mathit{MRR}}{\text{customers}_{\text{end}}} \\[8pt] \kappa = \frac{\text{customers}_{\text{churned}}}{\text{customers}_{\text{start}}} \\[8pt] \mathit{CAC} = \frac{\text{S\&M}}{\text{customers}_{\text{new}}} \end{gathered}$$

| Symbol | Meaning | Unit |
| --- | --- | --- |
| ARPA | Average revenue per account: monthly recurring revenue per customer (usage is not included) | currency per customer per month |
| $\kappa$ | Churn rate: customers lost in the month as a share of customers at its start | share per month |
| $\mathit{CAC}$ | Customer acquisition cost: S&M per new customer | currency per customer |

**Valid when.** The denominator is above 0; otherwise the KPI is empty for that month.

**Example.** €9,700 MRR over 97 customers is an ARPA of €100. Losing 2 of 100 customers is a churn rate of 2 %.
€10,000 S&M for 10 new customers is a CAC of €1,000.

### Lifetime value, LTV to CAC and CAC payback

$$\begin{gathered} \mathit{LTV} = \frac{\text{ARPA} \cdot \text{GM}}{\kappa} \\[8pt] \frac{\mathit{LTV}}{\mathit{CAC}} \\[8pt] \text{CAC payback} = \frac{\mathit{CAC}}{\text{ARPA} \cdot \text{GM}} \end{gathered}$$

| Symbol | Meaning | Unit |
| --- | --- | --- |
| $\mathit{LTV}$ | Lifetime value: gross margin a customer brings over their life | currency per customer |
| $\mathit{LTV}/\mathit{CAC}$ | How many times a customer pays back what it cost to win them | ratio |
| CAC payback | Months of gross margin needed to earn back the CAC | months |

**In plain words.** A customer stays $1/\kappa$ months on average and leaves ARPA × GM each month. Comparing that with
the cost to win them shows whether growth creates value; the CAC payback shows how long the cash is tied up.

**Valid when.** LTV needs $\kappa > 0$ (empty when churn is 0); CAC payback needs a gross margin above 0. GM here is
the company's total gross margin (usage and one-time revenue included), while ARPA counts MRR only. The formulas
assume churn and margin stay as they are this month; future money is not discounted.

**Example.** ARPA €100, gross margin 80 %, churn 2 %: $\mathit{LTV} = 100 \times 0.8 / 0.02 = 4{,}000$ euros. With a
CAC of €1,000, LTV to CAC is 4 and the CAC payback $1{,}000 / 80 = 12.5$ months.

### Contribution margin and break-even customers

$$\begin{gathered} \text{CM} = \text{revenue} - \text{variable costs} \\[8pt] C^{*} = \frac{\text{fixed costs}}{\text{CM} / \text{customers}_{\text{end}}} \end{gathered}$$

**In plain words.** The contribution margin is what is left after costs that rise with customers or revenue.
Break-even customers is how many customers, each bringing the average contribution, cover the fixed costs.

**Valid when.** Both the fixed costs and the contribution margin are above 0.

**Example.** €60,000 contribution from 400 customers is €150 each; with €40,000 fixed costs,
$C^{*} = 40{,}000 / 150 = 266.7$ customers.

### Burn multiple

$$\text{burn multiple} = \frac{\text{net burn}}{\text{net new ARR}}$$

**In plain words.** How many euros of cash are burned for each euro of new annual recurring revenue. Lower is
better.

**Valid when.** Both are above 0. In month 0 the prior ARR is $12 \cdot \mathit{MRR}_{\text{starting}}$.

**Example.** €60,000 net burn and €40,000 of new ARR in a month: a burn multiple of 1.5.

### Runway in a month

$$\text{runway}(t) = \frac{\max\bigl(0,\, \text{cash}_{\text{end}}(t)\bigr)}{\frac{1}{W}\sum_{i=t-W+1}^{t} \text{net burn}(i)}$$

**In plain words.** How many months the cash at the end of this month would last at the average burn of the last
$W$ months.

**Valid when.** The average burn is above 0 (otherwise the company is not burning cash and there is no runway
figure). This trailing average differs from the summary runway in section 10, which looks forward.

### Unit economics in the summary

The summary uses the same formulas on sums over the 12-month window from the as-of month, $a$ to
$\min(a + 12, n) - 1$:
$\text{ARPA} = \sum \mathit{MRR} / \sum \text{customers}_{\text{end}}$, $\text{GM} = \sum \text{GP} / \sum \text{revenue}$,
$\kappa = \sum \text{churned} / \sum \text{starting}$, $\mathit{CAC} = \sum \text{S\&M} / \sum \text{new}$ and cost per
customer $= \sum (\text{COGS} + \text{OPEX}) / \sum \text{customers}_{\text{end}}$. Plan economics fields named
`*_first_12m` use the same window starting at the as-of month, not the model start.

## 10. Runway, cash-out, funding need, break-even

`_summary`. $W$ is `assumptions.runwayBurnWindowMonths` (default 3).

### Runway

$$\begin{gathered} \text{currentCash} = \text{cash}_{\text{begin}}(a) \\[8pt] \overline{\text{burn}} = \frac{1}{W}\sum_{i=a}^{a+W-1} \text{net burn}(i) \\[8pt] \text{runway} = \frac{\text{currentCash}}{\overline{\text{burn}}} \end{gathered}$$

| Symbol | Meaning | Unit |
| --- | --- | --- |
| currentCash | Cash at the start of the as-of month | currency |
| $\overline{\text{burn}}$ | Average monthly net burn over the next $W$ months (`avgMonthlyNetBurn`) | currency per month |
| runway | Months the current cash lasts at that burn (`runwayMonths`) | months |

**In plain words.** Today's cash divided by what the company is about to burn each month.

**Valid when.** The burn is above 0 and cash above 0. If burn is above 0 and cash is 0 or less, runway is 0; if the
company is not burning cash, there is no runway figure (cash is not running out). The window is cut short at the
end of the horizon. It assumes the burn stays at that average, so also check the cash-out month below, which
follows the actual monthly figures.

**Example.** €600,000 in the bank and a burn of €40,000, €50,000 and €60,000 over the next three months: average burn
€50,000, runway $600{,}000 / 50{,}000 = 12$ months.

### Cash-out month and forecast runway

$$\begin{gathered} t_{\text{out}} = \min\{\, t \ge a : \text{cash}_{\text{end}}(t) < 0 \,\} \\[8pt] \text{runway}_{\text{forecast}} = t_{\text{out}} - a \;\;\text{(or } n - a \text{ if cash never runs out)} \end{gathered}$$

**In plain words.** The first month cash goes below zero, and how many months from now that is. When cash lasts the
whole horizon, the runway is reported as "beyond the horizon" (`runwayBeyondHorizon = true`).

**Valid when.** Always. The same figures excluding uncommitted (planned) funding are calculated on
$\text{cash}_{\text{end, excl. planned}}$ (fields `...ExcludingUncommitted`).

### Lowest cash and lowest runway

$$\begin{gathered} \text{minimumCash} = \min_{t \ge a} \text{cash}_{\text{end}}(t) \\[8pt] \text{minimumRunway} = \min_{t \ge a} \text{runway}(t) \end{gathered}$$

**In plain words.** The lowest point cash reaches from now on, and the shortest runway of any month (section 9's
trailing-window runway), each with its month. The lowest runway is the figure to check before a hire.

### Funding need

$$\text{fundingNeed} = \max\bigl(0,\; \text{minimumCashBuffer} - \text{minimumCash}\bigr)$$

| Symbol | Meaning | Unit |
| --- | --- | --- |
| minimumCashBuffer | The cash the company always wants to keep (`minimumCashBuffer`) | currency |

**In plain words.** How much more money is needed so that cash never falls below the buffer.

**Valid when.** Always. `fundingNeedExcludingUncommitted` uses the lowest cash excluding planned funding.

**Example.** A €100,000 buffer and lowest cash of €40,000: a funding need of €60,000.

### Break-even month

$$t_{BE} = \min\{\, t \ge a : \text{OR}(t) \ge 0 \text{ and } \text{customers}_{\text{end}}(t) > 0 \,\}$$

**In plain words.** The first month from now with an operating result of zero or more and at least one customer.
`breakEvenCustomers` is $C^{*}$ (section 9) in the as-of month.

**Valid when.** The month falls within the horizon; otherwise there is none. It does not require the following
months to stay profitable.

### Year summaries

Months are grouped by fiscal year (`fiscalYearStartMonth`; the label is the calendar year in which the fiscal year
ends, prefixed `FY` if the fiscal year does not start in January). Flows are summed; ending values are taken from
the last month.

## 11. Decimal and rounding policy

- All amounts are `Decimal`. JSON floats are converted via `Decimal(repr(x))` (`models/base.py`); outputs serialise
  as strings.
- Internal arithmetic is unrounded and uses Python's default decimal context (28 significant digits, half-even);
  only `safe_div` runs in `FINANCE_CONTEXT` (34 digits, half-up).
- Quantisation happens only on output, half-up (`_round`, `_summary`):

| Unit | Places |
| --- | --- |
| currency (series and summary money fields) | 2 |
| ratio lines, gross margin, churn | 6 |
| months (runway, payback) | 2 |
| count series | 4 |
| summary customers / FTE | 2; `ltvToCac` 4; `breakEvenCustomers` 1 |

- Ratio lines (`kind = ratio`) are recomputed from unrounded numerator and denominator before rounding; quarterly
  and annual aggregation re-derives them from aggregated (already rounded) components.

## 12. Warnings

Emitted in `ForecastResult.warnings`:

| Condition | Message (abridged) |
| --- | --- |
| New customers in month 0 but no plan has a positive mix | "...not assigned to any plan" |
| Headcount `endDate < startDate` | "Headcount '<role>' ends before it starts and is ignored." |
| Foreign-currency cash account without `fxRateToBase` | "...without an FX rate; excluded." |
| No cash position on or before the start month | "...opening cash is 0." |
| Funding event dated before model start | "...not included; include it in opening cash instead." |
| `fundingNeed > 0` | "Cash falls below the minimum buffer; funding need is X <ccy>." |
| `abs(bs.check) > 0.005` in any month | "Balance sheet does not balance; please report this as an engine defect." |

Override outcomes are reported separately in `appliedOverrides`.

## 13. Lineage fields

`ForecastResult` carries `engineVersion` (`finance_engine.ENGINE_VERSION`, currently `1.0.0`), `modelVersion`
(`meta.modelVersion`), `inputHash`, `calculatedAt` (UTC, not hashed), `scenario`, and `effectiveInput` (the input
after structural overrides).

$$\text{inputHash} = \operatorname{SHA\text{-}256}\bigl(\text{canonical JSON of the validated request}\bigr)$$

The canonical JSON is `json.dumps(request.model_dump(mode="json", by_alias=True), sort_keys=True,
separators=(",", ":"))` over the whole request (input, scenario reference, overrides) with defaults filled in. The
same request on the same engine version always gives the same hash and the same result. See
[scenario overrides](scenario-overrides.md#run-snapshot-and-hashes).

## For developers

Everything here is derived from `apps/finance-engine`: `calc/forecast.py` (`run_forecast`, `_summary`, `lagged`,
`opening_outstanding`, `_round`, `input_hash`), `calc/lines.py` (output line catalogue), `calc/overrides.py`
(per-month parameter resolution), `models/inputs.py`, `models/results.py` and `finance_domain/money.py`. In the code
`S[key][t]` is an output series and a parameter written `p(t)` is read through `OverrideBook.value(...)`. Keep this
page in sync with any change there.
