owlplanner 2025.12.5__py3-none-any.whl → 2026.1.26__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.
- owlplanner/In Discussion #58, the case of Kim and Sam.md +307 -0
- owlplanner/__init__.py +20 -1
- owlplanner/abcapi.py +24 -23
- owlplanner/cli/README.md +50 -0
- owlplanner/cli/_main.py +52 -0
- owlplanner/cli/cli_logging.py +56 -0
- owlplanner/cli/cmd_list.py +83 -0
- owlplanner/cli/cmd_run.py +86 -0
- owlplanner/config.py +315 -136
- owlplanner/data/__init__.py +21 -0
- owlplanner/data/awi.csv +75 -0
- owlplanner/data/bendpoints.csv +49 -0
- owlplanner/data/newawi.csv +75 -0
- owlplanner/data/rates.csv +99 -98
- owlplanner/debts.py +315 -0
- owlplanner/fixedassets.py +288 -0
- owlplanner/mylogging.py +157 -25
- owlplanner/plan.py +1044 -332
- owlplanner/plotting/__init__.py +16 -3
- owlplanner/plotting/base.py +17 -3
- owlplanner/plotting/factory.py +16 -3
- owlplanner/plotting/matplotlib_backend.py +30 -7
- owlplanner/plotting/plotly_backend.py +33 -10
- owlplanner/progress.py +66 -9
- owlplanner/rates.py +366 -361
- owlplanner/socialsecurity.py +142 -22
- owlplanner/tax2026.py +170 -57
- owlplanner/timelists.py +316 -32
- owlplanner/utils.py +204 -5
- owlplanner/version.py +20 -1
- {owlplanner-2025.12.5.dist-info → owlplanner-2026.1.26.dist-info}/METADATA +50 -158
- owlplanner-2026.1.26.dist-info/RECORD +36 -0
- owlplanner-2026.1.26.dist-info/entry_points.txt +2 -0
- owlplanner-2026.1.26.dist-info/licenses/AUTHORS +15 -0
- owlplanner/tax2025.py +0 -339
- owlplanner-2025.12.5.dist-info/RECORD +0 -24
- {owlplanner-2025.12.5.dist-info → owlplanner-2026.1.26.dist-info}/WHEEL +0 -0
- {owlplanner-2025.12.5.dist-info → owlplanner-2026.1.26.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
In Discussion #58, the case of Kim and Sam are used to investigate the effect of Roth conversions on a retirement plan. The previous case was focusing on the effect of Roth conversions on the net spending amount, and all cases considered had a target of zero bequest. We found that Roth conversions increase the net spending amount by a magnitude of the order of 1%. This finding was consistent across two scenarios involving fixed rates, and for two historical rate sequences, one staring from 1966, and the other from 1991.
|
|
2
|
+
|
|
3
|
+
Here we use the same case for comparison. Kim and Sam have the same account balances and allocations. Instead of optimizing for net spending however, we will now change the objective function to maximize the bequest under the constraint of a fixed yearly net spending amount of $145,000, adjusted annually for inflation.
|
|
4
|
+
|
|
5
|
+
The same four cases will be considered:
|
|
6
|
+
**Case 1)** Fixed conservative return rates at 6.0% S&P 500, 4.0% for Baa Corporate bonds, 3.3% for 10-year T-Notes, and 2.8% inflation
|
|
7
|
+
**Case 2)** Fixed optimistic return rates at 8.0% S&P 500, 4.5% for Baa Corporate bonds, 3.3% for 10-year T-Notes, and 2.8% inflation
|
|
8
|
+
**Case 3)** Historical rates starting in 1966
|
|
9
|
+
**Case 4)** Historical rates starting in 1991
|
|
10
|
+
Cases were optimized in two different ways for Medicare (selected as optimize or loop in the user interface):
|
|
11
|
+
a) Medicare/IRMAA (income-related monthly adjusted amounts) premiums were directly solved in the optimization
|
|
12
|
+
b) Medicare/IRMAA premiums were solved iteratively using a self-consistent loop wrapping an optimization step
|
|
13
|
+
|
|
14
|
+
Since the heirs' marginal income tax rate will influence the net after-tax bequest, three different rates are considered: 33%, 22%, and 0%.
|
|
15
|
+
|
|
16
|
+
## Case 1a-33: Fixed conservative rates with Medicare/IRMAA optimized with 33% heirs' marginal tax rate
|
|
17
|
+
|
|
18
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
19
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
20
|
+
| 200 | $145,000 | $1,069,166 | $166,051 | 15.53% | $1,025,607 |
|
|
21
|
+
| 150 | $145,000 | $1,069,166 | $166,051 | 15.53% | $1,025,607 |
|
|
22
|
+
| 100 | $145,000 | $1,069,275 | $166,160 | 15.54% | $1,033,921 |
|
|
23
|
+
| 50 | $145,000 | $1,047,152 | $144,037 | 13.76% | $998,896 |
|
|
24
|
+
| 10 | $145,000 | $978,573 | $75,458 | 7.71% | $610,001 |
|
|
25
|
+
| 0 | $145,000 | $903,115 | $0 | 0.00% | $0 |
|
|
26
|
+
|
|
27
|
+
## Case 1a-22: Fixed conservative rates with Medicare/IRMAA optimized with 22% heirs' marginal tax rate
|
|
28
|
+
|
|
29
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
30
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
31
|
+
| 200 | $145,000 | $1,069,166 | $131,463 | 12.30% | $1,025,607 |
|
|
32
|
+
| 150 | $145,000 | $1,069,166 | $131,463 | 12.30% | $1,025,607 |
|
|
33
|
+
| 100 | $145,000 | $1,069,275 | $131,572 | 12.30% | $1,033,921 |
|
|
34
|
+
| 50 | $145,000 | $1,055,429 | $117,726 | 11.15% | $800,000 |
|
|
35
|
+
| 10 | $145,000 | $995,230 | $57,527 | 5.78% | $397,992 |
|
|
36
|
+
| 0 | $145,000 | $937,703 | $0 | 0.00% | $0 |
|
|
37
|
+
|
|
38
|
+
## Case 1a-0: Fixed conservative rates with Medicare/IRMAA optimized with 0% heirs' marginal tax rate
|
|
39
|
+
|
|
40
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
41
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
42
|
+
| 200 | $145,000 | $1,162,471 | $75,603 | 6.50% | $818,525 |
|
|
43
|
+
| 150 | $145,000 | $1,162,471 | $75,603 | 6.50% | $818,525 |
|
|
44
|
+
| 100 | $145,000 | $1,162,870 | $76,002 | 6.54% | $771,138 |
|
|
45
|
+
| 50 | $145,000 | $1,158,923 | $72,055 | 6.22% | $713,428 |
|
|
46
|
+
| 10 | $145,000 | $1,113,193 | $26,325 | 2.36% | $280,000 |
|
|
47
|
+
| 0 | $145,000 | $1,086,868 | $0 | 0.00% | $0 |
|
|
48
|
+
|
|
49
|
+
## Case 1b-33: Fixed conservative rates with Medicare/IRMAA solved self-consistently with 33% heirs' marginal tax rate
|
|
50
|
+
|
|
51
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
52
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
53
|
+
| 200 | $145,000 | $1,064,285 | $178,884 | 16.81% | $1,022,709 |
|
|
54
|
+
| 150 | $145,000 | $1,064,285 | $178,884 | 16.81% | $1,022,709 |
|
|
55
|
+
| 100 | $145,000 | $1,067,113 | $181,712 | 17.03% | $1,033,921 |
|
|
56
|
+
| 50 | $145,000 | $1,038,173 | $152,772 | 14.72% | $993,873 |
|
|
57
|
+
| 10 | $145,000 | $970,478 | $85,077 | 8.77% | $610,001 |
|
|
58
|
+
| 0 | $145,000 | $885,401 | $0 | 0.00% | $0 |
|
|
59
|
+
|
|
60
|
+
## Case 1b-22: Fixed conservative rates with Medicare/IRMAA solved self-consistently with 22% heirs' marginal tax rate
|
|
61
|
+
|
|
62
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
63
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
64
|
+
| 200 | $145,000 | $1,064,285 | $128,667 | 12.09% | $1,022,709 |
|
|
65
|
+
| 150 | $145,000 | $1,064,285 | $128,667 | 12.09% | $1,022,709 |
|
|
66
|
+
| 100 | $145,000 | $1,067,113 | $131,495 | 12.32% | $1,033,921 |
|
|
67
|
+
| 50 | $145,000 | $1,053,266 | $117,648 | 11.17% | $800,000 |
|
|
68
|
+
| 10 | $145,000 | $993,123 | $57,505 | 5.79% | $395,376 |
|
|
69
|
+
| 0 | $145,000 | $935,618 | $0 | 0.00% | $0 |
|
|
70
|
+
|
|
71
|
+
## Case 1b-0: Fixed conservative rates with Medicare/IRMAA solved self-consistently with 0% heirs' marginal tax rate
|
|
72
|
+
|
|
73
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
74
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
75
|
+
| 200 | $145,000 | $1,160,358 | $80,814 | 6.96% | $818,525 |
|
|
76
|
+
| 150 | $145,000 | $1,160,358 | $80,814 | 6.96% | $818,525 |
|
|
77
|
+
| 100 | $145,000 | $1,160,332 | $80,788 | 6.96% | $818,525 |
|
|
78
|
+
| 50 | $145,000 | $1,156,796 | $77,252 | 6.68% | $713,428 |
|
|
79
|
+
| 10 | $145,000 | $1,111,080 | $31,536 | 2.84% | $280,000 |
|
|
80
|
+
| 0 | $145,000 | $1,079,544 | $0 | 0.00% | $0 |
|
|
81
|
+
|
|
82
|
+
## Case 2a-33: Fixed optimistic rates with Medicare/IRMAA optimized with 33% heirs' marginal tax rate
|
|
83
|
+
|
|
84
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
85
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
86
|
+
| 200 | $145,000 | $2,536,155 | $391,246 | 15.43% | $1,257,908 |
|
|
87
|
+
| 150 | $145,000 | $2,536,155 | $391,246 | 15.43% | $1,257,908 |
|
|
88
|
+
| 100 | $145,000 | $2,529,949 | $385,040 | 15.22% | $1,242,205 |
|
|
89
|
+
| 50 | $145,000 | $2,476,579 | $331,670 | 13.39% | $105,000 |
|
|
90
|
+
| 10 | $145,000 | $2,273,553 | $128,644 | 5.66% | $590,001 |
|
|
91
|
+
| 0 | $145,000 | $2,144,909 | $0 | 0.00% | $0 |
|
|
92
|
+
|
|
93
|
+
## Case 2a-22: Fixed optimistic rates with Medicare/IRMAA optimized with 22% heirs' marginal tax rate
|
|
94
|
+
|
|
95
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
96
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
97
|
+
| 200 | $145,000 | $2,536,155 | $324,858 | 12.81% | $1,205,857 |
|
|
98
|
+
| 150 | $145,000 | $2,536,155 | $324,858 | 12.81% | $1,205,857 |
|
|
99
|
+
| 100 | $145,000 | $2,533,590 | $322,293 | 12.72% | $1,180,111 |
|
|
100
|
+
| 50 | $145,000 | $2,491,684 | $280,387 | 11.25% | $1,000,000 |
|
|
101
|
+
| 10 | $145,000 | $2,325,163 | $113,866 | 4.90% | $590,001 |
|
|
102
|
+
| 0 | $145,000 | $2,211,297 | $0 | 0.00% | $0 |
|
|
103
|
+
|
|
104
|
+
## Case 2a-0: Fixed optimistic rates with Medicare/IRMAA optimized with 0% heirs' marginal tax rate
|
|
105
|
+
|
|
106
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
107
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
108
|
+
| 200 | $145,000 | $2,664,088 | $223,593 | 8.39% | $820,763 |
|
|
109
|
+
| 150 | $145,000 | $2,664,062 | $223,567 | 8.39% | $820,763 |
|
|
110
|
+
| 100 | $145,000 | $2,663,007 | $222,512 | 8.36% | $820,763 |
|
|
111
|
+
| 50 | $145,000 | $2,651,217 | $210,722 | 7.95% | $792,944 |
|
|
112
|
+
| 10 | $145,000 | $2,525,721 | $85,226 | 3.37% | $350,000 |
|
|
113
|
+
| 0 | $145,000 | $2,440,495 | $0 | 0.00% | $0 |
|
|
114
|
+
|
|
115
|
+
## Case 2b-33: Fixed optimistic rates with Medicare/IRMAA solved self-consistently with 33% heirs' marginal tax rate
|
|
116
|
+
|
|
117
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
118
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
119
|
+
| 200 | $145,000 | $2,516,421 | $391,903 | 15.57% | $1,235,627 |
|
|
120
|
+
| 150 | $145,000 | $2,516,421 | $391,903 | 15.57% | $1,235,627 |
|
|
121
|
+
| 100 | $145,000 | $2,513,287 | $388,769 | 15.47% | $1,238,060 |
|
|
122
|
+
| 50 | $145,000 | $2,464,280 | $339,762 | 13.79% | $1,092,714 |
|
|
123
|
+
| 10 | $145,000 | $2,251,867 | $127,349 | 5.66% | $590,001 |
|
|
124
|
+
| 0 | $145,000 | $2,124,518 | $0 | 0.00% | $0 |
|
|
125
|
+
|
|
126
|
+
## Case 2b-22: Fixed optimistic rates with Medicare/IRMAA solved self-consistently with 22% heirs' marginal tax rate
|
|
127
|
+
|
|
128
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
129
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
130
|
+
| 200 | $145,000 | $2,516,421 | $322,752 | 12.83% | $1,235,627 |
|
|
131
|
+
| 150 | $145,000 | $2,516,421 | $322,752 | 12.83% | $1,235,627 |
|
|
132
|
+
| 100 | $145,000 | $2,526,764 | $333,095 | 13.18% | $1,184,983 |
|
|
133
|
+
| 50 | $145,000 | $2,479,386 | $285,717 | 11.52% | $1,000,000 |
|
|
134
|
+
| 10 | $145,000 | $2,309,678 | $116,009 | 5.02% | $590,001 |
|
|
135
|
+
| 0 | $145,000 | $2,193,669 | $0 | 0.00% | $0 |
|
|
136
|
+
|
|
137
|
+
## Case 2b-0: Fixed optimistic rates with Medicare/IRMAA solved self-consistently with 0% heirs' marginal tax rate
|
|
138
|
+
|
|
139
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
140
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
141
|
+
| 200 | $145,000 | $2,656,383 | $198,591 | 7.48% | $820,763 |
|
|
142
|
+
| 150 | $145,000 | $2,656,358 | $198,566 | 7.48% | $820,763 |
|
|
143
|
+
| 100 | $145,000 | $2,655,303 | $197,511 | 7.44% | $820,763 |
|
|
144
|
+
| 50 | $145,000 | $2,643,512 | $185,720 | 7.03% | $792,944 |
|
|
145
|
+
| 10 | $145,000 | $2,537,957 | $80,165 | 3.16% | $303,990 |
|
|
146
|
+
| 0 | $145,000 | $2,457,792 | $0 | 0.00% | $0 |
|
|
147
|
+
|
|
148
|
+
## Case 3a-33: Historical rates starting in 1966 with Medicare/IRMAA optimized with 33% heirs' marginal tax rate
|
|
149
|
+
|
|
150
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
151
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
152
|
+
| 200 | $145,000 | $1,152,831 | $341,441 | 29.62% | $783,901 |
|
|
153
|
+
| 150 | $145,000 | $1,152,831 | $341,441 | 29.62% | $783,901 |
|
|
154
|
+
| 100 | $145,000 | $1,152,831 | $341,441 | 29.62% | $783,901 |
|
|
155
|
+
| 50 | $145,000 | $1,137,204 | $325,814 | 28.65% | $700,000 |
|
|
156
|
+
| 10 | $145,000 | $949,827 | $138,437 | 14.57% | $320,000 |
|
|
157
|
+
| 0 | $145,000 | $811,390 | $0 | 0.00% | $0 |
|
|
158
|
+
|
|
159
|
+
## Case 3a-22: Historical rates starting in 1966 with Medicare/IRMAA optimized with 22% heirs' marginal tax rate
|
|
160
|
+
|
|
161
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
162
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
163
|
+
| 200 | $145,000 | $1,152,831 | $341,441 | 29.62% | $783,901 |
|
|
164
|
+
| 150 | $145,000 | $1,152,831 | $341,441 | 29.62% | $803,367 |
|
|
165
|
+
| 100 | $145,000 | $1,152,831 | $341,441 | 29.62% | $783,901 |
|
|
166
|
+
| 50 | $145,000 | $1,137,204 | $325,814 | 28.65% | $700,000 |
|
|
167
|
+
| 10 | $145,000 | $949,827 | $138,437 | 14.57% | $320,000 |
|
|
168
|
+
| 0 | $145,000 | $811,390 | $0 | 0.00% | $0 |
|
|
169
|
+
|
|
170
|
+
## Case 3a-0: Historical rates starting in 1966 with Medicare/IRMAA optimized with 0% heirs' marginal tax rate
|
|
171
|
+
|
|
172
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
173
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
174
|
+
| 200 | $145,000 | $1,222,679 | $223,127 | 18.25% | $783,901 |
|
|
175
|
+
| 150 | $145,000 | $1,222,679 | $223,127 | 18.25% | $783,901 |
|
|
176
|
+
| 100 | $145,000 | $1,222,679 | $223,127 | 18.25% | $783,901 |
|
|
177
|
+
| 50 | $145,000 | $1,207,373 | $207,821 | 17.21% | $695,708 |
|
|
178
|
+
| 10 | $145,000 | $1,125,137 | $125,585 | 11.16% | $260,000 |
|
|
179
|
+
| 0 | $145,000 | $999,552 | $0 | 0.00% | $0 |
|
|
180
|
+
|
|
181
|
+
## Case 3b-33: Historical rates starting in 1966 with Medicare/IRMAA solved self-consistently with 33% heirs' marginal tax rate
|
|
182
|
+
|
|
183
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
184
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
185
|
+
| 200 | $145,000 | $1,150,869 | $340,733 | 29.61% | $783,901 |
|
|
186
|
+
| 150 | $145,000 | $1,150,869 | $340,733 | 29.61% | $783,901 |
|
|
187
|
+
| 100 | $145,000 | $1,150,869 | $340,733 | 29.61% | $783,901 |
|
|
188
|
+
| 50 | $145,000 | $1,124,033 | $313,897 | 27.93% | $695,239 |
|
|
189
|
+
| 10 | $145,000 | $945,858 | $135,722 | 14.35% | $320,000 |
|
|
190
|
+
| 0 | $145,000 | $810,136 | $0 | 0.00% | $0 |
|
|
191
|
+
|
|
192
|
+
## Case 3b-22: Historical rates starting in 1966 with Medicare/IRMAA solved self-consistently with 22% heirs' marginal tax rate
|
|
193
|
+
|
|
194
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
195
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
196
|
+
| 200 | $145,000 | $1,150,869 | $340,733 | 29.61% | $783,901 |
|
|
197
|
+
| 150 | $145,000 | $1,150,869 | $340,733 | 29.61% | $783,901 |
|
|
198
|
+
| 100 | $145,000 | $1,150,869 | $340,733 | 29.61% | $783,901 |
|
|
199
|
+
| 50 | $145,000 | $1,124,033 | $313,897 | 27.93% | $695,239 |
|
|
200
|
+
| 10 | $145,000 | $945,858 | $135,722 | 14.35% | $320,000 |
|
|
201
|
+
| 0 | $145,000 | $810,136 | $0 | 0.00% | $0 |
|
|
202
|
+
|
|
203
|
+
## Case 3b-0: Historical rates starting in 1966 with Medicare/IRMAA solved self-consistently with 0% heirs' marginal tax rate
|
|
204
|
+
|
|
205
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
206
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
207
|
+
| 200 | $145,000 | $1,218,844 | $225,877 | 18.53% | $783,901 |
|
|
208
|
+
| 150 | $145,000 | $1,218,844 | $225,877 | 18.53% | $783,901 |
|
|
209
|
+
| 100 | $145,000 | $1,218,844 | $225,877 | 18.53% | $783,901 |
|
|
210
|
+
| 50 | $145,000 | $1,203,538 | $210,571 | 17.50% | $695,708 |
|
|
211
|
+
| 10 | $145,000 | $1,121,353 | $128,386 | 11.45% | $260,000 |
|
|
212
|
+
| 0 | $145,000 | $992,967 | $0 | 0.00% | $0 |
|
|
213
|
+
|
|
214
|
+
## Case 4a-33: Historical rates starting in 1991 with Medicare/IRMAA optimized with 33% heirs' marginal tax rate
|
|
215
|
+
|
|
216
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
217
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
218
|
+
| 400 | $145,000 | $12,056,950 | $2,171,374 | 18.01% | $1,762,786 |
|
|
219
|
+
| 200 | $145,000 | $12,058,040 | $2,172,464 | 18.02% | $1,705,975 |
|
|
220
|
+
| 150 | $145,000 | $12,025,790 | $2,140,214 | 17.80% | $2,131,802 |
|
|
221
|
+
| 100 | $145,000 | $12,003,202 | $2,117,626 | 17.64% | $2,943,074 |
|
|
222
|
+
| 50 | $145,000 | $11,480,661 | $1,595,085 | 13.89% | $2,816,227 |
|
|
223
|
+
| 10 | $145,000 | $11,014,110 | $1,128,534 | 10.25% | $556,256 |
|
|
224
|
+
| 0 | $145,000 | $9,885,576 | $0 | 0.00% | $0 |
|
|
225
|
+
|
|
226
|
+
## Case 4a-22: Historical rates starting in 1991 with Medicare/IRMAA optimized with 22% heirs' marginal tax rate
|
|
227
|
+
|
|
228
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
229
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
230
|
+
| 400 | $145,000 | $12,073,210 | $1,870,449 | 15.49% | $1,760,142 |
|
|
231
|
+
| 200 | $145,000 | $12,074,606 | $1,871,845 | 15.50% | $1,719,322 |
|
|
232
|
+
| 150 | $145,000 | $12,042,813 | $1,840,052 | 15.28% | $1,930,667 |
|
|
233
|
+
| 100 | $145,000 | $12,024,366 | $1,821,605 | 15.15% | $2,707,227 |
|
|
234
|
+
| 50 | $145,000 | $11,608,739 | $1,405,978 | 12.11% | $2,709,587 |
|
|
235
|
+
| 10 | $145,000 | $10,496,321 | $293,560 | 2.80% | $555,571 |
|
|
236
|
+
| 0 | $145,000 | $10,202,761 | $0 | 0.00% | $0 |
|
|
237
|
+
|
|
238
|
+
## Case 4a-0: Historical rates starting in 1991 with Medicare/IRMAA optimized with 0% heirs' marginal tax rate
|
|
239
|
+
|
|
240
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
241
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
242
|
+
| 400 | $145,000 | $12,292,982 | $1,446,509 | 11.77% | $1,669,855 |
|
|
243
|
+
| 200 | $145,000 | $12,292,643 | $1,446,170 | 11.76% | $1,669,855 |
|
|
244
|
+
| 150 | $145,000 | $12,251,697 | $1,405,224 | 11.47% | $1,662,303 |
|
|
245
|
+
| 100 | $145,000 | $12,234,459 | $1,387,986 | 11.34% | $2,199,350 |
|
|
246
|
+
| 50 | $145,000 | $11,947,720 | $1,101,247 | 9.22% | $2,281,495 |
|
|
247
|
+
| 10 | $145,000 | $11,070,668 | $224,195 | 2.03% | $424,505 |
|
|
248
|
+
| 0 | $145,000 | $10,846,473 | $0 | 0.00% | $0 |
|
|
249
|
+
|
|
250
|
+
## Case 4b-33: Historical rates starting in 1991 with Medicare/IRMAA solved self-consistently with 33% heirs' marginal tax rate
|
|
251
|
+
|
|
252
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
253
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
254
|
+
| 400 | $145,000 | $12,006,601 | $2,182,176 | 18.17% | $1,906,415 |
|
|
255
|
+
| 200 | $145,000 | $11,996,099 | $2,171,674 | 18.10% | $2,022,783 |
|
|
256
|
+
| 150 | $145,000 | $11,968,496 | $2,144,071 | 17.91% | $2,079,499 |
|
|
257
|
+
| 100 | $145,000 | $11,941,959 | $2,117,534 | 17.73% | $2,877,714 |
|
|
258
|
+
| 50 | $145,000 | $11,407,587 | $1,583,162 | 13.88% | $2,850,001 |
|
|
259
|
+
| 10 | $145,000 | $10,150,350 | $325,925 | 3.21% | $570,001 |
|
|
260
|
+
| 0 | $145,000 | $9,824,425 | $0 | 0.00% | $0 |
|
|
261
|
+
|
|
262
|
+
## Case 4b-22: Historical rates starting in 1991 with Medicare/IRMAA solved self-consistently with 22% heirs' marginal tax rate
|
|
263
|
+
|
|
264
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
265
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
266
|
+
| 400 | $145,000 | $12,021,047 | $1,852,757 | 15.41% | $1,872,324 |
|
|
267
|
+
| 200 | $145,000 | $12,023,136 | $1,854,846 | 15.43% | $1,855,304 |
|
|
268
|
+
| 150 | $145,000 | $11,994,614 | $1,826,324 | 15.23% | $1,753,840 |
|
|
269
|
+
| 100 | $145,000 | $11,977,328 | $1,809,038 | 15.10% | $2,482,833 |
|
|
270
|
+
| 50 | $145,000 | $11,546,402 | $1,378,112 | 11.94% | $2,764,699 |
|
|
271
|
+
| 10 | $145,000 | $10,458,881 | $290,591 | 2.78% | $520,001 |
|
|
272
|
+
| 0 | $145,000 | $10,168,290 | $0 | 0.00% | $0 |
|
|
273
|
+
|
|
274
|
+
## Case 4b-0: Historical rates starting in 1991 with Medicare/IRMAA solved self-consistently with 0% heirs' marginal tax rate
|
|
275
|
+
|
|
276
|
+
| max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
|
|
277
|
+
|-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
|
|
278
|
+
| 400 | $145,000 | $12,295,204 | $1,151,599 | 9.37% | $1,508,030 |
|
|
279
|
+
| 200 | $145,000 | $12,290,863 | $1,147,258 | 9.33% | $1,510,600 |
|
|
280
|
+
| 150 | $145,000 | $12,285,610 | $1,142,005 | 9.30% | $1,511,254 |
|
|
281
|
+
| 100 | $145,000 | $12,259,890 | $1,116,285 | 9.11% | $1,739,219 |
|
|
282
|
+
| 50 | $145,000 | $12,060,261 | $916,656 | 7.60% | $1,676,283 |
|
|
283
|
+
| 10 | $145,000 | $11,381,993 | $238,388 | 2.09% | $398,028 |
|
|
284
|
+
| 0 | $145,000 | $11,143,605 | $0 | 0.00% | $0 |
|
|
285
|
+
|
|
286
|
+
## Observations
|
|
287
|
+
|
|
288
|
+
The results presented above demonstrate several important patterns regarding the impact of Roth conversions on bequest maximization:
|
|
289
|
+
|
|
290
|
+
**1. Heir tax rate is the dominant factor**: The benefits of Roth conversions increase dramatically with the heirs' marginal tax rate. For example, in Case 1a with maximum conversions (200k), the net benefit ranges from 6.50% (0% tax rate) to 15.53% (33% tax rate). This pattern is consistent across all cases and reflects the fundamental advantage of Roth conversions: by paying taxes now at potentially lower rates, heirs avoid paying taxes later at their own (potentially higher) marginal rates.
|
|
291
|
+
|
|
292
|
+
**2. Magnitude of benefits**: Unlike the previous case study focusing on spending optimization, where Roth conversions provided benefits on the order of 1%, bequest optimization shows substantially larger benefits. The net benefit percentages range from approximately 2% to 30%, depending on the scenario and heir tax rate. This suggests that Roth conversions can be particularly valuable for those prioritizing legacy planning.
|
|
293
|
+
|
|
294
|
+
**3. Historical rate scenarios show the largest benefits**: Case 3 (historical rates from 1966) consistently shows the highest net benefit percentages, particularly for the 33% heir tax rate scenario (approximately 29-30%). This likely reflects the impact of historical market conditions and tax bracket changes over the extended time horizon. Case 4 (historical rates from 1991) also shows substantial benefits, with net benefits reaching 18% for 33% heir tax rates.
|
|
295
|
+
|
|
296
|
+
**4. Fixed rate scenarios**: Both conservative (Case 1) and optimistic (Case 2) fixed rate scenarios show meaningful but more modest benefits compared to historical scenarios. Case 1 shows benefits ranging from 6.5% to 17% depending on tax rate, while Case 2 shows benefits from 3.4% to 15.6%. The optimistic scenario (Case 2) actually shows slightly lower percentage benefits than the conservative scenario (Case 1), which may reflect the interaction between higher returns and tax bracket dynamics.
|
|
297
|
+
|
|
298
|
+
**5. Optimization method consistency**: The differences between Medicare/IRMAA optimization methods (direct optimization vs. self-consistent loop) are generally small, typically within 1-2 percentage points. This consistency provides confidence that the results are robust to the computational approach used.
|
|
299
|
+
|
|
300
|
+
**6. Diminishing returns at higher conversion limits**: While higher conversion limits (e.g., 200k vs. 100k) generally provide better benefits, the incremental gains diminish. In many cases, the benefits plateau or even decrease slightly at the highest conversion limits, suggesting there is an optimal conversion amount that balances current tax costs with future tax savings.
|
|
301
|
+
|
|
302
|
+
**7. Comparison to spending optimization**: The contrast with the previous case study (Discussion #58) is striking. When optimizing for spending with zero bequest, Roth conversions provided approximately 1% benefit. When optimizing for bequest with fixed spending, the benefits are an order of magnitude larger (2-30%). This highlights how the objective function fundamentally changes the value proposition of Roth conversions.
|
|
303
|
+
|
|
304
|
+
**8. Tax rate sensitivity**: The sensitivity to heir tax rates is particularly pronounced. For 0% heir tax rates, benefits are modest (2-8%), as there are no future taxes to avoid. However, for 33% heir tax rates, benefits can exceed 15-30% in many scenarios. This suggests that Roth conversions are most valuable when heirs are expected to be in high tax brackets.
|
|
305
|
+
|
|
306
|
+
These findings suggest that Roth conversions can be a powerful tool for legacy planning, especially when heirs are expected to face high marginal tax rates. The benefits are substantial and consistent across different market scenarios, though the magnitude varies significantly based on the heirs' tax situation. As with the previous case study, the conventional wisdom that taxes are more likely to increase than decrease in the future would further favor conversion strategies.
|
|
307
|
+
|
owlplanner/__init__.py
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Owl planner package initialization and public API exports.
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2025-2026 The Owlplanner Authors
|
|
5
|
+
|
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU General Public License as published by
|
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
(at your option) any later version.
|
|
10
|
+
|
|
11
|
+
This program is distributed in the hope that it will be useful,
|
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
GNU General Public License for more details.
|
|
15
|
+
|
|
16
|
+
You should have received a copy of the GNU General Public License
|
|
17
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
18
|
+
"""
|
|
19
|
+
|
|
1
20
|
from owlplanner.plan import Plan # noqa: F401
|
|
2
21
|
from owlplanner.plan import clone # noqa: F401
|
|
3
|
-
from owlplanner.config import readConfig
|
|
22
|
+
from owlplanner.config import readConfig, saveConfig # noqa: F401
|
|
4
23
|
from owlplanner.rates import getRatesDistributions # noqa: F401
|
|
5
24
|
from owlplanner.version import __version__ # noqa: F401
|
|
6
25
|
|
owlplanner/abcapi.py
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
"""
|
|
2
|
+
Abstract API for building linear programming constraint matrices.
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
This module provides a solver-neutral API to build constraint matrices and
|
|
5
|
+
objective functions line by line, abstracting the building process to enable
|
|
6
|
+
use of various solvers (MOSEK, HiGHS, etc.) for comparison. The name ABCAPI
|
|
7
|
+
refers to A (matrix), B (bounds), C (constraints).
|
|
5
8
|
|
|
6
|
-
|
|
9
|
+
Copyright (C) 2025-2026 The Owlplanner Authors
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
of
|
|
11
|
+
This program is free software: you can redistribute it and/or modify
|
|
12
|
+
it under the terms of the GNU General Public License as published by
|
|
13
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
14
|
+
(at your option) any later version.
|
|
10
15
|
|
|
11
|
-
This
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
This approach has been successful with the MOSEK and the HiGHS solvers.
|
|
17
|
-
A for matrix, B for bounds, C for constraints. Thus the name ABCAPI.
|
|
18
|
-
|
|
19
|
-
Copyright © 2024 - Martin-D. Lacasse
|
|
20
|
-
|
|
21
|
-
Disclaimers: This code is for educational purposes only and does not constitute financial advice.
|
|
16
|
+
This program is distributed in the hope that it will be useful,
|
|
17
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19
|
+
GNU General Public License for more details.
|
|
22
20
|
|
|
21
|
+
You should have received a copy of the GNU General Public License
|
|
22
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
import numpy as np
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
class Row
|
|
28
|
+
class Row:
|
|
29
29
|
"""
|
|
30
|
-
Solver-neutral API to
|
|
31
|
-
A Row
|
|
30
|
+
Solver-neutral API to accommodate Mosek/HiGHS.
|
|
31
|
+
A Row represents a row in matrix A.
|
|
32
32
|
"""
|
|
33
33
|
|
|
34
34
|
def __init__(self, nvars):
|
|
@@ -58,7 +58,7 @@ class Row(object):
|
|
|
58
58
|
return self
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
class ConstraintMatrix
|
|
61
|
+
class ConstraintMatrix:
|
|
62
62
|
"""
|
|
63
63
|
Solver-neutral API for expressing constraints.
|
|
64
64
|
"""
|
|
@@ -143,7 +143,7 @@ class ConstraintMatrix(object):
|
|
|
143
143
|
return Alu, lb, ub
|
|
144
144
|
|
|
145
145
|
|
|
146
|
-
class Bounds
|
|
146
|
+
class Bounds:
|
|
147
147
|
"""
|
|
148
148
|
Solver-neutral API for bounds on variables.
|
|
149
149
|
"""
|
|
@@ -176,7 +176,7 @@ class Bounds(object):
|
|
|
176
176
|
self.ind.append(ii)
|
|
177
177
|
self.lb.append(lb)
|
|
178
178
|
self.ub.append(ub)
|
|
179
|
-
if lb
|
|
179
|
+
if np.isclose(lb, ub):
|
|
180
180
|
self.key.append("fx")
|
|
181
181
|
elif ub == np.inf and lb == -np.inf:
|
|
182
182
|
self.key.append("fr")
|
|
@@ -204,6 +204,7 @@ class Bounds(object):
|
|
|
204
204
|
return lb, ub
|
|
205
205
|
|
|
206
206
|
def integralityArray(self):
|
|
207
|
+
# All variables continuous by default.
|
|
207
208
|
integrality = np.zeros(self.nvars, dtype=int)
|
|
208
209
|
for ii in range(len(self.integrality)):
|
|
209
210
|
integrality[self.integrality[ii]] = 1
|
|
@@ -214,7 +215,7 @@ class Bounds(object):
|
|
|
214
215
|
return self.integrality
|
|
215
216
|
|
|
216
217
|
|
|
217
|
-
class Objective
|
|
218
|
+
class Objective:
|
|
218
219
|
"""
|
|
219
220
|
Solver-neutral objective function.
|
|
220
221
|
"""
|
owlplanner/cli/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# OWLCLI - a Command Line Interface for OWL
|
|
2
|
+
|
|
3
|
+
OWLCLI is a command line interface tool to streamline the listing, running and experimenting with OWL plan files outside the streamlit interface.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
OWLCLI is installed with the owlplanner module. Once OWLPLANNER is installed, owlcli can be run from the command line.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
At present, OWLCLI provides two commands: `list` and `run`.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
❯ owlcli list
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This command will list all the available OWL .toml plan files in the current directory.
|
|
18
|
+
|
|
19
|
+
To list all the available OWL plan files in the `examples` directory, relative to the current directory, use:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
❯ owlcli list examples
|
|
23
|
+
FILE PLAN NAME TIME LISTS FILE
|
|
24
|
+
--------------------------------------------------------------------------------
|
|
25
|
+
Case_jack+jill jack+jill ✓HFP_jack+jill.xlsx
|
|
26
|
+
Case_joe joe ✓HFP_joe.xlsx
|
|
27
|
+
Case_john+sally john+sally ✓HFP_john+sally.xlsx
|
|
28
|
+
Case_jon+jane Jon+Jane ✗HFP_jon+jane.xslx
|
|
29
|
+
Case_kim+sam-bequest kim+sam-bequest ✓HFP_kim+sam.xlsx
|
|
30
|
+
Case_kim+sam-spending kim+sam-spending ✓HFP_kim+sam.xlsx
|
|
31
|
+
case_drawdowncalc-comparison-1 drawdowncalc-com... ✗edited values
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The listing shows the file name, plan name and Household Financial Plan file (timeListsFile) associated with each plan.
|
|
35
|
+
|
|
36
|
+
✓ indicates that the Household Financial Plan file listed in the OWL Plan file exists.
|
|
37
|
+
✗ indicates that the Household Financial Plan file was not found.
|
|
38
|
+
*edited values* indicates that the plan file may have been changed since the Household Financial Plan file was created.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
To run an OWL plan file, use the `run` command followed by the plan file name:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
❯ owlcli run examples/Case_kim+sam-spending
|
|
45
|
+
Case status: solved
|
|
46
|
+
Results saved to: examples/Case_kim+sam-spending_results.xlsx
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This example runs the `Case_kim+sam-spending` plan file located in the `examples` directory. The results of the run are saved to a new Excel file with `_results.xlsx` appended to the original plan file name. A copy of the input OWL plan file is saved as the new first tab in the Excel file.
|
|
50
|
+
|
owlplanner/cli/_main.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface main entry point for Owl retirement planner.
|
|
3
|
+
|
|
4
|
+
This module provides the main CLI group and command registration for the
|
|
5
|
+
Owl command-line interface.
|
|
6
|
+
|
|
7
|
+
Copyright (C) 2025-2026 The Owlplanner Authors
|
|
8
|
+
|
|
9
|
+
This program is free software: you can redistribute it and/or modify
|
|
10
|
+
it under the terms of the GNU General Public License as published by
|
|
11
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
12
|
+
(at your option) any later version.
|
|
13
|
+
|
|
14
|
+
This program is distributed in the hope that it will be useful,
|
|
15
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17
|
+
GNU General Public License for more details.
|
|
18
|
+
|
|
19
|
+
You should have received a copy of the GNU General Public License
|
|
20
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import click
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
from .cli_logging import configure_logging, LOG_LEVELS
|
|
27
|
+
from .cmd_list import cmd_list
|
|
28
|
+
from .cmd_run import cmd_run
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@click.group()
|
|
32
|
+
@click.option(
|
|
33
|
+
"--log-level",
|
|
34
|
+
type=click.Choice(LOG_LEVELS, case_sensitive=False),
|
|
35
|
+
default="INFO",
|
|
36
|
+
show_default=True,
|
|
37
|
+
help="Set logging verbosity.",
|
|
38
|
+
)
|
|
39
|
+
@click.pass_context
|
|
40
|
+
def cli(ctx, log_level: str):
|
|
41
|
+
"""SSG command-line interface."""
|
|
42
|
+
ctx.ensure_object(dict)
|
|
43
|
+
ctx.obj["log_level"] = log_level.upper()
|
|
44
|
+
|
|
45
|
+
configure_logging(log_level)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
cli.add_command(cmd_list)
|
|
49
|
+
cli.add_command(cmd_run)
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
cli()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI logging configuration utilities.
|
|
3
|
+
|
|
4
|
+
This module provides logging configuration functions for the command-line
|
|
5
|
+
interface, including log level management and formatting.
|
|
6
|
+
|
|
7
|
+
Copyright (C) 2025-2026 The Owlplanner Authors
|
|
8
|
+
|
|
9
|
+
This program is free software: you can redistribute it and/or modify
|
|
10
|
+
it under the terms of the GNU General Public License as published by
|
|
11
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
12
|
+
(at your option) any later version.
|
|
13
|
+
|
|
14
|
+
This program is distributed in the hope that it will be useful,
|
|
15
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17
|
+
GNU General Public License for more details.
|
|
18
|
+
|
|
19
|
+
You should have received a copy of the GNU General Public License
|
|
20
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import sys
|
|
24
|
+
from loguru import logger
|
|
25
|
+
|
|
26
|
+
LOG_LEVELS = {
|
|
27
|
+
"TRACE",
|
|
28
|
+
"DEBUG",
|
|
29
|
+
"INFO",
|
|
30
|
+
"SUCCESS",
|
|
31
|
+
"WARNING",
|
|
32
|
+
"ERROR",
|
|
33
|
+
"CRITICAL",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def configure_logging(log_level: str = "INFO"):
|
|
38
|
+
log_level = log_level.upper()
|
|
39
|
+
|
|
40
|
+
if log_level not in LOG_LEVELS:
|
|
41
|
+
raise ValueError(f"Invalid log level: {log_level}")
|
|
42
|
+
|
|
43
|
+
logger.remove() # remove default handler
|
|
44
|
+
|
|
45
|
+
logger.add(
|
|
46
|
+
sys.stderr,
|
|
47
|
+
level=log_level,
|
|
48
|
+
format=(
|
|
49
|
+
# "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
|
|
50
|
+
"<level>{level:8}</level> | "
|
|
51
|
+
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
|
|
52
|
+
"<level>{message}</level>"
|
|
53
|
+
),
|
|
54
|
+
backtrace=(log_level == "TRACE"),
|
|
55
|
+
diagnose=(log_level == "TRACE"),
|
|
56
|
+
)
|