Accounting for a Lease Modification in Python

Implement an ASC 842 / IFRS 16 lease modification in Python: recompute the liability at a revised discount rate, adjust the ROU asset by the delta, with Decimal precision and a terminal assert.

Problem Statement Link to this section

A lessee renegotiates an in-flight lease: the landlord agrees to a higher rent in exchange for improvements, or the tenant extends the term by two years. The change is not a separate lease — it does not grant a standalone-priced additional right of use — so it is a modification that must be remeasured. This page answers one precise engineering question: given the carrying liability and right-of-use asset at the modification effective date, how do you recompute the liability at a revised discount rate and adjust the asset by exactly the right amount, with no gain or loss leaking into profit or loss? The trap is not the arithmetic; it is discounting the revised payments at the wrong rate, or netting the change into the wrong account. The whole calculation reduces to recomputing a present value on the revised remaining payments and pushing the difference into the right-of-use asset.

Standard Anchor Link to this section

Two provisions govern a modification that is not a separate lease and does not decrease scope:

  • IFRS 16.45 — for a modification that is not accounted for as a separate lease, at the effective date the lessee remeasures the lease liability by discounting the revised lease payments using a revised discount rate. For all modifications other than a decrease in scope, IFRS 16.45(b) requires a corresponding adjustment to the carrying amount of the right-of-use asset. No gain or loss arises on the measurement itself.
  • ASC 842-10-25-11 — for a modification not accounted for as a separate contract, the lessee remeasures and reallocates the remaining consideration, determines a discount rate at the modification effective date, and adjusts the right-of-use asset for the change in the lease liability. ASC 842 additionally requires the lessee to reassess lease classification at that date.

Both frameworks therefore agree: revised payments, revised rate, and the difference lands on the asset. The revised rate is struck at the effective date exactly as an original rate is struck at commencement — see discount rate determination and mapping. Continuing the schedule at the old rate is the defining error this page exists to prevent.

Formula / Algorithm Specification Link to this section

Let be the carrying lease liability immediately before the modification and its paired asset. With a revised periodic rate , revised remaining payments , and remaining periods, the revised liability is the present value of those payments:

The change in the liability is posted in full to the right-of-use asset:

The invariant a correct implementation must satisfy is that the asset moves by exactly the liability change and nothing routes to profit or loss: . After the adjustment, the schedule continues under the effective interest method from the new liability at the revised rate .

Recomputing the liability at the revised rate and adjusting the ROU asset Revised payments and a revised discount rate feed a present-value recomputation that produces the revised liability L prime. The difference delta L, equal to L prime minus the carrying liability L, is added to the carrying right-of-use asset to give the adjusted asset. No gain or loss is recognised. The carrying liability and the carrying right-of-use asset feed in from below as the prior amounts being adjusted. Revised payments P′ₖ · revised rate r′ Revised liability L′ = Σ P′ₖ /(1+r′)ᵏ ΔL = L′ − L Adjust ROU asset ROU′ = ROU + ΔL carrying liability L carrying ROU asset no P&L
The revised payments discounted at the revised rate give L′; the whole difference ΔL adjusts the right-of-use asset, and the carrying liability and asset feed in as the prior amounts. Nothing is recognised in profit or loss on a modification that is not a decrease in scope.

Annotated Python Snippet Link to this section

The function recomputes the liability with decimal.Decimal, adjusts the asset by the delta, and asserts the two invariants: the asset moved by exactly the liability change, and no gain or loss was recognized. The example renegotiates a lease at month 24 — rent rises and the revised rate is struck at 6% — and continues from the new balance.

from decimal import Decimal, ROUND_HALF_UP, getcontext

getcontext().prec = 28                     # audit-grade; no float drift in the PV sum
CENT = Decimal("0.01")


def q(x: Decimal) -> Decimal:
    return x.quantize(CENT, rounding=ROUND_HALF_UP)


def modify_lease(rou: Decimal, liability: Decimal,
                 revised_payments: tuple[Decimal, ...], revised_rate: Decimal) -> dict:
    """Remeasure a modification that is not a separate lease and not a scope
    decrease (IFRS 16.45(b) / ASC 842-10-25-11): revised payments at the revised
    rate; the change in liability adjusts the ROU asset, with no gain or loss."""
    revised_liab = q(sum(
        (p / (Decimal(1) + revised_rate) ** k
         for k, p in enumerate(revised_payments, start=1)),
        Decimal("0"),
    ))                                     # L' = PV of revised remaining payments
    delta = revised_liab - liability       # ΔL = L' − L
    new_rou = q(rou + delta)               # ROU' = ROU + ΔL (offset to the asset)
    return {"liability": revised_liab, "rou": new_rou, "delta": delta,
            "pl_gain_loss": Decimal("0.00")}


