Calculating Base-Year Expense Stops in Python

Turning a stored base-year amount into a defensible tenant charge is four Decimal operations in a fixed order: derive the tenant’s share factor, take their current-year share, subtract the stored stop, and floor the result at zero. This is the step-by-step calculation walkthrough beneath modeling expense stops and base-year clauses, the stage that decides how a negotiated base-year floor is stored; here we take that stored floor and actually compute the dollars a tenant reimburses. The worked example runs one 25,000-square-foot base-year lease and one 12,000-square-foot per-rentable-square-foot stop through the same clamp so you can watch every intermediate value settle to the cent. If you have ever reconciled a base-year statement by hand and arrived a few cents off the property accountant’s number, this page is the deterministic version of that arithmetic — the one an auditor can rerun a year later and reproduce exactly.

The recoverable band sits above the stored base-year stop A dollars-per-year axis rises on the left. A single column shows the tenant's current-year share of 225,000 dollars. A dashed base-year stop line crosses the column: the region below it, 200,000 dollars, is covered by the stored stop and recovers nothing; the band above it, 25,000 dollars, is the recoverable amount. A callout to the right states that the recoverable amount is the maximum of zero and the current-year share minus the stop, and that a per-rentable-square-foot stop is a negotiated rate multiplied by the tenant's own rentable area. $ / year Base-year stop Recoverable $25,000 Covered by stop $200,000 Current-year share = $225,000 Recoverable amount = max(0, share − stop) Per-RSF stop = rate × tenant RSF
One tenant's current-year share split by the stored base-year stop: only the band rising above the floor is recoverable, and a per-rentable-square-foot stop is just a negotiated rate times the tenant's own area.

Context & When to Use This Approach

Reach for this procedure whenever a lease recovers operating costs above a floor rather than from the first dollar — a base-year lease, a fixed expense stop, or the degenerate full-net case where the floor is zero. The parent stage has already done the hard modeling: it decided how the negotiated language is stored, and it guarantees that a base-year figure reaching this calculation has already been grossed up to a target occupancy. What remains is deterministic arithmetic, and the reason it earns its own walkthrough is that the arithmetic is small enough to get subtly wrong. Two large, nearly equal numbers — a current-year share around $225,000 and a stored stop around $200,000 — are differenced together, and any sub-cent drift in how the share was computed surfaces as a visible, disputable line on the tenant statement.

The worked example uses a single suburban asset, the Halyard Commerce Center, a 312,500 rentable-square-foot office campus. One tenant, lease L-31, occupies 25,000 square feet under a base-year lease whose grossed base-year pool is $2,500,000. A second tenant, lease L-77, occupies 12,000 square feet under a fixed stop of $7.25 per rentable square foot. Both leases recover against the same current-year recoverable pool of $2,812,500. Keeping two different lease structures side by side in one building is the point: the whole discipline here is that after the floor is derived, both tenants flow through the identical clamp, so the code branches exactly once and never again.

Use this recipe at year-end reconciliation, when estimated monthly charges are being trued up against the actual reconciled pool, and any time a mid-year estoppel or audit request needs a single tenant’s recovery recomputed from stored inputs. Do not use it to decide the floor — that belongs upstream. This stage assumes the stop is already correct and concerns itself only with applying it precisely and reproducibly.

It helps to be explicit about what the procedure deliberately excludes, because most base-year disputes trace back to something slipped into the wrong stage. The calculation does not build the recoverable pool, does not classify which expense lines are recoverable, does not apply any controllable-expense cap, and does not gross a base year up to occupancy. Each of those is a separate, upstream responsibility, and folding any of them into the clamp is how a recovery becomes impossible to audit. By the time a pool_total and a RecoveryProvision reach these four functions, every policy decision has already been made and frozen; what is left is pure, boring, checkable arithmetic. That narrowness is a feature. A tenant’s attorney can be handed the four inputs and the four operations and reproduce the charge on a napkin, and the property accountant can rerun the same stored provision against the same stored pool a year later and land on the identical cent. When a recovery is contested, the argument is almost never about the subtraction on this page — it is about whether the pool or the floor feeding it was right, which is exactly why keeping those concerns out of this stage matters.

Step-by-Step Implementation

The tenant’s share factor is the ratio of their rentable area to the building’s, and everything downstream multiplies through it:

