Rufineer v0.43.0
Account
Browse the docs

Step by step

Tutorial 1: a wind farm

Build a lender-grade model of a 40 MW wind farm in your browser, from a blank model, one step at a time. You write what it earns, costs and borrows; Rufineer writes the sources and uses, tax, the statements, the debt waterfall, the returns and the checks.

9 stepsEvery file on this page is testedEnds with a solved workbook

What you will build

This tutorial builds a complete project finance model for Highveld Wind, a 40 MW wind farm: 18 months of construction, 20 years of operation, a senior loan sculpted to a target DSCR, a debt service reserve, tax, three financial statements that balance, returns, a dashboard and sensitivities. You start from a blank model in the app and add one piece at a time; the preview compiles and solves the model as you type, so you see each step work before you export the workbook.

You write what the wind farm produces, earns, costs and borrows, and you say what each line is: revenue, a cost, capex, a loan, a reserve. From that, Rufineer writes the rest of the model the way every project finance model is laid out, as sheets of live formulas: sources and uses, fixed assets, tax, the income statement, the cash flow and its debt waterfall, the balance sheet, the returns and the checks. The finished model is about 100 lines.

Each step introduces a few ideas of the language:

StepWhat you meet
1. Start a modelNew model…, the model block, the timeline and its phases, the preview
2. Inputsinputs blocks, units, @source and @note, /// descriptions, | grow
3. Operationsmodules and lines, revenue and cost, | during, year fractions, the audit panel, the Conventions tab
4. Mistakesunit errors, kind lines are money, misspelt names, the Problems panel
5. Constructionrfn generate, rfn data, tables and the data workbook, spread, capex … life
6. The loanwindow, loan(size: … of costs, repay: sculpted(…)), a reserve, the sources and uses and the waterfall Rufineer writes
7. Taxthe tax setting, losses carried forward, the project's own tax
8. Resultsa line of your own in a sheet Rufineer writes, dashboard, sensitivities
9. Sensitivities and Excelthe Sensitivities tab, Export to Excel (Pro), the workbook in Excel

Before you start, sign up on this website, or sign in: nothing needs installing, and everything but the export is free. You write the model's files in the app, in your browser, with the file tree on the left and the preview beside the editor, which compiles and solves the model as you type. The commands shown as $ rfn … are typed in the app's Console (⌘K; Ctrl+K on Windows and Linux), where the rfn is optional; rfn build there is Export to Excel (Pro). Every file and every message on this page comes from the finished model, and the test suite replays the steps, so what you see is what Rufineer does.

Step 1: Start a model

You meet: New model…, the model block, the timeline, the preview.

In your models, choose New model…, name it highveld, choose Start from nothing and Create model:

Output
New model "highveld" (Start from nothing):
  model.rfn
  ops.rfn
  checks.rfn
  README.md

Start from nothing writes three files and a README: model.rfn, one small module and a checks file. (Start from the conventions writes the Starter, a complete generic plant: see the Guide.) The model opens in its workspace, with these files in the file tree on the left.

A Rufineer model is laid out like a FAST workbook. model.rfn says what the model is. Each module is one file, ops.rfn here: its inputs first, which go on the Inputs sheet, then its calculations, which become a sheet of their own. checks.rfn holds your own checks, and reports.rfn, when you add it, what people read: the dashboard and the sensitivities. Open model.rfn, and choose Split at the top (⌘2; Ctrl+2) to see the editor and the preview side by side. Then replace the file with this:

model.rfn
// model.rfn: what the model is, and its timeline.

model "Highveld Wind 40 MW" {
  currency  USD
  display   thousands  // calculation sheets show USD '000
  day_count act/365
  sheets    [ops]
}

timeline {
  start        2027-01-01  // financial close
  step         quarter     // one column per quarter
  construction 18m
  operations   20y
}

The timeline is the columns of every calculation sheet: a column per quarter from financial close, 6 quarters of construction and 80 of operation. construction and operations are phases. Each becomes a row of 1s and 0s (a flag) on the Timing sheet, and you use them to switch lines on and off. display thousands shows money in thousands on the calculation sheets; the numbers themselves stay in full. Each phase can have a step of its own, e.g. construction 18m step month: see Timeline.

