More accurate math
His and hers RMDs
The old engine treated a married couple's traditional IRA balances as a single combined pile. It correctly applied the RMD divisors to the correct sub-account, but we then recombined the RMD to the "combined" account value. That's not how the IRS actually does it - each spouse has their own RMD, calculated against their own IRA balance, with the divisor picked from either the Uniform Lifetime Table or the Joint Life Table II, depending on the age gap.
We now compute them separately and per spouse. Forced distributions land in the right person's account, the divisor picked is the one the IRS would actually use, and the downstream tax math reflects actual dollar flows instead of a blended approximation.
Every account is tracked separately.
Internally, we now track up to four kinds of accounts per spouse instead of merging them into one bucket:
Primary IRA — the portion they're keeping in a traditional IRA, untouched
IRA-to-Roth — the portion being converted (this is what funds the annuity)
Existing Roth — Roth dollars the client already has
New Roth - Roth dollars created by conversions during the plan
When income is pulled later in life, it follows a deliberate order — Primary IRA first, then IRA-to-Roth, then Existing Roth, then New Roth — because that order matters for tax outcomes and for protecting tax-free dollars as long as possible. The old engine couldn't model this faithfully; this one does.
Existing Roth balances
You now have Primary Roth A.V. and Spouse Roth A.V. fields for any Roth dollars the client already has before the conversion plan starts. These get drained first in the income pull chain (before any new conversions you create), which gives you a clear picture of when the annuity actually starts being touched.
If the client has no existing Roth, leave the fields at 0 - same as before.
The one UX change to actively know about
"Amount to Convert" means something new.
This is the most important thing to know. "Amount to Convert" is now the dollars the client is actually moving into the annuity - the check they write to the carrier - not the total IRA balance to plan around.
Concretely: if your client has $500k in a traditional IRA and wants to put $200k of it into an annuity, "Amount to Convert" = $200,000. The remaining $300k stays in a Primary IRA bucket, untouched, and the conversion math runs only on the $200k that's actually getting converted.
This matches what's literally happening at the carrier level and lets us model partial conversions much more accurately. Heads up: cases that ran on the old engine and that you reload may need their Amount-to-Convert value updated to match the new semantics.
Conversion mode + Constraint is now one dropdown.
There used to be two dropdowns - "Conversion Type" and "Constraint" - that interacted in confusing ways. They've been merged into a single Constraint dropdown with four options:
Specified Amount - pin a fixed annual conversion dollar amount
Tax Rate - convert to fill a specific tax bracket
IRMAA Tier - convert to fill a specific IRMAA tier
Optimized - let the engine pick the bracket/tier that maximizes lifetime wealth (new default)!
If you used "Specified" mode under the old engine, your case automatically maps to "Specified Amount" here.
Form: simpler first run, organized Advanced
The first-run form is now stripped down to fields that matter for a typical case. Anything power-user-only — death events, IRMAA tier picking, max conversion years, baseline rate of return, surrender mechanics — lives in the Advanced section.
Advanced is now grouped into clear sub-sections so you can find things fast:
Constraints — bracket cap, IRMAA tier, max conversion years, specified amount
Conversion — pay taxes externally
Mechanics — protect initial premium, surrender, penalty-free withdrawal
Timing — years to defer, baseline rate of return, end age
Heirs — heir tax, widow's penalty / death-event modeling
Click "Advanced" and these expand. Day-to-day cases usually don't need any of it.
Faster
Two changes you'll feel:
Compressed responses — engine results travel ~70% smaller, which shaves a couple of seconds off every blueprint render
Smart caching — once a case is computed, the result is cached. Re-rendering, switching tabs, downloading the Excel/PDF — all instant. The cache invalidates automatically when you change any input that affects the math, or when we ship an engine update (like this one), so you'll never see stale numbers
You'll feel both most on the dashboard list and when bouncing between Blueprint, Excel, and PDF on the same case.
A few quality-of-life additions
Net Income column — when you set a "net income" target on a case, the proof tables now show side-by-side what you asked for vs what the engine actually delivered. If they don't match in a given year, the constraint conflict is right there (e.g., a specified annual cap below your target).
Optiblend tax savings — cases using Optiblend now report how much in taxes the product saved versus a vanilla FIA equivalent. Useful for client-facing comparisons. Note: this number can legitimately be zero in scenarios where the conversion completes in a single year — Optiblend's deferred-then-burst growth pattern needs multiple years of conversion to differentiate from a vanilla FIA. The number is honest; if it shows zero, the answer for that specific case is "Optiblend made no difference."
Honest results under constraint conflicts — if your inputs make the target unreachable (e.g., non-IRA income alone exceeds your net income target), the engine reports the actual outcome with the binding Constraint flagged in the proof tables, instead of forcing a number that hides the conflict.
If something looks off
This is a major rewrite. We've tested extensively against historical cases and the engine runs automated invariant checks on every release, but real-world cases find creative ways to surprise us.
If you see numbers that look wrong, hit an error, or just want a second set of eyes — please reach out: [email protected]
Include the case number (the URL is /zlc/12345) and a quick description of what looks weird. We'll dig in directly.