# Modification at month 24: 36 payments remain, rent 9,000 → 10,250, revised rate 6%
carrying_rou = Decimal("198450.00")
carrying_liability = Decimal("193200.00")
new_payments = tuple(Decimal("10250.00") for _ in range(36))
r_monthly = (Decimal("1.06")) ** (Decimal(1) / Decimal(12)) - Decimal(1)

result = modify_lease(carrying_rou, carrying_liability, new_payments, r_monthly)

# Invariant 1 — the asset moved by exactly the liability change
assert result["rou"] - carrying_rou == result["delta"]
# Invariant 2 — a modification (not a scope decrease) recognises no gain or loss
assert result["pl_gain_loss"] == Decimal("0.00")
print("revised liability:", result["liability"])
print("adjusted ROU asset:", result["rou"], "| ΔL:", result["delta"])

The two assertions are the publishable-or-not gate. If a future refactor discounts at the original rate, the delta and both balances change but the invariants still hold — which is why they are necessary but not sufficient, and why the rate itself is checked in review, not only in code.

Correct vs. Incorrect: Revised Rate vs Original Rate Link to this section

The one decision that changes the answer is the rate. A modification uses a rate struck at the effective date; reusing the commencement rate is the classic defect.

Aspect Revised rate at effective date (correct) Original commencement rate (incorrect)
Standard basis IFRS 16.45 / ASC 842-10-25-11 Not permitted for a modification
What it reflects Current credit and market conditions Stale conditions from commencement
Effect on L′ Present value at today's cost of borrowing Mis-stated liability, wrong every period after
Downstream interest Constant revised rate on the new balance Interest inconsistent with the revised liability
When the original rate is right Never for a modification Only for an index or rate reassessment (CPI)

The confusion arises because a reassessment driven by an index or rate change does keep the original rate. A modification never does. Branch on the event type, not on habit — the distinction is spelled out in the parent lease modification and remeasurement accounting topic.

Gotcha / Failure Mode: Re-discounting the Whole Original Term Link to this section

A subtle failure is discounting the original full payment vector rather than the remaining revised one. The revised liability is the present value of the payments still to be made from the effective date forward — not a fresh valuation of the lease from its original commencement.

Before (WRONG — re-values from the original commencement, double-counting elapsed periods):

revised_liab = pv(all_payments_from_commencement, revised_rate)  # includes paid periods

After (correct — only the remaining revised payments, indexed from the effective date):

revised_liab = pv(revised_payments_remaining, revised_rate)      # from effective date forward

Debug checklist when the adjusted asset looks wrong:

  1. Confirm the payment window. revised_payments must contain only periods from the effective date to expiry, never already-paid periods.
  2. Confirm the rate. The rate must be the revised rate struck at the effective date, not the commencement rate.
  3. Confirm the offset account. For a modification that is not a scope decrease, the entire delta adjusts the ROU asset; if any amount hits profit or loss, the branch is wrong.
  4. Confirm precision. Every amount is Decimal; a float payment or rate reintroduces drift the terminal assert is meant to catch.

Frequently Asked Questions Link to this section

Does a lease modification create a gain or loss?

Not when it is a modification that is not a separate lease and does not decrease scope. Under IFRS 16.45(b) and ASC 842-10-25-11 the entire difference between the revised and prior liability adjusts the right-of-use asset, with no gain or loss on the measurement. A gain or loss arises only on a decrease in scope — a partial or full termination — where a proportional slice of both balances is derecognized first.

Which discount rate applies to the modified lease?

A revised rate determined at the modification effective date, reflecting current credit and market conditions, not the rate locked at commencement. Both IFRS 16.45 and ASC 842-10-25-11 require it. The only remeasurement that keeps the original rate is a reassessment driven by a change in an index or rate, such as a CPI reset, which is a different event with different mechanics.

Do I discount the whole lease again or only the remaining payments?

Only the remaining payments from the effective date to expiry, discounted at the revised rate. The revised liability replaces the carrying liability, so already-paid periods are excluded. Re-valuing from the original commencement double-counts elapsed time and overstates the liability, which then propagates into every subsequent period.