As soon as the file is replaced, the status at the top says Compiles · checks OK. Open its Sheets tab: the model is already a workbook, with a Dashboard and its master check, an Inputs sheet, the Timing sheet with period dates and phase flags, an Ops sheet for the module Blank wrote, and a Checks sheet.

Each sheet is laid out as Excel will lay it out, with live formulas; step 9 exports it. You never edit the workbook: you change the .rfn files and the preview follows.

Step 2: Inputs

You meet: inputs blocks, units, @source, @note, ///, | grow.

Every number the model uses is an input, written in an inputs block at the top of its module's file, and every input goes on the Inputs sheet, under the module's title. That's the FAST rule of one place for assumptions, and the compiler enforces it: a number typed into a calculation is moved to the Inputs sheet anyway. Open ops.rfn and replace the inputs block at its top with these three, leaving the module below it as it is:

ops.rfn
// ops.rfn: the wind farm and what it sells.

inputs "Wind farm" {
  capacity        = 40 MW    @source "Turbine supply agreement: 10 turbines of 4 MW"
  /// Net of wake and electrical losses, in a year of average wind (P50)
  capacity_factor = 34 %     @source "Wind resource assessment, P50"
  availability    = 97 %     @note "Turbine availability guaranteed by the O&M contract"
  degradation     = 0.3 %/y  @note "Output lost each year as the turbines age"
}

inputs "Revenue" {
  /// The PPA tariff in 2027 money: indexed each year from financial close,
  /// so the first year of operations already pays one year's indexation
  tariff = 85 USD/MWh  @source "Power purchase agreement, clause 7"
                       | grow 2.0 %/y from construction.start
}

inputs "Operating costs" {
  /// Quoted in 2027 money too, and indexed the same way
  om_fee     = 38_000 USD/MW/y  @source "Turbine O&M contract: full service"
                                | grow 2.5 %/y from construction.start
  insurance  = 420_000 USD/y | grow 2.5 %/y from construction.start
  /// Paid to the landowners as a share of revenue
  land_lease = 3.0 %            @source "Land lease agreements"
}

// ops.rfn: one module, one sheet in the workbook.

module ops "Operations" {
  revenue: USD = inputs.revenue | during operations
  costs:   USD = inputs.costs   | during operations
  ebitda:  USD = revenue - costs
}
  • Every number has a unit. MW, %, %/y, USD/MWh, USD/MW/y. Units are what let the compiler check your formulas and convert between them (MW to MWh, per year to per quarter).
  • @source and @note go into the Inputs sheet's notes column: where the number comes from, and anything a reviewer should know. A /// line above an input describes it, and shows when you hover over its name in the editor.
  • | grow 2.0 %/y from construction.start makes the tariff an indexed series: the compiler writes the growth factor on an Indexation sheet, and the model reads the escalated tariff in each period. The tariff is quoted in 2027 money, so it is indexed from financial close, not from the start of operations: by the first year of operation it has already grown once.
  • 40 MW, 38_000 USD/MW/y: underscores make large numbers readable and are ignored.

The preview's status turns red, 2 errors, and two names are underlined in the module at the bottom of ops.rfn. The Problems panel (⇧⌘M, Ctrl+Shift+M on Windows and Linux) says why, as does the preview's Problems tab:

Problems
error[name]: I can't find `inputs.revenue`
   --> ops.rfn:30:18
   |
30 |   revenue: USD = inputs.revenue | during operations
   |                  ^^^^^^^^^^^^^^ not defined

error[name]: I can't find `inputs.costs`
   --> ops.rfn:31:18
   |
31 |   costs:   USD = inputs.costs   | during operations
   |                  ^^^^^^^^^^^^ not defined

The module Blank wrote still uses the old inputs, so that is what you write next. Click a problem to jump to its line.

Step 3: Operations: what it earns and costs

You meet: modules, lines, revenue and cost, | during, year fractions, the audit panel, the Conventions tab.

A module is one calculation sheet. Each line in it is one row in the workbook: name: unit = formula, then optional modifiers after |, then an optional label in quotes. Replace the module in ops.rfn, below the inputs, so that the file reads:

ops.rfn
// ops.rfn: the wind farm and what it sells.

inputs "Wind farm" {
  capacity        = 40 MW    @source "Turbine supply agreement: 10 turbines of 4 MW"
  /// Net of wake and electrical losses, in a year of average wind (P50)
  capacity_factor = 34 %     @source "Wind resource assessment, P50"
  availability    = 97 %     @note "Turbine availability guaranteed by the O&M contract"
  degradation     = 0.3 %/y  @note "Output lost each year as the turbines age"
}

