glidepath 0.2.0__py3-none-any.whl

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 (93) hide show
  1. glidepath/__init__.py +3 -0
  2. glidepath/app/__init__.py +364 -0
  3. glidepath/app/backtest.py +281 -0
  4. glidepath/app/charts.py +759 -0
  5. glidepath/app/copy.py +174 -0
  6. glidepath/app/display.py +148 -0
  7. glidepath/app/drawdown.py +436 -0
  8. glidepath/app/example.py +66 -0
  9. glidepath/app/exports.py +487 -0
  10. glidepath/app/files.py +249 -0
  11. glidepath/app/firstrun.py +114 -0
  12. glidepath/app/forms.py +1750 -0
  13. glidepath/app/inspector.py +506 -0
  14. glidepath/app/labels.py +66 -0
  15. glidepath/app/montecarlo.py +399 -0
  16. glidepath/app/plan.py +354 -0
  17. glidepath/app/retirement.py +446 -0
  18. glidepath/app/scenarios.py +831 -0
  19. glidepath/app/shell.py +185 -0
  20. glidepath/app/tables.py +138 -0
  21. glidepath/core/__init__.py +390 -0
  22. glidepath/core/annuities.py +240 -0
  23. glidepath/core/backtest.py +514 -0
  24. glidepath/core/comparison.py +278 -0
  25. glidepath/core/config.py +82 -0
  26. glidepath/core/contributions.py +337 -0
  27. glidepath/core/engine.py +2811 -0
  28. glidepath/core/entities.py +264 -0
  29. glidepath/core/glide.py +289 -0
  30. glidepath/core/investments.py +175 -0
  31. glidepath/core/money.py +107 -0
  32. glidepath/core/montecarlo.py +609 -0
  33. glidepath/core/pensions.py +298 -0
  34. glidepath/core/periods.py +367 -0
  35. glidepath/core/provenance.py +271 -0
  36. glidepath/core/randomness.py +128 -0
  37. glidepath/core/region.py +46 -0
  38. glidepath/core/reporting.py +231 -0
  39. glidepath/core/results.py +504 -0
  40. glidepath/core/retirement.py +291 -0
  41. glidepath/core/returns.py +312 -0
  42. glidepath/core/scenarios.py +579 -0
  43. glidepath/core/state_pension.py +264 -0
  44. glidepath/core/tax.py +139 -0
  45. glidepath/core/withdrawals.py +461 -0
  46. glidepath/core/wrappers.py +278 -0
  47. glidepath/gui/__init__.py +6 -0
  48. glidepath/gui/assets/icon_128.png +0 -0
  49. glidepath/gui/assets/icon_16.png +0 -0
  50. glidepath/gui/assets/icon_24.png +0 -0
  51. glidepath/gui/assets/icon_256.png +0 -0
  52. glidepath/gui/assets/icon_32.png +0 -0
  53. glidepath/gui/assets/icon_48.png +0 -0
  54. glidepath/gui/assets/icon_64.png +0 -0
  55. glidepath/gui/assets/wordmark.png +0 -0
  56. glidepath/gui/charts.py +829 -0
  57. glidepath/gui/forms.py +359 -0
  58. glidepath/gui/inspector.py +186 -0
  59. glidepath/gui/main.py +51 -0
  60. glidepath/gui/scenarios.py +402 -0
  61. glidepath/gui/style.py +376 -0
  62. glidepath/gui/tableview.py +67 -0
  63. glidepath/gui/widgets.py +989 -0
  64. glidepath/persistence/__init__.py +48 -0
  65. glidepath/persistence/assumptions.py +112 -0
  66. glidepath/persistence/decode.py +747 -0
  67. glidepath/persistence/document.py +101 -0
  68. glidepath/persistence/encode.py +433 -0
  69. glidepath/persistence/migrations.py +158 -0
  70. glidepath/persistence/values.py +298 -0
  71. glidepath/py.typed +0 -0
  72. glidepath/regions/__init__.py +7 -0
  73. glidepath/regions/uk/__init__.py +189 -0
  74. glidepath/regions/uk/ages.py +156 -0
  75. glidepath/regions/uk/contributions.py +717 -0
  76. glidepath/regions/uk/data/age_rules.toml +78 -0
  77. glidepath/regions/uk/data/assumptions_default.toml +170 -0
  78. glidepath/regions/uk/data/returns_history.toml +150 -0
  79. glidepath/regions/uk/data/tax_year_2026_27.toml +98 -0
  80. glidepath/regions/uk/extension.py +479 -0
  81. glidepath/regions/uk/loader.py +704 -0
  82. glidepath/regions/uk/region.py +160 -0
  83. glidepath/regions/uk/schema.py +563 -0
  84. glidepath/regions/uk/state_pension.py +129 -0
  85. glidepath/regions/uk/tax.py +466 -0
  86. glidepath/regions/uk/wrappers.py +283 -0
  87. glidepath/regions/uk/years.py +92 -0
  88. glidepath-0.2.0.dist-info/METADATA +189 -0
  89. glidepath-0.2.0.dist-info/RECORD +93 -0
  90. glidepath-0.2.0.dist-info/WHEEL +4 -0
  91. glidepath-0.2.0.dist-info/entry_points.txt +3 -0
  92. glidepath-0.2.0.dist-info/licenses/LICENSE +21 -0
  93. glidepath-0.2.0.dist-info/licenses/LICENSE-DATA +28 -0
