Controllable vs Non-Controllable Expense Segregation

Segregating controllable from non-controllable CAM expenses is the classification layer that tags every recoverable dollar at the general-ledger level so a lease cap only ever clamps the costs a landlord can actually influence. It is the architectural stage within the expense allocation logic and rule engines calculation core that stands between raw posted expenses and every downstream ceiling: before a pro-rata share is ever computed, each line is stamped as controllable — landscaping, management fees, non-union labor, common-area supplies, the costs an owner can negotiate or defer — or non-controllable — property taxes, insurance premiums, most utilities, snow and ice removal, contractually fixed union labor. That stamp is not a display convenience; it decides which pool the cap engine is even allowed to touch, and a lease cap that is silently applied to a tax bill overbills a tenant in a way that survives a casual desk review and collapses under an audit. This page specifies the segregation system as a whole: the classification data model, the GL-code-driven rule set, the portfolio-consistent definitions that keep two properties labeling the same account the same way, the per-lease overrides that let a single tenant redefine a boundary its clause negotiated, and the dual-bucket ledger those rules produce for the cap stage to consume.

Segregating a recoverable pool into a capped controllable bucket and an uncapped non-controllable bucket A recoverable expense pool enters a controllability classifier that reads GL-code rules and per-lease overrides. Each line is routed into one of two buckets: the controllable bucket, labeled subject to cap, in primary green, which then flows into a cap gate; and the non-controllable bucket, labeled pass-through, in accent blue, which is recovered uncapped. The classifier runs before any allocation so the cap only ever sees the controllable bucket. Recoverable pool Classifier GL rules + overrides Controllable subject to cap Non-controllable pass-through Cap gate clamp controllable non-controllable
Every recoverable dollar is tagged at the GL level and routed into one of two buckets: the controllable bucket flows on into the cap gate, while the non-controllable bucket is recovered as an uncapped pass-through.

Prerequisites & Data Contracts

Segregation is a tagging pass, and a tag is only as trustworthy as the identity of the thing it labels. Three inputs must exist and be stable before the classifier runs, and each is owned by a stage upstream of this one.

A resolved GL account on every line. The unit of classification is a posted general-ledger line, and the account it carries is the primary key the rule set matches against. Those accounts are assigned by GL code mapping for CAM expenses, which turns a vendor invoice into a line posted to a specific account — say 6420 for exterior landscaping or 7110 for real-estate taxes. If the GL mapping is wrong, segregation faithfully tags the wrong thing: a drainage repair a contractor rebilled onto a capital account inherits that account’s non-controllable label and quietly exits the capped pool. The segregation layer therefore assumes a resolved, reconciled account per line and treats the mapping stage as its source of truth for what each line is.

A portfolio-consistent controllability definition. The whole point of a segregation architecture — rather than a per-property spreadsheet — is that account 6420 means the same thing in a suburban office park in Charlotte as it does in a mixed-use tower in Denver. That consistency is not free; it is manufactured by standardizing CAM taxonomies across portfolios, which normalizes divergent legacy charts of accounts onto one standard structure so a single default controllability map can cover the whole book. Without a normalized taxonomy every property needs its own rule set, drift is undetectable, and the “portfolio default” is a fiction. The data contract here is a versioned table of (gl_account or category) → default controllability, governed centrally and applied everywhere.

Per-lease override facts. Controllability is ultimately a lease term, and leases disagree. One anchor tenant’s lease may carve utilities out of the controllable definition it caps; a neighbor’s may fold non-union porter labor into it. These are not exceptions to be hard-coded — they are abstracted lease facts, keyed to a lease_version, that the segregation engine layers on top of the portfolio default. The contract is a set of override rows, each naming the account it redefines, the label it forces, and the clause that justifies it, sourced from the same lease abstraction work that defines the CAM expense categories in your lease. An override the engine cannot trace to a clause version is a liability, not a feature.

One property of these contracts deserves emphasis because it is what separates a segregation architecture from a per-property spreadsheet: the defaults are governed once, centrally, and the overrides are the only thing that varies from lease to lease. A property manager onboarding a newly acquired asset does not author a fresh controllability map; they map the asset’s chart onto the standard taxonomy, inherit the whole default set, and add only the handful of overrides its leases negotiated. That inversion — inherit almost everything, override almost nothing — is what keeps a thousand-tenant book from fragmenting into a thousand incompatible definitions of “controllable,” and it is why the portfolio-consistent taxonomy is a hard prerequisite rather than a nicety.

With those three contracts in place the segregation layer owns exactly one job: resolve each line to a controllability label using a defined precedence, and sum the results into disjoint buckets whose total equals the pool it started from.