inputs "Revenue" {
  /// The PPA tariff in 2027 money: indexed each year from financial close,
  /// so the first year of operations already pays one year's indexation
  tariff = 85 USD/MWh  @source "Power purchase agreement, clause 7"
                       | grow 2.0 %/y from construction.start
}

inputs "Operating costs" {
  /// Quoted in 2027 money too, and indexed the same way
  om_fee     = 38_000 USD/MW/y  @source "Turbine O&M contract: full service"
                                | grow 2.5 %/y from construction.start
  insurance  = 420_000 USD/y | grow 2.5 %/y from construction.start
  /// Paid to the landowners as a share of revenue
  land_lease = 3.0 %            @source "Land lease agreements"
}

// ops.rfn: what the wind farm produces, earns and spends.
//
// `revenue` and `cost` say what a line is: Rufineer puts each in the income
// statement and the cash flow. Such lines are money in the model's
// currency, so they need no unit.

module ops "Operations" {
  /// 1 in the first year of operation, then 0.3% less each year
  performance = decline(inputs.degradation)

  /// MW × capacity factor × hours in the period: the compiler works out the hours
  energy: MWh = inputs.capacity * inputs.capacity_factor * inputs.availability
              * performance | during operations

  revenue sales = energy * inputs.tariff  "Electricity sales"

  cost om         = inputs.capacity * inputs.om_fee | during operations  "Turbine O&M"
  cost insurance  = inputs.insurance                | during operations
  cost land_lease = sales * inputs.land_lease
}
  • energy is declared in MWh, but the formula is MW × % × % × a plain number. The compiler knows a MW for a quarter is so many MWh, so it multiplies by the hours in each period for you and shows that term in the formula.
  • revenue sales = … and cost om = … say what a line is. That is all Rufineer needs to put it in the income statement and the cash flow. A kind line is money in the model's currency, so it needs no unit.
  • | during operations multiplies by the operations flag: zero during construction. insurance is a per-year input on a per-period line, so the compiler multiplies by each quarter's year fraction.
  • decline(inputs.degradation) is 1 in the first year of operation and falls 0.3% a year after that.
  • Lines refer to each other by name. Inside a module, sales is this module's line; from anywhere else it is ops.sales.

The alert Blank wrote read a line this module no longer has. Replace checks.rfn:

checks.rfn
// checks.rfn: your own checks and alerts. Checks fail the master
// check; alerts only warn. The standard checks (sources equal uses, the
// balance sheet balances, the loan repaid …) come with the conventions.

alert "Operating loss" income.ebitda >= 0 | during operations

The preview's status is green again. In its Sheets tab, open the Ops sheet and click a quarter's energy: the audit panel slides in from the right with how the value is made: the formula in the model's names, the same with the values it reads, the Excel formula, and what it reads and what reads it, each with its value. Click one to follow the chain. In the Console (⌘K; Ctrl+K), rfn explain says the same in words:

Terminal
$ rfn explain ops.energy
ops.energy  "Energy"
  where   Ops!H24 (first period; the same formula runs across the row)
  unit    MWh
  formula =$D$10*$D$11*$D$12*H$23*H$15*8760*H$16
  note    MW × capacity factor × hours in the period: the compiler works out the hours
  reads   inputs.capacity, inputs.capacity_factor, inputs.availability, ops.performance, timing.year_frac, timing.operations
  read by ops.sales

The 8760 is the hours in a year, times the period's year fraction.

income.ebitda in the alert is a line you never wrote. Because the model now has revenue and costs, Rufineer writes its income statement, cash flow, balance sheet and returns, as sheets of the workbook with live formulas like any other. The Conventions tab shows what it wrote, as Rufineer; this is the income statement (rfn conventions income shows just this part):

Rufineer
module income "Income statement" {
  section "Revenue"
  sales:   USD = ops.sales  "Electricity sales"
  revenue: USD = sales      "Revenue"

  section "Operating costs"
  om:              USD = ops.om                       "Turbine O&M"
  insurance:       USD = ops.insurance                "Insurance"
  land_lease:      USD = ops.land_lease               "Land lease"
  operating_costs: USD = om + insurance + land_lease  "Operating costs"