share_factor=tenant_rsfbuilding_rsfshare\_factor = \frac{tenant\_rsf}{building\_rsf}

The current-year share distributes the recoverable pool by that factor, and the recoverable amount is the band of that share rising above the stored stop, floored at zero:

recoverable=max ⁣(0,  current_sharestop)recoverable = \max\!\left(0,\; current\_share - stop\right)

The clamp to zero is the load-bearing part of the formula. In a soft-cost year a tenant’s current-year share can land below their floor, and the lease does not convert that shortfall into a landlord refund — the floor works in one direction only, so the model returns zero rather than a negative charge.

Step 1 — Model the provision with base_year_amount and recovery_type

Store the lease’s recovery structure as a frozen dataclass. A single basis tag distinguishes the three structures, and each structure carries exactly the one figure it needs — a base-year lease carries base_year_amount, a per-rentable-square-foot lease carries stop_per_rsf, and a full-net lease carries neither. Every monetary field and every area is a Decimal, never a binary float, because the final subtraction magnifies rounding error. The __post_init__ guard rejects structurally impossible provisions at construction so the calculation functions can assume clean input.

from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal, ROUND_HALF_EVEN
from enum import Enum

CENTS = Decimal("0.01")
RATIO = Decimal("0.00000001")  # eight-place share factor


class StopBasis(str, Enum):
    """How the lease sets the recovery floor."""

    NNN = "nnn"                    # full pro-rata share, floor is zero
    BASE_YEAR = "base_year"        # floor = share of grossed base-year pool
    STOP_PER_RSF = "stop_per_rsf"  # floor = negotiated rate * tenant area


@dataclass(frozen=True)
class RecoveryProvision:
    """A stored, immutable expense-stop provision for one lease."""

    lease_id: str
    basis: StopBasis
    tenant_rsf: Decimal
    building_rsf: Decimal
    # base-year lease: the already-grossed base-year pool total, in dollars
    base_year_amount: Decimal | None = None
    # fixed-stop lease: the negotiated floor per rentable square foot
    stop_per_rsf: Decimal | None = None

    def __post_init__(self) -> None:
        if self.tenant_rsf <= 0 or self.building_rsf <= 0:
            raise ValueError("rentable areas must be positive")
        if self.tenant_rsf > self.building_rsf:
            raise ValueError("tenant_rsf cannot exceed building_rsf")
        if self.basis is StopBasis.BASE_YEAR and self.base_year_amount is None:
            raise ValueError("base_year basis requires base_year_amount")
        if self.basis is StopBasis.STOP_PER_RSF and self.stop_per_rsf is None:
            raise ValueError("stop_per_rsf basis requires stop_per_rsf")

    @property
    def share_factor(self) -> Decimal:
        """tenant_rsf / building_rsf, held to eight decimal places."""
        return (self.tenant_rsf / self.building_rsf).quantize(
            RATIO, rounding=ROUND_HALF_EVEN
        )

For lease L-31, share_factor is 25000 / 312500, which is 0.08000000 exactly — a clean ratio chosen so the intermediate values are easy to check by eye, though nothing in the code depends on the division coming out even.

Step 2 — Compute the current-year share

The current-year share distributes the recoverable pool by the tenant’s share factor and quantizes to the cent. This is the same rentable-area basis the pro-rata allocation algorithm uses to spread any pool; here it produces the single number the stop is subtracted from.

current_share=tenant_rsfbuilding_rsf×pool_curcurrent\_share = \frac{tenant\_rsf}{building\_rsf} \times pool\_{cur}

Step 3 — Recoverable equals max(0, share − stop)

Deriving the stop is where the three structures diverge, and it happens exactly once. A full-net provision has a zero floor; a base-year provision takes the tenant’s share of the grossed base-year pool; a fixed-stop provision multiplies the negotiated rate by the tenant’s own area. After the floor is resolved to one dollar figure, the recoverable clamp is basis-agnostic — it never needs to know which structure produced the number it subtracts.

def current_year_share(pool_total: Decimal, provision: RecoveryProvision) -> Decimal:
    """Tenant's pro-rata share of the current-year recoverable pool ($)."""
    share = provision.share_factor * pool_total
    return share.quantize(CENTS, rounding=ROUND_HALF_EVEN)