Algorithm or Rule Design

The rule engine resolves one question per line — controllable, non-controllable, or unresolved — and it resolves it by precedence rather than by a single lookup. Two rule sources feed the decision, and they are deliberately ordered:

  1. Portfolio default rules map a GL account, or a coarser expense category, to a controllability label that applies across the entire book. This is the baseline: real-estate taxes are non-controllable, landscaping is controllable, and both hold everywhere unless something more specific overrides them.
  2. Per-lease override rules map a GL account to a controllability label for one lease version only. They exist because a cap clause is a negotiated definition of “controllable,” and a specific tenant can move the boundary the whole portfolio otherwise agrees on.

Precedence is strict: a lease override, when one exists for the account under the lease being reconciled, wins over the portfolio default; the portfolio default applies when no override is present; and when neither source resolves the account, the line is routed to review rather than guessed. Guessing is the cardinal sin of this layer — defaulting an unknown to controllable pulls an unclassified tax line under a cap, while defaulting it to non-controllable strips a genuine controllable of the ceiling a tenant negotiated. An unmapped account is a decision waiting to be made, and the engine’s job is to surface it, not bury it in a subtotal.

The two rule tiers also differ in granularity, and the difference matters for maintenance. Portfolio defaults are best expressed at the coarsest level that is still correct — often a category or an account range rather than a single account — because a book-wide rule that has to enumerate every account number rots the moment a new account is opened. A rule that says “the entire 71007199 real-estate-tax range is non-controllable” survives a chart expansion that a rule listing 7110, 7115, 7120 individually does not. Overrides, by contrast, are almost always account-specific: a lease redefines one named line, not a whole category, so an override that reached across a range would quietly recapture accounts the clause never mentioned. The engine therefore reads defaults as the broad, stable backdrop and overrides as narrow, surgical exceptions, and it never lets an override widen its own blast radius beyond the account it names.

There is one more ordering subtlety worth stating explicitly. The precedence is evaluated per account, not per line, so two lines posted to the same account under the same lease always resolve to the same label — the resolution is a pure function of the account and the two rule maps, with no dependence on line order, amount, or description. That determinism is what makes a reconciliation replayable: rerun the segregation over the same ledger with the same rule versions and the partition is bit-for-bit identical, which is precisely the property an audit relies on when a statement is reopened years later.

The resolution reduces to a compact decision table, evaluated top to bottom, with the first matching row winning:

Precedence Lease override for account? Portfolio default for account? Resolved label
1 Yes → controllable (ignored) Controllable
2 Yes → non-controllable (ignored) Non-controllable
3 No Yes → controllable Controllable
4 No Yes → non-controllable Non-controllable
5 No No Review (quarantine)

Formally, segregation partitions the recoverable pool PP into a controllable set CC and a non-controllable set NN that share no line, with any unresolved lines held out in a review set QQ:

P=CNQ,CN=CQ=NQ=P = C \,\cup\, N \,\cup\, Q, \qquad C \cap N = C \cap Q = N \cap Q = \varnothing

and the monetary contract the downstream stages rely on is that the buckets conserve the pool to the cent:

pool_total=controllable_total+non_controllable_total+review_totalpool\_total = controllable\_total + non\_controllable\_total + review\_total

Only CC — the controllable bucket total — is eligible to reach a cap. NN is recovered as an uncapped pass-through, and QQ blocks the reconciliation until a human resolves it, because a pool with unclassified dollars cannot be honestly billed. Keeping the partition explicit, rather than computing a controllable subtotal and inferring the rest, is what lets the conservation equation act as a proof that no dollar was created, lost, or double-counted while the ledger was split.

Python Implementation

The implementation models the two rule sources as typed data, resolves each line by the precedence above, and emits a dual-bucket ledger. Every monetary value is a decimal.Decimal; binary float cannot represent a cent exactly, and a segregation layer that leaks a fraction of a cent per line breaks the conservation check that is the entire point of keeping the buckets disjoint. Rounding, where a total is finalized, uses an explicit ROUND_HALF_UP from the standard-library decimal module.

from __future__ import annotations

from dataclasses import dataclass, field
from decimal import Decimal, ROUND_HALF_UP
from enum import Enum

CENTS = Decimal("0.01")


class Controllability(str, Enum):
    """Whether a lease lets the landlord influence—and a cap govern—a cost."""
    CONTROLLABLE = "controllable"          # landscaping, mgmt fees, non-union labor, supplies
    NON_CONTROLLABLE = "non_controllable"  # taxes, insurance, utilities, snow/ice, union labor
    REVIEW = "review"                      # unresolved account: never inferred


