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.
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.
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"
)| 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.
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"
)| 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)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"
)| 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:
tax_depreciation transforms acquisition basis and capex
into fiscal charges,deductible_interest removes the debt cost that is
deductible under the simplified rule,taxable_income_pre_losses is the taxable base before
using prior losses,loss_cf_open and loss_cf_used show how
prior tax losses are mobilized,cash_is is the annual corporate income tax paid by the
SPV,after_tax_equity_cf is the equity cash flow net of that
annual tax.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"
)| 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"
)| 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:
cash_is,This French illustration is useful because it shows a coherent workflow:
But it still has clear limits.
Captured:
Not captured:
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.
tax_run_spv() adds a stylized SPV tax reading on top of
it.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.