def base_year_stop(provision: RecoveryProvision) -> Decimal:
    """Resolve the stored floor to a single dollar figure.

    NNN -> 0.00; BASE_YEAR -> share of the grossed base-year pool;
    STOP_PER_RSF -> negotiated rate multiplied by tenant area.
    """
    if provision.basis is StopBasis.NNN:
        return Decimal("0.00")
    if provision.basis is StopBasis.BASE_YEAR:
        assert provision.base_year_amount is not None
        stop = provision.share_factor * provision.base_year_amount
        return stop.quantize(CENTS, rounding=ROUND_HALF_EVEN)
    assert provision.stop_per_rsf is not None
    stop = provision.stop_per_rsf * provision.tenant_rsf
    return stop.quantize(CENTS, rounding=ROUND_HALF_EVEN)


def recoverable(share: Decimal, stop: Decimal) -> Decimal:
    """recoverable = max(0, share - stop), quantized to cents.

    A share below the floor recovers zero, never a negative charge.
    """
    delta = share - stop
    return max(delta, Decimal("0.00")).quantize(CENTS, rounding=ROUND_HALF_EVEN)

Walking lease L-31 through these three functions: the current-year share is 0.08 * 2,812,500 = $225,000.00; the base-year stop is 0.08 * 2,500,000 = $200,000.00; the recoverable amount is max(0, 225,000 − 200,000) = $25,000.00. That final figure is the band above the stop in the diagram, and it is the only number that reaches the tenant statement.

Step 4 — Handle the per-RSF stop variant

A fixed stop quoted per rentable square foot is not a total and must never be treated like one. The floor is the negotiated rate times the tenant’s own area — never times the building area, and never used directly as a lump sum. For lease L-77 at $7.25 per rentable square foot across 12,000 square feet, the stop is 7.25 * 12,000 = $87,000.00. That tenant’s share factor is 12,000 / 312,500 = 0.03840000, so their current-year share of the same $2,812,500 pool is 0.0384 * 2,812,500 = $108,000.00, and their recoverable amount is max(0, 108,000 − 87,000) = $21,000.00. The STOP_PER_RSF branch of base_year_stop already encodes exactly this multiplication, so the per-rentable-square-foot lease needs no separate code path — only a different basis on the stored provision. Confusing a per-square-foot rate with a building total is the classic order-of-magnitude error here: multiplying $87,000 by 312,500 square feet, or subtracting a per-square-foot rate straight from a dollar share, produces a number that is wrong by five zeros and, worse, sometimes looks plausible.

Step 5 — Store the result with provenance

The recoverable dollar figure alone is not enough to defend a year-end true-up; you also need the inputs that produced it. Capture the basis, the share factor, the current-year share, the resolved stop, and the recoverable amount in an immutable result, and fingerprint the whole record with hashlib so any later recomputation can be proved identical to the one that was billed.

import hashlib
import json


@dataclass(frozen=True)
class RecoveryResult:
    """A stored, reproducible recovery outcome for one lease-year."""

    lease_id: str
    basis: StopBasis
    share_factor: Decimal
    current_year_share: Decimal
    stop_amount: Decimal
    recoverable_amount: Decimal

    @property
    def fingerprint(self) -> str:
        """Stable 16-char audit hash of the billed inputs and output."""
        payload = json.dumps(
            {
                "lease_id": self.lease_id,
                "basis": self.basis.value,
                "share_factor": str(self.share_factor),
                "current_year_share": str(self.current_year_share),
                "stop_amount": str(self.stop_amount),
                "recoverable_amount": str(self.recoverable_amount),
            },
            sort_keys=True,
        )
        return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:16]


def resolve_recovery(
    provision: RecoveryProvision, pool_total: Decimal
) -> RecoveryResult:
    """Run one provision through share, stop, and clamp; store the result."""
    share = current_year_share(pool_total, provision)
    stop = base_year_stop(provision)
    return RecoveryResult(
        lease_id=provision.lease_id,
        basis=provision.basis,
        share_factor=provision.share_factor,
        current_year_share=share,
        stop_amount=stop,
        recoverable_amount=recoverable(share, stop),
    )

Storing the share factor and stop alongside the answer means a disputed statement can be reopened months later and every intermediate value reproduced without re-parsing the lease or rebuilding the pool. The fingerprint turns “trust me, that’s the number” into a checkable claim.

