Rufineer v0.43.0
Account
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 appIn the consoleWhat it does
New model… in your modelsCreates 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 modulegenerate 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 kindgenerate <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 dashboardgenerate dashboardWrites 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 casegenerate 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 tabconventions [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 tabcheckCompiles 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 sheetsdataCreates 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 nameexplain <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 rightbuild [--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.
Terminal
$ 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.

WriteRows
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:) alonecny (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.

Grammar
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.

WhereWords
Declarationsmodel timeline inputs table module statement check alert dashboard sensitivities scenarios
Timelinestart step fiscal_year ends spare
Inputs and tablesgrow from sheet
Modulesbalance solve funding tolerance
Fundingfirst 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)
Outputssection chart column line results case
Formulasif 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:

OldNow
period tenor = operations.start for 7ytenor = window(from: operations.start, for: 7y)
size: USD = … | base casesize: USD = base_case(…)
check "…" capex.idc convergednothing: every solve line gets its own check