FAILURE MAP
← Case archive

FA-63366 / Actuarial life tables / Open access

Life table person-years and expectation: Person-years start from the wrong survivor count · case 01

Life expectancies are overstated by about one year.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

L_x adds half the deaths to l_x instead of l_{x+1}.

VERIFIED REPAIR

Use l_{x+1} + d_x/2.

Unsuccessful approach: Dropping the half-year of deaths understates exposure.

Case contract

Input l (survivors by age, last entry is the open interval) and m_open (per mille central rate of the open interval, <=0 -> 'invalid open rate'). L_x = l_{x+1} + d_x/2, L_open = l_open/m, T_x = sum of L from x, e_x = T_x/l_x rounded to 4 places (None where l_x=0).

Why this case matters

Life-table arithmetic compounds across ages, so one misplaced index or assumption shifts every downstream value.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    l=x['l']; m=Fraction(x['m_open'],1000)
    if m<=0: return 'invalid open rate'
    L=[]
    for i in range(len(l)-1):
        d=l[i]-l[i+1]
        L.append(l[i]+Fraction(d,2))
    L.append(l[-1]/m)
    T=[]; acc=Fraction(0)
    for v in reversed(L):
        acc+=v; T.append(acc)
    T.reverse()
    return [round(float(T[i]/l[i]),4) if l[i] else None for i in range(len(l))]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['table 0', {'l': [100000, 99736, 99400, 99024, 98587, 98055, 97450, 96745], 'm_open': 800}, [8.1156, 7.1357, 6.1581, 5.1796, 4.2004, 3.2205, 2.2373, 1.25]], ['table 1', {'l': [100000, 99688, 99329, 98881, 98385, 97779, 97092], 'm_open': 250}, [9.8098, 8.8389, 7.869, 6.9024, 5.9347, 4.9684, 4.0]], ['table 2', {'l': [100000, 99380, 98687, 97892, 96992, 95970, 94815, 93535], 'm_open': 400}, [9.1434, 8.1973, 7.2514, 6.3062, 5.3601, 4.4118, 3.4595, 2.5]], ['table 3', {'l': [100000, 99494, 98897, 98225, 97474, 96626], 'm_open': 250}, [8.7891, 7.8312, 6.8755, 5.9191, 4.9609, 4.0]], ['table 4', {'l': [100000, 99458, 98844, 98161, 97382, 96516], 'm_open': 800}, [6.1275, 5.1581, 4.1871, 3.2127, 2.2344, 1.25]], ['extinct before open interval', {'l': [1000, 601, 200, 0], 'm_open': 500}, [1.301, 0.8328, 0.5, None]], ['two-age table', {'l': [5000, 1001], 'm_open': 1000}, [0.8003, 1.0]], ['zero open rate rejected', {'l': [100, 50], 'm_open': 0}, 'invalid open rate']], [['table 0', {'l': [100000, 99580, 99059, 98462, 97802], 'm_open': 500}, [5.9161, 4.9389, 3.9622, 2.9832, 2.0]], ['table 1', {'l': [100000, 99560, 99034, 98428, 97736], 'm_open': 400}, [6.4023, 5.4284, 4.4546, 3.4789, 2.5]], ['table 2', {'l': [100000, 99658, 99276, 98811, 98264, 97604], 'm_open': 800}, [6.1682, 5.1876, 4.2056, 3.2231, 2.2382, 1.25]], ['table 3', {'l': [100000, 99474, 98827, 98088], 'm_open': 250}, [6.897, 5.9308, 4.9664, 4.0]], ['table 4', {'l': [100000, 99385, 98714, 97935, 97044], 'm_open': 400}, [6.3717, 5.408, 4.4414, 3.4727, 2.5]], ['extinct before open interval', {'l': [1000, 602, 200, 0], 'm_open': 500}, [1.302, 0.8322, 0.5, None]], ['two-age table', {'l': [5000, 1002], 'm_open': 1000}, [0.8006, 1.0]], ['zero open rate rejected', {'l': [100, 50], 'm_open': 0}, 'invalid open rate']], [['table 0', {'l': [100000, 99748, 99427, 99064, 98638], 'm_open': 800}, [5.2086, 4.2205, 3.2325, 2.2425, 1.25]], ['table 1', {'l': [100000, 99540, 98977, 98365, 97658], 'm_open': 500}, [5.9103, 4.9353, 3.9605, 2.982, 2.0]], ['table 2', {'l': [100000, 99695, 99290, 98834, 98304], 'm_open': 500}, [5.9358, 4.9524, 3.9706, 2.9866, 2.0]], ['table 3', {'l': [100000, 99416, 98740, 97979, 97117, 96159], 'm_open': 250}, [8.7597, 7.8082, 6.8582, 5.9076, 4.9556, 4.0]], ['table 4', {'l': [100000, 99727, 99393, 98994, 98502, 97935, 97305], 'm_open': 800}, [7.1483, 6.1665, 5.1856, 4.2045, 3.223, 2.2387, 1.25]], ['extinct before open interval', {'l': [1000, 603, 200, 0], 'm_open': 500}, [1.303, 0.8317, 0.5, None]], ['two-age table', {'l': [5000, 1003], 'm_open': 1000}, [0.8009, 1.0]], ['zero open rate rejected', {'l': [100, 50], 'm_open': 0}, 'invalid open rate']], [['table 0', {'l': [100000, 99373, 98660, 97828, 96893], 'm_open': 400}, [6.3654, 5.4024, 4.4378, 3.4713, 2.5]], ['table 1', {'l': [100000, 99504, 98936, 98264], 'm_open': 500}, [4.941, 3.9631, 2.983, 2.0]], ['table 2', {'l': [100000, 99438, 98785, 98036, 97220, 96288, 95262, 94105], 'm_open': 500}, [8.7029, 7.7493, 6.7972, 5.8453, 4.8902, 3.9327, 2.9696, 2.0]], ['table 3', {'l': [100000, 99509, 98902, 98199, 97416, 96521, 95521], 'm_open': 250}, [9.7039, 8.7493, 7.8, 6.8522, 5.9033, 4.9534, 4.0]], ['table 4', {'l': [100000, 99565, 99005, 98381, 97680], 'm_open': 250}, [7.8651, 6.8973, 5.9335, 4.9679, 4.0]], ['extinct before open interval', {'l': [1000, 604, 200, 0], 'm_open': 500}, [1.304, 0.8311, 0.5, None]], ['two-age table', {'l': [5000, 1004], 'm_open': 1000}, [0.8012, 1.0]], ['zero open rate rejected', {'l': [100, 50], 'm_open': 0}, 'invalid open rate']], [['table 0', {'l': [100000, 99457, 98786, 98018, 97163, 96196, 95080, 93823], 'm_open': 250}, [10.569, 9.624, 8.686, 7.7501, 6.8139, 5.8774, 4.9405, 4.0]], ['table 1', {'l': [100000, 99396, 98710, 97875, 96955], 'm_open': 250}, [7.8228, 6.8673, 5.9115, 4.9577, 4.0]], ['table 2', {'l': [100000, 99541, 99001, 98405], 'm_open': 500}, [4.9455, 3.966, 2.9849, 2.0]], ['table 3', {'l': [100000, 99775, 99473, 99125, 98710, 98213, 97648], 'm_open': 250}, [9.8471, 8.8682, 7.8936, 6.9196, 5.9466, 4.9741, 4.0]], ['table 4', {'l': [100000, 99459, 98858, 98167, 97346, 96409], 'm_open': 800}, [6.1255, 5.1561, 4.1844, 3.2103, 2.2332, 1.25]], ['extinct before open interval', {'l': [1000, 605, 200, 0], 'm_open': 500}, [1.305, 0.8306, 0.5, None]], ['two-age table', {'l': [5000, 1005], 'm_open': 1000}, [0.8015, 1.0]], ['zero open rate rejected', {'l': [100, 50], 'm_open': 0}, 'invalid open rate']]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
