tests/testthat/test-ratios.R

test_that("add_credit_ratios: identités DSCR, debt yield et LTV forward", {
  dcf <- dcf_calculate(1e7, 0.05, 0.055, 5, 0.07)
  sch <- debt_built_schedule(6e6, 0.045, 5, type = "bullet")
  full <- cf_make_full_table(dcf, sch)
  rat  <- add_credit_ratios(full, sch, exit_yield = 0.055)

  # 1. conventions de NA au temps 0 
  t0 <- rat$year == 0

  expect_true(all(is.na(rat$dscr[t0])))
  expect_true(all(is.na(rat$interest_cover_ratio[t0])))
  expect_true(all(is.na(rat$debt_yield_current[t0])))

  # 2. identités DSCR et debt_yield_current là où définies 
  idx_dscr <- with(rat, year > 0 & payment > 0)
  idx_dyc  <- with(rat, year > 0 & outstanding_debt > 0)

  # Numerateur : basis = "noi" par défaut
  expect_equal(
    rat$dscr[idx_dscr],
    rat$noi[idx_dscr] / rat$payment[idx_dscr],
    tolerance = 1e-10
  )

  expect_equal(
    rat$debt_yield_current[idx_dyc],
    rat$noi[idx_dyc] / rat$outstanding_debt[idx_dyc],
    tolerance = 1e-10
  )

  # En dehors de ces zones, les ratios doivent être NA
  expect_true(all(is.na(rat$dscr[!idx_dscr])))
  expect_true(all(is.na(rat$debt_yield_current[!idx_dyc])))

  # 3. LTV forward 
  # Valeur forward théorique : NOI_{t+1} / exit_yield
  value_fwd_theo <- dplyr::lead(rat$noi) / 0.055

  # Dernière ligne : pas de NOI_{t+1} => LTV forward doit être NA
  n <- nrow(rat)
  expect_true(is.na(rat$ltv_forward[n]))
  expect_true(is.na(rat$value_forward[n]))

  # Sur les périodes où value_forward est définie et la dette positive
  idx_ltv <- with(rat, year < max(year) & outstanding_debt > 0)

  expect_equal(
    rat$value_forward[idx_ltv],
    value_fwd_theo[idx_ltv],
    tolerance = 1e-10
  )

  expect_equal(
    rat$ltv_forward[idx_ltv],
    rat$outstanding_debt[idx_ltv] / value_fwd_theo[idx_ltv],
    tolerance = 1e-10
  )

  # Là où la dette est nulle ou la valeur forward manquante, LTV forward doit être NA
  expect_true(all(is.na(rat$ltv_forward[!idx_ltv])))
})

test_that("credit_guardrails_fast: équivalence avec add_credit_ratios", {
  dcf <- dcf_calculate(1e7, 0.052, 0.057, 7, 0.072)
  sch <- debt_built_schedule(6.4e6, 0.048, 6, type = "bullet")

  # Chemin moteur: table compacte (sans merge full)
  compact_cf <- as.data.frame(dcf$cashflows)
  compact_cf$gei <- compact_cf$net_operating_income
  compact_cf$noi <- compact_cf$net_operating_income
  compact_cf$loan_init <- NA_real_

  fast_fun <- getFromNamespace("credit_guardrails_fast", "cre.dcf")
  fast <- fast_fun(
    cf_tab = compact_cf,
    debt_sched = sch,
    exit_yield = 0.057,
    dscr_basis = "noi",
    ignore_balloon_in_min = TRUE,
    maturity_year = 6
  )

  ratios <- add_credit_ratios(
    cf_tab = compact_cf,
    debt_sched = sch,
    exit_yield = 0.057,
    dscr_basis = "noi",
    ignore_balloon_in_min = TRUE,
    maturity_year = 6
  )

  ref_min_dscr <- attr(ratios, "min_dscr_pre_maturity")
  ref_max_ltv <- suppressWarnings(max(ratios$ltv_forward[ratios$year >= 1 & ratios$year <= 6], na.rm = TRUE))
  if (!is.finite(ref_max_ltv)) ref_max_ltv <- NA_real_

  expect_equal(fast$min_dscr_pre_maturity, ref_min_dscr, tolerance = 1e-12)
  expect_equal(fast$max_ltv_forward, ref_max_ltv, tolerance = 1e-12)
})

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.