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
| Metric | Formula |
|---|---|
| Cash in bank | Closing bank balance at month end |
| Net burn | Payments minus collections in the month (funding excluded) |
| Runway at current burn | Closing cash divided by the average net burn of the last 3 months |
| Zero-cash month | The month cash would run out at that burn: this month plus runway, rounded up |
| MRR | Monthly recurring revenue: the sum of all new, expansion, contraction and churn movements to date |
| ARR | MRR x 12. Growth is compared with 12 months earlier |
| Net revenue retention | MRR today from customers active 12 months ago, divided by their MRR then |
| Burn multiple | Net 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 margin | Revenue 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.
- The large number at the top is the runway for the selected month, shown in red below 12 months. Each stat has its change vs the previous month: green when it moved the right way (cash, runway and ARR up, net burn down), red when not.
- On the cash chart the solid line with a soft fill is actual cash and the dashed line over a hatch is the projection from the presets and sliders. The red pill on the axis marks the month the projection reaches zero. Three events are marked on the actual line, each picked from the data:
- Mar 2026: Hiring push starts, headcount 19 to 22
- Apr 2026: One-off recruiting agency fee, professional fees €27k
- Jul 2026: Largest customer churns, -€9.400 MRR
- The MRR bridge axis does not start at zero, otherwise the monthly movements would be invisible next to the total MRR.
- In the investor update, "Numbers in the draft" lists each number with the value it matched in that month's fact sheet. If a number fits more than one value within the rounding, the closest is shown.
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)
- PASS Cash: closing = opening + collections - payments + funding
- PASS Cash: opening = previous month's closing
- PASS Cash: payments = payroll + other opex
- PASS MRR: roll-forward of movements ties to customer snapshot each month
- PASS MRR: each customer's movements sum to their MRR (0 if churned)
- PASS Payroll: headcount cost ties to payroll in payments
- PASS Opex: opex table ties to other payments
- PASS Collections: monthly plans + annual upfront invoices tie to collections
AI update check
- First attempt: 6 of 12 drafts passed all checks.
- Final, after retries: 12 of 12 drafts pass. In the kept drafts 94 of 94 numbers match the data.
- 6 months needed a retry (Dec 2025, Jan 2026, Feb 2026, Mar 2026, Apr 2026, May 2026), each only one; 18 API calls in total.
- Checks failed most on the first attempt (months): numbers per sentence 3, cause not in the data 2, TL;DR numbers 2, TL;DR length 1, word limit 1.
The results are shown as the checker produced them, and the run was not repeated to improve them.
| Month | Attempt kept | Numbers matched | Not matched | Words | Other problems | Result |
|---|---|---|---|---|---|---|
| Oct 2025 | 1 | 7 / 7 | none | 108 | none | Pass |
| Nov 2025 | 1 | 8 / 8 | none | 92 | none | Pass |
| Dec 2025 | 2 | 9 / 9 | none | 75 | none | Pass |
| Jan 2026 | 2 | 9 / 9 | none | 86 | none | Pass |
| Feb 2026 | 2 | 7 / 7 | none | 80 | none | Pass |
| Mar 2026 | 2 | 8 / 8 | none | 87 | none | Pass |
| Apr 2026 | 2 | 7 / 7 | none | 76 | none | Pass |
| May 2026 | 2 | 9 / 9 | none | 75 | none | Pass |
| Jun 2026 | 1 | 8 / 8 | none | 91 | none | Pass |
| Jul 2026 | 1 | 9 / 9 | none | 85 | none | Pass |
| Aug 2026 | 1 | 7 / 7 | none | 88 | none | Pass |
| Sep 2026 | 1 | 6 / 6 | none | 70 | none | Pass |
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
- The checker has been revised twice, both times for false flags, and both changes are listed in docs/decisions.md. First, after the first run, it wrongly flagged 4 correct drafts that said "lost none" or "Nothing churned". Second, in review round 2, it flagged "100 %" four times in the v2 drafts: that number comes from our own rule about net revenue retention below 100 %, not from the data, so 100 followed by % is now skipped like the constants 3 and 12. Each fix has a case in the checker's self-test.
- The update format was changed once after reviewing two v1 drafts (Apr and Jul 2026): they were too dense, with 20 or more numbers in running prose, and had no ask. The v2 format is the five parts above. The v1 drafts are kept in data/ai/updates_v1.csv.
- Prompt v3 (review round 2) gives the model pre-formatted numbers, adds the TL;DR rules and the rule that placeholders go at the end of a sentence, and adds validate and retry (above). The v2 drafts are kept in data/ai/updates_v2.csv. This is the last prompt change for this project.
Limitations
- All data is synthetic, so the patterns are only as realistic as the assumptions behind them.
- Revenue equals MRR. No proration, VAT, tax, payables or receivables timing.
- The projection keeps other costs flat apart from the slider, treats collections as equal to revenue (annual upfront billing is ignored) and does not let new hires change the growth rate.
- Gross margin counts cloud hosting and customer success payroll as cost of revenue. Third-party software stays in operating costs because the data does not say which part serves customers.
- The AI check verifies numbers, named causes, structure and a few style rules. It does not judge writing quality. A draft could pass and still be badly written or stress the wrong thing. Every draft needs a founder to fill in the placeholders. Passing after a retry shows the draft meets the rules, not that the model follows them reliably: half the first drafts did not.
Known weaknesses in the v3 drafts (disclosed, not fixed)
- Sep 2026 writes the lost MRR as "-€1.520", with a minus sign. It also guesses "annual billing timing" as the cause of low collections.
- Jul 2026 has two sentences that end on a placeholder with no full stop.
- The Apr 2026 TL;DR says costs are rising faster than revenue. That is an inference. The checker cannot verify it.
- 2 of the 6 retries were caused by false flags in the cause check ("Contraction MRR was €0" and "churn was zero"). The checker was not fixed after the run.
- The rule that a planned hire is not a lowlight is only in the prompt. The checker does not test it.
- Each prompt version was run once. How much the drafts vary between runs is not measured.