table 0[8.1481, 7.1657, 6.1849, 5.2026, 4.2191, 3.2338, 2.2446, 1.25][8.1156, 7.1357, 6.1581, 5.1796, 4.2004, 3.2205, 2.2373, 1.25]Failed
table 1[9.8388, 8.8649, 7.8916, 6.9205, 5.9478, 4.9754, 4.0][9.8098, 8.8389, 7.869, 6.9024, 5.9347, 4.9684, 4.0]Failed
table 2[9.2081, 8.2561, 7.3036, 6.3507, 5.3957, 4.4372, 3.473, 2.5][9.1434, 8.1973, 7.2514, 6.3062, 5.3601, 4.4118, 3.4595, 2.5]Failed
table 3[8.8228, 7.8601, 6.8984, 5.9354, 4.9696, 4.0][8.7891, 7.8312, 6.8755, 5.9191, 4.9609, 4.0]Failed
table 4[6.1623, 5.1877, 4.2106, 3.2295, 2.2433, 1.25][6.1275, 5.1581, 4.1871, 3.2127, 2.2344, 1.25]Failed
extinct before open interval[2.301, 1.8328, 1.5, None][1.301, 0.8328, 0.5, None]Failed
two-age table[1.6001, 1.0][0.8003, 1.0]Failed
zero open rate rejectedinvalid open rateinvalid open ratePassed

