Calculation catalog › Medicare and ACA

MAGI annual

MAGI realized in a projection year for tax, ACA, and later IRMAA decisions.

Field: Modified adjusted gross income Unit: US dollars Basis: nominal dollars Computed by: Engine Status: Complete chain

Appears in: chart (magi); csv (magi); survivor-page (Tax around the transition cell "on MAGI a → b" (death year and first survivor year)); results-table (MAGI column); report (year-ledger block magi); scenarios-page (Annual "MAGI"); mcp (run_projection.years[].magi)

Calculation records

  • ACA household MAGI composition Composition record, justified by derivation

    Purpose: Assemble the program-specific household income the credit is measured against.

    Statement: tax/aca.ts#buildAcaHouseholdMagi computes current-year ACA household MAGI as federal AGI plus nontaxable Social Security (gross less taxable), plus known tax-exempt interest, plus the known foreign-exclusion addback, plus the MAGI of dependents required to file, floored at zero. Unknown tax-exempt interest, an unknown foreign addback or an unknown dependent filing status make the result non-actionable and publish a null MAGI with blockers instead. These addbacks change ACA MAGI evidence only and are never fed back into ordinary taxable income. Units: nominal USD. Rounding: none.

    Formula: MAGI = max(0, AGI + max(0, SSgross - SStaxable) + TEI + FEA + sum of required-filer dependent MAGI)

    • AGI: Federal adjusted gross income, carried signed until the household floor (unit: usd/year) (domain: any finite number)
    • SSgross: Gross Social Security benefits (unit: usd/year) (domain: nonnegative)
    • SStaxable: Taxable share of those benefits (unit: usd/year) (domain: nonnegative)
    • TEI: Tax-exempt interest, counted only when its state is known (unit: usd/year) (domain: nonnegative)
    • FEA: Foreign-exclusion addback, counted only when its state is known (unit: usd/year) (domain: nonnegative)

    Timing: one coverage year

    Rounding: none

    Limits:

    • Only dependents whose filing status is required contribute; a not-required dependent contributes zero and an unknown one blocks the whole result rather than being guessed
    • This is the ACA program MAGI, a different quantity from the IRMAA ledger MAGI on YearResult.magi even though both are catalogued under magi-annual

    Provenance: derived by codex, implemented by claude.

    Independent review: cursor

    Gates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass

    Worksheet: aca-household-magi-composition.md Mutation receipt: aca-household-magi-composition.mutation.md

    Evidence tests: aca.evidence.test.ts

    Implemented in: aca.ts

    Is the number of: magi-annual

    Feeds: aca-modeled-allowable-ptc-annual, aca-economic-net-premium-annual

  • Ledger MAGI composition Composition record, justified by derivation

    Purpose: Publish the five-term annual MAGI the IRMAA lookback and the ACA credit both read.

    Statement: projection/internal/types/result.ts#YearResult.magi publishes max(0, ordinary income realized + realized gains + qualified dividends + taxable Social Security + tax-exempt interest); projection/internal/annualFundingApplicationAndClosePhase.ts#annualFundingApplicationAndClosePhase is the site that computes it and writes the year into magiHistory. Untaxed Social Security is not a separate add-back, and foreign income enters only indirectly through taxable-Social-Security provisional income. Units: nominal USD. Rounding: none; the only transformation is the zero floor.

    Formula: magi = max(0, ordinaryRealized + gainsRealized + qualifiedDividends + taxableSs + taxExemptInterest)

    • ordinaryRealized: Ordinary income realized this year, the carryforward netting's ordinaryAfter (unit: usd/year) (domain: nonnegative; the netting floors it at zero)
    • gainsRealized: Signed net capital gain after carryforward netting (unit: usd/year) (domain: at or above minus the annual ordinary-offset limit)
    • qualifiedDividends: Qualified dividends realized this year (unit: usd/year) (domain: nonnegative)
    • taxableSs: Taxable share of Social Security under section 86 (unit: usd/year) (domain: nonnegative)
    • taxExemptInterest: Federally tax-exempt interest realized this year (unit: usd/year) (domain: nonnegative)

    Timing: once per projection year, in the funding-and-close phase

    Rounding: none

    Limits:

    • Asserted on real simulatePlan runs rather than at a seam. The positive case is a real plan whose single year realizes all five terms exactly as the worksheet states them (a recurring ordinary stream, a one-time capital-gain income, a taxable account whose wholly qualified dividend yield and municipal sleeve carry the third and fifth terms, and a Social Security benefit sized so that section 86 makes exactly the fourth taxable, with cost basis equal to balance so nothing else can realize a gain), and the published magi is checked against that year's own published components
    • The floor case is the only negative sum the ledger can reach: a deductible capital loss on the capital line with nothing else realized, because the ordinary-income term is applyCapitalLossCarryforward's ordinaryAfter and is floored at zero. The worksheet's first floor case gave ordinary income realized as minus 10,000, which that floor rules out; it was re-derived on the reachable path (a 20,000 carryforward carried into a year that realizes nothing else deducts the 3,000 annual limit on the capital line) and re-checked, and the test builds that year and checks each of the five terms and the published magi against the worksheet's inputs

    Provenance: derived by codex, implemented by claude.

    Independent review: cursor

    Gates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass

    Worksheet: medicare-magi-composition.md Mutation receipt: medicare-magi-composition.mutation.md

    Evidence tests: annualFundingApplicationAndClosePhase.evidence.test.ts

    Implemented in: result.ts; annualFundingApplicationAndClosePhase.ts

    Is the number of: magi-annual

    Feeds: irmaa-surcharge-annual, medicare-premiums-annual