  ebitda:       USD = revenue - operating_costs  "EBITDA"
  depreciation: USD = 0                          "Depreciation"
  ebit:         USD = ebitda - depreciation      "EBIT"

  section "Interest"
  interest:          USD = 0                        "Interest"
  profit_before_tax: USD = ebit - interest          "Profit before tax"
  tax:               USD = 0                        "Tax"
  profit:            USD = profit_before_tax - tax  "Profit after tax"
}

Each of your lines has its row; the totals, EBITDA and profit follow. Depreciation, interest and tax are zero for now: there is no capex, debt or tax yet. They fill in as you add them.

Step 4: Let the compiler check you

You meet: unit errors, kind lines are money, misspelt names.

A spreadsheet will happily add a quantity to a price. Rufineer won't. Try these three mistakes in ops.rfn, one at a time: each turns the preview's status red as you type, and the Problems panel says why. Undo each (⌘Z) before the next.

Add energy to a tariff: revenue sales = energy + inputs.tariff

Problems
error[unit]: can't add MWh and USD/MWh
   --> ops.rfn:41:19
   |
41 |   revenue sales = energy + inputs.tariff  "Electricity sales"
   |                   ^^^^^^ MWh
   |                            ------------- USD/MWh
   = help: did you mean `energy * inputs.tariff`?  (MWh × USD/MWh gives money)

Give revenue a unit that isn't money: revenue sales: MWh = energy * inputs.tariff

Problems
error[kind]: a revenue line is money in the model's currency, USD
   --> ops.rfn:41:18
   |
41 |   revenue sales: MWh = energy * inputs.tariff  "Electricity sales"
   |                  ^^^ not USD
   = help: convert it first, e.g. `revenue sales = inputs.amount / inputs.zar_per_usd`, or leave the unit out

Misspell an input: revenue sales = energy * inputs.tarif

Problems
error[name]: I can't find `inputs.tarif`
   --> ops.rfn:41:28
   |
41 |   revenue sales = energy * inputs.tarif  "Electricity sales"
   |                            ^^^^^^^^^^^^ not defined
   = help: did you mean `inputs.tariff`?

Each message points at the file, the line and the part of the formula, says what is wrong in the model's own terms, and suggests a fix; the editor underlines the same place. Nothing can be exported until every error is fixed.

Step 5: Construction and a data table

You meet: rfn generate, rfn data, tables and the data workbook, spread, capex … life.

A new module is a file of its own, its inputs then its calculations, and a place in the sheet order. rfn generate writes both: the file, with an inputs block to fill in, and the module in sheets. Type it in the console (⌘K):

Terminal
$ rfn generate module capex "Construction"
  create  capex.rfn
  update  model.rfn (sheets: ops, capex)

The capex profile (how much is paid in each construction quarter) is a table: a column of numbers too long to type as inputs. Tables live in one Excel file, the data workbook, named in model.rfn. Replace model.rfn and capex.rfn: the capex file has its inputs, the table, then the module.

model.rfn
// model.rfn: what the model is, and its timeline.

model "Highveld Wind 40 MW" {
  currency  USD
  display   thousands  // calculation sheets show USD '000
  day_count act/365
  data      "data/tables.xlsx"  // tables too long to type
  sheets    [ops, capex]
}

timeline {
  start        2027-01-01  // financial close
  step         quarter     // one column per quarter
  construction 18m
  operations   20y
}
capex.rfn
// capex.rfn: what the wind farm costs to build.

inputs "Construction" {
  /// Turbines, foundations, roads, substation and grid connection
  capex      = 58_000_000 USD  @source "EPC contract price"
  /// Depreciated straight line from the start of operations
  asset_life = 20y
}

/// Share of the capex paid in each construction quarter (in data/tables.xlsx)
table capex_profile: % sheet "Capex profile"

// capex.rfn: construction.
//
// `capex` is paid for while the wind farm is built (the Sources and uses
// sheet) and depreciated over its `life` once it runs (Fixed assets).

