This vignette investigates the internal coherence of the
income generation process within the DCF model - specifically,
the link between contractual lease parameters and the resulting
Net Operating Income (NOI).
It formalizes a key accounting relationship that underpins all
real-estate valuation models:
\[ \text{NOI}_t = \text{GEI}_t - \text{OPEX}_t - \text{CAPEX}_t \]
where
Verifying this chain ensures that rental assumptions (lease terms, indexation, vacancy, and maintenance policies) are faithfully transmitted through the model’s computation of operating performance.
# 1.1 Load a preset configuration including explicit lease events
cfg_path <- system.file("extdata", "preset_default.yml", package = "cre.dcf")
stopifnot(nzchar(cfg_path))
cfg <- yaml::read_yaml(cfg_path)
case <- run_case(cfg)
cf <- case$cashflows
# 1.2 Verify that all required variables are present
required_cols <- c("year", "gei", "opex", "capex", "noi")
stopifnot(all(required_cols %in% names(cf)))The preset_default.yml configuration encodes a stylised leasing pattern (initial occupancy, lease expiry, vacancy, reletting with CAPEX), which is suitable for testing how contractual assumptions propagate into GEI and NOI over time.
## 2. Analytical structure of the income chain
# 2.1 NOI as implemented in the engine: GEI - OPEX
cf <- cf |>
mutate(
noi_from_gei_opex = gei - opex,
resid_noi_core = noi_from_gei_opex - noi,
noi_after_capex = noi - capex
)
gei_min <- min(cf$gei, na.rm = TRUE)
gei_max <- max(cf$gei, na.rm = TRUE)
noi_min <- min(cf$noi, na.rm = TRUE)
noi_max <- max(cf$noi, na.rm = TRUE)
max_abs_resid_core <- max(abs(cf$resid_noi_core), na.rm = TRUE)
cat(
"\nIncome chain diagnostics (NOI core identity):\n",
sprintf("• Minimum GEI: %s\n", formatC(gei_min, format = 'f', big.mark = " ")),
sprintf("• Maximum GEI: %s\n", formatC(gei_max, format = 'f', big.mark = " ")),
sprintf("• Minimum NOI: %s\n", formatC(noi_min, format = 'f', big.mark = " ")),
sprintf("• Maximum NOI: %s\n", formatC(noi_max, format = 'f', big.mark = " ")),
sprintf("• Max |(GEI - OPEX) - NOI|: %s\n",
formatC(max_abs_resid_core, format = 'f', big.mark = " "))
)##
## Income chain diagnostics (NOI core identity):
## • Minimum GEI: 0.0000
## • Maximum GEI: 204 020.0000
## • Minimum NOI: -61 818.0600
## • Maximum NOI: 204 020.0000
## • Max |(GEI - OPEX) - NOI|: 0.0000
The variable residual_noi measures, for each period, the deviation from the ideal accounting identity. A well-specified model should yield residuals numerically indistinguishable from zero (up to floating-point tolerance).
Rather than imposing unrealistic sign constraints (for instance, that NOI must always be non-negative), the vignette focuses on coherence conditions that should hold for a broad spectrum of strategies, including transitional years.
## 3. Logical and accounting consistency checks
# 3.1 Finiteness
stopifnot(all(is.finite(cf$gei)))
stopifnot(all(is.finite(cf$opex)))
stopifnot(all(is.finite(cf$capex)))
stopifnot(all(is.finite(cf$noi)))
# 3.2 Non-negative OPEX / CAPEX
stopifnot(min(cf$opex, na.rm = TRUE) >= -1e-8)
stopifnot(min(cf$capex, na.rm = TRUE) >= -1e-8)
# 3.3 NOI never exceeds GEI when costs are non-negative
stopifnot(all(cf$noi <= cf$gei + 1e-8))
# 3.4 NOI core identity: GEI - OPEX == NOI
stopifnot(all(abs(cf$resid_noi_core) < 1e-6))
cat(
"\n✓ Accounting checks passed:\n",
" • NOI in the engine is equal to GEI minus OPEX (CAPEX is treated separately).\n",
" • OPEX and CAPEX remain non-negative, and NOI never exceeds GEI.\n"
)##
## ✓ Accounting checks passed:
## • NOI in the engine is equal to GEI minus OPEX (CAPEX is treated separately).
## • OPEX and CAPEX remain non-negative, and NOI never exceeds GEI.
These tests together ensure that:
GEI, OPEX, CAPEX, and NOI are all finite;
cost blocks are not spuriously negative;
NOI does not exceed GEI when costs are non-negative;
the identity \[NOIt=GEIt-OPEXt-CAPEXt\] holds up to numerical tolerance.
In many empirical cases, especially for value-added or opportunistic strategies, NOI can be temporarily negative (e.g. vacancy plus heavy CAPEX). It is therefore useful to document the distribution of NOI rather than to impose an a priori positivity constraint.
## 4.1 Share of periods with negative NOI
neg_noi_share <- mean(cf$noi < 0, na.rm = TRUE)
cat(
"\nNOI sign diagnostics:\n",
sprintf("• Share of periods with NOI < 0: %.1f%%\n", 100 * neg_noi_share),
if (neg_noi_share > 0)
" --> Indicates at least one transitional year with negative operating result (vacancy, works, etc.).\n"
else
" --> All periods exhibit non-negative operating result in this configuration.\n"
)##
## NOI sign diagnostics:
## • Share of periods with NOI < 0: 16.7%
## --> Indicates at least one transitional year with negative operating result (vacancy, works, etc.).
This diagnostic does not impose any additional constraint; it simply quantifies the presence (or absence) of transitional loss-making years in the income profile.
cf |>
select(year, gei, opex, capex, noi,
noi_from_gei_opex, noi_after_capex, resid_noi_core) |>
head(10) |>
knitr::kable(
digits = 2,
caption = "GEI --> NOI (core identity) and NOI after CAPEX (first 10 years)"
)| year | gei | opex | capex | noi | noi_from_gei_opex | noi_after_capex | resid_noi_core |
|---|---|---|---|---|---|---|---|
| 0 | 0.0 | 0.00 | 0.00 | 0.00 | 0.00 | 0.0 | 0 |
| 1 | 200000.0 | 0.00 | 0.00 | 200000.00 | 200000.00 | 200000.0 | 0 |
| 2 | 202000.0 | 0.00 | 0.00 | 202000.00 | 202000.00 | 202000.0 | 0 |
| 3 | 204020.0 | 0.00 | 0.00 | 204020.00 | 204020.00 | 204020.0 | 0 |
| 4 | 0.0 | 61818.06 | 309090.30 | -61818.06 | -61818.06 | -370908.4 | 0 |
| 5 | 197714.8 | 0.00 | 29657.21 | 197714.76 | 197714.76 | 168057.5 | 0 |
This table makes the GEI–OPEX–CAPEX–NOI cascade explicit in the time dimension and shows how the residual remains numerically negligible.
The numerical checks and the illustrative table jointly indicate that:
Gross Effective Income (GEI) correctly translates the contractual rent schedule into cash inflows, after adjusting for vacancy, rent-free periods, and any explicit incentives embedded in the lease events of preset_default.yml;
Operating expenses (OPEX) and CAPEX are deducted in a mechanically consistent way to obtain NOI in each period;
the residuals of the GEI –> OPEX/CAPEX –> NOI identity are effectively zero, confirming the internal accounting closure of the model.
From an analytical standpoint, this vignette demonstrates that lease-level assumptions (areas, headline rents, indexation, renewal or relocation events, vacancy durations, capex per square metre) propagate transparently into the operating-income block of the DCF engine.
In applied work, such validation is essential: it ensures that observed differences in NOI trajectories across scenarios or assets can be interpreted as stemming from genuine differences in lease structure and asset management strategy rather than from hidden computational artefacts.