The suite runs against two independently written engines: the browser engine that powers the Valuation Lab on this site, and the reporting engine that produces the specimen Survey Report. They share no code. Where both are asked the same question they must give the same answer, which is itself one of the strongest tests available.
Closed-form identities
Where actuarial mathematics gives an exact answer, the engine must reproduce it exactly. An annuity-certain must equal (1 − vn)/d. Under a constant force of mortality the life annuity must equal 1/(1 − vp). At zero interest it must equal curtate life expectancy plus one. These are not approximations and the engine agrees to nine decimal places.
Published benchmarks
Mortality is calibrated so that period life expectancy at 65 reproduces the published Belgian figures rather than a convenient shape. The statutory minimum return and its 2025 step are checked against the Law of 18 December 2015.
Invariants and behaviour
The obligation must fall as the discount rate rises, rise with salary growth, rise with longevity improvement, and fall when retirement is deferred. Components must sum to the total, and 612 individual valuations must add to the portfolio figure.
Every check, and what it is measured against
| Check | Expected value comes from | Engine | Expected | Result |
|---|---|---|---|---|
| 1. Closed-form identities | ||||
| Annuity-certain 40 yrs at 2.00% | a-due(n) = (1 - v^n)/d | 27.9026 | 27.9026 | PASS |
| Annuity-certain 40 yrs at 2.00% | a-due(n) = (1 - v^n)/d | 27.9026 | 27.9026 | PASS |
| Annuity-certain 40 yrs at 4.45% | a-due(n) = (1 - v^n)/d | 19.3584 | 19.3584 | PASS |
| Annuity-certain 40 yrs at 4.45% | a-due(n) = (1 - v^n)/d | 19.3584 | 19.3584 | PASS |
| Annuity-certain 40 yrs at 8.00% | a-due(n) = (1 - v^n)/d | 12.8786 | 12.8786 | PASS |
| Annuity-certain 40 yrs at 8.00% | a-due(n) = (1 - v^n)/d | 12.8786 | 12.8786 | PASS |
| Constant q=0.02 at 4.00% | a-due = 1/(1 - v*p) | 17.3333 | 17.3333 | PASS |
| Constant q=0.02 at 4.00% | a-due = 1/(1 - v*p) | 17.3333 | 17.3333 | PASS |
| Constant q=0.05 at 4.45% | a-due = 1/(1 - v*p) | 11.0529 | 11.0529 | PASS |
| Constant q=0.05 at 4.45% | a-due = 1/(1 - v*p) | 11.0529 | 11.0529 | PASS |
| Constant q=0.10 at 6.00% | a-due = 1/(1 - v*p) | 6.625 | 6.625 | PASS |
| Constant q=0.10 at 6.00% | a-due = 1/(1 - v*p) | 6.625 | 6.625 | PASS |
| Duration of a single 20-yr payment | D_mod = n/(1+i) | 19.1479 | 19.1479 | PASS |
| Duration of a single 20-yr payment | D_mod = n/(1+i) | 19.1479 | 19.1479 | PASS |
| Zero interest: a-due(65) = e(65) + 1 | identity at i = 0 | 3.96784 | 3.96784 | PASS |
| Zero interest: a-due(65) = e(65) + 1 | identity at i = 0 | 3.96784 | 3.96784 | PASS |
| 2. Engine invariants | ||||
| 100bp-bump duration within 3% of analytic derivative | convexity is second order | 21.4417 | 21.0595 | PASS |
| 100bp-bump duration within 4% of analytic derivative | convexity is second order | 22.8221 | 22.8221 | PASS |
| 25bp move predicted by duration to within 5% | first-order Taylor, convexity excluded | 3.77217 | no | PASS |
| Accrued + future service = full projected PV | PUC attribution identity | 89,211,211 | 89,211,211 | PASS |
| Active + deferred + pensioner = total DBO | additivity | 46,990,330 | 46,990,330 | PASS |
| Active + deferred + retiree = total DBO | additivity | 9,917,978 | 9,917,978 | PASS |
| Cohort mode and flat-age mode give different answers | the switch actually does something | yes | yes | PASS |
| Cohort mode assigns retirement age by year of birth | statutory schedule 65 / 66 / 67 | yes | yes | PASS |
| DBO decreasing in the discount rate | monotonicity | yes | yes | PASS |
| DBO increasing in salary growth | monotonicity | yes | yes | PASS |
| DBO strictly decreasing in the discount rate | monotonicity | yes | yes | PASS |
| DBO strictly increasing in salary growth | monotonicity | yes | yes | PASS |
| Flat-age 66 and 67 are not the same number | guards against a vacuous comparison | yes | yes | PASS |
| Later retirement reduces the DBO (flat-age mode) | directional | yes | yes | PASS |
| Mortality improvement increases the DBO | directional | yes | yes | PASS |
| Mortality improvement increases the DBO | directional | yes | yes | PASS |
| Sum of 612 individual valuations = portfolio | no cross-member interaction | 46,990,330 | 46,990,330 | PASS |
| 3. Published benchmarks | ||||
| Life expectancy at 65, female | Statbel period tables | 21.9 | 21.9 | PASS |
| Life expectancy at 65, female (Statbel ~21.9) | Statbel period tables | 21.8999 | 21.9 | PASS |
| Life expectancy at 65, male | Statbel period tables | 18.6 | 18.6 | PASS |
| Life expectancy at 65, male (Statbel ~18.6) | Statbel period tables | 18.601 | 18.6 | PASS |
| Statutory minimum return from 1 Jan 2025 | WAP art.24, 2025 step | 2.5 | 2.5 | PASS |
| Statutory minimum return from 1 Jan 2025 | WAP art.24, 2025 step | 2.5 | 2.5 | PASS |
| Statutory minimum return pre-2025 | WAP art.24 | 1.75 | 1.75 | PASS |
| Statutory minimum return pre-2025 | WAP art.24 / Law 18 Dec 2015 | 1.75 | 1.75 | PASS |
| 3b. Real published valuation | ||||
| Convexity signature matches within 2% | same disclosure, 25bp bump | 1.07708 | 1.07433 | PASS |
| Downward move exceeds upward, as the published report shows | convexity direction | yes | yes | PASS |
| Implied duration matches Mercer's disclosure within 10% | Mercer, Cayman Islands Government, IPSAS 39, 2021 | 20.8156 | 20.4732 | PASS |
| 4. Duration matching | ||||
| D-A: curve rate read at 29.3 yrs | euro AA curve interpolation | 4.63 | 4.63 | PASS |
| D-B: curve rate read at 21.2 yrs | euro AA curve interpolation | 4.59329 | 4.59328 | PASS |
| D-C: curve rate read at 12.2 yrs | euro AA curve interpolation | 4.32913 | 4.32915 | PASS |
| Longer duration reads a higher rate (A > B > C) | upward-sloping curve | yes | yes | PASS |
| 4. Statutory guarantee mechanism | ||||
| Guarantee binds when the floor exceeds the contract rate | employer obligation, not a discount-rate test | yes | yes | PASS |
| Guarantee does not bind when the contract pays above the floor | employer obligation | no | no | PASS |
| Guaranteed account splits generations by calendar year | WAP art.24, step 1 Jan 2025 | 15,411 | 15,411 | PASS |
| 5. Conformity checker | ||||
| Conforming years reported as conforming | no false positives | yes | yes | PASS |
| Contribution shortfall detected | seeded error 1 | yes | yes | PASS |
| Guarantee under-credit detected | seeded error 2 | yes | yes | PASS |
| Under-credit flagged in 2025, the year the floor stepped | WAP art.24 step | yes | yes | PASS |
Two findings the suite caught that reading the code did not
Mortality seven times too heavy
An early build of the reporting engine used a mortality law that implied an 8.7% chance of death at age 65, against a Belgian reality of about 1.2%. Life expectancy at 65 came out at 6.6 years instead of 18.6. Because pensioners were assumed to die a decade early, every annuity factor and the whole obligation were understated. The published-benchmark check found it immediately; no amount of reading the formula would have.
The guarantee split off by one year
The browser engine decided which contributions earn the 2.50% statutory floor by counting years backwards rather than by calendar year, which credited the new rate to a 2024 generation that is entitled to 1.75%. It is a small sum, and it sat inside the exact mechanism this whole review turns on. The generation test now pins it to the 1 January 2025 step.
The suite runs again every time the model changes
This is not a one-off exercise for a proposal. The validation suite ships with the engine, and it is how the Embassy's own team can satisfy itself in later years that nothing has drifted.
Re-run it yourself
Two commands, no licence, no dependency on us. The browser engine's suite runs with node js/validate.mjs; the reporting engine's with python validate.py. Each prints a pass or fail line per check and exits non-zero if anything breaks.
Change one assumption a year
Next year's review is mostly the same plan with a new census, a new curve reading and a year of ledger. The assumption file and the census are the inputs that change. The method, the tests and the report architecture do not.
The tests are the handover
Documentation goes stale, tests do not. If a future change breaks the annuity identity or moves life expectancy away from the published figure, the suite says so before anyone signs a report.