Records that feed this number

  • Annual ordinary dividends distributed by a taxable account Formula record, justified by derivation

    Purpose: The non-qualified remainder of a taxable account's generated dividends.

    Statement: projection/internal/distributedTaxableYieldRows.ts#distributedTaxableYieldRows computes, for each taxable account whose start-of-year balance is strictly positive, ordinaryDividends = dividends - qualified, where dividends = startBalance x dividendYieldPct / 100 and qualified applies the resolved qualified ratio; projection/internal/annualIncomeSetup.ts#annualIncomeSetup folds those rows into the published projection/internal/types/result.ts#YearResult.incomes.ordinaryDividends. Qualified and ordinary dividends partition the same dividends and never add on top of them. Units: nominal USD per year. Rounding: none.

    Formula: ordinaryDividends = dividends - dividends * clamp(qualifiedRatio ?? blendedQualifiedRatio ?? 0.85, 0, 1), dividends = startBalance * dividendYieldPct / 100

    • dividends: Generated dividends on the start balance (unit: usd/year) (domain: nonnegative)
    • qualifiedRatio: Qualified share of dividends (unit: 1) (domain: clamped to [0, 1]; absent falls back to the blend, then to 0.85)

    Timing: once per taxable account per projection year, before the wage pass

    Rounding: none

    Limits:

    • Asserted on two real simulatePlan runs of the projection's first year, where the start-of-year balance is the plan balance. Plan assumptions beyond the worksheet's inputs: a single 51-year-old filing single in KY with a zero state rate, zero inflation, zero account return and cost basis equal to balance; the account carries no allocation, so the defaults case exercises the 0.85 fallback
    • The subtraction is performed in binary floating point, so the explicit case's $700 arrives as 700.0000000000002 and is compared under the worksheet's half-cent tolerance rather than exactly
    • This is the ordinary share of dividends only; taxable interest is a separate member of taxable yield

    Provenance: derived by codex, implemented by claude.

    Independent review: cursor

    Gates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass

    Worksheet: income-ordinary-dividends-annual.md Mutation receipt: income-ordinary-dividends-annual.mutation.md

    Evidence tests: distributedTaxableYieldRows.evidence.test.ts

    Implemented in: distributedTaxableYieldRows.ts; annualIncomeSetup.ts; result.ts

    Is the number of: income-ordinary-dividends-annual

    Feeds: tax-total-annual, magi-annual

  • Annual qualified dividends distributed by a taxable account Formula record, justified by derivation

    Purpose: The qualified share of a taxable account's generated dividends.

    Statement: projection/internal/distributedTaxableYieldRows.ts#distributedTaxableYieldRows computes, for each taxable account whose start-of-year balance is strictly positive, dividends = startBalance x dividendYieldPct / 100 and qualified = dividends x clamp(qualifiedRatio ?? blendedQualifiedRatio ?? DEFAULT_QUALIFIED_DIVIDEND_RATIO, 0, 1), and projection/internal/annualIncomeSetup.ts#annualIncomeSetup folds those rows into the published projection/internal/types/result.ts#YearResult.incomes.qualifiedDividends. DEFAULT_QUALIFIED_DIVIDEND_RATIO is 0.85, a fraction rather than a percent. Units: nominal USD per year. Rounding: none.

    Formula: qualified = (startBalance * dividendYieldPct / 100) * min(1, max(0, qualifiedRatio ?? blendedQualifiedRatio ?? 0.85))

    • startBalance: Start-of-year balance, the prior year's close (unit: usd) (domain: strictly positive, else no row)
    • dividendYieldPct: Account dividend yield, in percent of the start balance (unit: percent/year) (domain: nonnegative)
    • qualifiedRatio: Qualified share of dividends (unit: 1) (domain: clamped to [0, 1]; absent falls back to the blend, then to 0.85)

    Timing: once per taxable account per projection year, before the wage pass

    Rounding: none

    Limits:

    • Asserted on two real simulatePlan runs of the projection's first year, where the start-of-year balance is the plan balance. Plan assumptions beyond the worksheet's inputs: a single 51-year-old filing single in KY with a zero state rate, zero inflation, zero account return and cost basis equal to balance; the account carries no allocation, so the defaults case exercises the 0.85 fallback rather than a blend
    • The default is 0.85 as a fraction; reading it as 85 percent of a percent is one of the worksheet's wrong readings and the clamp to [0, 1] is what stops it
    • An explicit account ratio wins over the blend and over the default, so the explicit case proves precedence, not just arithmetic

    Provenance: derived by codex, implemented by claude.

    Independent review: cursor

    Gates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass

    Worksheet: income-qualified-dividends-annual.md Mutation receipt: income-qualified-dividends-annual.mutation.md

    Evidence tests: distributedTaxableYieldRows.evidence.test.ts

    Implemented in: distributedTaxableYieldRows.ts; annualIncomeSetup.ts; result.ts

    Is the number of: income-qualified-dividends-annual

    Feeds: tax-total-annual, magi-annual

  • Annual taxable interest distributed by a taxable account Formula record, justified by derivation

    Purpose: The interest a taxable account generates on its start-of-year balance.

    Statement: projection/internal/distributedTaxableYieldRows.ts#distributedTaxableYieldRows computes, for each taxable account whose start-of-year balance is strictly positive, interest = startBalance x interestYieldPct / 100, and projection/internal/annualIncomeSetup.ts#annualIncomeSetup folds those rows into the published projection/internal/types/result.ts#YearResult.incomes.taxableInterest. The rate is the account's own interestYieldPct when set, else the allocation blend's interest yield, else 0, each floored at 0; the start-of-year balance is the prior year's closing balance, and a zero or negative one produces no yield row at all. reinvestDividends decides where the generated cash goes, never whether it is characterized as interest. Units: nominal USD per year. Rounding: none.

    Formula: interest = startBalance * max(0, interestYieldPct ?? blendedInterestYieldPct ?? 0) / 100

    • startBalance: Start-of-year balance, the prior year's close (unit: usd) (domain: strictly positive, else no row)
    • interestYieldPct: Account interest yield, in percent of the start balance (unit: percent/year) (domain: nonnegative; overrides the allocation blend)

    Timing: once per taxable account per projection year, before the wage pass

    Rounding: none

    Limits:

    • Asserted on two real simulatePlan runs of the projection's first year, where the start-of-year balance is the plan balance. Plan assumptions beyond the worksheet's inputs: a single 51-year-old filing single in KY with a zero state rate, zero inflation, zero account return and cost basis equal to balance, so nothing but the stated yields can move the year; the account carries no allocation, so the defaults case exercises the no-blend fallback the worksheet names
    • A percent, not a fraction: 2.25 means 2.25 percent, and reading it as a fraction is one of the worksheet's wrong readings
    • The interest field is characterization, not cash delivery; with reinvestment on, the same dollars are credited back to the account and add nothing to the year's cash inflows

    Provenance: derived by codex, implemented by claude.

    Independent review: cursor

    Gates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass

    Worksheet: income-taxable-interest-annual.md Mutation receipt: income-taxable-interest-annual.mutation.md

    Evidence tests: distributedTaxableYieldRows.evidence.test.ts

    Implemented in: distributedTaxableYieldRows.ts; annualIncomeSetup.ts; result.ts

    Is the number of: income-taxable-interest-annual

    Feeds: tax-total-annual, magi-annual

  • QCD income offset and qualified slice Composition record, justified by derivation

    Purpose: Separate the physical charitable gift from the slice of it that is excluded from income.

    Statement: projection/internal/types/result.ts#YearResult.qcd publishes the gross physical gift while its excludable income offset is the qualified from-RMD slice less the remaining section 408(d)(8)(A) second-sentence offset; projection/internal/annualLegacyQcdOwnerCharacterPlan.ts#annualLegacyQcdOwnerCharacterPlan computes that character, with the qualification ceiling being the owner's aggregate includible IRA amount (pre-distribution owned-IRA balance less aggregate basis) rather than the taxable fraction of the RMD. Gift beyond the RMD carries no offset; gift beyond aggregate includible dollars is non-qualified ordinary distribution charged to the from-RMD portion first; rmd stays gross. Units: nominal USD. Rounding: none.

    Formula: qualified = min(gift, max(0, preDistribution - basis)); offsetApplied = min(qualified, max(0, section219 - alreadyTaken)); nonQualified = gift - qualified; nonQualifiedFromRmd = min(fromRmd, nonQualified); qualifiedFromRmd = fromRmd - nonQualifiedFromRmd; incomeOffsetDelta = min(qualifiedFromRmd, qualified - offsetApplied)

    • gift: Gross physical gift for the owner-year (unit: usd/year) (domain: nonnegative)
    • fromRmd: Part of the gift that satisfied the RMD (unit: usd/year) (domain: 0 <= fromRmd <= gift)
    • preDistribution: Pre-distribution aggregate owned-IRA balance (unit: usd) (domain: nonnegative)
    • basis: Aggregate nondeductible IRA basis (unit: usd) (domain: nonnegative)
    • section219: Post-70.5 deductible section 219 contributions, net of reductions already consumed (unit: usd) (domain: nonnegative)

    Timing: one owner-year

    Rounding: none; the cross-year donor-offset ledger is carried in integer cents

    Limits:

    • The qualification ceiling is statutory: section 408(d)(8)(B), last sentence, treats a distribution as a qualified charitable distribution only to the extent it would be includible in gross income, and section 408(d)(8)(D) measures that extent as if every IRA of the owner were one contract distributed in full, which is the aggregate includible amount (pre-distribution owned-IRA balance less aggregate basis) this record uses; the second sentence of section 408(d)(8)(A) supplies the post-70.5 section 219 reduction. The allocation of the non-qualified remainder between the from-RMD and beyond-RMD portions has no statutory text and is an engine convention: the remainder is charged to the from-RMD portion first. Either order gives the same total ordinary inclusion (25,000 on the worksheet inputs: 50,000 - 35,000 + 10,000 under a qualified-first allocation, 50,000 - 30,000 + 5,000 under the engine's), so tax-total-annual and magi-annual do not depend on the order; only the split between the two published character rows does. The worksheet was first derived on the qualified-first order and re-derived on the engine's; the evidence pins the re-derived 30,000 and 5,000 and is green. The gross gift (YearResult.qcd) and the gross RMD are written by the gift planner and the RMD phase, not by the character planner this test exercises, so they are not asserted here.
    • The evidence asserts the qualified slice through the exported plan builder rather than reading a private local: the slice is recovered from the published qualifiedFromRmd and nonQualifiedBeyondRmd rows
    • An unprovable or contradictory offset history falls back to a zero exclusion rather than guessing, and writes nothing to the cross-year ledger; this record is asserted on a provable history

    Provenance: derived by codex, implemented by claude.

    Independent review: cursor

    Gates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass

    Worksheet: qcd-income-offset-qualified-slice.md Mutation receipt: qcd-income-offset-qualified-slice.mutation.md

    Evidence tests: annualLegacyQcdOwnerCharacterPlan.evidence.test.ts

    Implemented in: result.ts; annualLegacyQcdOwnerCharacterPlan.ts; annualLegacyQcdGiftPlan.ts

    Is the number of: qcd-annual

    Feeds: tax-total-annual, magi-annual

  • QCD per-donor limit and age-70.5 annual proxy Data record, justified by derivation

    Purpose: Cap each donor at the indexed annual QCD limit and gate eligibility on the annual age proxy.

    Statement: projection/internal/types/result.ts#YearResult.qcd caps each donor at year2026.rmd.qcdAnnualLimit multiplied by the applicable limit-growth factor, and uses an annual proxy for age 70.5: eligible at attained age 71, or at attained age 70 when the birth month is June or earlier. projection/internal/annualLegacyQcdGiftPlan.ts#annualLegacyQcdGiftPlan applies both, with the household scalar capped at the sum of the per-donor caps and unused capacity never pooled into a donor who has already exhausted their own limit. Units: nominal USD per donor per year. Rounding: none; a full drain of a source publishes whole ledger cents.

    Formula: eligible(p) = alive(p) and (age(p) >= 71 or (age(p) = 70 and birthMonth(p) <= 6)); cap = qcdAnnualLimit x limitGrowth; requested = min(qcdAnnual x inflFactor, cap x donorCount)

    • qcdAnnualLimit: year2026.rmd.qcdAnnualLimit (unit: usd/donor/year) (domain: positive)
    • limitGrowth: Limit-growth factor to the projection year: 1 in a year whose QCD limit the IRS has published in its notice of retirement-plan limits, otherwise the inflation path from that limit's latest published year (unit: 1) (domain: positive)
    • age(p): Attained age of person p this year (unit: years) (domain: integer >= 0)
    • birthMonth(p): Birth month of person p (unit: month) (domain: integer 1..12)

    Timing: one projection year, per donor

    Rounding: none

    Limits:

    • The age gate is an annual proxy for 70.5, not a dated test: a July-born donor attaining 70 is refused for the whole year even though the statute would admit the second half of it
    • The cap is per donor, not per household: a two-donor plan carries twice the capacity, and the scalar arm never pools one donor's unused capacity into another
    • The evidence exercises the scalar arm (no named QCD request); a named request returns an empty plan from this helper and is a different path
    • Restated 2026-09-28 under decision D-2027-ROLLOVER: the growth factor and the named arm read the QCD limit's own publication (params/index.ts#componentScale, actions/annualQcdTaxCharacterPostPass.ts#qcdLimitPublishedFor), not the whole set of published figures. The named arm executes only in a tax year whose limit is published, so a gift dated in a year whose November notice has landed executes even while that year's brackets are projected; every year through 2026 reads exactly as before

    Provenance: derived by codex, implemented by claude.

    Independent review: grok

    Gates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass

    Worksheet: qcd-limit-and-age-proxy.md Mutation receipt: qcd-limit-and-age-proxy.mutation.md

    Evidence tests: annualLegacyQcdGiftPlan.evidence.test.ts

    Implemented in: result.ts; annualLegacyQcdGiftPlan.ts; index.ts; annualQcdTaxCharacterPostPass.ts

    Is the number of: qcd-annual

    Feeds: tax-total-annual, magi-annual

Engine source: engine/src/projection/internal/types/result.ts (YearResult.magi)

The evidence links on this page (each record's worksheet, mutation receipt, evidence tests and implementation, and the source links) are pinned to engine commit 2624dbdba9fd, committed 2026-10-11. The live calculation index and output-family census links point at the repository's main branch, so you can check this snapshot against them.