Applying Cumulative Caps to Controllable CAM
A cumulative cap on controllable CAM lets a low-growth year bank headroom that a later high-growth year can draw down, so the ceiling that clamps year four is not last year’s number times a rate — it is a running allowance compounded off a fixed base year. That distinction is the whole subject of this page, and it sits one level below controllable vs non-controllable expense segregation: the segregation stage produces the controllable subtotal, and the math here decides how much of that subtotal a lease actually permits you to recover once the ceiling compounds across several reconciliation periods. The three cap structures a commercial lease commonly names — year-over-year, cumulative, and compounded — look almost identical in a single year and diverge sharply over four or five, because two of them let unused room carry forward and one does not. Getting the carry-forward and the compounding root wrong is how a statement quietly overbills a tenant by thousands of dollars, or quietly forfeits recovery the landlord was entitled to, in a way that survives a desk review and detonates in an audit.
Context & When to Use This Approach
Reach for this math the moment a lease caps controllable CAM with a percentage that references anything other than the immediately preceding year. A flat annual ceiling applied year by year needs nothing more than a multiply; the machinery here earns its keep only when the lease compounds a rate over a horizon and — critically — when it lets a below-cap year subsidize an above-cap year later. The clause language that triggers each structure is specific and worth reading literally:
- Year-over-year (non-cumulative). “Controllable expenses shall not increase by more than 5% over the amount billed in the prior calendar year.” Each year stands alone; unused room is gone at year end, and a spike above the ceiling is permanently forfeited by the landlord.
- Cumulative (simple). “The 5% annual cap shall be cumulative and non-compounding, measured from the base year.” The allowance grows in equal steps off the base amount, and headroom banks forward, so a quiet year lets a busy year catch up.
- Compounded (cumulative). “The cap shall be 5% per annum, compounded annually and cumulative, from the base year.” The allowance grows geometrically off the base amount and still banks forward — the most landlord-favorable of the three.
These definitions decide real dollars. On a $100,000.00 controllable base at 5%, a year-over-year ceiling in year four is $100,000.00 × 1.05, or $105,000.00, computed off the prior year and reset annually; a compounded cumulative ceiling in that same year is $100,000.00 × 1.05⁴, or $121,550.63, and it carries three years of unused room with it. The gap is not academic — it is the difference between what you may bill and what a tenant’s consultant will claw back. The controllable subtotal this math consumes is produced upstream by the segregation stage, so if you have not yet built that split, start with classifying controllable expenses with Python; the cap is only ever allowed to touch the controllable pool, never taxes or insurance. For the single-year ceiling mechanics and the data contracts a cap engine consumes, see managing expense caps and controllable limits and the boundary-labelling recipe in handling controllable vs non-controllable CAM expenses; this page assumes the split is done and concentrates on the multi-year compounding and carry-forward.
The core of a compounded cap is a single geometric expression. Let be the annual cap rate, the controllable subtotal in the base year, and the number of full years since that base year. The single-year ceiling is
while a cumulative but non-compounding cap grows in equal steps instead:
Carry-forward is what makes either cumulative structure distinct from a year-over-year cap. A cumulative cap constrains the running total, not each year in isolation: the amount you may recover through year is the sum of the annual ceilings to date, and this year’s billing is bounded by whatever of that cumulative allowance the prior years left unused —
so overflow above a single year’s ceiling is not lost; it is redistributed forward against banked room.
Two policy facts have to be correct before any of these formulas produce a defensible number, and both are drawn from the lease abstract rather than inferred from the ledger. The first is the base year itself: it fixes the exponent and therefore every downstream ceiling, and a renewal or an exercised option often re-sets it, which resets the compounding root to a new controllable subtotal. The second is the base amount — the controllable pool measured in that base year — which must be the segregated controllable figure, not the gross CAM pool, or the ceiling inherits taxes and insurance the cap was never meant to touch. Because both facts are lease-versioned, a reopened prior period must replay the base year and base amount that were actually in force then; hard-coding either as a constant is how a statement that reconciled cleanly in one system fails to reproduce in the next. The worked example that follows carries a $100,000.00 base and a 5% rate so the compounding is easy to check by hand: at $100,000.00 × 1.05², year two’s compounded ceiling is $110,250.00, and the arithmetic in the code should tie out to exactly that cent.
Step-by-Step Implementation
The pipeline below takes a cap policy and a run of consecutive-year controllable subtotals and produces, for each year, the recoverable amount after the cap, the room banked, and any overflow redistributed forward. Every monetary value is decimal.Decimal, quantized to cents with ROUND_HALF_UP, because a compounded factor raised to the fourth power then multiplied by a six-figure base amplifies any binary rounding drift into whole cents that break the tie-out against the billed statement.
Step 1 — Model the cap structure and policy. Represent the three lease structures as an enum and the cap terms as one immutable policy record. The base year and base amount are the compounding root; freezing the record keeps a reopened prior period reproducible.
from __future__ import annotations
from dataclasses import dataclass
from decimal import Decimal, ROUND_HALF_UP
from enum import Enum
class CapStructure(str, Enum):
"""How a lease grows the controllable ceiling across reconciliation years."""
YEAR_OVER_YEAR = "year_over_year" # compounds off prior year; no carry-forward
CUMULATIVE = "cumulative" # simple growth off base year; banks headroom
COMPOUNDED = "compounded" # geometric growth off base year; banks headroom
CENTS = Decimal("0.01")
def money(value: Decimal) -> Decimal:
"""Quantize a controllable amount to whole cents, half-up like a billed line."""
return value.quantize(CENTS, rounding=ROUND_HALF_UP)
@dataclass(frozen=True)
class ControllableCapPolicy:
"""The cap terms a lease imposes on the controllable CAM pool."""
base_year: int
base_amount: Decimal # controllable subtotal in the base year, the compounding root
annual_rate: Decimal # e.g. Decimal("0.05") for a 5% cap
structure: CapStructure
Step 2 — Track the prior base and compute the single-year ceiling. The number of full years since the base year, n, drives the growth factor. A compounded or year-over-year ceiling uses the geometric factor (1 + r) ** n; a cumulative-simple ceiling uses the linear factor 1 + r * n. Anchoring n on the base year — not on the prior actual — is what stops a spike in one year from permanently ratcheting the base upward.
def annual_ceiling(policy: ControllableCapPolicy, year: int) -> Decimal:
"""Return the single-year controllable ceiling for `year` under the policy.
n counts full years since the base year, so the base year itself (n = 0)
sits at the base amount and every later year grows by the lease rate.
"""
n = year - policy.base_year
if n < 0:
raise ValueError("reconciliation year precedes the cap base year")
rate = policy.annual_rate
if policy.structure is CapStructure.CUMULATIVE:
factor = Decimal(1) + rate * n # simple accumulation off the base
else:
factor = (Decimal(1) + rate) ** n # compounded off the base year
return money(policy.base_amount * factor)
def carries_forward(structure: CapStructure) -> bool:
"""Cumulative-style caps bank unused room; a year-over-year cap forfeits it."""
return structure is not CapStructure.YEAR_OVER_YEAR
Step 3 — Compute the capped controllable, carry room forward, and redistribute overflow. Walk the years in order while holding two pieces of state: banked headroom that cumulative structures accrue, and carried_overflow, the demand deferred out of prior years. Each year’s demand is the actual controllable plus any deferred overflow; the amount billable is the single-year ceiling plus banked room; the excess redistributes forward when the structure allows it and is forfeited when it does not.
@dataclass(frozen=True)
class CappedYear:
"""One year of the cap walk: what was recoverable and what moved forward."""
year: int
actual: Decimal
ceiling: Decimal # this year's single-year ceiling
billed: Decimal # recoverable controllable after the cap
carried_in: Decimal # overflow deferred in from prior years
overflow: Decimal # demand not recovered this year
carried_out: Decimal # overflow redistributed to future years (0 if forfeited)
def apply_cumulative_cap(
policy: ControllableCapPolicy,
actuals: dict[int, Decimal],
) -> list[CappedYear]:
"""Clamp consecutive years of controllable spend to the cap, banking unused
headroom and redistributing overflow forward when the structure permits."""
banked = Decimal("0.00") # unused allowance accrued to date
carried_overflow = Decimal("0.00") # demand deferred from prior years
results: list[CappedYear] = []
for year in sorted(actuals):
ceiling = annual_ceiling(policy, year)
carried_in = carried_overflow
demand = actuals[year] + carried_in
allowable = ceiling + banked if carries_forward(policy.structure) else ceiling
billed = min(demand, allowable)
if billed < Decimal("0.00"):
billed = Decimal("0.00")
overflow = money(demand - billed)
if carries_forward(policy.structure):
banked = money(allowable - billed) # room this year left on the table
carried_overflow = overflow # deferred into the next year
else:
banked = Decimal("0.00")
carried_overflow = Decimal("0.00") # year-over-year forfeits the excess
results.append(
CappedYear(
year=year,
actual=actuals[year],
ceiling=ceiling,
billed=money(billed),
carried_in=carried_in,
overflow=overflow,
carried_out=carried_overflow,
)
)
return results
Step 4 — Read the banked room and the redistributed overflow off the result. The CappedYear list is the audit trail: carried_in and carried_out show exactly how a below-ceiling year funded a later above-ceiling year, and overflow with a zero carried_out marks a year-over-year forfeiture the landlord absorbed. This record is what a tenant statement cites when it explains why year three recovered more than its own annual ceiling. Keeping the deferred and forfeited amounts as explicit fields, rather than collapsing them into a single billed number, is what lets a dispute be answered with arithmetic instead of assertion: a tenant challenging a year that billed above its stated ceiling can be shown the exact banked room, sourced from named prior years, that funded the difference. It also keeps a year-over-year forfeiture visible as a business decision — a landlord who is quietly eating $2,750.00 of unrecoverable controllable every spike year should see that on a report, not discover it at renewal.
def total_recovered(schedule: list[CappedYear]) -> Decimal:
"""Sum the recoverable controllable across the capped schedule, to the cent."""
return money(sum((row.billed for row in schedule), Decimal("0.00")))
Gotchas & Known Limitations
The arithmetic is short; the ways a real lease breaks it are not. Treat each of the following as a case the engine must handle explicitly rather than absorb into a wrong ceiling.
- Do not confuse cumulative with year-over-year. A cumulative cap constrains the running total and banks room; a year-over-year cap constrains each year alone and forfeits room. Applying year-over-year math to a cumulative clause under-recovers; applying cumulative math to a year-over-year clause over-recovers. The clause word “cumulative” is load-bearing — read it, do not assume it.
- Compounded and non-compounded diverge with the horizon. At 5% over four years, compounding adds a full percentage point of base to the ceiling versus simple growth. The gap widens every year, so a mislabelled
structureis invisible in year one and material by year five. - Anchor the base on the base year, not the prior actual. Computing
(1 + r)off last year’s actual instead of the base amount lets a single spike ratchet the ceiling permanently upward, which is exactly the drift a cumulative cap exists to prevent. - Reset the base year when the lease says to. Renewals and options frequently re-set the base year; carrying the old
base_amountthrough a reset overstates every subsequent ceiling. The base year is a lease fact drawn from the abstract, not a constant. - A gross-up must run before the cap, not after. If occupancy-driven costs are normalized, the gross-up happens first so the ceiling clamps the normalized base; capping a raw pool and then grossing up caps the wrong number. This ordering is part of the wider expense allocation logic and rule engines sequence.
- Never carry money as
float.1.05 ** 4in binary floating point is not exactly1.21550625; multiplied by a six-figure base and summed across a portfolio, the drift crosses a cent and fails the tie-out. Keep every amount, rate, and factor inDecimal.
Verification
A cap bug is invisible until a tenant’s consultant recomputes the recoverable controllable by hand and finds either an over-bill or forfeited room, so the walk is tested against a fixture where the correct recovery is known from the clause. The scenario below runs the same actuals through a compounded cumulative cap and a year-over-year cap: the cumulative structure banks two quiet years and fully recovers a spike that pierces its own annual ceiling, while the year-over-year structure clamps that same spike and forfeits the excess.
from decimal import Decimal
def test_cumulative_banks_room_and_yoy_forfeits() -> None:
actuals = {
2023: Decimal("98000.00"), # base year, under the base amount
2024: Decimal("100000.00"), # quiet year, banks more room
2025: Decimal("113000.00"), # spike above the single-year ceiling
2026: Decimal("105000.00"),
}
base = Decimal("100000.00")
rate = Decimal("0.05")
compounded = ControllableCapPolicy(2023, base, rate, CapStructure.COMPOUNDED)
schedule = apply_cumulative_cap(compounded, actuals)
# Year 2025's own ceiling is 100000 * 1.05**2 = 110250.00, below the 113000
# actual, yet banked room from 2023-2024 recovers the spike in full.
y2025 = next(row for row in schedule if row.year == 2025)
assert y2025.ceiling == Decimal("110250.00")
assert y2025.billed == Decimal("113000.00")
assert y2025.overflow == Decimal("0.00")
# Cumulative recovery equals the sum of every actual, to the cent.
assert total_recovered(schedule) == Decimal("416000.00")
# The same spike under a year-over-year cap is clamped and the excess lost.
yoy = ControllableCapPolicy(2023, base, rate, CapStructure.YEAR_OVER_YEAR)
yoy_2025 = next(row for row in apply_cumulative_cap(yoy, actuals) if row.year == 2025)
assert yoy_2025.billed == Decimal("110250.00")
assert yoy_2025.overflow == Decimal("2750.00")
assert yoy_2025.carried_out == Decimal("0.00") # forfeited, never rebilled
Because every amount is Decimal, the assertions use exact equality rather than a floating-point tolerance, so a one-cent leak in a compounded factor fails the test instead of hiding under an isclose. The two structures sharing one fixture is the point: the cumulative cap recovers the full $416,000.00 by spending banked room, while the year-over-year cap recovers $110,250.00 in the spike year and forfeits $2,750.00 — the exact figure a landlord loses by drafting, or misreading, the wrong cap.
With the multi-year ceiling settled, the recoverable controllable feeds tenant allocation, and the boundary that decides which dollars are even eligible for the cap is built one stage earlier in classifying controllable expenses with Python, the sibling to this page under controllable vs non-controllable expense segregation.
Related
- Controllable vs Non-Controllable Expense Segregation — the parent stage that labels each dollar and hands this math the controllable subtotal it clamps.
- Managing Expense Caps and Controllable Limits — the data contracts and single-year ceiling mechanics that this multi-year walk builds on.
- Handling Controllable vs Non-Controllable CAM Expenses — the deterministic split that must run before any ceiling is applied so the cap touches only the controllable pool.