Shipmind Labs

A quarterly margin is not the sum of three monthly margins. If your model rolls up every line the same way, it is already wrong, and the error sits in the lines nobody checks.

We ran into this while building finmodel, our open-source financial modelling library. The rule we landed on is narrow: only input lines roll up. Everything else is either carried across an edge or evaluated again.

Three kinds of line, and each one behaves differently.

An input line sums by default. Revenue, headcount cost, ad spend: a quarter is January plus February plus March. You can override that, since a rollups mapping can declare FIRST or LAST instead of SUM, which matters for anything that is a level rather than a flow.

A carry is a balance, and it takes an edge rather than a total. A carry names an opening, a closing, and optionally a movement. Cash at the start of Q1 is cash at the start of January, so construction claims FIRST for the opening and LAST for a closing that is an input. Nothing gets summed. Summing opening balances is how you end up reporting four times your cash.

A computed line does not roll up at all. It gets evaluated a second time, over the rolled-up inputs. So a quarter's ratio is the quarter's numerator over the quarter's denominator, not an average or a sum of monthly ratios.

What actually saved us was refusing to be clever about disagreements.

The claim step raises a spec error instead of resolving a conflict: if the mapping disagrees with what a carry requires, if a line is carried twice, if opening and closing are the same id, if an opening is not an input line, or if someone hands a rollup to a computed line. Those are all modelling mistakes, and each of them produces plausible-looking numbers if you let it through. A run also requires values for exactly the twelve month ids, not a superset and not a subset.

An opening is not read from its own month's values. That is a spec error too. It comes from the previous month's closing, and only the first month may take an opening from the mapping, or zero. One source of truth for continuity, and no month can quietly restate where it started.

Then the model proves itself. For every carry that declares a movement, each period gets a proof: opening plus movement must equal closing. If it doesn't, or if the chain touches a not-applicable value, the run fails and carries the proof with it. Not a warning in a log, but a failed run with the arithmetic attached.

The broader point generalises past finance. When you aggregate a derived value, you have a choice: aggregate the derived numbers, or re-derive from aggregated inputs. The first is faster and almost always wrong. The second is the one we think survives an audit.

So if you maintain a model that aggregates across time, it is worth checking how your ratios get computed at the quarter level, re-derived or rolled up.

Was this useful?

Building something similar?

or email hello@shipmindlabs.com