The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
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[@]metanet.ch.

French investment illustration: stylized SPV tax impact

Package cre.dcf

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

2 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"
)
Before-tax baseline for the stylized French core-office case
scenario irr_project irr_equity min_dscr max_ltv_forward ops_share tv_share
debt_bullet 0.046 0.062 5.257 0.449 0.37 0.63

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

3 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"
)
French-like tax assumptions used in this vignette
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
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)

4 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"
)
Stylized French SPV tax table
year noi tax_depreciation deductible_interest taxable_income_pre_losses loss_cf_open loss_cf_used cash_is after_tax_equity_cf
0 0 0 0 0 0 0 0 -30816000
1 2376000 1760000 451968 164032 0 0 41008 1883024
2 2399760 1760000 451968 187792 0 0 46948 1900844
3 2423758 1760000 451968 211790 0 0 52947 1918842
4 2447995 1760000 451968 236027 0 0 59007 1937020
5 2472475 1760000 451968 260507 0 0 65127 1955380
6 2497200 1760000 451968 285232 0 0 71308 1973924
7 2541279 1761592 451968 327719 0 0 81930 1991459
8 2566692 1763200 451968 351524 0 0 87881 2010761
9 2592359 1764825 451968 375566 0 0 93892 2030257
10 2618283 1766465 451968 19818340 0 0 4954585 26518385

This is the main reading grid:

5 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"
)
Bridge from pre-tax to after-tax equity cash flows
year pre_tax_equity_cf cash_is after_tax_equity_cf
0 -30816000 0 -30816000
1 1924032 41008 1883024
2 1947792 46948 1900844
3 1971790 52947 1918842
4 1996027 59007 1937020
5 2020507 65127 1955380
6 2045232 71308 1973924
7 2073388 81930 1991459
8 2098642 87881 2010761
9 2124148 93892 2030257
10 31472970 4954585 26518385

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"
)
Stylized before-tax versus after-tax SPV reading
pre_tax_equity_irr after_tax_spv_equity_irr total_cash_is total_tax_depreciation exit_year_cash_is
0.062 0.047 5554632 17616083 4954585

In this example:

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

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

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.