@dataclass(frozen=True)
class ExpenseLine:
    """One posted general-ledger line entering the controllability split."""
    line_id: str
    gl_account: str
    description: str
    amount: Decimal


@dataclass(frozen=True)
class PortfolioRule:
    """Book-wide default: a GL account's controllability across the portfolio."""
    gl_account: str
    label: Controllability
    taxonomy_version: str   # the standardized taxonomy that justifies this default


@dataclass(frozen=True)
class LeaseOverride:
    """A single lease redefining one account's controllability by clause."""
    gl_account: str
    label: Controllability
    lease_version: str
    clause_ref: str

The resolver encodes the precedence table directly. It is deliberately total — every account produces a label, and an unresolved account produces REVIEW rather than an exception, so a chart-of-accounts change surfaces as a quarantined dollar instead of a crash mid-reconciliation.

def resolve_label(
    account: str,
    portfolio: dict[str, PortfolioRule],
    overrides: dict[str, LeaseOverride],
) -> Controllability:
    """Resolve one account's controllability: lease override, then portfolio
    default, then REVIEW. The first source that resolves the account wins."""
    override = overrides.get(account)
    if override is not None:            # precedence 1-2: the lease redefines the boundary
        return override.label
    default = portfolio.get(account)
    if default is not None:             # precedence 3-4: the portfolio-wide default
        return default.label
    return Controllability.REVIEW       # precedence 5: never guess an unmapped account

The segregation pass walks the ledger once, resolves each line, and accumulates three disjoint totals plus a tagged trail. The result is the dual-bucket ledger the rest of the pipeline consumes — a controllable total eligible for a cap, a non-controllable pass-through total, and a review total that must be zero before the reconciliation is allowed to proceed.

@dataclass
class SegregatedLedger:
    """The controllability partition: two recoverable buckets plus quarantine."""
    controllable_total: Decimal = field(default_factory=lambda: Decimal("0.00"))
    non_controllable_total: Decimal = field(default_factory=lambda: Decimal("0.00"))
    review_total: Decimal = field(default_factory=lambda: Decimal("0.00"))
    tagged: list[tuple[str, Controllability]] = field(default_factory=list)

    @property
    def pool_total(self) -> Decimal:
        """Every recoverable dollar, regardless of bucket—the conservation base."""
        total = self.controllable_total + self.non_controllable_total + self.review_total
        return total.quantize(CENTS, rounding=ROUND_HALF_UP)


def segregate(
    lines: list[ExpenseLine],
    portfolio: dict[str, PortfolioRule],
    overrides: dict[str, LeaseOverride],
) -> SegregatedLedger:
    """Partition a recoverable pool into controllable and non-controllable
    buckets, holding unresolved lines in a review total for a human to clear."""
    ledger = SegregatedLedger()
    for line in lines:
        label = resolve_label(line.gl_account, portfolio, overrides)
        ledger.tagged.append((line.line_id, label))
        if label is Controllability.CONTROLLABLE:
            ledger.controllable_total += line.amount
        elif label is Controllability.NON_CONTROLLABLE:
            ledger.non_controllable_total += line.amount
        else:
            ledger.review_total += line.amount
    return ledger

Because the override map is passed in per reconciliation, the same portfolio defaults drive every property while each lease supplies only its own deviations — the portfolio-wide labeling stays consistent, and the lease-specific redefinitions stay auditable to a clause. A focused, expanded treatment of this classifier, including drift detection against the prior period, lives in classifying controllable expenses with Python.

Validation Rules & Edge Cases

