Operating-company / LBO pack: recurring operating lines, policy-driven
working capital, capex, scheduled term debt, cash taxes, and entry/exit —
benchmarked in benchmarks/opco/ against an independent month-by-month
reference. All lowering is template-driven.
Activation
use pack "opco" version "0.1.0"Contract types
All contracts accept instance suffixes (opco.revenue_line.saas,
opco.revenue_line.services, ...) which suffix the lowered stream names.
Growth is annual-compound stepped continuously on the model clock:
value(t) = amount * (1 + growth_rate)^(time.t / 12).
Operating lines
opco.revenue_line—amount(monthly), optionalgrowth_rate. Streamopco.revenue.recurring.opco.opex_line— same terms. Streamopco.opex.recurring(outflow).opco.working_capital— fixed monthly WC outflow (amount).opco.working_capital_policy— DSO/DPO/DIO-driven:WC(t) = annualized revenue * ar_days/365 + annualized opex * (inv_days - ap_days)/365from the modeled streams (phase-2 series lookups). Books the full initial WC in the first period, the period-over-period change afterwards, and releases the ending balance in the final period whenrelease_at_end = 1. Terms:ar_days,ap_days,inv_days(all default 0),release_at_end.opco.capex_line— fixedamount(+growth_rate) pluspct_of_revenueof the modeled revenue streams. Streamopco.capex.
Financing
opco.term_debt— scheduled term loan:principal,rate,io_months(default 0),amort_months; optionalfunded_at_close(default 1) controls the proceeds inflow atterm_start. After the IO period the loan amortizes level-pay overamort_months; the remaining balance pays as a balloon at the contract'sterm_end. Streamsopco.debt.proceeds,opco.debt.interest,opco.debt.principal. Cash sweeps and revolvers need per-period persistent state and are not in v0.1.opco.acquisition— purchasepricepaid atterm_start(the equity check when paired with debt proceeds at the same date).
Taxes
opco.cash_taxes—tax_rateonmax(0, EBITDA - D&A - interest)per period. EBITDA and interest come from the modeled streams; D&A is a declared deduction (da_monthly, optionalda_growth), not a cash stream. No NOL carryforwards (losses floor at zero tax per period; carryforwards need H3-style state). Streamopco.taxes.
Exit
opco.exit_multiple—base_value * exit_multipleat the contract'sterm_start.opco.exit_ebitda—exit_multiple× trailing-12-month EBITDA derived from the modeled streams, net ofselling_costs, atterm_start.
Metrics
domain.opco.revenue, .ebitda, .ebitda_margin, .capex,
.working_capital (net investment; releases net out), .taxes,
.debt_service, .fcf (EBITDA − capex − WC − cash taxes; note taxes
deduct interest, so this is FCF after the interest tax shield),
.fcf_to_debt_service.
Diagnostics (E7xxx)
E7001_OPCO_LINE_MISSING_AMOUNT,E7002_OPCO_LINE_INVALID_SCHEDULE,E7003_OPCO_LINE_INVALID_GROWTHE7010_OPCO_WC_MISSING_AMOUNT_OR_RULE,E7011_OPCO_WC_INVALID_SCHEDULEE7020_OPCO_EXIT_MISSING_MULTIPLE,E7021_OPCO_EXIT_INVALID_MULTIPLE,E7022_OPCO_EXIT_MISSING_BASE_VALUE,E7023_OPCO_EXIT_INVALID_SCHEDULEE7024_OPCO_EXIT_EBITDA_INVALID_MULTIPLEE7030_OPCO_DEBT_INVALID_AMORT,E7031_OPCO_DEBT_INVALID_RATE- Missing templated terms surface as
E5006_MISSING_CONTRACT_TERM.
Not in v0.1 (planned waterfall & capital-stack work)
- Cash-flow sweeps, revolver draws/paydowns, PIK toggles (need per-period persistent state).
- NOL carryforwards.
- Waterfall distributions to the capital stack.
Provenance and determinism
Generated streams carry source contract file/span and
generated_by.pack/rule_id; rule ordering, diagnostics ordering, IDs and
results are deterministic under identical inputs.
Quick start
A services business bought in an LBO — revenue/opex lines, working capital, capex, term debt:
version 0.1
model "my-buyout"
use pack "opco" version "0.1.0"
time calendar monthly from 2026-01 for 60
entity operating target
contract opco.revenue_line on entity operating.target {
term 2026-01..2030-12
terms { amount = 1000000 growth_rate = 0.06 }
}
contract opco.opex_line on entity operating.target {
term 2026-01..2030-12
terms { amount = 650000 growth_rate = 0.04 }
}
// Net working capital nets to zero over the full term because
// release_at_end returns the investment at exit — that is the point of the
// term, not an inert stream.
// examples-allow: working_capital.adjustment — released in full at exit
contract opco.working_capital_policy on entity operating.target {
term 2026-01..2030-12
terms { ar_days = 45 ap_days = 30 inv_days = 10 release_at_end = 1 }
}
contract opco.capex_line on entity operating.target {
term 2026-01..2030-12
terms { amount = 40000 pct_of_revenue = 0.01 }
}
contract opco.term_debt on entity operating.target {
term 2026-01..2030-12
terms { principal = 20000000 rate = 0.09 amort_months = 84 }
}Run it
cfdl compile my-buyout --packs packs --out my-buyout/ir.json
cfdl run my-buyout/ir.json --packs packs --pack opco --out my-buyout/results.json --rate 0.10Recipes
Scheduled term debt (IO period, level-pay amortization via
ipmt/ppmt, balloon at maturity, proceeds at close):
contract opco.term_debt on entity operating.target {
term 2026-01..2030-12
terms {
principal = 14000000
rate = 0.085
io_months = 12
amort_months = 84
}
}Trailing-EBITDA exit (the LBO convention — trailing twelve months, not forward):
contract opco.exit_ebitda on entity operating.target {
term 2030-12..2030-12
terms { exit_multiple = 8.5 }
}Full worked model: benchmarks/opco/lbo_buyout/ (validated against an
independent recursive reference) and the LBO notebook in
examples/notebooks/.
Metrics reference
Computed automatically whenever a model runs with the opco pack, alongside the core metrics (NPV, IRR, MOIC, payback, WAL). Enumerated from the pack definition, so this list is always complete.
| Metric | Type | Built from |
|---|---|---|
domain.opco.revenue | money | derived |
domain.opco.ebitda | money | derived |
domain.opco.ebitda_margin | number | domain.opco.ebitda ÷ domain.opco.revenue |
domain.opco.capex | money | derived |
domain.opco.working_capital | money | derived |
domain.opco.taxes | money | derived |
domain.opco.debt_service | money | derived |
domain.opco.fcf | money | derived |
domain.opco.fcf_to_debt_service | number | domain.opco.fcf ÷ domain.opco.debt_service |
Validations reference
Checked at compile time. Each is a stable diagnostic code that is never renamed or reused; see diagnostics.
| Code | Rejects |
|---|---|
E7001_OPCO_LINE_MISSING_AMOUNT | OpCo line is missing required numeric term 'amount'. |
E7002_OPCO_LINE_INVALID_SCHEDULE | OpCo line term range is missing, invalid, or outside model timeline. |
E7003_OPCO_LINE_INVALID_GROWTH | OpCo line has invalid 'growth_rate' term. |
E7010_OPCO_WC_MISSING_AMOUNT_OR_RULE | OpCo working capital requires term 'amount' or a supported rule expression. |
E7011_OPCO_WC_INVALID_SCHEDULE | OpCo working capital term range is missing, invalid, or outside model timeline. |
E7020_OPCO_EXIT_MISSING_MULTIPLE | OpCo exit is missing required term 'exit_multiple'. |
E7021_OPCO_EXIT_INVALID_MULTIPLE | OpCo exit 'exit_multiple' must be greater than 0. |
E7022_OPCO_EXIT_MISSING_BASE_VALUE | OpCo exit requires numeric term 'base_value'. |
E7023_OPCO_EXIT_INVALID_SCHEDULE | OpCo exit term range is missing, invalid, or outside model timeline (exit occurs at term_start). |
E7024_OPCO_EXIT_EBITDA_INVALID_MULTIPLE | OpCo EBITDA exit requires 'exit_multiple' greater than 0. |
E7030_OPCO_DEBT_INVALID_AMORT | OpCo term debt requires 'amort_months' greater than 0. |
E7031_OPCO_DEBT_INVALID_RATE | OpCo term debt requires a non-negative 'rate'. |
Worked example models
Benchmark cases are validated period-by-period against an independent reference implementation.