knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE) library(cre.dcf) library(dplyr) library(tibble)
This vignette explains a deliberate scope choice in cre.dcf: the package is currently designed as a property-level, before-tax DCF engine.
That choice is methodological and practical at the same time.
The portability argument is an implementation choice inferred from those chapters. The textbooks justify a strong before-tax core; the package then keeps that core jurisdiction-agnostic on purpose.
The package now also includes a first generic SPV-level tax helper, tax_run_spv(). The important design point is that this tax layer sits on top of the before-tax core rather than replacing it.
The current public API is strongest where the manuals are most universal:
PBTCF),This is already a meaningful analytical perimeter. It supports property comparison, financing comparison, exit-dependence diagnostics, and lease analysis without hard-coding any national tax code into the engine.
By contrast, after-tax analysis quickly becomes specific to:
That is exactly why cre.dcf does not yet pretend to return a fully jurisdiction-specific after-tax investment value. The new tax helper is intentionally generic and stylized.
The present version already produces most of the building blocks that a future SPV-level tax layer would need.
cfg_path <- system.file("extdata", "preset_core.yml", package = "cre.dcf") cfg <- yaml::read_yaml(cfg_path) case <- run_case(cfg) tax_basis_preview <- case$cashflows |> select(year, gei, noi, pbtcf, capex, interest, sale_proceeds, equity_flow) |> filter(year <= 4 | year == max(year)) knitr::kable( tax_basis_preview, digits = 0, caption = "Current outputs that can feed a future SPV-level tax layer" )
The economic logic is already explicit:
gei captures effective income,noi captures operating income net of recurring expenses,pbtcf captures the property-level cash flow before debt and taxes,interest and sale_proceeds are already separated in the consolidated table.This is one of the main reasons the package can add a fiscal layer later without rewriting the current core.
tax_rate does and does not doThe package still exposes a tax_rate inside the WACC-oriented discount-rate blocks.
tpl <- dcf_spec_template() tibble( KE = tpl$disc_rate_wacc$KE, KD = tpl$disc_rate_wacc$KD, tax_rate = tpl$disc_rate_wacc$tax_rate )
That field is part of the discounting convention. It adjusts the debt leg in a WACC-style formula. It is not a cash-tax engine.
In the current version, the following statements are true:
disc_rate_wacc$tax_rate affects discount-rate construction,scr_ratio can still act as a fallback in some discounting workflows,So the package already acknowledges tax in the cost-of-capital sense, but not yet in the jurisdictional cash-flow sense.
A generic before-tax engine travels well because it focuses on the economics that are most stable across jurisdictions:
The parts that differ the most from one country to another can then be isolated in a future tax specification instead of being mixed into the core valuation engine.
This is especially important if the same package may later be used for:
The tax layer is optional and leaves run_case() untouched.
The intended split is:
future_tax_blocks <- tibble::tribble( ~block, ~consumes_from_core, ~adds_from_tax_spec, ~target_output, "Tax depreciation", "price, capex, holding period", "asset split, depreciation lives, start rule", "tax_depreciation", "Interest deductibility", "interest", "deductibility rule", "deductible_interest, interest_disallowed", "Simple corporate tax", "taxable base after adjustments", "statutory rate", "cash_is", "Loss carryforwards", "negative taxable income", "carryforward rule", "loss_cf_open, loss_cf_used, loss_cf_close" ) knitr::kable( future_tax_blocks, caption = "Target blocks for a future SPV-level tax layer" )
For version 1 of that tax layer, the scope should remain intentionally narrow:
That scope is large enough to support realistic teaching cases and comparative illustrations, but still narrow enough to remain portable across stylized jurisdictions.
The key design principle is that the before-tax case is still the source object, and the tax layer consumes it.
tax_basis <- tax_basis_spv(case) tax_spec <- tax_spec_spv( corp_tax_rate = 0.25, depreciation_spec = depreciation_spec( acquisition_split = tibble::tribble( ~bucket, ~share, ~life_years, ~method, ~depreciable, "land", 0.20, NA, "none", FALSE, "building", 0.65, 30, "straight_line", TRUE, "fitout", 0.15, 10, "straight_line", TRUE ), capex_bucket = "fitout", start_rule = "full_year" ), interest_rule = interest_rule(mode = "full"), loss_rule = loss_rule(carryforward = TRUE, carryforward_years = Inf) ) tax_res <- tax_run_spv(tax_basis, tax_spec) tax_res$tax_table |> select( year, noi, tax_depreciation, deductible_interest, taxable_income_pre_losses, loss_cf_open, loss_cf_used, cash_is, after_tax_equity_cf ) |> filter(year <= 4 | year == max(year))
tax_res$summary
This is enough to show the intended layering:
tax_basis_spv() extracts a tax basis from that case,tax_run_spv() adds depreciation, deductible interest, simple CIT, and loss carryforwards.The yearly tax table now exposes columns such as:
tax_depreciation,deductible_interest,interest_disallowed,taxable_income_pre_losses,loss_cf_open,loss_cf_used,loss_cf_close,cash_is,after_tax_equity_cf.It remains deliberately modest:
The package now includes a French investment vignette built on tax_run_spv().
That vignette should be read for what it is:
In other words, the French vignette is useful precisely because it sits on top of the generic architecture rather than driving the architecture itself.
The absence of a full tax engine in the current release is not a methodological weakness. It is a scope decision.
tax_rate input is part of discounting, not a fiscal cash-flow model.tax_run_spv() helper stays optional, generic, and jurisdiction-agnostic at the API level.This keeps cre.dcf scientifically defensible today while supporting applied tax vignettes, including the current stylized French investment illustration built on tax_run_spv().
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.