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.