SHA-256 / ddf25fcafb227acc445b4dac5895737fdc8041069882af1985af17e2a33bf3d2

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    l=x['l']; m=Fraction(x['m_open'],1000)
    if m<=0: return 'invalid open rate'
    L=[]
    for i in range(len(l)-1):
        d=l[i]-l[i+1]
        L.append(l[i+1])
    L.append(l[-1]/m)
    T=[]; acc=Fraction(0)
    for v in reversed(L):
        acc+=v; T.append(acc)
    T.reverse()
    return [round(float(T[i]/l[i]),4) if l[i] else None for i in range(len(l))]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['table 0', {'l': [100000, 99736, 99400, 99024, 98587, 98055, 97450, 96745], 'm_open': 800}, [8.1156, 7.1357, 6.1581, 5.1796, 4.2004, 3.2205, 2.2373, 1.25]], ['table 1', {'l': [100000, 99688, 99329, 98881, 98385, 97779, 97092], 'm_open': 250}, [9.8098, 8.8389, 7.869, 6.9024, 5.9347, 4.9684, 4.0]], ['table 2', {'l': [100000, 99380, 98687, 97892, 96992, 95970, 94815, 93535], 'm_open': 400}, [9.1434, 8.1973, 7.2514, 6.3062, 5.3601, 4.4118, 3.4595, 2.5]], ['table 3', {'l': [100000, 99494, 98897, 98225, 97474, 96626], 'm_open': 250}, [8.7891, 7.8312, 6.8755, 5.9191, 4.9609, 4.0]], ['table 4', {'l': [100000, 99458, 98844, 98161, 97382, 96516], 'm_open': 800}, [6.1275, 5.1581, 4.1871, 3.2127, 2.2344, 1.25]], ['extinct before open interval', {'l': [1000, 601, 200, 0], 'm_open': 500}, [1.301, 0.8328, 0.5, None]], ['two-age table', {'l': [5000, 1001], 'm_open': 1000}, [0.8003, 1.0]], ['zero open rate rejected', {'l': [100, 50], 'm_open': 0}, 'invalid open rate']], [['table 0', {'l': [100000, 99580, 99059, 98462, 97802], 'm_open': 500}, [5.9161, 4.9389, 3.9622, 2.9832, 2.0]], ['table 1', {'l': [100000, 99560, 99034, 98428, 97736], 'm_open': 400}, [6.4023, 5.4284, 4.4546, 3.4789, 2.5]], ['table 2', {'l': [100000, 99658, 99276, 98811, 98264, 97604], 'm_open': 800}, [6.1682, 5.1876, 4.2056, 3.2231, 2.2382, 1.25]], ['table 3', {'l': [100000, 99474, 98827, 98088], 'm_open': 250}, [6.897, 5.9308, 4.9664, 4.0]], ['table 4', {'l': [100000, 99385, 98714, 97935, 97044], 'm_open': 400}, [6.3717, 5.408, 4.4414, 3.4727, 2.5]], ['extinct before open interval', {'l': [1000, 602, 200, 0], 'm_open': 500}, [1.302, 0.8322, 0.5, None]], ['two-age table', {'l': [5000, 1002], 'm_open': 1000}, [0.8006, 1.0]], ['zero open rate rejected', {'l': [100, 50], 'm_open': 0}, 'invalid open rate']], [['table 0', {'l': [100000, 99748, 99427, 99064, 98638], 'm_open': 800}, [5.2086, 4.2205, 3.2325, 2.2425, 1.25]], ['table 1', {'l': [100000, 99540, 98977, 98365, 97658], 'm_open': 500}, [5.9103, 4.9353, 3.9605, 2.982, 2.0]], ['table 2', {'l': [100000, 99695, 99290, 98834, 98304], 'm_open': 500}, [5.9358, 4.9524, 3.9706, 2.9866, 2.0]], ['table 3', {'l': [100000, 99416, 98740, 97979, 97117, 96159], 'm_open': 250}, [8.7597, 7.8082, 6.8582, 5.9076, 4.9556, 4.0]], ['table 4', {'l': [100000, 99727, 99393, 98994, 98502, 97935, 97305], 'm_open': 800}, [7.1483, 6.1665, 5.1856, 4.2045, 3.223, 2.2387, 1.25]], ['extinct before open interval', {'l': [1000, 603, 200, 0], 'm_open': 500}, [1.303, 0.8317, 0.5, None]], ['two-age table', {'l': [5000, 1003], 'm_open': 1000}, [0.8009, 1.0]], ['zero open rate rejected', {'l': [100, 50], 'm_open': 0}, 'invalid open rate']], [['table 0', {'l': [100000, 99373, 98660, 97828, 96893], 'm_open': 400}, [6.3654, 5.4024, 4.4378, 3.4713, 2.5]], ['table 1', {'l': [100000, 99504, 98936, 98264], 'm_open': 500}, [4.941, 3.9631, 2.983, 2.0]], ['table 2', {'l': [100000, 99438, 98785, 98036, 97220, 96288, 95262, 94105], 'm_open': 500}, [8.7029, 7.7493, 6.7972, 5.8453, 4.8902, 3.9327, 2.9696, 2.0]], ['table 3', {'l': [100000, 99509, 98902, 98199, 97416, 96521, 95521], 'm_open': 250}, [9.7039, 8.7493, 7.8, 6.8522, 5.9033, 4.9534, 4.0]], ['table 4', {'l': [100000, 99565, 99005, 98381, 97680], 'm_open': 250}, [7.8651, 6.8973, 5.9335, 4.9679, 4.0]], ['extinct before open interval', {'l': [1000, 604, 200, 0], 'm_open': 500}, [1.304, 0.8311, 0.5, None]], ['two-age table', {'l': [5000, 1004], 'm_open': 1000}, [0.8012, 1.0]], ['zero open rate rejected', {'l': [100, 50], 'm_open': 0}, 'invalid open rate']], [['table 0', {'l': [100000, 99457, 98786, 98018, 97163, 96196, 95080, 93823], 'm_open': 250}, [10.569, 9.624, 8.686, 7.7501, 6.8139, 5.8774, 4.9405, 4.0]], ['table 1', {'l': [100000, 99396, 98710, 97875, 96955], 'm_open': 250}, [7.8228, 6.8673, 5.9115, 4.9577, 4.0]], ['table 2', {'l': [100000, 99541, 99001, 98405], 'm_open': 500}, [4.9455, 3.966, 2.9849, 2.0]], ['table 3', {'l': [100000, 99775, 99473, 99125, 98710, 98213, 97648], 'm_open': 250}, [9.8471, 8.8682, 7.8936, 6.9196, 5.9466, 4.9741, 4.0]], ['table 4', {'l': [100000, 99459, 98858, 98167, 97346, 96409], 'm_open': 800}, [6.1255, 5.1561, 4.1844, 3.2103, 2.2332, 1.25]], ['extinct before open interval', {'l': [1000, 605, 200, 0], 'm_open': 500}, [1.305, 0.8306, 0.5, None]], ['two-age table', {'l': [5000, 1005], 'm_open': 1000}, [0.8015, 1.0]], ['zero open rate rejected', {'l': [100, 50], 'm_open': 0}, 'invalid open rate']]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
