manyfutures 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. manyfutures-0.1.0/.gitignore +16 -0
  2. manyfutures-0.1.0/CHANGELOG.md +43 -0
  3. manyfutures-0.1.0/GLOSSARY.md +64 -0
  4. manyfutures-0.1.0/LICENSE +21 -0
  5. manyfutures-0.1.0/PKG-INFO +106 -0
  6. manyfutures-0.1.0/README.md +83 -0
  7. manyfutures-0.1.0/docs/adr/0001-end-age-is-the-only-way-a-death-enters-the-model.md +42 -0
  8. manyfutures-0.1.0/docs/adr/README.md +8 -0
  9. manyfutures-0.1.0/docs/decisions.md +1072 -0
  10. manyfutures-0.1.0/prek.toml +17 -0
  11. manyfutures-0.1.0/pyproject.toml +115 -0
  12. manyfutures-0.1.0/scripts/ag2026_guard.py +128 -0
  13. manyfutures-0.1.0/scripts/clean_install_check.py +80 -0
  14. manyfutures-0.1.0/src/manyfutures/__init__.py +32 -0
  15. manyfutures-0.1.0/src/manyfutures/analytics.py +96 -0
  16. manyfutures-0.1.0/src/manyfutures/constants.py +96 -0
  17. manyfutures-0.1.0/src/manyfutures/data.py +127 -0
  18. manyfutures-0.1.0/src/manyfutures/life_table.csv +9220 -0
  19. manyfutures-0.1.0/src/manyfutures/models.py +856 -0
  20. manyfutures-0.1.0/src/manyfutures/mortgage.py +184 -0
  21. manyfutures-0.1.0/src/manyfutures/paths.py +493 -0
  22. manyfutures-0.1.0/src/manyfutures/places/__init__.py +31 -0
  23. manyfutures-0.1.0/src/manyfutures/places/at.py +134 -0
  24. manyfutures-0.1.0/src/manyfutures/places/es.py +133 -0
  25. manyfutures-0.1.0/src/manyfutures/places/fr.py +92 -0
  26. manyfutures-0.1.0/src/manyfutures/places/it.py +97 -0
  27. manyfutures-0.1.0/src/manyfutures/places/nl.py +145 -0
  28. manyfutures-0.1.0/src/manyfutures/places/uk.py +116 -0
  29. manyfutures-0.1.0/src/manyfutures/py.typed +0 -0
  30. manyfutures-0.1.0/src/manyfutures/readouts.py +49 -0
  31. manyfutures-0.1.0/src/manyfutures/scenario.py +852 -0
  32. manyfutures-0.1.0/src/manyfutures/simulation.py +1807 -0
  33. manyfutures-0.1.0/src/manyfutures/tax.py +218 -0
  34. manyfutures-0.1.0/src/manyfutures/us_cpi.csv +113 -0
  35. manyfutures-0.1.0/tests/__init__.py +0 -0
  36. manyfutures-0.1.0/tests/example_household.py +59 -0
  37. manyfutures-0.1.0/tests/test_ag2026_guard.py +186 -0
  38. manyfutures-0.1.0/tests/test_clean_install.py +45 -0
  39. manyfutures-0.1.0/tests/test_mc_portfolio.py +4744 -0
  40. manyfutures-0.1.0/tests/test_package.py +18 -0
  41. manyfutures-0.1.0/tests/test_portfolio_engine_boundary.py +66 -0
  42. manyfutures-0.1.0/tests/test_portfolio_scenario.py +1840 -0
  43. manyfutures-0.1.0/tests/test_readme.py +38 -0
  44. manyfutures-0.1.0/uv.lock +507 -0
