Browse the docs
Start here
Guide
Sign in, start your first model in the browser, learn the project layout, then follow a small solar model from a new model to an Excel workbook.
Getting started
Rufineer is to a project finance model what Rails is to a web application: a project layout every model shares, generators that add a piece where it belongs, and conventions that write the plumbing. You write what the project earns, costs, builds and borrows, and say what each line is; Rufineer writes the sources and uses, depreciation, tax, the income statement, the cash flow and its debt waterfall, the balance sheet, the returns and the checks, and exports it all as a FAST-standard Excel workbook with live formulas.
A model is a folder of text files, and you write it in your browser, in the app at /app. Nothing needs installing: the compiler and the calculations run in the page, so the model compiles and solves as you type, a preview beside the editor shows the results and the sheets, and every value can be audited. Your models are kept with your account. With Pro, Export to Excel gives the workbook.
- Sign up, or sign in, on this website (your account). Writing, checking and previewing models is free.
- New model… in your models: give it a name, then choose Start from the conventions for a complete model that builds at once (Your first model), Start from nothing to write every line yourself (Tutorial 1), or a tutorial's finished model.
- Write the model's
.rfnfiles in the editor, and watch the preview beside it: it recompiles and solves as you type (below). - Export to Excel (with Pro): the solved, FAST-standard workbook, downloaded by your browser.
What Rufineer does for you
- The statements, written for you. Say what each line is (
revenue,cost,capex, a loan) and Rufineer writes sources and uses, tax, the statements, a waterfall in the order of the loans, the returns and the checks. Change any line by writing your own. - What a lender's model needs. Costs in several currencies, converted each period (
convert) at rates bought forward (fx); a loan's rate swapped (swap); VAT capitalised, refunded or paid by a VAT facility; tax holidays, losses that lapse, an interest cap, a minimum tax and withholding tax; accelerated allowances (VAT, tax, currencies). The example Kariba Solar has all of it. - Units, checked.
USD,MWh,%/y,USD/kWh. Per-year figures are converted to each period for you, and sums that make no sense don't compile. - No iterative calculation.
solvesplits a circular reference into calculated, pasted and difference rows. A macro written into the workbook settles it. - FAST layout, every time. Every number on the Inputs sheet, a Timing sheet, one sheet per module with its imports at the top, and no cross-sheet references inside calculations.
- Lenders' cases in one click. Sensitivities run case by case in the workbook, with the loan kept at its base-case size. A case that changes nothing is flagged.
- An editor that knows finance. The app underlines errors as you type, shows each line's formula in names when you hover, goes to a line's definition (F12) and its readers (⇧F12), completes names after a dot, each function's arguments and the values they take, units and modifiers, and formats every file the same way (The workspace).
The workspace
1. Your models
Your models lists the models in your account, the last changed first. New model… asks for a Name and what to start from: Start from the conventions (model.rfn and modules of revenue, costs, capex and a loan; Rufineer writes the statements, tax, returns and checks), Start from nothing (only model.rfn, one small module and checks.rfn), or, under Or from a tutorial's finished model, one of the tutorials' models: Highveld Wind 40 MW, Mopane Solar 40 MWac, Msasa Court, Mukwa Cement, Nyala Hydro or Highveld Wind, by hand. Import a .zip… makes a model from a .zip of a model's folder: its .rfn files, data workbooks (.xlsx) and notes (.md). Each model can be opened, renamed, downloaded (a .zip of its files) or deleted. Models are saved as you type and kept with your account.
2. The editor and the preview
Opening a model opens its workspace. The model's files are in the file tree on the left: a click opens a file, a double click keeps it open in a tab. + makes a new file, XL+ imports an Excel workbook (or drop one on the page), and right-clicking a file offers Open, Download, Copy path, Rename… and Delete…. Code, Split and Preview at the top (⌘1, ⌘2, ⌘3; Ctrl+1, 2, 3) show the editor, both side by side, or the preview; ⌘B shows or hides the files. The model recompiles and solves as you type, in your browser.
The status at the top says whether the model compiles and its checks pass; the preview's tabs show the Overview (results and charts), the Inputs (to try values and switches without changing the files), the Sheets as Excel will lay them out, the Sensitivities (Run sensitivities), the Checks, the Problems and what the Conventions wrote. Click any value to audit it. In the editor, problems are underlined as you type; hover a name to see its formula, F12 goes to where it is declared and ⇧F12 lists what reads it, ⇧⌥F (Shift+Alt+F) formats the file and ⌘P goes to a file. It saves by itself; ⌘S saves at once, and ⌥W (Alt+W) closes the tab.
The commands that add to a model (generate, conventions, explain, data) run in the Console (⌘K; Ctrl+K), under the editor, with or without rfn in front (The console). + Generate beside it adds a module or a line from a form.
3. Free, and Pro for the export
Writing, checking and previewing a model is free: the editor, the preview, the audit, the sensitivities, the console, and your models kept with your account and downloadable any time. Export to Excel, the solved, lender-grade workbook with its macros, is part of Pro, a monthly subscription (pricing). The workbook is built by Rufineer's service: choose Export to Excel at the top right of the workspace, or type build in the console, and your browser downloads it.
Your first model
Start from the conventions writes the Starter, a complete model that builds at once: a 50 MW solar plant with 18 months of construction and 20 years of operations, a senior loan, a debt service reserve, tax, sensitivities and checks. It is 66 lines, inputs included; Rufineer writes the statements. Every number is a placeholder, there to be replaced by your project's. The files on this page are exactly what it writes; the test suites build them and recalculate the workbook in LibreOffice with every check passing.
1. Create the model
In your models, choose New model…, name it My model, choose Start from the conventions and Create model. The model opens, with these files in the file tree:
my_model/
├── model.rfn the model: currency, tax, timeline, sheet order
├── ops.rfn Operations: capacity, tariff, operating costs (its inputs),
│ then energy and the `revenue` and `cost` lines
├── capex.rfn Construction: costs and asset life, then the `capex` lines
├── debt.rfn Debt: gearing, interest, tenor, tax rate, discount rate,
│ then the loan and the debt service reserve
├── reports.rfn what people read: the sensitivities
├── checks.rfn your own checks
└── README.mdEach module is one file, and one sheet in the workbook: its inputs first, which go on the Inputs sheet under the module's title, then its calculations. reports.rfn and checks.rfn hold what people read. Project layout has the rules. Start from nothing writes model.rfn, one small module in ops.rfn and checks.rfn, for starting from nothing.
2. The model and its timeline
// model.rfn: what the model is, its timeline, and the settings its
// conventions read.
//
// You write what the project earns, costs and borrows, and say what each
// line is (`revenue`, `cost`, `capex`, a `loan`). Rufineer writes the rest:
// sources and uses, fixed assets, tax, the income statement, the cash flow
// and its waterfall, the balance sheet, the returns and the checks.
// The Conventions tab shows them.
//
// Each module is one file, its inputs then its calculations, and one sheet
// in the workbook (ops.rfn: Operations). A module that grows can be a
// folder of files instead (debt/senior.rfn, debt/reserves.rfn).
model "My model" {
currency USD
display thousands // calculation sheets show USD '000
day_count act/365
tax inputs.tax_rate // tax on profit, with losses carried forward
discount inputs.discount_rate // the NPVs on the Returns sheet
sheets [ops, capex, debt]
}
timeline {
start 2027-01-01 // financial close
step quarter
construction 18m
operations 20y
}tax and discount are settings the conventions read: the tax rate on profit, and the rate the NPVs are measured at. sheets sets the order of your calculation sheets; the ones Rufineer writes follow them.
3. What the plant earns and costs
A module is one file: its numbers first, in inputs blocks, then its workings, in the module block.
// ops.rfn: what the plant produces, earns and costs to run: its inputs,
// then its calculations.
//
// `revenue` and `cost` say what a line is: the conventions put it in the
// income statement and the cash flow. Kind lines are in the model's
// currency, so they need no unit.
inputs "Plant" {
capacity = 50 MW
capacity_factor = 28 %
/// The PPA price, indexed every year from the start of operations
tariff = 85 USD/MWh | grow 2 %/y from operations.start
}
inputs "Operating costs" {
/// O&M, per kW of capacity
om = 22 USD/kW/y | grow 2.5 %/y from operations.start
insurance = 450_000 USD/y | grow 2.5 %/y from operations.start
}
module ops "Operations" {
/// Capacity × capacity factor × the hours in each quarter
energy: MWh = inputs.capacity * inputs.capacity_factor * 8_760 h/y | during operations
"Energy sold"
revenue sales = energy * inputs.tariff "Electricity sales"
cost om = inputs.capacity * inputs.om | during operations "O&M"
cost insurance = inputs.insurance | during operations "Insurance"
}energy is declared in MWh and the formula is MW × % × hours a year, so the compiler multiplies by each quarter's year fraction and shows that term in the formula. revenue sales and cost om say what the lines are: that is all Rufineer needs to put them in the income statement and the cash flow.
4. Construction
// capex.rfn: construction: what building the plant costs, then the
// spending.
//
// `capex` is spent, paid for by the loans and share capital while the
// plant is being built (the Sources and uses sheet) and depreciated over
// its `life` once it runs (the Fixed assets sheet).
inputs "Construction costs" {
epc = 55_000_000 USD @source "EPC contract"
development = 3_000_000 USD
/// How long the plant is depreciated over
asset_life = 25y
}
module capex "Construction" {
capex epc = spread(inputs.epc, over: construction) life inputs.asset_life
capex development = inputs.development | during construction.first life inputs.asset_life
}Capex is paid for while the plant is built, depreciated over its life once it runs.
5. The loan
// debt.rfn: the debt, and the tax and returns settings.
//
// A loan written here draws its `share:` of the construction costs, is
// paid from the cash flow in the order the loans are written, and has
// its interest during construction added to the balance. Share capital
// pays the rest. A `reserve` sits in the waterfall where it is written:
// it holds back that much of the debt service above it.
inputs "Senior loan" {
/// The share of each quarter's construction costs the loan pays
gearing = 70 %
senior_rate = 8 %/y
tenor = 15y
}
inputs "Tax and returns" {
tax_rate = 25 %
discount_rate = 10 %/y
}
module debt "Debt" {
/// Repaid over the tenor, from the start of operations
tenor = window(from: operations.start, for: inputs.tenor)
senior = loan(share: inputs.gearing, rate: inputs.senior_rate, over: tenor, repay: annuity)
"Senior loan"
/// Six months of debt service, held back for the lenders
reserve dsra = 6m "Debt service reserve"
}The loan draws 70% of each quarter's construction costs (share capital pays the rest), has its interest during construction added to the balance, and is repaid as an annuity from the cash flow. The reserve below it holds back six months of its debt service. See Conventions for everything a loan and a reserve can say.
6. Reports and checks
// reports.rfn: what people read beyond the sheets: the sensitivities (and,
// when you add them, your own statements and dashboard).
//
// The Run sensitivities macro solves each case and fills the Sensitivities
// sheet.
sensitivities {
results {
returns.project_irr "Project IRR"
returns.equity_irr "Equity IRR"
returns.min_dscr "Minimum DSCR"
}
case "Tariff -10%" { inputs.tariff * 90 % }
case "Capacity factor -2%" { inputs.capacity_factor - 2 % }
case "EPC +10%" { inputs.epc * 110 % }
case "Interest +1%" { inputs.senior_rate + 1 %/y }
}// checks.rfn: your own checks and alerts. The standard ones come
// with the conventions: sources equal uses, the balance sheet balances,
// each loan is repaid, cash never goes negative.
alert "Minimum DSCR below 1.20x" returns.min_dscr >= 1.20 xChecks fail the master check; alerts only warn. Rufineer adds the checks every such model needs, and writes a dashboard because the project has none of its own.
7. What Rufineer writes
From those lines, Rufineer writes seven sheets and three statements. The preview's Conventions tab shows them as Rufineer; this is the cash flow and its waterfall, as conventions cashflow prints it in the console:
$ rfn conventions cashflow
module cashflow "Cash flow" {
section "Cash flow available for debt service"
revenue: USD = income.revenue "Revenue"
costs: USD = income.operating_costs "Operating costs"
tax: USD = income.tax "Tax paid"
capex: USD = capex.epc + capex.development - sources.spend "Capex after construction"
cfads: USD = revenue - costs - tax - capex "Cash flow available for debt service"
section "Debt service, in order of rank"
/// What the company has to pay its debt: this period's cash flow and the cash in the bank
available: USD = cfads + cash_opening "Cash available for debt service"
with_dsra: USD = available + debt.dsra_opening "Cash with debt service reserve added"
after_senior: USD = with_dsra - debt.senior.service "Cash after senior loan"
after_dsra: USD = after_senior - debt.dsra "Cash after debt service reserve"
section "To the shareholders"
/// Construction is paid for by the loans and the share capital: net, nothing
funding: USD = debt.senior.drawn + sources.equity - sources.spend "Funding, net"
for_shareholders: USD = after_dsra + funding "Cash for shareholders"
/// Dividends only out of profits: what profits don't cover waits in the bank
distributable: USD = max(0, balance.retained_earnings.opening + income.profit)
"Profits available to distribute"
dividends: USD = max(0, min(for_shareholders, distributable)) "Dividends"
/// When the company is wound up, whatever is left goes back to the shareholders
capital_return: USD = max(0, for_shareholders - dividends) | during operations.last
"Capital returned"
section "Cash at bank"
cash: USD = for_shareholders - dividends - capital_return "Cash at bank"
cash_opening: USD = prev(cash) "Cash at bank, opening"
}And the returns, measured on the cash flows above:
module returns "Returns" {
section "The project, before financing"
project_flow: USD = income.ebitda - tax.unlevered_charge - (capex.epc + capex.development)
"Project cash flow"
project_irr: %/y = irr(project_flow) "Project IRR"
project_npv: USD = npv(inputs.discount_rate, project_flow) "Project NPV"
section "The shareholders"
/// Share capital in, dividends and capital out
equity_flow: USD = cashflow.dividends + cashflow.capital_return - sources.equity
"Equity cash flow"
equity_irr: %/y = irr(equity_flow) "Equity IRR"
equity_npv: USD = npv(inputs.discount_rate, equity_flow) "Equity NPV"
section "Cover"
senior_dscr: x = dscr(cfads: cashflow.cfads, loan: debt.senior) "Senior loan: DSCR"
min_dscr: x = min(senior_dscr, during: debt.senior.test_date) "Minimum DSCR"
}To change any of it, write it yourself: a line in a module named cashflow replaces the line of that name, your own dashboard replaces Rufineer's (Conventions has the list).
8. Export
The status says Compiles · checks OK, and the Overview shows the results as you change the files. With Pro, choose Export to Excel at the top right (or type build in the console); your browser downloads the workbook:
my_model.xlsm
Dashboard master check, alerts, key results, two charts
Navigator a link to every sheet and input group; every sheet's Navigate button comes back here
Sensitivities each case's results next to the base case
Inputs every number, the sensitivity cases and the active-case switch
Timing Indexation Scenarios
Ops Capex Debt your modules
Sources and uses Fixed assets Tax what Rufineer writes …
Income statement Cash flow Balance sheet Returns
Annual income Annual cash flow Annual balance sheet … and the statements by year
ChecksOpen the file in Excel. The workbook opens solved: the reserve's target depends on the debt it covers, a loop Rufineer settles before it writes the workbook, and the Solve macro settles again after you change an input in Excel (enable macros when Excel asks). Run sensitivities fills the Sensitivities sheet; the preview's Sensitivities tab shows the same, free, without leaving the app. With the placeholder numbers the project IRR is 12.1% (after the tax it would pay without debt), the equity IRR 18.3% and the minimum DSCR 1.63x, and every check passes: sources equal uses, the balance sheet balances in every quarter, the loan is repaid.
9. Add to it
Add a revenue named heat, then a loan named mezzanine, in the console (⌘K; the rfn is optional):
$ rfn generate revenue heat
insert ops.rfn
insert ops.rfn
$ rfn generate loan mezzanine
insert debt.rfn
insert debt.rfnEach generator adds a line of its kind at the end of the module it belongs in (--in another), and its inputs to the inputs block above the module (a loan's in a block of its own), with placeholder numbers, formatted: the model still builds, and the statements pick the new line up. Revenue, cost, cost of sales, other income, capex, loan and reserve can be generated. Tutorial 1 builds a wind farm this way from a blank model.
Project layout
Every Rufineer project has the same shape, so you always know where a line goes and where to find it.
my_model/
├── model.rfn what the model is: name, currency, tax, timeline, sheet order
├── ops.rfn a module: its inputs blocks first, then module ops "Operations" { … }
├── capex.rfn
├── debt.rfn
├── reports.rfn statements, dashboard, sensitivities (when you want them)
├── checks.rfn your own checks and alerts
└── data/tables.xlsx tables too long to type, when there are anyThree rules
- One module, one file, one sheet.
debt.rfnholds itsinputsblocks, thenmodule debt, and becomes the Debt sheet. Its inputs go on the Inputs sheet, under the module's title. - A number is an input. Calculations only combine inputs and other lines. A number typed into a calculation is moved to the Inputs sheet anyway.
sheetsin model.rfn sets the order of the calculation sheets. The Inputs sheet follows the same order.
A module that grows can be a folder instead: debt/senior.rfn, debt/mezzanine.rfn and debt/reserves.rfn each write module debt { … }, with inputs blocks of their own if they need them, and the parts make one module and one sheet, in the order sheets lists them (sheets [ops, capex, debt/senior, debt/mezzanine, debt/reserves]). See The shape of a model.
The first layout, each module's numbers in inputs/ops.rfn beside its workings in calculations/ops.rfn, and outputs/ for the rest, still compiles exactly as before. In the app, the menu (☰) of a model laid out that way has Use one file per module…, which moves the files: inputs/x.rfn and calculations/x.rfn become x.rfn, outputs/checks.rfn becomes checks.rfn and the other outputs reports.rfn. Nothing changes in the workbook: the same sheets, and every number the same.
Generators
A module named reserves, with the title Debt service reserve:
$ rfn generate module reserves "Debt service reserve"
create reserves.rfn
update model.rfn (sheets: ops, capex, debt, reserves)The new file starts with a commented-out inputs block to fill in, then an empty module reserves "Debt service reserve" { }. A line of a kind goes where it belongs, with its inputs in the inputs block above the module: a revenue into ops, capex into capex, loan and reserve into debt (making the module if it isn't there; --in to choose another). generate dashboard and generate case write into reports.rfn. Names are lower case (letters, digits and _); keywords, sheet names and the timeline's phases are refused. g is short for generate. In a model in the first layout, the generators write there, as before.
How the files are read
All the files make one model: any line can use any other by its full name (debt.senior, cashflow.cfads), whichever file it is in, and whether you wrote it or Rufineer did. The compiler reads model.rfn first, then the modules' files and folders in the order of sheets, then the rest by name. A model is a plain folder: Download the model's files (.zip) in the menu (☰) gives it to you as it is, to keep in git or send to a reviewer, and Import a .zip… brings a folder with a model.rfn back in.