The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Before-tax by design: scope, portability, and future tax extensions

Package cre.dcf

1 Purpose

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.

2 Why the core still stops at the before-tax level

The current public API is strongest where the manuals are most universal:

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.

3 What the core already gives us

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"
)
Current outputs that can feed a future SPV-level tax layer
year gei noi pbtcf capex interest sale_proceeds equity_flow
0 0 0 0 0 0 0 -30816000
1 2377950 2376000 2376000 0 451968 0 1924032
2 2401730 2399760 2399760 0 451968 0 1947792
3 2425747 2423758 2423758 0 451968 0 1971790
4 2450004 2447995 2447995 0 451968 0 1996027
10 2628946 2618283 2601877 16405 451968 49867061 31472970

The economic logic is already explicit:

This is one of the main reasons the package can add a fiscal layer later without rewriting the current core.

4 What the current tax_rate does and does not do

The 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
)
## # A tibble: 1 × 3
##      KE    KD tax_rate
##   <dbl> <dbl>    <dbl>
## 1  0.08  0.04     0.28

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:

So the package already acknowledges tax in the cost-of-capital sense, but not yet in the jurisdictional cash-flow sense.

5 Why this helps with multi-jurisdiction portability

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:

6 The current SPV-level tax helper

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"
)
Target blocks for a future SPV-level tax layer
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

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.

7 A working generic SPV tax run

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))
## # A tibble: 6 × 9
##    year      noi tax_depreciation deductible_interest taxable_income_pre_losses
##   <int>    <dbl>            <dbl>               <dbl>                     <dbl>
## 1     0       0                0                    0                        0 
## 2     1 2376000          1760000               451968                   164032 
## 3     2 2399760          1760000               451968                   187792 
## 4     3 2423758.         1760000               451968                   211790.
## 5     4 2447995.         1760000               451968                   236027.
## 6    10 2618283.         1766465.              451968                 19818340.
## # ℹ 4 more variables: loss_cf_open <dbl>, loss_cf_used <dbl>, cash_is <dbl>,
## #   after_tax_equity_cf <dbl>
tax_res$summary
## # A tibble: 1 × 7
##   corp_tax_rate acquisition_price total_tax_depreciation total_cash_is
##           <dbl>             <dbl>                  <dbl>         <dbl>
## 1          0.25          48000000              17616083.      5554632.
## # ℹ 3 more variables: total_loss_generated <dbl>, final_loss_cf <dbl>,
## #   total_after_tax_equity_cf <dbl>

This is enough to show the intended layering:

The yearly tax table now exposes columns such as:

It remains deliberately modest:

8 How to read the French fiscal-impact vignette

The package now includes a French investment vignette built on tax_run_spv().

That vignette should be read for what it is:

  1. an illustration of one country-like parameterization of the generic engine,
  2. a teaching-oriented bridge from before-tax DCF to SPV-level cash taxes,
  3. not a complete encoding of French tax law.

In other words, the French vignette is useful precisely because it sits on top of the generic architecture rather than driving the architecture itself.

9 Summary

The absence of a full tax engine in the current release is not a methodological weakness. It is a scope decision.

This keeps cre.dcf scientifically defensible today while supporting applied tax vignettes, including the current stylized French investment illustration built on tax_run_spv().

Geltner, David, Norman Miller, Jim Clayton, and Piet Eichholtz. 2014a. “Chapter 11 - Nuts and Bolts for Real Estate Valuation: Cash Flow Proformas and Discount Rates.” In Commercial Real Estate Analysis and Investments, 3rd ed. Mbition.
———. 2014b. “Chapter 14 - After-Tax Investment Analysis and Corporate Real Estate.” In Commercial Real Estate Analysis and Investments, 3rd ed. Mbition.
Hartzell, David, and Andrew Baum. 2020. “Chapter 5 - Basic Valuation and Investment Analysis.” In Real Estate Investment: Strategies, Structures, Decisions. Wiley.

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.