What-If Analysis Without the Spreadsheet
A controller at a major restaurant chain told us about his modification workflow. Every potential lease change required building a new Excel model from scratch: copy the amortization schedule, manually adjust the terms, recalculate PV, check the classification, compare to the original. For a single modification, he might build five or six versions before finding the right structure.
"We'd spend half a day just modeling different scenarios. What if we extend? What if we reduce rent? What if we exit early? Each one is a separate spreadsheet."
We built a system where the controller just asks.
The architecture
The scenario engine has two layers. The conversational layer handles intent parsing and tool routing. The calculation layer runs the actual ASC 842 math: load the lease, merge overrides, generate a full amortization schedule, summarize, compute deltas.
The key architectural decision: scenarios are entirely read-only. The agent calls model_scenario, which runs the full calculation pipeline in memory without persisting anything. Nothing touches the database until the user explicitly commits. This means controllers can explore freely without fear of corrupting production data.
Forward and reverse
The system supports two fundamentally different modes. Forward modeling: "What if we extend to 10 years?" Change the inputs, see the outputs. Reverse modeling: "What term gives us $50K/month?" Set the target, find the inputs.
model_scenario. Merges the user's changes with current lease data, runs the full ASC 842 calculation, and returns a before/after comparison. Read-only — nothing is persisted until the user commits.Forward is a single call to model_scenario. Reverse wraps it in a bisection loop. The system detects whether the relationship is normal or inverted (longer term means lower monthly payment, which is inverted), sets initial bounds, and halves the search space each iteration:
export async function reverseScenario(input: ReverseInput): Promise<ReverseResult> {
let low = input.lowerBound;
let high = input.upperBound;
for (let i = 0; i < MAX_ITERATIONS; i++) {
const mid = Math.round((low + high) / 2);
const result = await modelScenario({ ...input.base, [input.variable]: mid });
const error = Math.abs(result[input.targetMetric] - input.targetValue) / input.targetValue;
if (error <= TOLERANCE) return { converged: true, value: mid, iterations: i + 1 };
const overshoot = result[input.targetMetric] > input.targetValue;
if (input.inverted ? overshoot : !overshoot) low = mid;
else high = mid;
}
return { converged: false };
}Bisection in action
Watch the algorithm converge. Pick a target monthly expense for an $8M lease at 4.5%, and the bisection search finds the lease term that achieves it, narrowing the bounds by half each iteration.
The feasibility classification matters: if no term between 12 and 360 months can hit the target, the agent says so rather than returning a nonsensical answer. Integer rounding is handled separately: lease terms must be whole months, so the final answer snaps to the nearest integer and re-verifies.
The impact
Controllers used to spend half a day per modification exploring scenarios. Now they ask in natural language and get structured before/after comparisons in seconds.
The modification service behind the scenes uses decimal.js for all monetary math: PV recalculation, gain/loss computation, SSP penalty allocation. When the user commits a scenario, the service generates journal entries, updates the amortization schedule, and records the full before/after state for the audit trail.
Every scenario the controller explored is preserved in the conversation history. The auditor can see not just what was committed, but what alternatives were considered and why they were rejected.
See Intelligence and Lease Accounting to learn more about these capabilities.
Previous: Roll Forwards That Generate Themselves