French investment illustration: stylized SPV tax impact

knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE)
library(cre.dcf)
library(dplyr)
library(tibble)
library(scales)
library(yaml)

Purpose

This vignette illustrates how tax_run_spv() can be used on a French office investment case.

It is intentionally a stylized French-like SPV illustration, not a legal or tax opinion. As of April 1, 2026, the normal French corporate income-tax rate is 25%, following the official French tax administration's published corporate-tax guidance consulted for this vignette.

The rest of the tax assumptions below are simulated package inputs chosen to remain within the current scope of tax_run_spv():

The point of the vignette is therefore methodological: show how a French case can be parameterized in the current generic engine without pretending to replicate the full French tax code.

Start from a French operating case

We use the package's preset_core.yml, which already describes a stylized Paris 8e office investment.

cfg_path <- system.file("extdata", "preset_core.yml", package = "cre.dcf")
cfg <- yaml::read_yaml(cfg_path)
case <- run_case(cfg)

bullet_summary <- case$comparison$summary |>
  filter(scenario == "debt_bullet") |>
  transmute(
    scenario,
    irr_project,
    irr_equity,
    min_dscr,
    max_ltv_forward,
    ops_share,
    tv_share
  )

knitr::kable(
  bullet_summary,
  digits = 3,
  caption = "Before-tax baseline for the stylized French core-office case"
)

At this stage, the core DCF is still entirely before tax. The tax layer comes next and consumes the consolidated cash-flow table.

Translate the French-like SPV into tax inputs

The current tax engine needs two objects:

tax_basis <- tax_basis_spv(case)

tax_assumptions <- tibble::tribble(
  ~item, ~value, ~comment,
  "Corporate income tax", "25%", "Official French normal CIT rate as of April 1, 2026",
  "Land share", "20%", "Stylized non-depreciable portion",
  "Building share", "65%", "Stylized depreciable structure",
  "Fit-out share", "15%", "Stylized depreciable tenant-improvement bucket",
  "Building life", "30 years", "Stylized straight-line life",
  "Fit-out life", "10 years", "Stylized straight-line life",
  "Interest deductibility", "full", "Current engine scope, not full French law",
  "Loss carryforward", "on", "Stylized carryforward allowed",
  "Offset cap", "50%", "Stylized cap used to keep the rule conservative"
)

knitr::kable(
  tax_assumptions,
  caption = "French-like tax assumptions used in this vignette"
)
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,
    offset_cap_pct = 0.50
  )
)

tax_res <- tax_run_spv(tax_basis, tax_spec)

Read the yearly tax table

The yearly table below is the main output of tax_run_spv().

tax_view <- 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
  )

knitr::kable(
  tax_view,
  digits = 0,
  caption = "Stylized French SPV tax table"
)

This is the main reading grid:

Compare pre-tax and after-tax equity cash flows

The generic engine does not yet build a complete French investor-level valuation. It does, however, let us measure the tax drag at the SPV level.

equity_bridge <- tax_res$tax_table |>
  select(year, pre_tax_equity_cf, cash_is, after_tax_equity_cf)

knitr::kable(
  equity_bridge,
  digits = 0,
  caption = "Bridge from pre-tax to after-tax equity cash flows"
)

We can also compare the before-tax leveraged IRR with a stylized after-tax SPV equity IRR computed on after_tax_equity_cf.

pre_tax_equity_irr <- case$leveraged$irr_equity
after_tax_equity_irr <- irr_safe(tax_res$tax_table$after_tax_equity_cf)

tax_highlights <- tibble(
  pre_tax_equity_irr = pre_tax_equity_irr,
  after_tax_spv_equity_irr = after_tax_equity_irr,
  total_cash_is = tax_res$summary$total_cash_is,
  total_tax_depreciation = tax_res$summary$total_tax_depreciation,
  exit_year_cash_is = tax_res$tax_table$cash_is[
    tax_res$tax_table$year == max(tax_res$tax_table$year)
  ]
)

knitr::kable(
  tax_highlights,
  digits = 3,
  caption = "Stylized before-tax versus after-tax SPV reading"
)

In this example:

What this vignette captures and what it does not

This French illustration is useful because it shows a coherent workflow:

  1. build the investment case,
  2. extract a tax basis,
  3. define a country-like SPV parameterization,
  4. read the yearly tax consequences.

But it still has clear limits.

Captured:

Not captured:

Summary

This vignette shows how cre.dcf can already support a realistic French-style teaching case without hard-coding French tax law into the package core.



Try the cre.dcf package in your browser

Any scripts or data that you put into this service are public.

cre.dcf documentation built on April 10, 2026, 5:08 p.m.