table 0[8.0993, 7.1207, 6.1448, 5.1681, 4.191, 3.2138, 2.2337, 1.25][8.1156, 7.1357, 6.1581, 5.1796, 4.2004, 3.2205, 2.2373, 1.25]Failed
table 1[9.7952, 8.8259, 7.8578, 6.8934, 5.9281, 4.9649, 4.0][9.8098, 8.8389, 7.869, 6.9024, 5.9347, 4.9684, 4.0]Failed
table 2[9.1111, 8.1679, 7.2253, 6.284, 5.3423, 4.3992, 3.4528, 2.5][9.1434, 8.1973, 7.2514, 6.3062, 5.3601, 4.4118, 3.4595, 2.5]Failed
table 3[8.7722, 7.8168, 6.864, 5.911, 4.9565, 4.0][8.7891, 7.8312, 6.8755, 5.9191, 4.9609, 4.0]Failed
table 4[6.1101, 5.1434, 4.1753, 3.2044, 2.23, 1.25][6.1275, 5.1581, 4.1871, 3.2127, 2.2344, 1.25]Failed
extinct before open interval[0.801, 0.3328, 0.0, None][1.301, 0.8328, 0.5, None]Failed
two-age table[0.4004, 1.0][0.8003, 1.0]Failed
zero open rate rejectedinvalid open rateinvalid open ratePassed

