Finance Platform · Learn

Learn

Reference

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) 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). 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

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

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.

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.

Markdown · version 263da8e4e22a