Browse the docs
Look it up
Reference
The commands (in the app and its console), the standard library, the grammar and the keywords.
Commands
Most commands are in the app itself: a button, a tab of the preview or a key. All of them can be typed in the app's Console (⌘K; Ctrl+K on Windows and Linux), as you would have typed them after rfn on the command line; the rfn is optional. The console has every option.
| In the app | In the console | What it does |
|---|---|---|
| New model… in your models | Creates a model, one file per module: model.rfn, the modules' files and checks.rfn. Start from the conventions is the Starter, a plant (ops.rfn), its capex (capex.rfn) and a loan (debt.rfn) written with kinds, and its sensitivities (reports.rfn), so it builds at once with its statements (the first model); Start from nothing is model.rfn, one small module in ops.rfn and checks.rfn. A tutorial's finished model can be the start too. | |
| + Generate: a module | generate module <name> ["Title"] | Adds <name>.rfn, a commented-out inputs block then the module, and puts the sheet in sheets (Project layout). g for short. |
| + Generate: a line of a kind | generate <kind> <name> [--in module] | Adds a line of a kind to the module's file, and its inputs to the inputs block above the module: revenue, cost, cost_of_sales and income in ops, capex in capex, loan and reserve in debt (the module is made if it isn't there). The files are formatted after. |
| Overview › Create dashboard | generate dashboard | Writes a dashboard into reports.rfn from the model's own lines: its IRRs and NPVs, the DSCR, the funding, a column chart of revenue and EBITDA and a line chart of the DSCR. For a model that has no dashboard of its own; change it as you like. |
| Sensitivities › Add sensitivity case | generate case [input] | Adds a case that moves input (or the input that usually matters most and no case moves yet: the tariff, the yield, the EPC, O&M …) by +10%, then −10%. Makes the sensitivities block in reports.rfn if there is none, comparing the dashboard's results. |
| The preview's Conventions tab | conventions [module] | What the conventions write for the model, as Rufineer: all of it, or one sheet (sources, assets, tax, income, cashflow, balance, returns). |
| The underlines, the preview's status and Problems tab | check | Compiles the model and reports unit errors, undeclared loops, misspelled names (with suggestions), missing data-workbook sheets. The app does this as you type. |
| The data workbook's Add the missing sheets | data | Creates the data workbook, or adds a sheet for each new table. Profiles used by spread get one labelled row per period. Existing values are kept. |
| Format Document (⇧⌥F; Shift+Alt+F) | fmt [files or folders] | Formats every .rfn file in the model (or the files and folders named) the standard way: indentation, spacing, and names, units, =, modifiers, labels and notes lined up in columns. A line over 100 characters is broken where it can be (lists of names, formulas before an operator or a | during, labels), and lined up again. Only spaces, blank lines and line breaks change, never the model. --check lists the files that aren't formatted. Files with syntax errors are skipped. |
| Click a value: the audit panel; hover a name | explain <line> | Where a line lands, its Excel formula for the first period, what it reads and what reads it. The audit panel shows the same for any cell, with the values, and follows the chain. As Excel's F2 does, the cells it reads are outlined on the sheet, each in its own colour (the same colour on its name in the formulas), and the cells that read it are dashed. |
| Export to Excel, at the top right | build [--exclude-macros] | With Pro: downloads <model>.xlsm, solved, with the Solve and RunSensitivities macros and their buttons. Export without macros (.xlsx) in the menu (☰), or --exclude-macros, writes a plain .xlsx. The workbook is built on Rufineer's server. |
| ☰ › Download the model's files (.zip) | Downloads the model's folder: its .rfn files, data workbooks and notes. Import a .zip… in your models makes a model from one. |
$ rfn explain debt.principal
debt.principal "Principal"
where Debt!H33 (first period; the same formula runs across the row)
unit USD
formula =IF(H$23,MIN(MAX(H$32/$D$12-H$31,0),H$26),0)
note Sculpted: repay what the target DSCR allows, never more than is owed
reads debt.tenor, debt.cfads, inputs.target_dscr, debt.interest, debt.loan.opening
read by debt.dscr, debt.import.debt.principal …Still to come: exporting one scenario only, and importing an existing workbook as a first draft of a rebuild.
Library components
Components are functions that build several rows. Each is written as a whole line; its rows are named after the line.
| Write | Rows |
|---|---|
tenor = window(from: date, for: 7y) | tenor (1 inside it), tenor.start, tenor.end |
plant = depreciation(cost:, life:, from:), or schedule: [50 %, 25 %, 25 %] for life: | plant (the charge), .added, .cost_to_date, .in_service, .within_life (a schedule: .year_1, .lag_2, .year_2 …), .opening, .book_value |
losses = tax_losses(profit:)optional: expire: (a length), limit: (a share of the profit) | .profit, .opening, .added, .used, .closing, .taxable; with expire: .made_to_date, .due, .used_to_date, .expired_to_date, .expired; with limit: .limit |
cny = fx(spot:, forward:, hedged:), or points: for forward:; fx(spot:) alone | cny (the blended rate), .spot, .forward, .hedged (Forward cover) |
base = swap(floating:, fixed:, hedged:)optional: spread: | base (the blended rate), .floating, .fixed, .spread, .hedged (Hedging the rate) |
size: USD = base_case(formula) | size.live, size.base_case (stored by the Solve macro), size, and a check |
toll = seek(inputs.toll, returns.equity_irr == inputs.target_irr)or seek(input:, target:) | toll (the input, as the Solve macro left it), toll.gap (left side less right side), and a check that the target is met |
senior = loan(drawn:, rate:, over:, repay:)optional: interest_every:, principal_every:, cash:, capitalise:, refinance:; with the conventions, size: (70 % of costs, 70 % of uses, an amount) or the older share: in place of drawn:, and signed: base_case, upfront_fee:, commitment_fee:, interest_during_construction: paid | rolled_up, financing_costs: capitalised | amortised | expensed, rank:, by: shareholders (Funding) | senior (the closing balance), .opening, .drawn, .repaid, .interest, .interest_paid, .principal, .service, the date flags; with cash: .interest_due, .principal_due, .arrears; with capitalise: .capitalised; with refinance: .refinanced (Loans) |
cover: x = dscr(cfads:, loan:), llcr(cfads:, loan:) | the cover ratio on the loan's test dates |
The reserves, waterfall tiers and partnership promotes on the roadmap will be components of the same kind.
Grammar
All of it. Words in quotes are the keywords; everything else is a name, a number or a formula.
file := declaration*
declaration := model | timeline | inputs | table | module | statement | check | dashboard | sensitivities | scenarios
model := "model" STRING "{" (SETTING VALUE)* "}" currency, display, decimals, day_count, data, sheets,
tax, discount, reports, conventions, drawdown,
vat, vat_refund, vat_facility, navigator, valid_until
tax := "tax" (formula | "{" (TAXRULE formula)* "}")
TAXRULE := rate | holiday | holiday_rate | loss_expiry | loss_limit | interest_cap | minimum | withholding
drawdown := "drawdown" (ORDER | "{" ("before" "financial_close" ":" NAME)?
("from" "financial_close" ":" ORDER)? ("equity" "in_order" LIST)? "}")
ORDER := pro_rata ("on" (uses | costs))? | equity_first | debt_first | back_ended | "in_order" LIST
LIST := "[" (NAME | "[" NAME ("," NAME)* "]") ("," …)* "]" [ ]: pari passu
timeline := "timeline" "{" "start" DATE ("step" STEP)? ("fiscal_year" "ends" MONTH)? phase* ("spare" LENGTH)? "}"
phase := NAME (LENGTH | NAME "(" (case ":" LENGTH),+ ")") ("step" STEP)?
STEP: month | quarter | half | year
inputs := "inputs" STRING "{" input* "}"
input := DOC* NAME "=" value STRING? annotation* ("|" "grow" formula "from" formula)?
annotation := "@source" STRING | "@note" STRING
table := DOC* "table" NAME ":" unit ("sheet" STRING)? ("by" NAME | "by" "[" NAME ("," NAME)* "]")?
scenarios := DOC* "scenarios" NAME STRING? ("start" NAME)? "{" (NAME STRING?)+ "}" an input's value may then be NAME "(" case ":" value, … ")"
module := "module" NAME STRING? "{" item* "}"
item := line | "solve" line | KIND line | "shares" NAME "up" "to" formula STRING? | balance | funding
line := DOC* NAME (":" unit)? "=" formula ("|" "during" formula)* TAIL* STRING? ("tolerance" formula)?
KIND := revenue | cost_of_sales | cost | income | capex | reserve | use | shares
read only before a line's name
TAIL := ("life" | "allowance") formula | "vat" (recoverable | capitalised | none) capex only
balance := "balance" NAME ":" unit "{" ("start" formula)? (("+" | "-") formula STRING?)* "}"
funding := "funding" NAME "{" "first" (source | "pro_rata" "{" shared* "}") ("then" source)* "}"
source := NAME ("up" "to" formula | "rest")
shared := NAME ("share" formula ("up" "to" formula)? | "rest")
statement := "statement" NAME STRING? ("|" ("sum" "by" GRAIN | "at" GRAIN ("end" | "start")))?
"{" (("+" | "-" | "=") formula STRING? | "+" MODULE | "section" STRING)* "}"
GRAIN: year | half
check := ("check" | "alert") STRING formula ("at" NAME)? ("tolerance" formula)? ("|" "during" formula)*
dashboard := "dashboard" "{" ("section" STRING | NAME STRING? | chart)* "}"
chart := "chart" ("column" | "line") STRING "{" (NAME STRING?)* "}"
sensitivities := "sensitivities" "{" "results" "{" (NAME STRING?)* "}" ("case" STRING "{" change* "}")* "}"
change := NAME ("*" | "+" | "-") value | NAME "=" NAME the second picks a scenario's case
formula := value | NAME | NAME "(" arguments? ")" | "(" formula ")" | "-" formula
| formula OPERATOR formula | "if" formula "then" formula "else" formula
| formula "of" NAME in a loan's size: or a class of shares' up to
| "{" NAME ":" formula ("," NAME ":" formula)* "}" a record: a capex's depreciation:
| "[" formula ("," formula)* "]" a list: depreciation's schedule:
OPERATOR := + - * / ^ == != < <= > >= "and" "or"
arguments := (NAME ":")? formula ("," (NAME ":")? formula)*
value := NUMBER unit? | DATE
unit := WORD ("/" WORD)* | "%" | "%/y"Keywords
Rufineer has 47 keywords, and most read as plain English. A test keeps the list from growing: a new capability is a function unless it can't be one.
| Where | Words |
|---|---|
| Declarations | model timeline inputs table module statement check alert dashboard sensitivities scenarios |
| Timeline | start step fiscal_year ends spare |
| Inputs and tables | grow from sheet |
| Modules | balance solve funding tolerance |
| Funding | first then pro_rata share up to rest |
After a | | during sum by at end (then year or half, which are names, like the steps month and quarter) |
| Outputs | section chart column line results case |
| Formulas | if else and or of |
Functions are not keywords: they are listed on the Language page, and your editor completes them.
Nor are the kinds of line (revenue, cost_of_sales, cost, income, capex, reserve, use, shares), the words inside a drawdown setting, and a capex's life, allowance and vat: they are read only in front of a line's name, or after a capex's formula, so revenue: USD = … is still a line called revenue (see Conventions).
Older forms
These still compile, with a warning that shows the new way:
| Old | Now |
|---|---|
period tenor = operations.start for 7y | tenor = window(from: operations.start, for: 7y) |
size: USD = … | base case | size: USD = base_case(…) |
check "…" capex.idc converged | nothing: every solve line gets its own check |