The resolver is a two-tier lookup; the ways a real portfolio bends it are where the value of a dedicated segregation layer shows up. Each of the following is a case the engine must handle by design rather than absorb into a wrong bucket.

  • Ambiguous accounts that sit on the boundary. Snow and ice removal, and most utilities, are the classic ambiguous cases: some leases treat them as controllable, most treat them as non-controllable pass-throughs because an owner cannot negotiate away a blizzard or a utility tariff. The portfolio default should reflect the common treatment, and any lease that disagrees supplies an override — the same account legitimately carries different labels across two properties, and that is a feature of a lease-driven system, not a bug in the data.
  • Partly controllable lines. A single posted amount can straddle the boundary — the classic example is a capital improvement whose gross cost is non-controllable but whose lease-permitted amortized annual slice re-enters the controllable pool. A single-label classifier cannot represent this. The correct architecture never labels the gross line; it requires the amortization to be posted as its own GL line with its own account and rule, so segregation sees two lines with two clean labels rather than one line it must split.
  • Lease-specific redefinitions of the boundary. An override is precisely a lease redefining what “controllable” means for its cap. The engine must apply overrides only to the lease that owns them and must key them to a lease_version, so reopening a prior year replays that year’s boundary and not today’s. An override edited in place, rather than versioned, makes every historical statement for that tenant unreproducible.
  • A category that must never be capped. Real-estate taxes are the canonical line that must never enter a controllable bucket, because taxes are the archetypal non-controllable pass-through and no legitimate lease caps them as controllable. A defensive segregation layer can pin such categories as non-overridable, so that even a malformed override cannot pull a tax account under a cap — the boundary has a floor the classifier will not cross.
  • Conservation across the buckets. The load-bearing invariant is that the two recoverable buckets plus the review total equal the pool the layer received, to the cent. If they do not, a line was dropped, double-counted, or silently coerced, and the entire downstream recovery is built on a pool that no longer reconciles to the ledger. This check runs every reconciliation, not just in tests.
  • A non-empty review bucket blocks the run. Any dollar in review means an account the rules could not resolve — a new vendor account, a post-acquisition chart migration, a clause the abstraction has not yet captured. The reconciliation must halt until review is cleared to zero, because a cap applied to a pool that still hides unclassified dollars produces a confidently wrong statement.

Integration Points

Segregation sits early in the calculation core, and what it hands downstream is the reason its position is load-bearing.

  • The controllable bucket feeds the cap engine. The controllable_total this layer produces is the exact figure managing expense caps and controllable limits clamps to a lease ceiling, and the cumulative-cap variant of that clamp is detailed in applying cumulative caps to controllable CAM. The cap engine is designed to trust that it never sees a non-controllable dollar; segregation is what earns that trust. The narrower, calculation-level split that the cap parent already documents in handling controllable vs non-controllable CAM expenses is the same idea applied to one recovery; this page is the segregation system those calculations draw their labels from.
  • Controllable is not the same axis as variable. The most common conceptual error is to conflate the controllable/non-controllable split with the fixed/variable split that gross-up normalization logic operates on. They are orthogonal. Gross-up normalizes variable costs — those that move with occupancy, like janitorial hours or utility consumption — up to a stabilized level; controllability is about whether a landlord can influence a cost, cap or no cap. A management fee is controllable but largely fixed; a utility bill is variable but usually non-controllable. Two independent tags, two independent engines, and the segregation label must never be reused as a gross-up flag.
  • Ordering relative to gross-up. When a lease both grosses up and caps, the gross-up runs on the variable slice before the cap clamps the controllable bucket, so the ceiling clamps a normalized figure. Segregation runs first of all, because both the gross-up and the cap need to know which pool they are operating on before they touch a number.
  • Exclusions run alongside, not instead. Controllability tagging does not remove a carved-out cost — a related-party or above-market charge stays challengeable even when correctly labeled controllable — so segregation runs in concert with the exclusion engine rather than replacing it. A line can be controllable and excluded, and the two determinations are made independently.

Testing & Verification

Segregation bugs are invisible until a tenant’s consultant recomputes the controllable bucket by hand and finds a utility line the lease never capped, so the layer is tested against small fixtures where the correct partition is known from the portfolio defaults and the lease clause. Two assertions carry the weight: override precedence resolves correctly over the portfolio default, and the buckets conserve the pool to the cent.

from decimal import Decimal


def _portfolio() -> dict[str, PortfolioRule]:
    """Book-wide defaults for a standardized chart of accounts."""
    return {
        "6420": PortfolioRule("6420", Controllability.CONTROLLABLE, "tax-2026.1"),      # landscaping
        "6510": PortfolioRule("6510", Controllability.CONTROLLABLE, "tax-2026.1"),      # management fee
        "7110": PortfolioRule("7110", Controllability.NON_CONTROLLABLE, "tax-2026.1"),  # real-estate tax
        "7240": PortfolioRule("7240", Controllability.NON_CONTROLLABLE, "tax-2026.1"),  # utilities (default)
    }


def test_lease_override_beats_portfolio_default() -> None:
    # Tenant's lease (Cedar Point, suite 300) folds utilities into controllable.
    overrides = {
        "7240": LeaseOverride("7240", Controllability.CONTROLLABLE, "2026.2", "§6.3(b)"),
    }
    lines = [
        ExpenseLine("L1", "6420", "Grounds maintenance", Decimal("14200.00")),
        ExpenseLine("L2", "6510", "Property management fee", Decimal("9300.00")),
        ExpenseLine("L3", "7110", "Real-estate taxes", Decimal("61000.00")),
        ExpenseLine("L4", "7240", "Common-area electricity", Decimal("18500.00")),
    ]
    ledger = segregate(lines, _portfolio(), overrides)

    # Utilities crossed into the controllable bucket for THIS lease only.
    assert ledger.controllable_total == Decimal("42000.00")   # 14200 + 9300 + 18500
    assert ledger.non_controllable_total == Decimal("61000.00")
    assert ledger.review_total == Decimal("0.00")

    labels = dict(ledger.tagged)
    assert labels["L4"] is Controllability.CONTROLLABLE


