Back to the dashboard

How this works

The data is fictional

Nordlys Analytics ApS does not exist. Every customer, employee, cost and cash figure was generated by a Python script with a fixed random seed. Customer names are random combinations of invented word parts. The company is a Copenhagen B2B SaaS with a €3,0m seed round in Oct 2024, 15 employees growing to 25, and 24 months of data from Oct 2024 to Sep 2026. Some events were planted on purpose so there is a story to tell: a hiring push in spring 2026, the largest customer leaving in Jul 2026, a price increase on new contracts from Jun 2026, a strong expansion month in Nov 2025 and a one-off recruiting fee in Apr 2026.

Where the numbers come from

CSV files are loaded into DuckDB. Every metric is calculated once, in one SQL view per file. Those views are exported to a single JSON file, which is the only thing this website reads. The site makes no API calls.

Metric definitions

MetricFormula
Cash in bankClosing bank balance at month end
Net burnPayments minus collections in the month (funding excluded)
Runway at current burnClosing cash divided by the average net burn of the last 3 months
Zero-cash monthThe month cash would run out at that burn: this month plus runway, rounded up
MRRMonthly recurring revenue: the sum of all new, expansion, contraction and churn movements to date
ARRMRR x 12. Growth is compared with 12 months earlier
Net revenue retentionMRR today from customers active 12 months ago, divided by their MRR then
Burn multipleNet burn over 3 months divided by net new ARR over the same 3 months. Left empty when ARR did not grow. It is high in Aug and Sep 2026 (7,1 and 12,3) because the largest customer churned in Jul 2026 and cancelled out most of the new ARR in those 3 months
Gross marginRevenue minus cost of revenue (cloud hosting plus customer success payroll), divided by revenue

Reading the dashboard

The (i) buttons next to titles, stats and sliders hold the short explanations. Everything in them is also explained on this page.

Why runway uses a 3-month average

Annual customers pay 12 months upfront, so collections jump in renewal months. A single month of burn can even be negative. Averaging three months smooths that out but still reacts quickly to changes such as the spring 2026 hiring push. Six or twelve months would hide that change for too long.

The scenario sliders

The dashed line starts from Sep 2026 with €1.579.630 in the bank and projects 24 months. Each month: MRR grows by the growth rate and shrinks by the churn rate; cash changes by MRR minus payroll minus other costs plus any funding. Payroll starts at €168.672 a month and rises 3 % every January (own assumption, for everyone including new hires). The page opens on the Current plan preset: growth 3,77 % and churn 1,35 % of MRR per month (12-month averages), other costs €36.437 a month (3-month average), 2 full-time hires and 2 student assistants. Hiring push has 6 full-time hires; Downside has half the growth and 1,5 times the churn. A student assistant (part-time, about 15 hours a week) costs €1.420 a month including holiday pay and starts in the first projected month. New hires add cost but do not change the growth rate, and customers are assumed to pay in the month revenue is earned. A full-time hire costs €6.719 a month: the average loaded cost per team, weighted by an assumed hiring mix of 40 % engineering, 35 % sales, 15 % customer success and 10 % G&A. This is a tool for asking "what if", not a forecast. A test checks that the browser calculation equals an independent Python version to within €1 a month.

What the AI does and does not do

For each month from Oct 2025 to Sep 2026 there is a draft investor update in five parts. The subject line and the key metrics table (cash, net burn, runway, MRR, ARR growth, customers and headcount, each with the change vs last month) are not written by AI: they come straight from the SQL views. Claude (model claude-sonnet-5-5, prompt v3) wrote the other three parts from a fact sheet of that month's numbers: a TL;DR of at most 30 words and two numbers that names the main event of the month, up to two highlights and two lowlights, and one ask for investors. Every number in the fact sheet is given twice, exact and in a display form computed in Python (for example "€55.811 (display: €55,8k)"), and the model is told to copy one of them and never round on its own: in earlier versions it wrote "€55k" for €55.811. The fact sheet only contains data up to that month; a test rebuilds the database without the later months and confirms the input is identical. The text was generated once and stored. The website never calls the AI, and the sliders do not get AI text.

These are drafts for a founder to finish. The data shows what happened, not why: it does not say why a customer left, what a professional fee was for or why someone was hired. Where such a reason matters, the AI was told to leave a placeholder such as "[Founder: add reason]" instead of guessing. The text is shown exactly as the model wrote it; there is no separate rewriting step.

A script without AI then checks every draft. Each number must match a value in that month's data within the rounding the text uses. Causes the text names (for example churn or hiring) must be visible in the data; this is a crude keyword list. It also checks the structure (one TL;DR, one ask, at most two highlights and two lowlights, at least one lowlight when a key metric got worse, at most two numbers per highlight or lowlight, a TL;DR of at most 30 words and two numbers that does not lead with runway) and a few style rules (at most 110 words, no banned filler words such as "significant" or "robust", no em dashes, placeholders only in square brackets and at the end of a sentence). None of this says anything about writing quality.

Validate and retry. The model does not follow format rules reliably, so the pipeline checks every draft and asks for one or two corrections. Both the first-attempt and final results are shown. The same checks run inside the drafting script right after each draft. If a draft fails, the script sends one follow-up message in the same conversation listing exactly which checks failed and the offending sentence or number, and asks for a corrected full draft. The follow-up contains only the checker's output, no correct values. At most two retries per month (three attempts); the last attempt is kept, pass or fail. This rule, the limit and the cost cap of USD 2,00 for the whole run were set and committed before the run, and the run was done once. Every attempt is stored in data/ai/update_attempts.csv.

Check results

Data reconciliation (raw data)

AI update check

The results are shown as the checker produced them, and the run was not repeated to improve them.

MonthAttempt keptNumbers matchedNot matchedWordsOther problemsResult
Oct 202517 / 7none108nonePass
Nov 202518 / 8none92nonePass
Dec 202529 / 9none75nonePass
Jan 202629 / 9none86nonePass
Feb 202627 / 7none80nonePass
Mar 202628 / 8none87nonePass
Apr 202627 / 7none76nonePass
May 202629 / 9none75nonePass
Jun 202618 / 8none91nonePass
Jul 202619 / 9none85nonePass
Aug 202617 / 7none88nonePass
Sep 202616 / 6none70nonePass

How to read the first-attempt flags: two of them were false flags by the cause check, not model errors. Dec 2025 wrote "Contraction MRR was €0" and Feb 2026 wrote "churn was zero"; the keyword list only ignores a cause after words such as "no" or "not", so it read both as naming a cause the data does not support. Those two months used a retry they did not need. The checker was not changed after the run. The real first-attempt errors were three or four numbers in one highlight or lowlight (3 months), a TL;DR with three numbers (2) and one draft with 31 words in the TL;DR and 112 in total. No draft had a wrongly rounded number.

Changes after the first run

Limitations

Known weaknesses in the v3 drafts (disclosed, not fixed)