@@ -0,0 +1,160 @@
1
+ """The UK region bundle and shipped assumption set (roadmap 4.1; planning §4.2).
2
+
3
+ Builds the :class:`~glidepath.core.Region` the engine runs against:
4
+ the UK fiscal calendar (tax years, 6 April to 5 April), the age, tax,
5
+ wrapper, and contribution rulesets over the shipped data files, and a
6
+ content-version string naming each file with its ``verified_on`` date —
7
+ the region part of the run manifest (planning §4.6).
8
+
9
+ Also converts the shipped ``assumptions_default.toml`` into the core
10
+ :class:`~glidepath.core.AssumptionSet` (every entry
11
+ ``DEFAULT_ASSUMPTION``-provenanced, carrying its basis), and derives
12
+ the future-years extension from the ``policy.tax.future_years`` and
13
+ ``inflation.cpi`` assumptions. Both reads happen at region-build time —
14
+ before the run's read recorder exists — so the extension's effect on a
15
+ result is identified through the region data version, not the
16
+ assumption read list.
17
+ """
18
+
19
+ from datetime import UTC, datetime, time
20
+
21
+ from glidepath.core import (
22
+ AnnualCalendar,
23
+ Assumption,
24
+ AssumptionKey,
25
+ AssumptionSet,
26
+ Provenance,
27
+ Rate,
28
+ Region,
29
+ decimal_assumption_value,
30
+ )
31
+ from glidepath.regions.uk.ages import UkAgeRules
32
+ from glidepath.regions.uk.contributions import UkContributionRuleset
33
+ from glidepath.regions.uk.extension import FutureYearsExtension, FutureYearsPolicy
34
+ from glidepath.regions.uk.loader import (
35
+ AGE_RULES_FILENAME,
36
+ ASSUMPTIONS_FILENAME,
37
+ available_tax_years,
38
+ data_file_digest,
39
+ load_age_rules,
40
+ load_default_assumptions,
41
+ load_tax_year,
42
+ tax_year_filename,
43
+ )
44
+ from glidepath.regions.uk.schema import SCHEMA_VERSION
45
+ from glidepath.regions.uk.state_pension import UkStatePensionScheme
46
+ from glidepath.regions.uk.tax import UkTaxSystem
47
+ from glidepath.regions.uk.wrappers import UkWrapperRuleset
48
+
49
+ _TAX_YEAR_ANCHOR_MONTH = 4
50
+ _TAX_YEAR_ANCHOR_DAY = 6
51
+
52
+
53
+ def default_assumption_set() -> AssumptionSet:
54
+ """The shipped UK defaults as a core assumption set (planning §7).
55
+
56
+ Every entry carries ``DEFAULT_ASSUMPTION`` provenance with its
57
+ default value equal to its effective value; overriding one is the
58
+ caller's concern (a new :class:`~glidepath.core.Assumption` with
59
+ ``USER_OVERRIDE`` or ``SCENARIO_OVERRIDE`` provenance in a new
60
+ set). ``recorded_on`` is the data file's ``verified_on`` at
61
+ midnight UTC.
62
+ """
63
+ file = load_default_assumptions()
64
+ recorded_on = datetime.combine(file.meta.verified_on, time.min, tzinfo=UTC)
65
+ return AssumptionSet(
66
+ Assumption(
67
+ key=entry.key,
68
+ value=entry.value,
69
+ default_value=entry.value,
70
+ provenance=Provenance.DEFAULT_ASSUMPTION,
71
+ source=entry.basis,
72
+ recorded_on=recorded_on,
73
+ description=f"Shipped UK default for '{entry.key.value}'",
74
+ )
75
+ for entry in file.defaults
76
+ )
77
+
78
+
79
+ def future_years_extension(assumptions: AssumptionSet) -> FutureYearsExtension:
80
+ """The tax-data extension the ``policy.tax.future_years`` value asks for.
81
+
82
+ Reads the policy table and the CPI assumption directly from the
83
+ set (module docstring: region construction happens before the
84
+ run's read recorder exists).
85
+ """
86
+ policy = FutureYearsPolicy.from_assumption_value(
87
+ assumptions.get(AssumptionKey.POLICY_TAX_FUTURE_YEARS).value
88
+ )
89
+ cpi = Rate(decimal_assumption_value(assumptions.get(AssumptionKey.INFLATION_CPI)))
90
+ return FutureYearsExtension(policy=policy, cpi=cpi)
91
+
92
+
93
+ def uk_region(future_years: FutureYearsExtension | None = None) -> Region:
94
+ """The UK region bundle over every shipped data file (planning §4.2).
95
+
96
+ Without ``future_years`` the bundle answers only for the shipped
97
+ tax years and fails loudly past them; pass
98
+ :func:`future_years_extension` to project beyond the last shipped
99
+ file (planning §5.3).
100
+ """
101
+ return Region(
102
+ calendar=AnnualCalendar(
103
+ anchor_month=_TAX_YEAR_ANCHOR_MONTH, anchor_day=_TAX_YEAR_ANCHOR_DAY
104
+ ),
105
+ ages=UkAgeRules.from_shipped_data(),
106
+ tax=UkTaxSystem.from_shipped_data(future_years),
107
+ wrappers=UkWrapperRuleset.from_shipped_data(future_years),
108
+ contributions=UkContributionRuleset.from_shipped_data(future_years),
109
+ state_pension=UkStatePensionScheme.from_shipped_data(),
110
+ data_version=_data_version(future_years),
111
+ )
112
+
113
+
114
+ def _data_version(future_years: FutureYearsExtension | None) -> str:
115
+ """A deterministic content-version string over the shipped files.
116
+
117
+ Names every file with its ``verified_on`` date and a short content
118
+ digest — the date alone cannot tell two same-day revisions apart
119
+ (planning §4.6) — plus the full future-years policy (mode, freeze
120
+ ends incl. the Scottish band groups, CPI): a region-build input
121
+ whose effect must be identifiable from the version string, not the
122
+ assumption read list.
123
+
124
+ ``returns_history.toml`` is deliberately absent: it prices no
125
+ assumption and no base projection, and this string doubles as the
126
+ saved-plan ``assumptions_resolved_against`` fingerprint
127
+ (:mod:`glidepath.app.files`) — including it would warn "shipped
128
+ defaults have changed" on every saved plan after a history-only
129
+ refresh. A backtest names its series by carrying it on the result
130
+ (:class:`glidepath.core.BacktestResult`).
131
+ """
132
+ parts = [f"uk schema={SCHEMA_VERSION}"]
133
+ for start_year in available_tax_years():
134
+ year = load_tax_year(start_year)
135
+ digest = data_file_digest(tax_year_filename(start_year))
136
+ parts.append(
137
+ f"tax_year {year.meta.tax_year} verified {year.meta.verified_on}"
138
+ f" sha256={digest}"
139
+ )
140
+ ages = load_age_rules()
141
+ ages_digest = data_file_digest(AGE_RULES_FILENAME)
142
+ parts.append(f"age_rules verified {ages.meta.verified_on} sha256={ages_digest}")
143
+ assumptions = load_default_assumptions()
144
+ assumptions_digest = data_file_digest(ASSUMPTIONS_FILENAME)
145
+ parts.append(
146
+ f"assumptions verified {assumptions.meta.verified_on}"
147
+ f" sha256={assumptions_digest}"
148
+ )
149
+ if future_years is not None:
150
+ policy = future_years.policy
151
+ detail = policy.mode.value
152
+ if policy.frozen_until_start_year is not None:
153
+ detail += f" until={policy.frozen_until_start_year}"
154
+ if policy.scotland is not None:
155
+ detail += (
156
+ f" scot_lower_until={policy.scotland.lower_frozen_until_start_year}"
157
+ f" scot_upper_until={policy.scotland.upper_frozen_until_start_year}"
158
+ )
159
+ parts.append(f"future_years {detail} cpi={future_years.cpi.value}")
160
+ return "; ".join(parts)