module capex "Construction" {
  /// The EPC price, paid quarter by quarter along the profile in the data workbook
  capex epc = spread(inputs.capex, over: construction,
                     profile: inputs.capex_profile)  life inputs.asset_life
              "EPC contract"
}
  • spread(total, over: construction, profile: …) pays the EPC price along the profile, one quarter at a time.
  • capex epc = … life inputs.asset_life says the line is capital spend. While the wind farm is built it is a use of funds, on a Sources and uses sheet; once it runs it is depreciated straight line over its life, on a Fixed assets sheet, and the income statement and balance sheet follow. Without a life (land) it would be kept at cost.

The preview's status says 1 error:

Problems
error[data]: the data workbook `data/tables.xlsx` doesn't exist yet
   --> capex.rfn:11:1
   |
11 | table capex_profile: % sheet "Capex profile"
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ needs it
   = help: run `rfn data` to create it with the right sheets

The data workbook doesn't exist yet. Run rfn data: it creates the workbook, with a sheet for each table and a row for each period.

Terminal
$ rfn data
Created tables.xlsx; added sheet: Capex profile. Type the values in column B of each new sheet.
data/tables.xlsxCapex profile
AB
1PeriodCapex profile (%)
2Q1 2027
3Q2 2027
4Q3 2027
5Q4 2027
6Q1 2028
7Q2 2028
Capex profile
As rfn data writes it. Column A is the period; the shaded cells in column B are yours to fill in.

data/tables.xlsx is in the file tree now: open it to see its sheets, with the cells the model reads shaded. Choose Download, open the copy in Excel, type the profile into column B (5%, 10%, 20%, 30%, 25% and 10%) and save it. Then choose Replace… and pick your copy, or drop it on the page: the model reads the workbook again at once.

data/tables.xlsxCapex profile
AB
1PeriodCapex profile (%)
2Q1 20275.0%
3Q2 202710.0%
4Q3 202720.0%
5Q4 202730.0%
6Q1 202825.0%
7Q2 202810.0%
Capex profile
After you type the profile. It adds up to 100%, and the exported workbook checks that.

Each value becomes an input on the model's Inputs sheet, so every number is still in one place. To change the profile, change the cells and save.

With no debt yet, the shareholders pay for all of it: the share capital on the Sources and uses sheet is the whole cost.

Step 6: The loan and its reserve

You meet: window, loan(size: … of costs, repay: sculpted(…)), a reserve, the sources and uses and the waterfall Rufineer writes.

Generate another module:

Terminal
$ rfn generate module debt "Senior debt"
  create  debt.rfn
  update  model.rfn (sheets: ops, capex, debt)

Replace debt.rfn with the lenders' terms and the loan:

debt.rfn
// debt.rfn: the senior loan, from the lenders' term sheet.

inputs "Senior debt" {
  gearing       = 75 %     @note "Senior debt: its share of the construction costs"
  interest_rate = 7.5 %/y  @source "Term sheet: fixed all-in rate"
  target_dscr   = 1.30 x   @source "Term sheet: repayments sculpted to this DSCR"
  tenor         = 15y      @note "From the start of operations"
}

// debt.rfn: the senior loan and its reserve.

module debt "Senior debt" {
  /// The repayment window: 15 years from the start of operations
  tenor  = window(from: operations.start, for: inputs.tenor)
  /// Sized at `gearing` of the construction costs, drawn pro rata with the
  /// share capital, and repaid as the target DSCR allows from the cash flow
  senior = loan(size: inputs.gearing of costs, rate: inputs.interest_rate, over: tenor,
                repay: sculpted(dscr: inputs.target_dscr))  "Senior loan"

  /// The lenders want six months of debt service held back
  reserve dsra = 6m  "Debt service reserve"
}
  • tenor = window(from: operations.start, for: inputs.tenor) is a window of time with a start date, an end date and a flag, like a phase. The loan is repaid within it.
  • size: inputs.gearing of costs: the loan is 75% of the construction costs, and share capital pays the rest. Each quarter it draws its share of what is spent, pro rata with the share capital (the default drawdown), so it is fully drawn when construction ends. The interest charged while the wind farm is built is added to the loan (rolled up), so it is a use of funds too, without a circular reference.
  • repay: sculpted(dscr: inputs.target_dscr): each quarter repays what the cash flow allows at a DSCR of 1.30x. The cash flow is the one Rufineer writes, cashflow.cfads, so you don't name it.
  • reserve dsra = 6m is a debt service reserve account, where it is written in the waterfall: below the senior loan, it holds back the next six months of its debt service, is drawn if the cash flow falls short, and is released as the loan is repaid.

