CFDL

Energy & microgrids: solar/wind PPA and merchant revenue, battery storage arbitrage, capacity payments, O&M, investment tax credits, capex, and level-pay project debt. All rules are template-driven ({{contract.*}} + defaults) — no hardcoded amounts.

Conventions

  • Annual quantities (mwh_year, om_year, payment_year) spread evenly across months.
  • Escalation and degradation step annually: factor ^ floor(t / 12), matching common project-finance Excel practice.
  • energy.debt_service uses the engine's decimal-exact pmt() (Excel sign conventions).

Contract types

ContractRequired termsOptional (default)
energy.ppamwh_year, ppa_priceescalation (0), degradation (0), availability (1)
energy.merchantmwh_year, priceprice_escalation (0), degradation (0), availability (1)
energy.storage_arbitragemwh_cycled_year, spreaddegradation (0)
energy.capacitypayment_year
energy.omom_yearescalation (0)
energy.itccredit— (fires on term_start)
energy.capexamount— (fires on term_start)
energy.debt_servicerate, term_months, principal
energy.ptcmwh_year, credit_per_mwhescalation (0), degradation (0), availability (1); term bounds the credit window
energy.macrs_shieldbasis, tax_ratelife (5; also 7/15/20) — IRS Pub 946 GDS half-year tables via macrs_rate()

Tax attributes (ITC, PTC, MACRS shield) report under domain.energy.tax_benefits and are excluded from revenue/EBITDA.

Not yet modeled (roadmap)

Tax equity / partnership flip structures (HLBV), DSCR-sculpted debt sizing, full tax computation (the MACRS stream models the shield value, not taxable income). Planned for later pack increments.

Quick start

A 2 MW solar-plus-storage microgrid with a 25-year escalating PPA:

version 0.1
model "my-microgrid"
use pack "energy" version "0.1.0"
time calendar monthly from 2026-01 for 300

entity project microgrid

contract energy.capex on entity project.microgrid {
  term 2026-01..2026-01
  terms { amount = 2400000 }
}

contract energy.ppa on entity project.microgrid {
  term 2026-01..2050-12
  terms {
    mwh_year = 4200
    ppa_price = 85
    escalation = 0.02
    degradation = 0.005
  }
}

contract energy.om on entity project.microgrid {
  term 2026-01..2050-12
  terms { om_year = 70000 escalation = 0.025 }
}

Vintages are contract-anchored: each contract's escalation and degradation clocks start at its own term start (its COD), not the model start.

Run it

cfdl compile my-microgrid --packs packs --out my-microgrid/ir.json
cfdl run my-microgrid/ir.json --packs packs --pack energy --out my-microgrid/results.json --rate 0.08

--pack energy computes the pack's domain metrics (revenue, EBITDA, domain.energy.tax_benefits, debt service) alongside NPV/IRR/MOIC.

Recipes

Add the investment tax credit (fires once, at its term start; reports under tax benefits, not EBITDA):

contract energy.itc on entity project.microgrid {
  term 2026-12..2026-12
  terms { credit = 720000 }
}

Level-pay project debt (decimal-exact pmt(), Excel sign conventions):

contract energy.debt_service on entity project.microgrid {
  term 2026-01..2045-12
  terms { rate = 0.06 term_months = 240 principal = 1600000 }
}

Wind with PTC and MACRS: pair energy.ptc (per-MWh credit over the credit window) with energy.macrs_shield (IRS Pub 946 GDS tables via macrs_rate()); see benchmarks/energy/wind_ptc_macrs/.

Full worked models: benchmarks/energy/solar_ppa_microgrid/ and the solar microgrid notebook in examples/notebooks/.

Metrics reference

Computed automatically whenever a model runs with the energy pack, alongside the core metrics (NPV, IRR, MOIC, payback, WAL). Enumerated from the pack definition, so this list is always complete.

MetricTypeBuilt from
domain.energy.revenuemoneyenergy.ppa.revenue, energy.merchant.revenue, energy.storage.margin, energy.capacity.revenue
domain.energy.opexmoneyenergy.om.expense
domain.energy.ebitdamoneyenergy.ppa.revenue, energy.merchant.revenue, energy.storage.margin, energy.capacity.revenue, energy.om.expense
domain.energy.debt_servicemoneyenergy.debt.service
domain.energy.dscrnumberdomain.energy.ebitda ÷ domain.energy.debt_service
domain.energy.tax_benefitsmoneyenergy.itc.credit, energy.ptc.credit, energy.macrs.shield

Validations reference

Checked at compile time. Each is a stable diagnostic code that is never renamed or reused; see diagnostics.

CodeRejects
E8001_ENERGY_INVALID_DEGRADATIONEnergy 'degradation' must be a fraction between 0 and 1.
E8002_ENERGY_INVALID_AVAILABILITYEnergy 'availability' must be a fraction between 0 and 1.
E8003_ENERGY_INVALID_ESCALATIONEnergy 'escalation' must be greater than or equal to -1.
E8004_ENERGY_INVALID_PRICE_ESCALATIONEnergy 'price_escalation' must be greater than or equal to -1.
E8010_ENERGY_INVALID_MACRS_LIFEEnergy MACRS 'life' must be one of 5, 7, 15, or 20.
E8011_ENERGY_INVALID_TAX_RATEEnergy 'tax_rate' must be a fraction between 0 and 1.
E8020_ENERGY_DEBT_INVALID_RATEEnergy debt 'rate' must be non-negative.
E8021_ENERGY_DEBT_INVALID_TERM_MONTHSEnergy debt 'term_months' must be a whole number greater than 0.
E8022_ENERGY_DEBT_INVALID_PRINCIPALEnergy debt 'principal' must be greater than 0.

Worked example models

Benchmark cases are validated period-by-period against an independent reference implementation.