SHA-256 / f5296e929ba6c395fdb2c5dca09d54a286ab93e5b3a0d9f9c26c2e04964794bc

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    l=x['l']; m=Fraction(x['m_open'],1000)
    if m<=0: return 'invalid open rate'
    L=[]
    for i in range(len(l)-1):
        d=l[i]-l[i+1]
        L.append(l[i+1]+Fraction(d,2))
    L.append(l[-1]/m)
    T=[]; acc=Fraction(0)
    for v in reversed(L):
        acc+=v; T.append(acc)
    T.reverse()
    return [round(float(T[i]/l[i]),4) if l[i] else None for i in range(len(l))]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['table 0', {'l': [100000, 99736, 99400, 99024, 98587, 98055, 97450, 96745], 'm_open': 800}, [8.1156, 7.1357, 6.1581, 5.1796, 4.2004, 3.2205, 2.2373, 1.25]], ['table 1', {'l': [100000, 99688, 99329, 98881, 98385, 97779, 97092], 'm_open': 250}, [9.8098, 8.8389, 7.869, 6.9024, 5.9347, 4.9684, 4.0]], ['table 2', {'l': [100000, 99380, 98687, 97892, 96992, 95970, 94815, 93535], 'm_open': 400}, [9.1434, 8.1973, 7.2514, 6.3062, 5.3601, 4.4118, 3.4595, 2.5]], ['table 3', {'l': [100000, 99494, 98897, 98225, 97474, 96626], 'm_open': 250}, [8.7891, 7.8312, 6.8755, 5.9191, 4.9609, 4.0]], ['table 4', {'l': [100000, 99458, 98844, 98161, 97382, 96516], 'm_open': 800}, [6.1275, 5.1581, 4.1871, 3.2127, 2.2344, 1.25]], ['extinct before open interval', {'l': [1000, 601, 200, 0], 'm_open': 500}, [1.301, 0.8328, 0.5, None]], ['two-age table', {'l': [5000, 1001], 'm_open': 1000}, [0.8003, 1.0]], ['zero open rate rejected', {'l': [100, 50], 'm_open': 0}, 'invalid open rate']], [['table 0', {'l': [100000, 99580, 99059, 98462, 97802], 'm_open': 500}, [5.9161, 4.9389, 3.9622, 2.9832, 2.0]], ['table 1', {'l': [100000, 99560, 99034, 98428, 97736], 'm_open': 400}, [6.4023, 5.4284, 4.4546, 3.4789, 2.5]], ['table 2', {'l': [100000, 99658, 99276, 98811, 98264, 97604], 'm_open': 800}, [6.1682, 5.1876, 4.2056, 3.2231, 2.2382, 1.25]], ['table 3', {'l': [100000, 99474, 98827, 98088], 'm_open': 250}, [6.897, 5.9308, 4.9664, 4.0]], ['table 4', {'l': [100000, 99385, 98714, 97935, 97044], 'm_open': 400}, [6.3717, 5.408, 4.4414, 3.4727, 2.5]], ['extinct before open interval', {'l': [1000, 602, 200, 0], 'm_open': 500}, [1.302, 0.8322, 0.5, None]], ['two-age table', {'l': [5000, 1002], 'm_open': 1000}, [0.8006, 1.0]], ['zero open rate rejected', {'l': [100, 50], 'm_open': 0}, 'invalid open rate']], [['table 0', {'l': [100000, 99748, 99427, 99064, 98638], 'm_open': 800}, [5.2086, 4.2205, 3.2325, 2.2425, 1.25]], ['table 1', {'l': [100000, 99540, 98977, 98365, 97658], 'm_open': 500}, [5.9103, 4.9353, 3.9605, 2.982, 2.0]], ['table 2', {'l': [100000, 99695, 99290, 98834, 98304], 'm_open': 500}, [5.9358, 4.9524, 3.9706, 2.9866, 2.0]], ['table 3', {'l': [100000, 99416, 98740, 97979, 97117, 96159], 'm_open': 250}, [8.7597, 7.8082, 6.8582, 5.9076, 4.9556, 4.0]], ['table 4', {'l': [100000, 99727, 99393, 98994, 98502, 97935, 97305], 'm_open': 800}, [7.1483, 6.1665, 5.1856, 4.2045, 3.223, 2.2387, 1.25]], ['extinct before open interval', {'l': [1000, 603, 200, 0], 'm_open': 500}, [1.303, 0.8317, 0.5, None]], ['two-age table', {'l': [5000, 1003], 'm_open': 1000}, [0.8009, 1.0]], ['zero open rate rejected', {'l': [100, 50], 'm_open': 0}, 'invalid open rate']], [['table 0', {'l': [100000, 99373, 98660, 97828, 96893], 'm_open': 400}, [6.3654, 5.4024, 4.4378, 3.4713, 2.5]], ['table 1', {'l': [100000, 99504, 98936, 98264], 'm_open': 500}, [4.941, 3.9631, 2.983, 2.0]], ['table 2', {'l': [100000, 99438, 98785, 98036, 97220, 96288, 95262, 94105], 'm_open': 500}, [8.7029, 7.7493, 6.7972, 5.8453, 4.8902, 3.9327, 2.9696, 2.0]], ['table 3', {'l': [100000, 99509, 98902, 98199, 97416, 96521, 95521], 'm_open': 250}, [9.7039, 8.7493, 7.8, 6.8522, 5.9033, 4.9534, 4.0]], ['table 4', {'l': [100000, 99565, 99005, 98381, 97680], 'm_open': 250}, [7.8651, 6.8973, 5.9335, 4.9679, 4.0]], ['extinct before open interval', {'l': [1000, 604, 200, 0], 'm_open': 500}, [1.304, 0.8311, 0.5, None]], ['two-age table', {'l': [5000, 1004], 'm_open': 1000}, [0.8012, 1.0]], ['zero open rate rejected', {'l': [100, 50], 'm_open': 0}, 'invalid open rate']], [['table 0', {'l': [100000, 99457, 98786, 98018, 97163, 96196, 95080, 93823], 'm_open': 250}, [10.569, 9.624, 8.686, 7.7501, 6.8139, 5.8774, 4.9405, 4.0]], ['table 1', {'l': [100000, 99396, 98710, 97875, 96955], 'm_open': 250}, [7.8228, 6.8673, 5.9115, 4.9577, 4.0]], ['table 2', {'l': [100000, 99541, 99001, 98405], 'm_open': 500}, [4.9455, 3.966, 2.9849, 2.0]], ['table 3', {'l': [100000, 99775, 99473, 99125, 98710, 98213, 97648], 'm_open': 250}, [9.8471, 8.8682, 7.8936, 6.9196, 5.9466, 4.9741, 4.0]], ['table 4', {'l': [100000, 99459, 98858, 98167, 97346, 96409], 'm_open': 800}, [6.1255, 5.1561, 4.1844, 3.2103, 2.2332, 1.25]], ['extinct before open interval', {'l': [1000, 605, 200, 0], 'm_open': 500}, [1.305, 0.8306, 0.5, None]], ['two-age table', {'l': [5000, 1005], 'm_open': 1000}, [0.8015, 1.0]], ['zero open rate rejected', {'l': [100, 50], 'm_open': 0}, 'invalid open rate']]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