Every loan says what it draws. Take the size: away and the compiler asks:

Problems
error[call]: how much does `senior` draw?
   --> debt.rfn:17:3
   |
17 |   senior = loan(rate: inputs.interest_rate, over: tenor,
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `share:`, `size:` or `drawn:`
   = help: `size: 70 % of costs` (or `of uses`), `size: 50 mUSD`, or `drawn: …` yourself

The Sources and uses sheet now has the loan. This is what Rufineer wrote for it (rfn conventions sources): the loan's size, its share of each quarter's spend, each quarter's draw and what it has drawn so far, the share capital paying the rest, and the interest added to the loan:

Rufineer
module sources "Sources and uses" {
  /// Construction: every period before operations start
  building  = before(operations.start)  "Construction"
  /// From financial close (the start of construction) until operations start
  financing = construction              "From financial close"

  section "Uses of funds"
  epc:      USD = capex.epc                 | during building  "EPC contract"
  spend:    USD = epc                                          "Construction costs"
  needs:    USD = spend                                        "Uses paid by drawdowns"
  /// Interest during construction added to the loans (rolled up): the loans pay it themselves
  interest: USD = (debt.senior.capitalised) | during building
                  "Interest during construction, added to the loans"
  uses:     USD = needs + interest                             "Total uses"

  section "What each source provides"
  senior_size:  USD = inputs.gearing * total_costs  "Senior loan: size"
  /// Its size over the uses from financial close
  senior_share: %   = senior_size / sum(needs, during: financing)
                      "Senior loan: share of each drawdown"

  section "Sources of funds"
  senior:      USD = min(needs * senior_share, max(0, senior_size - prev(senior_to_date)))
                   | during financing  "Senior loan"
  /// Whatever the other sources don't pay
  equity:      USD = needs - senior | during building  "Share capital"
  capitalised: USD = interest                          "Interest added to the loans"
  total:       USD = senior + capitalised + equity     "Total sources"

  section "Drawn to date"
  senior_to_date: USD = cumulative(senior)  "Senior loan: drawn to date"

  section "Totals"
  total_costs:  USD = sum(spend)   "Total construction costs"
  total_uses:   USD = sum(uses)    "Total project cost"
  total_equity: USD = sum(equity)  "Total share capital"
}

These are ordinary Rufineer lines, and you could write any of them yourself: a line in a module sources of your own replaces the one of the same name. Or write what the loan draws on the loan, drawn: …, and it is no longer sized and drawn pro rata. The same wind farm written that way, site/tutorials/tutorial/alternative/, gives the same results in every case (the test suite checks):

debt.rfn
// debt.rfn: the senior loan, from the lenders' term sheet.

inputs "Senior debt" {
  gearing       = 75 %     @note "Senior debt: its share of the construction costs"
  interest_rate = 7.5 %/y  @source "Term sheet: fixed all-in rate"
  target_dscr   = 1.30 x   @source "Term sheet: repayments sculpted to this DSCR"
  tenor         = 15y      @note "From the start of operations"
}

// debt.rfn: the senior loan and its reserve.

module debt "Senior debt" {
  /// The repayment window: 15 years from the start of operations
  tenor      = window(from: operations.start, for: inputs.tenor)
  /// What the loan draws, written out: `gearing` of each quarter's
  /// construction costs. Share capital pays the rest
  drawn: USD = capex.epc * inputs.gearing | during construction  "Senior loan: drawn"
  /// Repays what the target DSCR allows from the cash flow
  senior     = loan(drawn: drawn, rate: inputs.interest_rate, over: tenor,
                    repay: sculpted(dscr: inputs.target_dscr))  "Senior loan"

  /// The lenders want six months of debt service held back
  reserve dsra = 6m  "Debt service reserve"
}

By hand declares only the sources and uses (conventions funding) and writes the rest out, the tax, the waterfall and the statements; its step 6 writes the sources and uses out too.

The reserve's target is the debt service ahead, which depends on how much is repaid before then, which depends on the cash the reserve holds back: a circular reference. A FAST model never switches on Excel's iterative calculation; Rufineer writes the target as a solve line. The preview solves it as you type (the Overview tab lists the loop and how close it came), and the exported workbook opens solved, with a Solve macro to settle it again after you change an input in Excel. Here is the waterfall Rufineer wrote, loan and reserve in the order you wrote them (the Conventions tab, or rfn conventions cashflow):

Rufineer
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 - 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.needs  "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"
}