@@ -0,0 +1,16 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ build/
6
+ dist/
7
+
8
+ # Environments and tool caches
9
+ .venv/
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+
13
+ # Editors and systems
14
+ .DS_Store
15
+ .idea/
16
+ .vscode/
@@ -0,0 +1,43 @@
1
+ # Changelog
2
+
3
+ What each release changed, and whether it moved the headline, the chance of running short.
4
+
5
+ ## 0.1.0 (2026-10-26)
6
+
7
+ The first release: the engine, moved here from the private dashboard it was built for, with the projected life
8
+ table of Statistics Netherlands (CBS).
9
+
10
+ ### The engine, moved
11
+
12
+ - **The Scenario seam** at the package's top level: `run`, `return_band`, `compare` and `inputs_that_matter` run a
13
+ `Scenario`, the household's decisions, from a `Start`, its money and debts on a date and the market's past
14
+ returns, on `Assumptions`, its facts and the model's constants, and give an `Outcome` or a `ReturnBand`.
15
+ - **The rest under the module names it had in the dashboard**: `models`, `constants`, `data`, `places` (nine places
16
+ in six countries, each tax regime with its source and date), `tax`, `mortgage`, `paths`, `simulation`,
17
+ `analytics` and `readouts`.
18
+ - **The household's facts have no default** (decision 9): a run is always told whose life it runs on. The model's
19
+ constants keep their defaults, and the Scenario's defaults are a made-up example household's choices.
20
+ - **It reaches out to nothing**: it depends on numpy and pydantic alone, fetches nothing (the caller hands it the
21
+ return history), and its measures give plain records rather than tables.
22
+ - **US inflation** bundled as `us_cpi.csv`: the CPI-U each December, FRED series CPIAUCNS (public domain), each
23
+ year's own taken off its return (decision 2).
24
+ - **Its words and reasons**: the glossary (`GLOSSARY.md`), modelling decisions 1 to 35 (`docs/decisions.md`) and
25
+ ADR 0001, the End Age (`docs/adr/`). The README runs the example household's plan in under twenty lines.
26
+
27
+ ### The CBS life table
28
+
29
+ - CBS's projected life table, StatLine 86246NED (the Kernprognose 2025-2070, published 16 December 2025, CC BY 4.0),
30
+ bundled as `life_table.csv` with its source, date, licence and reduction in its header: the chance of dying
31
+ before the next birthday for men and for women, at ages 0 to 99 or older, in each year from 2025 to 2070.
32
+ - Each adult's chances are read along their own birth year at run time, so the End Age is drawn for any two birth
33
+ years the table covers (decision 26).
34
+ - It replaces the actuaries' table AG2026, which is all rights reserved: AG2026 never entered this repository, and
35
+ a check in CI keeps it out.
36
+
37
+ ### The headline
38
+
39
+ - The move changed no number: before its tests moved to the example household, the moved engine passed the
40
+ dashboard's own engine tests on the dashboard's household, 324 of 324. The CBS table, which the dashboard took up
41
+ just before the move, moved the End Ages a little and the headline with them.
42
+ - The tests here are pinned on the example household and the CBS table. Its default plan runs short in about one
43
+ future in five: 21.7% ± 0.6 at the default 5,000 futures.
@@ -0,0 +1,64 @@
1
+ # manyfutures
2
+
3
+ The engine's words: what a plan, its futures and its measures are called here, in the code, the docstrings, the
4
+ tests and the docs. Each is defined once. The modelling decisions in [docs/decisions.md](docs/decisions.md) and the
5
+ ADRs in [docs/adr/](docs/adr/) use them. The words came with the engine from the dashboard it was built for.
6
+
7
+ ## Language
8
+
9
+ **Scenario**:
10
+ The household's decisions for one plan: when to retire, what to spend in retirement and how much of its spending
11
+ it must be able to pay (Must-have Spending), the Bridge Income and until when, whether, when and where to
12
+ downsize, and whether to sell the home as a last resort (the Last-resort Sale).
13
+ _Avoid_: config, inputs
14
+
15
+ **Retirement**:
16
+ Leaving full-time work, at the Scenario's retirement age. From then the household's salaries fall to its Bridge
17
+ Income and its spending becomes the Scenario's annual spending.
18
+ _Avoid_: stopping work (the household keeps earning Bridge Income)
19
+
20
+ **Bridge Income**:
21
+ The household's gross earnings after Retirement, from consulting, contract work or a partner's job, in today's
22
+ euros, until an age the Scenario sets.
23
+ _Avoid_: post-retirement salary, pt salary, part-time salary
24
+
25
+ **End Age**:
26
+ The age, in whole years, at which the later of the two deaths falls on one simulated path, drawn at random for that
27
+ path from CBS's projected life table: the plan has to last to the end of the year it falls in. The one way a death
28
+ enters the model: nothing else in the plan changes with it.
29
+ _Avoid_: life expectancy, horizon, death age, survivor
30
+
31
+ **Must-have Spending**:
32
+ The part of the household's baseline spending it must fund every year, in today's euros. Housing, children, the
33
+ student loan and taxes are always paid and sit outside it.
34
+ _Avoid_: floor, essential spending
35
+
36
+ **Nice-to-have Spending**:
37
+ Baseline spending above Must-have Spending.
38
+ _Avoid_: discretionary spending
39
+
40
+ **Money Left at the End**:
41
+ What a simulated future leaves at the end of the year its End Age falls in, in today's euros: its liquid money (the
42
+ portfolio and the cash reserve) plus its home's value less what is still owed on it.
43
+ _Avoid_: terminal wealth, bequest, estate, final liquid (that is the portfolio alone)
44
+
45
+ **Running Short**:
46
+ A year in which more than a tenth of Must-have Spending goes unfunded.
47
+ _Avoid_: shortfall, failure, depletion
48
+
49
+ **Spending Rule**:
50
+ How the household's spending is set each year from Retirement: Fixed (the chosen amount every year) or Sustainable
51
+ (recomputed each year from what the money can carry).
52
+ _Avoid_: amortisation, guardrail, withdrawal policy
53
+
54
+ **Return Band**:
55
+ The same run at the assumed average return and at one point lower and one point higher.
56
+ _Avoid_: parameter band, prior, sensitivity
57
+
58
+ **Last-resort Sale**:
59
+ Selling the home when liquid money cannot cover next year's need, and renting from then on.
60
+ _Avoid_: equity release, forced sale
61
+
62
+ **Assumptions**:
63
+ The household facts and model constants a run reads, as distinct from the Scenario's decisions.
64
+ _Avoid_: parameters, config
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thomas Pinder
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.5
2
+ Name: manyfutures
3
+ Version: 0.1.0
4
+ Summary: A household's retirement plan run over many simulated futures: the chance it runs short, and what moves it.
5
+ Project-URL: Repository, https://github.com/thomaspinder/manyfutures
6
+ Project-URL: Issues, https://github.com/thomaspinder/manyfutures/issues
7
+ Author: Thomas Pinder
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Topic :: Office/Business :: Financial
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: numpy>=2.4.4
21
+ Requires-Dist: pydantic>=2.12.5
22
+ Description-Content-Type: text/markdown
23
+
24
+ # manyfutures
25
+
26
+ Runs a household's retirement plan over many simulated futures: how often the plan runs short, the money left at
27
+ the end, and which of its inputs matter most.
28
+
29
+ The engine was built for a private dashboard and moved here in 0.1.0: the plan and its assumptions, the simulated
30
+ futures and the measures over them, with the projected life table of Statistics Netherlands (CBS) for how long each
31
+ future lasts. Every amount is in today's euros. The household lives in Amsterdam, under Dutch tax, unless its plan
32
+ moves it to one of the other places the engine knows.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install manyfutures
38
+ ```
39
+
40
+ or `uv add manyfutures`. Python 3.11 or later; the only dependencies are numpy and pydantic.
41
+
42
+ ## An example
43
+
44
+ A made-up household: two adults born in 1990 and 1992, earning 180,000 a year between them and spending 40,000, with
45
+ one child, a home bought in 2024 with a 350,000 mortgage, and a student loan of £20,000. Its plan is the `Scenario`'s
46
+ defaults: retiring at 58 on 40,000 a year, 30,000 of it Must-have Spending, with 40,000 a year of Bridge Income until
47
+ the pension at 67 and a downsize to a 350,000 home.
48
+
49
+ ```python
50
+ from datetime import date
51
+
52
+ import numpy as np
53
+ from manyfutures import Assumptions, Scenario, Start, readouts, run
54
+ from manyfutures.models import Adult, MortgageTerms, Position, StudentLoanTerms
55
+
56
+ household = Assumptions( # its facts; every other Assumption is one of the engine's own model constants
57
+ first_adult=Adult(born=date(1990, 1, 1), life_series="men"), partner=Adult(born=1992, life_series="women"),
58
+ gross_income=180_000, working_years_spending=40_000, pension=20_000, child_birth_ages=(33,),
59
+ daycare_days_per_week=2, daycare_monthly_per_day=500, childcare_reimbursement_rate=0.33, after_school_monthly=300,
60
+ mortgage=MortgageTerms(rate=0.035, term=30, start=date(2024, 1, 1)),
61
+ student_loan=StudentLoanTerms(rate=0.03, repayment=500, repayment_day=1),
62
+ )
63
+ position = Position(portfolio=350_000, crypto=0, cash_buffer=30_000, home_value=520_000, mortgage_balance=340_000,
64
+ student_loan=20_000)
65
+ returns = np.random.default_rng(1).normal(0.07, 0.17, 120) # made up: one a year, 1906 to 2025
66
+ start = Start(today=date(2026, 6, 1), position=position, returns=returns, return_years=np.arange(1906, 2026))
67
+ metrics = run(Scenario(), start, assumptions=household).metrics
68
+ print("Chance of running short:", readouts.chance(metrics.chance_of_running_short, metrics.chance_of_running_short_se))
69
+ ```
70
+
71
+ It prints:
72
+
73
+ ```text
74
+ Chance of running short: 21.7% ± 0.6
75
+ ```
76
+
77
+ About one future in five runs short: in at least one of its years more than a tenth of the Must-have Spending goes
78
+ unfunded. The ± is one standard error of the simulation, in points, over the 5,000 futures a `Scenario` runs by
79
+ default.
80
+
81
+ The three inputs:
82
+
83
+ - **`Assumptions`**: the household's facts, which have no default, and the model's constants, which do (inflation,
84
+ investment costs, the life table, the Running Short threshold and the rest).
85
+ - **`Start`**: where the plan begins, the household's money and debts on a date (a `Position`) and the market's past
86
+ returns, each with its calendar year. Here the returns are made up; a real run is handed a history such as the
87
+ S&P 500's. The engine fetches nothing.
88
+ - **`Scenario`**: the household's decisions, each with a default, so a Scenario names only those that differ.
89
+
90
+ `run` gives an `Outcome`: its `metrics` (the chance of running short, how deep and for how long, the money left at
91
+ the end, the same readouts over the futures that last to 95), the End Ages and the portfolio at each age.
92
+ `return_band` runs the same plan with every year's return a point lower and a point higher, `compare` runs it in every
93
+ place, and `inputs_that_matter` moves one hand-set input at a time.
94
+
95
+ ## The engine's words and decisions
96
+
97
+ - [GLOSSARY.md](GLOSSARY.md): the words, each defined once.
98
+ - [docs/decisions.md](docs/decisions.md): the modelling decisions, numbered as they were made, each with its rule,
99
+ its reason and the code and tests that hold it.
100
+ - [docs/adr/](docs/adr/): the decisions that are not to be reopened, the End Age's among them.
101
+ - [CHANGELOG.md](CHANGELOG.md): what each release changed, and whether it moved the headline.
102
+
103
+ ## Licence
104
+
105
+ MIT: see [LICENSE](LICENSE). The bundled life table is CBS's (CC BY 4.0) and the US inflation the BLS's, through FRED
106
+ (public domain); each file's header says where it comes from.
@@ -0,0 +1,83 @@
1
+ # manyfutures
2
+
3
+ Runs a household's retirement plan over many simulated futures: how often the plan runs short, the money left at
4
+ the end, and which of its inputs matter most.
5
+
6
+ The engine was built for a private dashboard and moved here in 0.1.0: the plan and its assumptions, the simulated
7
+ futures and the measures over them, with the projected life table of Statistics Netherlands (CBS) for how long each
8
+ future lasts. Every amount is in today's euros. The household lives in Amsterdam, under Dutch tax, unless its plan
9
+ moves it to one of the other places the engine knows.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install manyfutures
15
+ ```
16
+
17
+ or `uv add manyfutures`. Python 3.11 or later; the only dependencies are numpy and pydantic.
18
+
19
+ ## An example
20
+
21
+ A made-up household: two adults born in 1990 and 1992, earning 180,000 a year between them and spending 40,000, with
22
+ one child, a home bought in 2024 with a 350,000 mortgage, and a student loan of £20,000. Its plan is the `Scenario`'s
23
+ defaults: retiring at 58 on 40,000 a year, 30,000 of it Must-have Spending, with 40,000 a year of Bridge Income until
24
+ the pension at 67 and a downsize to a 350,000 home.
25
+
26
+ ```python
27
+ from datetime import date
28
+
29
+ import numpy as np
30
+ from manyfutures import Assumptions, Scenario, Start, readouts, run
31
+ from manyfutures.models import Adult, MortgageTerms, Position, StudentLoanTerms
32
+
33
+ household = Assumptions( # its facts; every other Assumption is one of the engine's own model constants
34
+ first_adult=Adult(born=date(1990, 1, 1), life_series="men"), partner=Adult(born=1992, life_series="women"),
35
+ gross_income=180_000, working_years_spending=40_000, pension=20_000, child_birth_ages=(33,),
36
+ daycare_days_per_week=2, daycare_monthly_per_day=500, childcare_reimbursement_rate=0.33, after_school_monthly=300,
37
+ mortgage=MortgageTerms(rate=0.035, term=30, start=date(2024, 1, 1)),
38
+ student_loan=StudentLoanTerms(rate=0.03, repayment=500, repayment_day=1),
39
+ )
40
+ position = Position(portfolio=350_000, crypto=0, cash_buffer=30_000, home_value=520_000, mortgage_balance=340_000,
41
+ student_loan=20_000)
42
+ returns = np.random.default_rng(1).normal(0.07, 0.17, 120) # made up: one a year, 1906 to 2025
43
+ start = Start(today=date(2026, 6, 1), position=position, returns=returns, return_years=np.arange(1906, 2026))
44
+ metrics = run(Scenario(), start, assumptions=household).metrics
45
+ print("Chance of running short:", readouts.chance(metrics.chance_of_running_short, metrics.chance_of_running_short_se))
46
+ ```
47
+
48
+ It prints:
49
+
50
+ ```text
51
+ Chance of running short: 21.7% ± 0.6
52
+ ```
53
+
54
+ About one future in five runs short: in at least one of its years more than a tenth of the Must-have Spending goes
55
+ unfunded. The ± is one standard error of the simulation, in points, over the 5,000 futures a `Scenario` runs by
56
+ default.
57
+
58
+ The three inputs:
59
+
60
+ - **`Assumptions`**: the household's facts, which have no default, and the model's constants, which do (inflation,
61
+ investment costs, the life table, the Running Short threshold and the rest).
62
+ - **`Start`**: where the plan begins, the household's money and debts on a date (a `Position`) and the market's past
63
+ returns, each with its calendar year. Here the returns are made up; a real run is handed a history such as the
64
+ S&P 500's. The engine fetches nothing.
65
+ - **`Scenario`**: the household's decisions, each with a default, so a Scenario names only those that differ.
66
+
67
+ `run` gives an `Outcome`: its `metrics` (the chance of running short, how deep and for how long, the money left at
68
+ the end, the same readouts over the futures that last to 95), the End Ages and the portfolio at each age.
69
+ `return_band` runs the same plan with every year's return a point lower and a point higher, `compare` runs it in every
70
+ place, and `inputs_that_matter` moves one hand-set input at a time.
71
+
72
+ ## The engine's words and decisions
73
+
74
+ - [GLOSSARY.md](GLOSSARY.md): the words, each defined once.
75
+ - [docs/decisions.md](docs/decisions.md): the modelling decisions, numbered as they were made, each with its rule,
76
+ its reason and the code and tests that hold it.
77
+ - [docs/adr/](docs/adr/): the decisions that are not to be reopened, the End Age's among them.
78
+ - [CHANGELOG.md](CHANGELOG.md): what each release changed, and whether it moved the headline.
79
+
80
+ ## Licence
81
+
82
+ MIT: see [LICENSE](LICENSE). The bundled life table is CBS's (CC BY 4.0) and the US inflation the BLS's, through FRED
83
+ (public domain); each file's header says where it comes from.
@@ -0,0 +1,42 @@
1
+ ---
2
+ status: accepted
3
+ date: 2026-10-25
4
+ ---
5
+
6
+ # The End Age is the only way a death enters the model
7
+
8
+ A review of the household-finance literature, made for the dashboard the engine was first built in, argued, with
9
+ every strand and both of its critics behind it, that a household plan should carry two lives: a survivor phase with
10
+ its own income fall, lower needs, a sale of the home at the first death, and life cover sized to the survivor's
11
+ living standard. We decided against all of it. Each simulated path draws one **End Age** from a life table (the later
12
+ of two draws, one per adult, from CBS's projected life table, StatLine 86246NED) and the plan has to last to it;
13
+ nothing else in the plan changes when a death occurs, and the model never asks who died. The household the engine
14
+ was first built for judged the survivor case not a money problem for planning, and held that if one of them died
15
+ there would be more important things to worry about as a household than the plan. The random End Age is kept
16
+ because the horizon still has to be random: stopping every path at a fixed age moved the headline a long way and
17
+ hid the long-lived futures in which running short happens.
18
+
19
+ The decision was made while the engine was part of that dashboard, and it travelled with the engine to this
20
+ package in 0.1.0. Decision 26 in [the decisions](../decisions.md) holds how the End Age is drawn and what it does.
21
+
22
+ ## Considered options
23
+
24
+ - Two lives with a survivor phase (the review's recommendation): rejected as above; also the survivor-income
25
+ figures the review found are American and would have been hand-set.
26
+ - A fixed horizon at a life-expectancy age (the engine's prior behaviour): rejected because the horizon is the
27
+ single largest driver of the headline and the long-lived tail is where the risk sits.
28
+ - The End Age from the current-year CBS period table: rejected because it ignores future gains in longevity and
29
+ cuts about five years off the life of an adult in their thirties. CBS's projected table allows for them, and is
30
+ what the End Age is drawn from.
31
+ - The actuaries' projection table AG2026, which the End Age was first drawn from: rejected and replaced on
32
+ 2026-10-25 by CBS's projected table, because AG2026 is all rights reserved and the engine is published, and CBS's
33
+ table is open (CC BY 4.0). The End Ages moved little. AG2026 never entered this repository, and a check in CI
34
+ keeps it out (`scripts/ag2026_guard.py`).
35
+
36
+ ## Consequences
37
+
38
+ This is not to be reopened: the household the engine was first built for asked that it not be, and that holds here
39
+ too. No survivor rules, no first-death sale, no life-cover lever, however strongly a source argues for them. The End
40
+ Age is capped at the model's last age, 100 (`MAX_MODEL_AGE`), which truncates about one future in five of the
41
+ example household's (`test_three_in_five_futures_last_to_95_and_six_in_seven_to_90`); lifting the cap is a
42
+ one-constant change if wanted.
@@ -0,0 +1,8 @@
1
+ # Architecture decision records
2
+
3
+ A decision recorded here is settled: it says what was decided, what else was considered, and why it is not to be
4
+ reopened. The modelling decisions, each a rule with its reason and the code that holds it, are in
5
+ [the decisions](../decisions.md).
6
+
7
+ - [0001 The End Age is the only way a death enters the model](0001-end-age-is-the-only-way-a-death-enters-the-model.md)
8
+ (accepted, 2026-10-25)