Author: Package cre.dcf

# 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.

- Methodologically, the manuals first structure valuation around operating cash flows, terminal value, and leverage before opening the more jurisdiction-specific after-tax layer (Geltner et al. 2014a, 2014b; Hartzell and Baum 2020).
- Practically, a before-tax core is much easier to mobilize across countries, vehicles, and investor types.

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:

- build the operating pro forma,
- expose the property-before-tax cash flow (`PBTCF`),
- isolate terminal value,
- layer leverage,
- read underwriting constraints.

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:

- depreciation lives and asset splits,
- limits on interest deductibility,
- treatment of capital expenditures,
- loss carryforwards,
- exit taxation,
- investor and vehicle structure.

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:

- `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.

## 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:

- `disc_rate_wacc$tax_rate` affects discount-rate construction,
- `scr_ratio` can still act as a fallback in some discounting workflows,
- neither of those fields computes annual corporate tax liabilities,
- neither of those fields applies depreciation, loss carryforwards, or tax rules at exit.

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:

- rents, vacancy, expenses, capital expenditure,
- financing terms,
- debt service,
- terminal value,
- value composition between operations and exit.

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:

- a French SPV,
- a German asset-holding vehicle,
- a UK single-asset company,
- or a teaching case where only stylized tax assumptions are needed.

## 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:

- straight-line tax depreciation,
- deductible interest under a simple rule,
- a single corporate income-tax rate,
- loss carryforwards.

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 DCF engine still produces the economic case,
- `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:

- no investor-level taxes,
- no withholding taxes,
- no jurisdiction-specific interest-cap regimes,
- no recapture logic,
- no treaty logic.

## 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.

- The package is strongest on the textbook before-tax core.
- That core is explicit enough to feed a generic SPV-level tax layer.
- The current `tax_rate` input is part of discounting, not a fiscal cash-flow model.
- The new `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()`.

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.