The loan is paid from the cash available to it, interest first, then principal; anything it can't pay is owed as arrears, and an alert says so. Dividends are paid only out of profits: cash that profits don't cover waits in the bank (the dividend trap), and whatever is left when the company is wound up goes back to the shareholders. A second loan written under the reserve (a mezzanine, a shareholder loan) would be paid from what the reserve leaves.

Click a quarter's principal repayment on the Debt sheet to audit it, or ask rfn explain:

Terminal
$ rfn explain debt.senior.principal
debt.senior.principal  "Senior loan: repaid"
  where   Debt!H49 (first period; the same formula runs across the row)
  unit    USD
  formula =MIN(H$48,MAX(0,H$43-H$54))
  reads   debt.senior.principal_due, debt.senior.cash, debt.senior.interest_paid
  read by debt.senior, debt.senior.arrears, debt.senior.service

Step 7: Tax

You meet: the tax setting, losses carried forward, the project's own tax.

Tax is a setting of the model: one line in model.rfn, and its rate as an input. The Tax sheet is one Rufineer writes, so the rate goes in a file named after it, tax.rfn, with only an inputs block: on the Inputs sheet it sits under Tax.

model.rfn
// model.rfn: what the model is, and its timeline.

model "Highveld Wind 40 MW" {
  currency  USD
  display   thousands  // calculation sheets show USD '000
  day_count act/365
  data      "data/tables.xlsx"  // tables too long to type
  tax       inputs.tax_rate     // company tax, with losses carried forward
  sheets    [ops, capex, debt]
}

timeline {
  start        2027-01-01  // financial close
  step         quarter     // one column per quarter
  construction 18m
  operations   20y
}
tax.rfn
// tax.rfn: corporate tax.

inputs "Tax" {
  tax_rate = 25 %  @source "Income Tax Act"
}

The model compiles, and the Conventions tab now has a Tax sheet:

Rufineer
module tax "Tax" {
  rate: % = inputs.tax_rate  "Tax rate"

  section "The company's tax"
  profit: USD = income.ebitda - assets.tax_depreciation - income.interest
                "Taxable profit before losses"
  /// Losses are carried forward against later profits
  losses      = tax_losses(profit: profit)
  charge: USD = losses.taxable * rate  "Tax"

  section "Tax as if there were no debt, for the project IRR"
  unlevered_profit: USD = income.ebitda - assets.tax_depreciation  "Taxable profit without interest"
  unlevered             = tax_losses(profit: unlevered_profit)
  unlevered_charge: USD = unlevered.taxable * rate                 "Tax without debt"
}
  • Taxable profit is EBITDA less tax depreciation and interest. Tax depreciation is the depreciation of the Fixed assets sheet, unless a capex says otherwise (capex … life 25y allowance 5y writes it off for tax over 5 years).
  • Losses in the early years, when interest and depreciation are high, are carried forward against later profits (tax_losses).
  • Two taxes. charge is what the company pays: it comes off the cash flow before debt service. unlevered_charge is what the wind farm would pay with no debt; the project IRR uses it, so the project's return doesn't count the debt's tax shield.

Step 8: Results

You meet: a line of your own in a sheet Rufineer writes, dashboard, sensitivities.

Rufineer has already written a Returns sheet: the project and equity cash flows and IRRs, the DSCR. To measure the equity NPV at the shareholders' cost of equity, name the rate in model.rfn:

model.rfn
// model.rfn: what the model is, and its timeline.

model "Highveld Wind 40 MW" {
  currency  USD
  display   thousands  // calculation sheets show USD '000
  day_count act/365
  data      "data/tables.xlsx"     // tables too long to type
  tax       inputs.tax_rate        // company tax, with losses carried forward
  discount  inputs.cost_of_equity  // the NPVs on the Returns sheet
  sheets    [ops, capex, debt]
}

timeline {
  start        2027-01-01  // financial close
  step         quarter     // one column per quarter
  construction 18m
  operations   20y
}

Anything Rufineer writes can be added to or replaced. A module with the name of one of its sheets adds its lines to that sheet; a line with the name of one of the sheet's lines replaces it. The equity multiple isn't on the Returns sheet, so add it, in a new file returns.rfn (+ in the file tree), with the cost of equity as its input:

