ActuarialOS for Excel
Function Reference
Signature, plain-English use, and the method, convention and engine anchor behind every function. The same guide the ribbon's Help dialog opens in Excel.
- Source
- Published verbatim from the add-in’s own
FUNCTIONS.md
The complete guide to the aos_* worksheet functions. Two audiences:
- Using the tool (analysts): each function's signature and a plain-English "use it for" line. Most builders are also on the ActuarialOS ribbon (or the All Formulas dropdown), which write the formula for you with a live preview. As you type
=aos_*(in a cell, in-cell IntelliSense names each argument and describes the one you're on; the ribbon Help → Function Reference opens this guide, searchable. - Understanding the calculations (actuaries): each function's method / convention / anchor — what it computes, the conventions it follows, and which
actuarialEngine.jsvalue it reproduces. Every signedaos_*number is cross-checked to that engine to machine epsilon (the "anchor").
Verify before signing. Functions reproduce the engine exactly, but some embedded DATA is an attested input — see § Verify before signing. Cross-checked: 25,864 cases, max rel err 1.1e-15.
Conventions used throughout
- Default payment convention = MONTHLY, paid in advance (due). A bare
aos_ax(and the rest of the annuity family) values 12 payments/year at the start of each month — the real-world pension convention. To get an annual immediate factor, passfreq=1, timing="immediate"; to get annual due,freq=1, timing="due". (Exception:aos_LumpSum417defaults to annual, engine-anchored.) - Annuity timing (the engine anchor). At
freq=1, timing="immediate",aos_axreproducespvInPayLifeEXACTLY: "survive to the START of the payment period, pay at the END" (annual annuity-immediate, "v·ä"). The exact m-thly kernel reduces to this at frequency 1 / immediate, so monthly/due are true refinements of that convention, not approximations. - Monthly survival uses UDD (uniform distribution of deaths) for intra-year survival.
- Generational mortality.
valYearis the calendar year at the valuation age; attained agex+tis reached invalYear+t. Static projection (aos_Mort) projects a table to one year. - Discount basis. The interest argument of every annuity accepts a flat rate, a 3-value §430 segment triple, or a spot yield curve (
aos_Curve). - COLA. A scalar (level) or an
aos_Colaselect-and-ultimate schedule (with an optional cap). - Mortality tables. Use the real published SOA tables for signed work; the four "Combined Healthy" base tables are illustrative (see Verify). Any table arg accepts a named id, an
aos_Blend(...)two-table blend, or anaos_Table(...)custom table from cell ranges. - Cell references. Any scalar/text arg can be a cell reference; range args take ranges.
Commutation
| Function | Use it for | Method / anchor |
|---|---|---|
aos_Dx(age, rate, mort, [ageAdj], [method]) | Discounted survivors Dₓ | lₓ built from a radix at the table's youngest defined age (ratio = ActOS survival annuities); 50/50 blend + UDD/1124 method args. |
aos_Nx(age, rate, mort, …) | Σ Dₓ (annuity-due numerator) | aos_Nx/aos_Dx reproduces the engine annuity-due to machine epsilon. |
aos_Nx12, aos_Cx, aos_Mx | Monthly Nx; Cx / Mx commutation | Standard commutation columns on the same lₓ basis. |
aos_Mort(table, [ageAdj], [scale], [calYear]) | Build the mortality argument | Static projection to calYear by a band or 2-D generational scale (== genImprovementFactor). |
aos_CommTable(minAge, maxAge, rate, mortality, [blend], [header]) | Spilling commutation table | Spills Age, Dx, Nx, Cx, Mx, Nx12 — one row per integer age over [minAge, maxAge], with an optional header row. Same kernel as aos_Dx/aos_Nx/… (so Nx/Dx reproduces the engine annuity-due); ages outside the table's defined range return #NUM. A dynamic-array formula — enter once and it spills. |
Mortality & scale
| Function | Use it for | Method / anchor |
|---|---|---|
aos_qx(table, x, [y], [ageAdj], [scale], [year], [gen]) | Mortality rate q | One-year q(x), or (y-x)q(x) = 1 − l(y)/l(x) when y is given. q == genQx. Static or generational (gen). |
aos_px(table, x, [y], [ageAdj], [scale], [year], [gen]) | Survival probability p | One-year p(x), or (y-x)p(x) = l(y)/l(x) when y is given. |
aos_lx(table, x, [ageAdj], [scale], [year], [gen]) | Life-table survivors l(x) | Radix-100000 life table from the table's min age, built from genQx. |
aos_Deaths(table, x, [y], [ageAdj], [scale], [year], [gen]) | Life-table deaths d(x) | l(x)·q(x) one-year, or l(x) − l(y) when y is given. (Named aos_Deaths, not aos_dx: Excel is case-insensitive and aos_Dx is the commutation column.) |
aos_Scale(scale, age) | Improvement rate at an age | == getImprovementRate (band scales). |
aos_Blend(A, B, w, [wB]) | A weighted two-table blend id "A|B|w" (or "A|B|w|wB") | Understood by GenQx, so ANY function can use w·A + (1−w)·B. Give wB explicitly for a weighted SUM whose weights need not total 1 (e.g. 0.6045·M + 0.5525·F adjusted-table blends); each component projects on its own sex's scale. |
aos_Floor(table) | Survivable pre-eligibility ages ("FLOOR~…" id) | Wraps any table/blend/splice id so ages BELOW its first defined age get qx = 0 instead of the default missing-age fallback — e.g. a Retiree (50+) table in a conversion-factor grid spanning ages 20+. At/above the floor, identical to the inner id. |
aos_Table(ages, rates, [gender], [baseYear]) | A custom table from cell ranges (inline) | Builds a CUSTOM|… id; reproduces a named table's annuity exactly (incl. scale projection). gender is "M" (default), "F", or `"U"` for a UNISEX table — under a 2-D generational scale a unisex table is improved on the 50/50 blend of the male and female cumulative improvement factors (the IRS unisex construction; "U:0.6" states another male weight). baseYear states the year the supplied qx apply — where improvement is projected FROM (e.g. 2014 for RP-2014 rates), appended to the id as |by=<year>. It is REQUIRED to use the table with an improvement scale: a scale applied to a table that declares no base year is REFUSED, never assumed — omit it only when no scale will be applied (then nothing reads it and the answer is unchanged). |
aos_RegisterTable(name, ages, rates, [gender], [baseYear]) | Name a custom table → reference it by name | Put in a cell; then any function resolves the name, e.g. =aos_qx("MyPlan", 65), and it appears in aos_Tables(). The rate DATA stays in the ranges (reproducible — no hidden machine state). Session-global names; reference the register cell (or recalc) so dependents refresh. baseYear has the same meaning — and the same requirement under an improvement scale — as aos_Table's. |
aos_QxTable(table, [minAge], [maxAge], [scale], [calYear], [header]) | Spilling raw mortality table (verify / compare / view) | Spills Age, qx, lx, tPx — the EXACT qx the engine uses (optionally projected by scale to calYear), for verifying a table against the SOA publication or comparing against another tool. Omit `minAge`/`maxAge` to view the whole table (defaults to the table's full defined age range), e.g. =aos_QxTable("Pri-2012 Male"). qx == genQx; lx is a radix-100000 life table from minAge; tPx is survival from minAge. Dynamic-array spill. The Mortality dialog's "full table" function builds this. |
aos_Project(table, [scale], [baseYear], [targetYear]) | Static-projection table id | Builds a "PROJ~…" id: the table improved by scale from baseYear to targetYear and FROZEN at that level (the same rate every payment year). Use as any annuity/commutation table arg (pass scale="None" there — the projection is baked in). A table that declares no base year (a CUSTOM|… spec with no by=) is refused under any scale — state it on aos_Table / aos_RegisterTable, or use a published table. |
aos_ProjectGen(table, [scale], [baseYear]) | Generational projection re-based to a base year | Builds a "GENBASE~…" id: generational improvement (keeps advancing each future payment year, unlike aos_Project) measured from baseYear instead of the scale's intrinsic 2012 anchor. baseYear = the table's published base reproduces the plain generational result (so a bare scale arg). Use it to value a table whose rates are as-of a year other than 2012. Pass scale="None" on the consuming function (the scale is baked in). A table that declares no base year is refused under any scale, exactly as in aos_Project. |
Annuities — core
| Function | Use it for | Method / anchor |
|---|---|---|
aos_ax(table, age, rate, [valYear], [scale], [cola], [commence], [freq], [timing], [beforeTable]) | PV of a $1/yr single-life annuity | == pvInPayLife. Deferral is engine-anchored (deferral survival × vⁿ × annuity-at-commencement); exact m-thly (1/12) with UDD; beforeTable = before/after-commencement mortality. |
aos_axy(tableA, ageA, tableB, ageB, rate, …) | PV of a joint-life annuity (pays while BOTH alive) | == pvInPayJoint. |
aos_axVector(table, ages, rate, [valYear], [scale], [cola], [commence], [freq], [timing], [beforeTable]) | Vectorized `aos_ax` (spills) | One aos_ax value per age in the ages range, spilling in the range's row/column orientation. Same kernel as aos_ax. Omit commence for an immediate annuity at each age; pass a scalar commence to defer every age to that fixed commencement. |
aos_axTrace(table, age, rate, [valYear], [scale], [cola], [freq], [timing], [header]) | Explain this number (spills) | The term-by-term decomposition of a $1/yr immediate single-life annuity: Age, PayTime, tPx, Discount, COLA, Payment, Term, Cumulative — one row per payment, then a TOTAL row. The Term column sums to aos_ax (mirrors that kernel exactly), so a reviewer can see survival × discount × COLA build the number. For peer review and teaching. |
Annuities — optional forms
| Function | Use it for | Method / anchor |
|---|---|---|
aos_axc(table, age, certainYears, rate, …) | n-year certain & life | Payments in [0,n) guaranteed (weight 1), then life-contingent — via the generalized AxForm kernel. |
aos_axt(table, age, tempYears, rate, …) | n-year temporary life annuity | No payments at/after time n. |
aos_axJS(memberTable, memberAge, survivorTable, survivorAge, jsPct, rate, …) | Contingent J&S ($1 to member, jsPct to survivor) | == ActOS ä_member + jsPct·(ä_survivor − ä_joint). |
aos_axRefund(table, age, refundBase, rate, [valYear], [scale], [cola]) | Modified cash refund life annuity | Life annuity + PV of the at-death shortfall: if total payments < refundBase at death, the lump-sum shortfall is refunded at end of the death year (completed payments counted). refundBase=0 == plain life annuity (anchor). Annual. Mechanical convention; installment variant = `aos_axInstallmentRefund`; contribution-base refund is the user's. |
aos_axInstallmentRefund(table, age, refundBase, rate, [valYear], [scale], [cola], [freq], [timing]) | Installment-refund life annuity | Payments GUARANTEED (continue to a beneficiary) until cumulative reaches refundBase, then life-contingent — a refund-certain annuity = certain-&-life with the recovery period (n = refundBase level; ln(1+cola·refundBase)/ln(1+cola) with COLA). refundBase=0 == plain life. Cash-refund variant: aos_axRefund. |
aos_axSSLevel(table, age, ssAge, rate, …) | SS level-income factor L | L = (deferred-to-ssAge annuity)/(immediate annuity). Pre-ssAge pension = base + SS·L; post-ssAge = that − SS. ssAge==age → L=1 (anchor). |
aos_ConvFactor(memberTable, memberAge, survivorTable, survivorAge, jsPct, rate, …, [popup]) | Single-life → J&S conversion factor | = ä(single life)/ä(J&S). Multiply a single-life benefit by this for the actuarially-equivalent J&S benefit. jsPct=0 → 1 (anchor). popup=TRUE → pop-up J&S (the member reverts to the full single-life benefit if the survivor dies first): factor = ä_joint/(ä_joint + jsPct·(ä_survivor − ä_joint)). Actuarial-equivalence baseline — a plan's PUBLISHED factor table overrides it and is user-supplied. |
aos_ConvFactorForm(table, age, form, param, rate, …) | Single-life → optional-form conversion factor | = ä(single life)/ä(form). form = "certain & life" / "temporary" / "certain only" / "cash refund" / "installment refund"; param = the guarantee/term years or the refund base. Anchors: "certain & life" with param=0 → 1; "temporary" covering the whole life → 1. Cash refund is annual (matches aos_axRefund). Same equivalence baseline — published factors override. |
aos_LumpSum417(commenceAge, commenceYear, rate, [cola], [freq], [maleTable], [femaleTable], [scale], [applicableTable]) | §417(e) minimum lump-sum factor | Per $1/yr of annual life annuity at commencement: an annuity-DUE on the applicable basis (rate = the 3-segment §430 triple for the lookback month, or a curve / flat) with the unisex applicable mortality (50/50 M/F blend, generational — default Pri-2012, pass the MP scale). Accrued annual benefit × factor = the minimum lump sum. Reproduces the engine's lumpSumLifeFactorAt (engine-anchored). Pick the lookback-month rates via aos_EconMonthlyLookup. |
aos_ERF(retireAge, nra, reductionPct, [subsidized], [service], [unreducedAge], [unreducedService], [unreducedPoints]) | Plan early-retirement reduction factor | reductionPct per year early from NRA (or from an unreduced age when the service/points condition is met — Rule-of-X), floored at 0; subsidized=TRUE or retireAge ≥ NRA → 1. reductionPct omitted or 0 = no reduction (factor 1.0). Reproduces the engine's earlyRetirementFactor. This is the PLAN's stated reduction formula; a plan's tabulated ERF table overrides it and is user-supplied. |
Annuities — mid-of-year (fractional age)
There are two MOY conventions (they agree only at an integer age, where both reduce to pvInPayLife/pvInPayJoint):
| Function | Use it for | Method / anchor |
|---|---|---|
aos_axMOY(table, age, rate, [valYear], [scale], [cola], [colaMethod]) | Single life on the member's anniversary schedule | Annual payments at t = 1−g, 2−g, … from a fractional age a0+g; UDD partial-first-year survival; colaMethod = full or partial-year. Integer age == pvInPayLife. |
aos_axMOYm(table, age, rate, …, [freq], [timing], [commence], [beforeTable], [colaPartial], [monthlyBasis]) | Single life, forward from valuation (monthly / deferred) | Payments at regular 1/m intervals forward from the valuation date; UDD survival from the fractional age. Integer age == aos_ax; integer-age/commence deferral == aos_ax deferred. Level/aos_Cola COLA (no partial-year). monthlyBasis = "Exact" (default, the engine anchor) or "Woolhouse" (2-term ä⁽ᵐ⁾ = ä − (m−1)/2m — ties to legacy valuation tools; at freq=1, immediate it gives ä − 1). |
aos_axyMOY(tableA, ageA, tableB, ageB, rate, …, [colaPartial], [monthlyBasis]) | Joint, forward from valuation | Pays while both survive; one schedule from valuation. Integer ages == aos_axy. monthlyBasis = "Exact" (default) or "Woolhouse" (legacy-tie), as for aos_axMOYm. |
aos_axMOYStatus(table, age, status, rate, …, [monthlyBasis], [commence]) | Valuation 3-status mid-year model | status = Active (commence + value at the mid-year point; attained age = age+0.5), Inactive-Deferred (commence at the middle of the commencement year, value at BOY), or Inactive-InPay (commence + value at BOY). Reproduces a legacy valuation tool's mid-year statuses. Integer-age InPay == aos_axMOYm. |
Monthly basis (`aos_axMOYStatus`). The sub-annual method is selectable: `Woolhouse` (default) uses the 2-term Woolhouse approximation ä⁽ᵐ⁾ = ä − (m−1)/2m — the industry standard (SOA factor tables, most valuation systems) and what legacy tools emit, so in-pay factors tie out; `Exact` sums the m monthly payments under UDD (the add-in's engine-anchored convention), which sits ~0.0055 lower for monthly — that gap is exactly the third Woolhouse term (the (δ+μ) mortality-curvature correction). Pick the basis your plan/peer-review/audit expects. The mid-year first half-year is always valued exactly (the 6 monthly payments), so Active/Inactive-Deferred may differ by ~1e-4 from a legacy tool that approximates the first half-year with a 7/24 + 5/24 closed form — a deliberate, more-exact choice.
COLA & interest helpers
| Function | Use it for | Method / anchor |
|---|---|---|
aos_Cola(selectRates, ultimate, [max]) | A select-and-ultimate COLA schedule | Threaded through all annuity kernels; an ultimate-only schedule equals the level path. |
aos_SegmentDF(t, seg1, seg2, seg3) | §430 three-segment discount factor at t | seg1 on (0,5], seg2 on (5,20], seg3 beyond — == segmentDiscountFactor. |
aos_Curve(years, rates, [compounding], [valuationOffset]) | Build a spot-curve interest spec | For the rate arg of any annuity; == yieldCurve.js (linear interp, flat-extrapolated). compounding = "annual" (default, (1+s)^-t) or "semiannual" ((1+s/2)^-2t, the PPA "half year rates" convention). valuationOffset = years from the curve's as-of date to the attained-age point (default 0 = value at the curve date); >0 discounts on the curve's FORWARD segment, normalized to the attained age — the "PV Factor at Decrement Age" a funding tool (fxAct/Quantify) reports for a deferred/mid-year-commencing benefit. To match such a factor: value as Inactive-InPay at the commencement age with valuationOffset = years from the valuation date to commencement (do NOT also use the deferred status — the offset already carries the deferral). |
aos_AdjustCurve(curve, schedMaturities, schedBps) | Apply a maturity-dependent bps spread schedule to a curve spec | curve = a spec string (aos_Curve / aos_HqmCurve / aos_TreasuryCurve / aos_Pbgc4044Curve) or a 2-column [maturity, rate] range; returns an adjusted spec for any annuity's rate arg. The spread is interpolated between schedule maturities and held flat beyond; negative bps allowed; a single-entry schedule is a parallel shift; the spec's compounding/valuationOffset tail is preserved. == rate-engine adjustCurve (golden cross-checked). This is a user-supplied adjustment — label and justify it; it is never a bond-universe percentile selection. |
aos_SpotRate(t, years, rates) | Spot rate at maturity t | == spotRateForYear. |
Economic / statutory / PBGC
| Function | Use it for | Method / anchor |
|---|---|---|
aos_EconAnnualLookup(table, year, [indexRate]) | IRS qualified-plan limits suite | table = "401(a)(17)"/"comp", "415(b)"/"benefit", "415(c)", "402(g)", "414(v)"/"catch-up", "414(q)"/"HCE", "416(i)"/"key", or "wage base". == statutoryLimits.js (2020–2026, IRS cola-table + Notice 2025-67); post-table years compound then floor to the limit's statutory step (engine 2026.06.79 — $5,000 comp/415(b)/HCE/key, $1,000 415(c), $500 402(g)/414(v)); wage base single-sourced from covered comp, flat-held (no index). |
aos_Limit415Adjusted(commenceAge, year, [rate], [maleTable], [femaleTable], [scale]) | §415(b) dollar limit, age-adjusted | The §415(b) DB dollar limit adjusted to actuarial equivalence outside the 62–65 plateau: L · ä(ref)/ä(commence) where ref = 62 (commence < 62, reduced) or 65 (commence > 65, increased), L = benefitLimit(year), and ä is an immediate annual life annuity-DUE on the §415 basis (default 5% + unisex 50/50 applicable mortality, generational via scale). Ages 62–65 return L unchanged. Standard simplified adjustment; the §415(b)(2)(E) "lesser/greater of plan basis vs 5%+applicable" comparison is the user's. |
aos_CoveredComp(yearOfBirth, [determinationYear], [rounded]) | IRC §401(l)(5)(E) covered compensation | The 35-year average of the SSA taxable wage bases ending in the year the employee attains social security retirement age (the §415(b)(8) whole-year SSRA: 65 for birth < 1938, 66 for 1938–1954, 67 for 1955+). Wage bases for years after determinationYear are held at the determination-year base; unrounded = floor(35-yr average / 12) × 12. rounded=TRUE → the IRS rounded table (nearest $3,000, capped at the wage base). Cross-checked to the dollar against IRS Rev. Rul. 2026-1 (the SSA wage-base series is validated by the exact match over all 87 birth years — see coveredComp.test.js). determinationYear defaults to / clamps at the latest published wage base (2026). |
aos_EconMonthlyLookup("Segment 1|2|3"|"24-mo Segment …", year, month) | §430 segment rates (spot or 24-mo avg) | == getSegmentRates/get24MonthAverageRates. |
aos_SegRate(years, year, month, [basis], [planYear]) | §430 segment rate by duration | The segment is chosen by the time to payment — 0-5 yrs = seg 1, 6-20 = seg 2, 21+ = seg 3 — and returns that segment's rate. basis = spot (default) / 24mo / stabilized. |
aos_Seg25Avg(planYear, seg) | §430 25-year average segment rate | IRS Table 2A (incl. ARPA 5% floor), decimal; seg 1/2/3. Returns `#N/A` for a plan year the IRS has not published — it no longer holds the nearest year's rate forward. Attested input — verify. |
aos_SegCorridor(planYear, "min"|"max") | MAP-21/ARPA/IIJA stabilization corridor | Applicable min/max fraction: 95%/105% for 2020–2030, then widening 5%/yr to 70%/130% after 2034; 90%/110% pre-2020. (Corrected 2026-08-06 — this previously widened from 2026, i.e. ARPA §9706(a)(1) as enacted, missing IIJA §80602, which extended 95%/105% through 2030. Verified vs IRS Funding Table 1A and Notice 2026-19.) |
aos_SegStabilized(planYear, rateYear, rateMonth, seg) | Stabilized §430 segment rate | The 24-mo average for rateYear/rateMonth clamped to the corridor around the 25-yr average; == stabilizeSegmentRates. |
aos_Pbgc4044(year, month, maturity) | PBGC §4044 monthly spot rate | Published month-end curve (series begins 2024-07); reproduces spotRateForYear. Attested input — verify. |
aos_Pbgc4044Curve(year, month) | PBGC §4044 curve spec (interest arg) | A curve usable as any annuity's rate arg. |
aos_Hqm(year, month, maturity) | §430/PPA full yield curve (Treasury HQM) spot rate | The full monthly yield curve — the Treasury HQM corporate-bond curve, the source of the §430 segment rates; published monthly spot (0.5–30, flat beyond), reproduces spotRateForYear. Attested input — verify. |
aos_HqmCurve(year, month) | §430/PPA full yield curve spec (interest arg) | Value an annuity directly on the full yield curve (the Treasury HQM curve) — usable as any annuity's rate arg. Same curve as aos_FullYieldCurve. |
aos_FullYieldCurve(year, month) | §430(h)(2)(D)(ii) / PPA full yield curve (interest arg) | The elect-able full monthly yield curve (alternative to the three segment rates) as a curve spec — this is the HQM curve under its funding name (== aos_HqmCurve). |
aos_HqmTable(year, month, [header]) | Full yield curve, spilling | Spills Maturity, Spot for every published half-year tenor (0.5–30) — see/plot the whole PPA/§430 curve. Each Spot == aos_Hqm at that maturity. |
aos_Treasury(year, month, maturity, [spreadBps]) | Treasury PAR yield (month-end CMT) | Published Daily Treasury Par Yield Curve Rates, month-end; interpolated, flat beyond 30. PAR basis (semiannual coupon-equivalent), NOT a spot rate — for a corporate spot curve use aos_Hqm. Optional parallel spread in bps ("Treasury + spread"). Attested input — verify vs home.treasury.gov. |
aos_TreasuryCurve(year, month, [spreadBps]) | Treasury par curve spec (interest arg) | The month's par curve (+ spread) as a curve spec. Caution: par yields, not spots — discounting on it is the labeled "Treasury + spread" approximation convention; for spot discounting use aos_HqmCurve. |
aos_TreasuryTable(year, month, [header], [spreadBps]) | Treasury par curve, spilling | Spills Maturity, Par yield for every published tenor of the month (tenors vary — a 1.5-mo tenor appears 2025-02). Each row == aos_Treasury at that maturity, spread included. |
aos_PbgcPremium(year, "flat"|"vrp"|"cap"|"me") | PBGC premium rates | Single-employer flat $/participant, variable-rate $/\$1,000 UVB, VRP per-participant cap $, or multiemployer flat $/participant. Attested input — verify. |
aos_PbgcMaxGuarantee(year, [age]) | PBGC §4022(b) max guarantee | Straight-life max guaranteeable benefit, $/month; age defaults 65, ages 45–65 apply the §4022 age-adjustment factors (interpolated). Attested input — verify. |
aos_SsBendPoint(year, 1|2) | Social Security PIA bend points | The first/second dollar bend point in the PIA formula (90%/32%/15%). Attested input — verify vs SSA. |
aos_SsCola(year) | Social Security COLA (decimal) | The CPI-W–based benefit increase effective that January. Attested input — verify vs SSA. |
aos_SsAwi(year) | National Average Wage Index | The SSA AWI ($); ~2-year publication lag. Attested input — verify vs SSA. |
aos_SsNRA(birthYear, [output]) | Social Security full (normal) retirement age | The statutory §416(l) table (not a published series): 65 through 1937, +2 mo/yr to 66 (1943–54), +2 mo/yr to 67 (1960+). output = "years" (default, decimal e.g. 66.8333) | "months" | "ym" ("66y 10m"). SSA treats a Jan-1 birth as the prior year — pass the adjusted year if that applies. |
aos_SsPIA(aime, eligibilityYear, [colaToYear]) | Social Security PIA from AIME | 90/32/15 bend formula on the eligibility-year (age-62) bend points, dime-rounded; optional COLA-forward. Supply AIME. Attested input — verify vs SSA. |
aos_Cpi(year) | BLS CPI-U annual average | All Urban Consumers, U.S. city average (1982-84=100); for plan COLAs tied to CPI-U. (SS COLA = CPI-W-based: aos_SsCola.) Attested input — verify vs BLS. |
Dates
| Function | Use it for | Method / anchor |
|---|---|---|
aos_DateAdd(date, years, months, days, [eom], [method]) | Add y/m/d (eom-aware); service adjustment | method shifts an entry date FORWARD by a break, quantized to whole months/years (down/up/near) for credited service. Default "exact". Break-in-service eligibility is the user's. |
aos_DateDiff(start, end, [interval], [rounding]) | Difference in y/m/d, with rounding | "y"/none == ActOS ageAtDate. rounding="component" returns the calendar component; interval "ymd" returns the "Xy Ym Zd" breakdown string. |
Diagnostics
| Function | Use it for | Method / anchor |
|---|---|---|
aos_Version() | Version stamp (audit) | A one-line stamp: the add-in version, the ActOS ENGINE_VERSION these numbers reproduce, the data-library version, and the generation date. Put it next to a signed result. |
aos_License() | License status | The beta key / tier (metadata only — never plan data). During the beta the add-in always runs (fail-open); set your beta key via the ribbon License button. |
aos_Versions() | Full version block (spills) | Spills component, value rows — add-in, engine, data manifest, mortality-table count, generation date. The audit stamp for a signed workbook. |
aos_Attest(table) | Assumptions behind a calc | For a mortality table id: its basis/gender/base year, the data-provenance caveat, and the engine + data + generation stamp — a one-cell attestation to drop beside a signed number. Omit table for the engine/data stamp only. |
aos_Tables([contains]) | Discover table ids (spills) | Lists the available mortality table ids, optionally filtered to those containing a substring. Copy a name into any table argument — the fix for #NAME? from a mis-typed table. |
aos_Scales() | Discover improvement-scale ids (spills) | Lists the scale ids (incl. "None") valid for the scale argument. |
aos_Check(table) | Validate a table id | "OK" if the table exists, else an actionable #AOS message naming the nearest valid table (edit-distance "did you mean…?"). Run it to debug a #NAME? before signing. |
aos_Default(value, fallback) | Reproducible firm default | Returns value when supplied, else fallback. Point every formula at one firm-config cell with a built-in fallback — e.g. aos_Default(Config!$B$1, "Pri-2012 Male"). The resolved value stays visible in the cell; there is no hidden global default state (a deliberate choice — silent machine-level defaults would let the same formula give different numbers, breaking reproducibility). |
Verify before signing
Functions reproduce actuarialEngine.js exactly; the data is the attest surface.
- Mortality tables. Use the real published SOA tables (Pri-2012 / RP-2014 Total-Dataset Employee / Healthy Retiree-or-Annuitant / Disabled; WC/TQ role tables). The four "Combined Healthy" base tables are illustrative/approximate — not for signed work. Confirm any table against the SOA publication.
- Legacy historical tables (for old plan docs / §415·§417 conversions / prior-basis ties):
GAM-83 Male,GAM-83 Female,UP-94 Male,UP-94 Female,GATT-83 Unisex— published SOA annuitant tables imported fromdata/source/historical-tables.xlsx. These are STATIC tables (no built-in projection). NOTE: the engine clamps the lookup age to [18, 120], so although the CSVs carry the full published range (UP-94 from age 1, GAM/GATT from age 5), ages below 18 return the age-18 rate — irrelevant for annuitant/pension use but don't read these tables below 18. - Public-plan tables (Pub-2010 / Pub-2016) for governmental plans:
PubT-/PubS-/PubG-(Teachers / Safety / General){2010,2016}×Employee/Healthy Retiree/Disabled Retiree/Contingent Survivor×Male/Female— the SOA Pub-2010 study, Total Dataset, amount-weighted (e.g.PubG-2010 Healthy Retiree Male). STATIC tables (no built-in projection). Attested published INPUT — verify against the SOA Pub-2010 publication before signed use. (Above-/Below- Median income variants are not loaded.) - Generational improvement scales
MP-2021andMP-2019(2-D, age × calendar year) project qₓ forward; both are stored as the forward slice from base year 2012 (years 2013-2036 / 2013-2034), flat-held at the ultimate beyond. Use via thescalearg, e.g.aos_Mort(table, 0, "MP-2019", 2030)oraos_qx(table, x, , , "MP-2019", 2030, TRUE). Sourced from the SOA rates files — verify against the SOA publication for signed use. (The PLANMP201D 1-D scale was removed with the Pre-82 subsystem 2026-08-26, ActOS2026.06.143.) - PBGC §4044 spot curves are an attested published INPUT — verify against the PBGC ERISA 4044 Yield Curves workbook for the measurement month.
- Statutory limits cover only §401(a)(17) and §415(b); statutory projection past the table is unrounded.
- Optional-form / service conventions (cash refund, SS level-income, conversion factor, DateAdd service-adjustment) are mechanical actuarial-equivalence baselines — a plan's published factors / break-in-service rules override them and are supplied by the user.
See PROJECT.md (§7 limitations, §8 findings) for the full conventions and caveats.
Glossary (concepts behind the functions)
For the new analyst. The same glossary (plus a "Your first annuity" walkthrough) is in the ribbon Help dialog; a full worked workbook is Addin/SAMPLES.md.
- Mortality table — yearly death probabilities (qₓ) by age, e.g.
Pri-2012 Male.aos_Tables()lists the loaded ids. Ids match forgivingly — case, spaces, and hyphens are ignored ("pri2012male"resolves toPri-2012 Male); the exact published id is still what attestations and audit output show. - qₓ / pₓ — qₓ = chance a life age x dies within the year; pₓ = 1 − qₓ (survival to x+1).
- Improvement scale — how mortality falls over calendar time (e.g.
MP-2021);aos_Scales()lists them. Static projects a table to one year and holds it; generational keeps improving qₓ as a person ages through future years. - Annuity (ä) — PV today of $1/yr while a life survives:
aos_ax(single),aos_axy(joint). Due pays at the start of each period, immediate at the end (thetimingarg). - Commutation functions — pre-summed columns (Dₓ, Nₓ, Cₓ, Mₓ) making a PV a ratio (ä = Nₓ/Dₓ);
aos_Dx/aos_Nx/aos_CommTable. - COLA — yearly % the benefit grows in payment (level, or
aos_Colaselect-and-ultimate with a cap). - Deferral / commencement — a benefit starting later than today; the PV discounts and applies survival to the commencement age (the
commencearg). - Discount basis — flat rate, a §430 three-segment set, or a spot curve (
aos_Curve/aos_Pbgc4044Curve); any can be the annuityratearg. - §430 segment rates — three IRS interest rates for years 0–5 / 5–20 / 20+ (
aos_EconMonthlyLookup,aos_SegmentDF). - §417(e) lump sum — minimum lump-sum value = annual benefit × an annuity-due factor on the applicable basis (
aos_LumpSum417). `applicableTable` (2026.06.91) names the PUBLISHED §417(e) applicable mortality table, e.g."PPA Static 2026"— one token for the basis AND its year, the same vocabulary the JS engine uses (lumpSumApplicableTable). When given it REPLACES the male/female blend, because a published static table already embeds improvement to its own year — so passscaleas"None"or omit it: a scale STATED besideapplicableTableis refused with#AOS: applicableTable carries its own basis — remove the scale argument(since 2026-09-05; it used to be discarded silently). OmitapplicableTablefor the computed 50/50 blend. An unpublished year returns#NAME?rather than the nearest year — a static table is fixed by statute, not extrapolated. The same rule holds for everyaos_function: a PPA static table under any improvement scale refuses (#AOS: … already embeds improvement to its own year) rather than compounding a second one. - §415(b) / §401(a)(17) — IRS caps on the annual DB benefit / counted pay (
aos_EconAnnualLookup,aos_Limit415Adjusted). - Covered compensation — 35-yr average of SSA wage bases to SSRA, for integrated formulas (
aos_CoveredComp). - J&S — keeps paying a % to the survivor: ä_member + jsPct·(ä_survivor − ä_joint) (
aos_axJS,aos_ConvFactor). - Early-retirement factor (ERF) — reduction for commencing before NRA (
aos_ERF; plan tables override). - Optional form — an alternate payment shape actuarially equivalent to the normal form (
aos_axc,aos_axt,aos_axRefund,aos_axSSLevel). - Engine anchor / golden — every
aos_number reproduces a specific ActuarialOS engine value, checked to machine precision by a generated golden grid. - Provenance / attestation — where a table's numbers come from and how far they're verified;
aos_Atteststamps it (basis + caveat + engine/data version) beside a signed result. - Spill (dynamic array) — a function returning a whole range from one cell (
aos_CommTable,aos_axTrace); legacy Excel needs Ctrl+Shift+Enter.
← ActuarialOS for Excel · Questions: hello@actuarialos.io