table 0[8.1156, 7.1357, 6.1581, 5.1796, 4.2004, 3.2205, 2.2373, 1.25][8.1156, 7.1357, 6.1581, 5.1796, 4.2004, 3.2205, 2.2373, 1.25]Passed
table 1[9.8098, 8.8389, 7.869, 6.9024, 5.9347, 4.9684, 4.0][9.8098, 8.8389, 7.869, 6.9024, 5.9347, 4.9684, 4.0]Passed
table 2[9.1434, 8.1973, 7.2514, 6.3062, 5.3601, 4.4118, 3.4595, 2.5][9.1434, 8.1973, 7.2514, 6.3062, 5.3601, 4.4118, 3.4595, 2.5]Passed
table 3[8.7891, 7.8312, 6.8755, 5.9191, 4.9609, 4.0][8.7891, 7.8312, 6.8755, 5.9191, 4.9609, 4.0]Passed
table 4[6.1275, 5.1581, 4.1871, 3.2127, 2.2344, 1.25][6.1275, 5.1581, 4.1871, 3.2127, 2.2344, 1.25]Passed
extinct before open interval[1.301, 0.8328, 0.5, None][1.301, 0.8328, 0.5, None]Passed
two-age table[0.8003, 1.0][0.8003, 1.0]Passed
zero open rate rejectedinvalid open rateinvalid open ratePassed

SHA-256 / 2af500718da98e43f7fe9edc431212faf190e94ff131872a412a65ab2ba3e543

Verification & scope

A deterministic bounded teaching model of a stipulated toy actuarial contract. Rates are small synthetic tables, exact rationals are used where practical and floats are rounded at the output; it makes no claim of conformance with any published table, standard of practice or regulation. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:47:13.643168+00:00.

Case digest / 93b4836db7519e1f0b35fefe1726d61c2f38848a512c9c26081adf1649245023