returns.rfn
// returns.rfn: what the returns are measured against.

inputs "Valuation" {
  cost_of_equity = 14 %/y  @note "For the NPVs"
}

// returns.rfn: a line of your own on the Returns sheet.
//
// A module with the name of one Rufineer writes adds to it: a line with a
// new name goes at the end, a line with the name of one of its lines
// replaces that line. Inside it, the other lines are read by their names.

module returns {
  /// How many times over the shareholders get their money back
  equity_multiple: x = multiple(equity_flow)  "Equity multiple"
}

Rufineer also writes a dashboard for a model that has none. This one is yours: the results people ask for first, on the first sheet, next to the master check. It goes in a new file, reports.rfn, with the sensitivities. A sensitivity case changes some inputs by multiplying (* 110 %) or adding (+ 1 %/y, - 4 %). The changes become inputs themselves, and a macro runs every case.

reports.rfn
// reports.rfn: the key results and two charts, on the Dashboard.

dashboard {
  section "Project"
  sources.total_uses  "Total project cost"

  section "Returns"
  returns.project_irr      "Project IRR"
  returns.equity_irr       "Equity IRR"
  returns.equity_npv       "Equity NPV"
  returns.equity_multiple  "Equity multiple"

  section "Debt"
  returns.min_dscr  "Minimum DSCR"

  chart column "Revenue and EBITDA" {
    income.revenue  "Revenue"
    income.ebitda   "EBITDA"
  }
  chart line "Senior loan and reserve" {
    debt.senior  "Senior loan"
    debt.dsra    "Debt service reserve"
  }
}

// reports.rfn: what happens if things go differently.
// 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     "Min DSCR"
  }

  case "Low wind (capacity factor 30%)" { inputs.capacity_factor - 4 % }
  case "Capex +10%"                     { inputs.capex * 110 % }
  case "Interest rate +1%"              { inputs.interest_rate + 1 %/y }
  case "Tariff −5%"                     { inputs.tariff * 95 % }
}

Step 9: Sensitivities and Excel

The Overview tab shows the dashboard's results and charts, and the master check: sources equal uses, the balance sheet balances in every quarter, the loan is repaid, the cash never goes negative. Open the Sensitivities tab and choose Run sensitivities: it solves each case in turn, as the workbook's macro does, and lays the results side by side.

CaseProject IRREquity IRRMin DSCRChecks
Base case10.6%14.6%1.30xall OK
Low wind (capacity factor 30%)8.6%10.8%1.30xall OK
Capex +10%9.4%12.6%1.30xall OK
Interest rate +1%10.6%14.0%1.30xall OK
Tariff −5%9.8%13.2%1.30xall OK

The loan is sized by gearing and repaid at a DSCR of 1.30x, so the minimum DSCR is the target in every case; what changes is how long repayment takes, and every case still repays within the 15 years. The equity IRR, just over the 14% cost of equity, is why the equity NPV on the Dashboard is small and positive.

Try a what-if without touching the files: in the Inputs tab, type over the tariff and the results follow at once (the files don't change; Back to the source puts it back).

Then choose Export to Excel at the top right of the app (or type rfn build in the console). The export is the one part of Rufineer that needs Pro; everything before it in this tutorial is free. Your browser downloads highveld.xlsm. Open it in Excel: every formula in it is live, and it opens solved. After the sheets you wrote (Ops, Capex, Debt) come the ones Rufineer wrote: Sources and uses, Fixed assets, Tax, Income statement, Cash flow, Balance sheet, Returns, and the three statements by year. Enable macros when Excel asks: Solve settles the reserve again after you change an input there, and Run sensitivities fills the Sensitivities sheet.

From here, every change is the same loop: edit a .rfn file, watch the preview, export when you need the workbook.

Where next

  • By hand builds this wind farm again with what Rufineer wrote for you written out: the tax, the waterfall, the dividend trap, the statements, and the funding's corkscrews and solved loops. Do it once to see what is under the hood.
  • Tutorial 5 has a refinancing, a mezzanine loan and a shareholder loan in one waterfall, and costs in another currency.
  • Conventions lists what each kind of line does and how to change any of it.
  • The console: every command it runs, and the generators (rfn generate loan mezzanine).