Gotchas & Known Limitations

  • Never float the money. A base-year recovery differences two large near-equal amounts; representing either as a binary float lets a fraction of a cent of drift survive into the statement. Keep pool totals, stops, shares, and even areas and rates in Decimal, and quantize with an explicit rounding mode.
  • Per-RSF is a rate, not a total. A $7.25 per-square-foot stop and an $87,000 building total are different data shapes. Store which form the lease used, normalize to one canonical dollar stop at storage time, and never multiply a lump sum by area or subtract a rate from a dollar share.
  • The clamp is not optional. In a soft-cost year the current-year share can fall below the floor. Returning the raw negative delta would credit the tenant money the lease never owes them. Floor at zero, and log the negative delta separately if you want to flag a base year that may have been set too high.
  • This stage assumes the base year is already grossed. A base-year figure captured during partial occupancy understates full-building variable cost. Grossing it up is a precondition handled upstream and detailed in handling base-year gross-up in CAM recoveries; feeding an ungrossed amount here silently overcharges the tenant.
  • Caps sit outside this clamp. When a controllable-expense cap governs the same pool, the pool must be clamped to its ceiling before the current-year share is taken, so this walkthrough deliberately receives an already-capped pool_total. Cap ordering is its own concern.
  • Quantize at the boundaries, not mid-formula. Rounding the share factor to eight places and the dollar figures to cents is deliberate; rounding every partial product would accumulate a different, harder-to-reproduce error. Keep the rounding points few and named.

Verification

The recovery functions are pure, so verification is table-driven: known inputs in, exact Decimal outputs out. Pin the two worked leases, and add the two boundary cases where clamp bugs hide — a share exactly at the stop, and a share one cent below it.

from decimal import Decimal

import pytest

POOL = Decimal("2812500.00")

L31 = RecoveryProvision(
    lease_id="L-31",
    basis=StopBasis.BASE_YEAR,
    tenant_rsf=Decimal("25000"),
    building_rsf=Decimal("312500"),
    base_year_amount=Decimal("2500000.00"),  # already grossed upstream
)
L77 = RecoveryProvision(
    lease_id="L-77",
    basis=StopBasis.STOP_PER_RSF,
    tenant_rsf=Decimal("12000"),
    building_rsf=Decimal("312500"),
    stop_per_rsf=Decimal("7.25"),
)


def test_base_year_recovery_is_band_above_stop() -> None:
    result = resolve_recovery(L31, POOL)
    assert result.current_year_share == Decimal("225000.00")
    assert result.stop_amount == Decimal("200000.00")
    assert result.recoverable_amount == Decimal("25000.00")


def test_per_rsf_stop_multiplies_rate_by_tenant_area() -> None:
    result = resolve_recovery(L77, POOL)
    assert result.stop_amount == Decimal("87000.00")
    assert result.recoverable_amount == Decimal("21000.00")


def test_share_at_the_stop_recovers_zero() -> None:
    assert recoverable(Decimal("200000.00"), Decimal("200000.00")) == Decimal("0.00")


def test_share_one_cent_below_stop_recovers_zero() -> None:
    assert recoverable(Decimal("199999.99"), Decimal("200000.00")) == Decimal("0.00")


def test_fingerprint_is_stable_across_recomputation() -> None:
    assert resolve_recovery(L31, POOL).fingerprint == resolve_recovery(L31, POOL).fingerprint


def test_ungrossed_base_year_missing_amount_is_rejected() -> None:
    with pytest.raises(ValueError):
        RecoveryProvision(
            lease_id="L-31b", basis=StopBasis.BASE_YEAR,
            tenant_rsf=Decimal("25000"), building_rsf=Decimal("312500"),
        )

Three habits make this suite trustworthy. Assert against Decimal literals, not float results, or the comparison can pass on representation alone and defeat the reason for using decimal at all. Always test the boundary at the stop itself, because off-by-one clamp errors live precisely at the threshold where the max flips. And assert that the same provision and pool always yield the same fingerprint — reproducibility is what lets a recovery be recomputed and defended a full reconciliation cycle later.

With the stop resolved, the current-year share taken, and the clamp applied, this walkthrough completes the arithmetic that modeling expense stops and base-year clauses sets up at storage time; the next precision to get right is making sure the base-year figure feeding it was normalized correctly, which is the subject of handling base-year gross-up in CAM recoveries.