def test_conservation_and_quarantine_of_unmapped_account() -> None:
    lines = [
        ExpenseLine("L1", "6420", "Grounds maintenance", Decimal("14200.00")),
        ExpenseLine("L2", "7110", "Real-estate taxes", Decimal("61000.00")),
        ExpenseLine("L3", "9990", "New vendor, unmapped", Decimal("2750.00")),
    ]
    ledger = segregate(lines, _portfolio(), overrides={})

    # The unmapped account is quarantined, never inferred into a bucket.
    assert ledger.review_total == Decimal("2750.00")
    assert dict(ledger.tagged)["L3"] is Controllability.REVIEW

    # Conservation: the three buckets reconstruct the pool exactly, to the cent.
    gross = sum((line.amount for line in lines), Decimal("0.00"))
    assert ledger.pool_total == gross
    assert (
        ledger.controllable_total
        + ledger.non_controllable_total
        + ledger.review_total
    ) == gross

Because every amount is Decimal, the conservation assertion uses exact equality rather than a floating-point tolerance, so a one-cent leak between buckets fails the test instead of hiding under isclose. The override test guards the case a portfolio-wide default cannot express — a single lease that moved the boundary — and proves the redefinition applies to that tenant’s ledger without disturbing the book-wide labeling every other property depends on.

Frequently Asked Questions

Why do lease caps apply only to controllable expenses and not to the whole pool? Because a cap is a promise a landlord can actually keep. Controllable costs — landscaping, management fees, non-union labor, supplies — are the ones an owner can negotiate, defer, or shop competitively, so capping their growth is a commitment about the owner’s own discipline. Non-controllable costs — property taxes set by an assessor, insurance premiums set by a carrier, utility tariffs, a blizzard’s worth of snow removal — are outside the owner’s control, and no rational owner agrees to absorb their overruns. Applying a cap to the full pool would force the landlord to eat tax and insurance increases it cannot influence, so leases cap only the controllable slice, which is exactly why that slice must be segregated before any ceiling is computed.

Is a controllable expense the same as a variable expense? No, and conflating them is one of the most common modeling errors in CAM. Controllability is about whether the landlord can influence a cost; variability is about whether the cost moves with occupancy. They are independent axes. A management fee is controllable but largely fixed; common-area utilities are variable with occupancy but usually non-controllable. The controllable/non-controllable split feeds the cap engine, while the fixed/variable split feeds gross-up normalization logic. They are two separate tags produced by two separate rules, and reusing one as the other silently caps the wrong pool or grosses up the wrong costs.

Who defines whether a cost is controllable — the accountant or the lease? The lease defines it; the accounting system enforces it. Controllability originates in the cap clause a tenant negotiated, which sets the boundary of what “controllable operating expenses” means for that tenant. The segregation layer expresses that boundary as a portfolio-wide default for the common case and a per-lease override for the negotiated exceptions, both traced to an abstracted lease version. An accountant maintains the mapping, but the mapping’s authority comes from the clause, not from convenience, which is why every rule carries a taxonomy version or a clause reference.

How can one lease redefine controllability without breaking the rest of the portfolio? Through a precedence-ordered override that applies to that lease alone. The portfolio default labels an account for the whole book; a lease override, keyed to that lease’s version, redefines the account for only that reconciliation and wins over the default when present. Because overrides are passed in per lease rather than edited into the shared defaults, one tenant folding utilities into its controllable cap changes nothing for the property next door, and reopening a prior year replays the exact overrides that were in force then.

Segregation is the quiet decision that governs every dollar the cap engine is later allowed to touch: get the controllable/non-controllable boundary right, keep it consistent across the portfolio, and let each lease bend it only through a versioned, clause-traced override, and the downstream ceiling clamps a base the lease actually subjected to it. The controllable bucket this layer produces flows straight into managing expense caps and controllable limits, draws its portfolio-wide consistency from standardizing CAM taxonomies across portfolios, and inherits the resolved accounts it tags from GL code mapping for CAM expenses — three contracts that, held together, turn a raw ledger into two honest buckets.