The knowledge that wouldn't transfer
Thirty years in IT, fifteen in FP&A — and one problem I couldn't solve: how to hand my modeling knowledge to another human. The answer turned out to be a language.
Farseer is used in this example because it is the platform I build and work with.
I’ve been in IT for over thirty years — writing code since elementary school. I belong to the generation that grew up on the C64. By university I was earning my pocket money on serious work — programming for enormous systems with enormous databases, back when knowing how to program was exotic enough to charge for properly. The one limit was geography: in the country I lived in, that knowledge was hard to price at what it was worth elsewhere. Life was still great.
Fifteen years ago I turned toward FP&A. It was new and it was hard in the right way — logically demanding, full of things I didn’t know. Excel was a workbench with infinite options, and I sank happily into night marathons: computing ratios until dawn, building models of new-service launches and their impact on business-as-usual.
For the last six years I’ve worked in Farseer, on a database with its own formula language. The concept is simple — other vendors, like Pigment, apply it too — and the models it produces are powerful and expressive. I invested heavily in learning financial modeling properly, respecting everything data-warehouse science already knows: you can’t just wire formulas together without normalizing the data schema underneath. Data scales only if it’s structured well, and a model scales only if the data does.
The problem
Then came the wall: transferring that knowledge to another person was nearly impossible.
A financial model consisted of a theoretical part you could explain — and a practical part that existed only as the state of a user interface. Making the cognitive connection between the two was, for anyone new to the industry, close to hopeless. I experimented with every profile of person. In the search for people who could hold both halves, we dug through the whole of Croatia — and could count the experts on our fingers.
Technical people struggled to absorb the business knowledge and were awkward in front of clients. Business-minded people struggled with the technical implications of an implementation. Every hire ended up some blend of the two, with no recipe for finding the right one. Training juniors was brutal for the same reason: documenting a financial model was nearly impossible. Sometimes I reached for graphs, sometimes for screenshots, sometimes for helper Excels. None of it survived contact with a real question.
The realization
Breaking my head against this, I understood something that now looks obvious: every set of formulas and models is really one graph — a flow of data you can follow from source to output.
I tried to write a book about it. It didn’t work. What worked was experimenting: first with drawn graphs — which collapse into spaghetti after the N-th variable — then away from drawing entirely, toward a textual notation from which you can reconstruct the feel of a model without ever seeing a diagram. Step by step, that notation became FML — internally we call it FSL, Farseer Specific Language, our domain-specific subset.
# dimensions need no declaration — naming one in a grain is enough: # Clients, SKUs, Brands, Years, Months # relations — the FK graph that does the roll-ups link SKUs -> Brands # variables — each declares its grain, then its formula "Quantity IN" { Clients, SKUs, Years, Months } "Unit Price IN" { SKUs, Years, Months } "Revenue OUT" { Clients, SKUs, Years, Months } = "Quantity IN" * "Unit Price IN"
And the diagrams came back — but as output instead of input. Because the notation carries the whole structure, the tooling can draw the graph from the text, on demand: the picture is always true, and nobody maintains it.
The same model, three ways
A revenue module is a toy. So take the smallest thing every finance person recognises — a three-statement model, where the P&L feeds the balance sheet, the balance sheet feeds the cash flow, and the cash flow has to come back and agree with the balance sheet.
Here it is as a spreadsheet. Nothing is wrong with it: the numbers are right, the formulas are right, and a competent analyst built it.
Now the same model as text — in FML, with Farseer’s own formula language on the right-hand side of every =. Every line declares where a number lives (its grain), whether a human types it (IN) or the model computes it, and how balances roll forward.
# --- inputs: the only cells a planner types into --- "Revenue IN" { Months } "Cost of sales IN" { Months } "Opex IN" { Months } "Capex IN" { Months } "DSO days IN" { Months } "DPO days IN" { Months } "Depreciation rate IN" { Months } # --- imports: opening balances, loaded from the ledger --- IMPORT_PPE_opening { Months } @readonly IMPORT_Equity_opening { Months } @readonly IMPORT_Cash_opening { Months } @readonly # --- profit & loss --- "Depreciation" { Months } = delay("PPE", 1) * "Depreciation rate IN" @readonly "Net income OUT" { Months } = "Revenue IN" - "Cost of sales IN" - "Opex IN" - "Depreciation" @readonly # --- balance sheet: opening balance + what moved this month --- "Receivables" { Months } = "Revenue IN" * "DSO days IN" / 30 @rollup last @readonly "Payables" { Months } = "Cost of sales IN" * "DPO days IN" / 30 @rollup last @readonly "PPE" { Months } = cumsum("Capex IN" - "Depreciation", 0, IMPORT_PPE_opening) @rollup last @readonly "Equity" { Months } = cumsum("Net income OUT", 0, IMPORT_Equity_opening) @rollup last @readonly "Cash" { Months } = cumsum("Net change in cash OUT", 0, IMPORT_Cash_opening) @rollup last @readonly # --- cash flow: the indirect walk, straight off the two above --- "CF operating" { Months } = "Net income OUT" + "Depreciation" - ("Receivables" - delay("Receivables", 1)) + ("Payables" - delay("Payables", 1)) @readonly "CF investing" { Months } = -"Capex IN" @readonly "Net change in cash OUT" { Months } = "CF operating" + "CF investing" @readonly # --- the control: assets - liabilities - equity, must read zero --- "BS control" { Months } = round("Cash" + "Receivables" + "PPE" - "Payables" - "Equity", 1) @readonly
Thirty-odd lines, and every question the spreadsheet couldn’t answer is answered in place. Which numbers are entered? The seven marked IN — everything else is @readonly, computed by the model, and the platform enforces it. What does “the statements close” actually mean? The last line — a control that must read zero in every period, or something is wrong. What happens when a second entity arrives? You add it to the grain, once, and every balance rolls forward per entity.
Three Farseer formula functions carry most of the weight here — the same ones a modeller uses in the platform’s UI:
cumsum(movement, 0, opening)— the roll-forward. It runs through the timeline and accumulates the movement period by period, starting from the opening balance handed to it as the third argument. The middle argument is a growth factor:0means plain accumulation, while a rate there compounds — which is how you model a loan or an interest-bearing balance with the same function.delay(X, 1)— the same variable one month earlier. It does two jobs in this model: depreciation is charged on last month’s PP&E rather than this month’s (which also keeps the formula from chasing its own tail), and the cash flow needs the change in a balance, which is simply this month minusdelay(…, 1).round(X, 1)— rounds to one decimal, so the control reads a clean zero instead of floating-point dust from the divisions above it.
And because the structure is written down, the picture draws itself:
A junior can read that. A senior can review it — in git, as a diff, exactly the way a developer reviews source code: two versions side by side, every change visible, with the reason for it written next to the line that changed. The knowledge that refused to transfer for a decade suddenly had a carrier.
And then it turned out humans weren’t the most important readers. What happened when I handed the language to an AI is the next article.