owlplanner 2025.1.29__tar.gz → 2025.2__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.
- {owlplanner-2025.1.29 → owlplanner-2025.2}/PKG-INFO +1 -1
- {owlplanner-2025.1.29 → owlplanner-2025.2}/pyproject.toml +1 -1
- {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/plan.py +12 -10
- owlplanner-2025.2/src/owlplanner/version.py +1 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Documentation.py +0 -1
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Optimization_Parameters.py +13 -8
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/owlbridge.py +7 -11
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/requirements.txt +1 -1
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/sskeys.py +12 -7
- owlplanner-2025.1.29/src/owlplanner/version.py +0 -1
- {owlplanner-2025.1.29 → owlplanner-2025.2}/.devcontainer/devcontainer.json +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/.flake8 +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/.github/workflows/github-actions-runtests.yml +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/.gitignore +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/INSTALL.md +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/LICENSE +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/README.md +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/AD-taxDef.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/AD-taxFree.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/AD-taxable.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/Hist_Bequest.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/Hist_Spending.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/MC-tutorial2a.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/MC-tutorial2b.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/OwlUI.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/allocations.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/owl.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/profile.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/ratesCorrelations.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/ratesPlot.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/savingsPlot.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/sourcesPlot.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/spendingPlot.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/taxIncomePlot.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/taxesPlot.png +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/owl.pdf +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/owl.tex +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/case_jack+jill.toml +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/case_joe.toml +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/case_john+sally.toml +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/case_kim+sam-bequest.toml +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/case_kim+sam-spending.toml +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/jack+jill.xlsx +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/joe.xlsx +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/john+sally.xlsx +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/template.xlsx +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/notebooks/john+sally.ipynb +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/notebooks/kim+sam.ipynb +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/notebooks/template.ipynb +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/notebooks/tutorial_1.ipynb +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/notebooks/tutorial_2.ipynb +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/notebooks/tutorial_3.ipynb +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/owlplanner.cmd +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/requirements.txt +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/__init__.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/abcapi.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/config.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/data/__init__.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/data/rates.csv +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/logging.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/progress.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/rates.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/tax2025.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/timelists.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/utils.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/tests/test_logger.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/tests/test_regressions.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/tests/test_repro.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/tests/test_toml_cases.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/tests/test_units.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/About_Owl.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Asset_Allocation.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Assets.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Basic_Info.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Case_Results.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Case_Summary.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Case_Worksheets.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Fixed_Income.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Historical_Range.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Logs.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Monte_Carlo.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Quick_Start.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/README.md +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Rates_Selection.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Settings.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Wages_And_Contributions.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/main.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/plots.py +0 -0
- {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/progress.py +0 -0
|
@@ -397,16 +397,6 @@ class Plan(object):
|
|
|
397
397
|
|
|
398
398
|
return None
|
|
399
399
|
|
|
400
|
-
def setPreviousMAGI(self, magi, units='k'):
|
|
401
|
-
"""
|
|
402
|
-
Set MAGI for two previous years to the plan. Values are in nominal $k.
|
|
403
|
-
"""
|
|
404
|
-
assert len(magi) == 2, "MAGI must have two values."
|
|
405
|
-
fac = u.getUnits(units)
|
|
406
|
-
u.rescale(magi, fac)
|
|
407
|
-
self.mylog.vprint('Setting previous years MAGI to:', [u.d(magi[i]) for i in range(2)])
|
|
408
|
-
self.prevMAGI = np.array(magi)
|
|
409
|
-
|
|
410
400
|
def rename(self, newname):
|
|
411
401
|
"""
|
|
412
402
|
Override name of the plan. Plan name is used
|
|
@@ -1619,6 +1609,7 @@ class Plan(object):
|
|
|
1619
1609
|
'noRothConversions',
|
|
1620
1610
|
'withMedicare',
|
|
1621
1611
|
'solver',
|
|
1612
|
+
'previousMAGIs',
|
|
1622
1613
|
]
|
|
1623
1614
|
# We will modify options if required.
|
|
1624
1615
|
if options is None:
|
|
@@ -1647,6 +1638,17 @@ class Plan(object):
|
|
|
1647
1638
|
if objective == 'maxSpending' and 'bequest' not in myoptions:
|
|
1648
1639
|
self.mylog.vprint('Using bequest of $1.')
|
|
1649
1640
|
|
|
1641
|
+
if 'previousMAGIs' in myoptions:
|
|
1642
|
+
magi = myoptions['previousMAGIs']
|
|
1643
|
+
if len(magi) != 2:
|
|
1644
|
+
raise ValueError("previousMAGIs must have two values.")
|
|
1645
|
+
|
|
1646
|
+
if 'units' in options:
|
|
1647
|
+
units = u.getUnits(options['units'])
|
|
1648
|
+
else:
|
|
1649
|
+
units = 1000
|
|
1650
|
+
self.prevMAGI = units * np.array(magi)
|
|
1651
|
+
|
|
1650
1652
|
self._adjustParameters()
|
|
1651
1653
|
|
|
1652
1654
|
if 'solver' in options:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2025.02"
|
|
@@ -252,7 +252,6 @@ the calculations by a factor of 2 to 3, which can be useful when running Monte C
|
|
|
252
252
|
If the age of individuals makes them eligible for Medicare within the next two years,
|
|
253
253
|
additional cells will appear for entering the Modified Adjusted Gross Income (MAGI) for past years.
|
|
254
254
|
These numbers are needed to calculate the Income-Related Monthly Adjusted Amounts (IRMAA).
|
|
255
|
-
Note that these values are not included in the *case* parameter file when saving.
|
|
256
255
|
|
|
257
256
|
The time profile of the net spending amount
|
|
258
257
|
can be selected to either be *flat* or follow a *smile* shape.
|
|
@@ -24,6 +24,7 @@ if ret is None or kz.caseHasNoPlan():
|
|
|
24
24
|
else:
|
|
25
25
|
kz.runOncePerCase(initProfile)
|
|
26
26
|
|
|
27
|
+
st.write('##### Objective')
|
|
27
28
|
col1, col2 = st.columns(2, gap='large', vertical_alignment='top')
|
|
28
29
|
with col1:
|
|
29
30
|
choices = ['Net spending', 'Bequest']
|
|
@@ -41,6 +42,7 @@ else:
|
|
|
41
42
|
ret = kz.getNum("Desired annual net spending (\\$k)", 'netSpending', help=helpmsg)
|
|
42
43
|
|
|
43
44
|
st.divider()
|
|
45
|
+
st.write('##### Roth Conversions')
|
|
44
46
|
col1, col2 = st.columns(2, gap='large', vertical_alignment='top')
|
|
45
47
|
with col1:
|
|
46
48
|
iname0 = kz.getKey('iname0')
|
|
@@ -49,7 +51,6 @@ else:
|
|
|
49
51
|
fromFile = kz.getKey('readRothX')
|
|
50
52
|
kz.initKey('maxRothConversion', 50)
|
|
51
53
|
ret = kz.getNum("Maximum Roth conversion (\\$k)", 'maxRothConversion', disabled=fromFile, help=helpmsg)
|
|
52
|
-
# caseHasNoContributions = (kz.getKey('stTimeLists') is None)
|
|
53
54
|
ret = kz.getToggle('Convert as in wages and contributions tables', 'readRothX')
|
|
54
55
|
|
|
55
56
|
with col2:
|
|
@@ -62,8 +63,9 @@ else:
|
|
|
62
63
|
"noRothConversions", help=helpmsg)
|
|
63
64
|
|
|
64
65
|
st.divider()
|
|
66
|
+
st.write('##### Medicare')
|
|
65
67
|
kz.initKey('withMedicare', True)
|
|
66
|
-
col1, col2
|
|
68
|
+
col1, col2 = st.columns(2, gap='large', vertical_alignment='top')
|
|
67
69
|
with col1:
|
|
68
70
|
helpmsg = "Do or do not perform additional Medicare and IRMAA calculations."
|
|
69
71
|
ret = kz.getToggle('Medicare and IRMAA calculations', 'withMedicare', help=helpmsg)
|
|
@@ -75,16 +77,19 @@ else:
|
|
|
75
77
|
kz.initKey('MAGI'+str(ii), 0)
|
|
76
78
|
if years[ii] > 0:
|
|
77
79
|
ret = kz.getNum(f"MAGI for year {years[ii]} ($k)", 'MAGI'+str(ii), help=helpmsg)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
|
|
81
|
+
if owb.hasMOSEK():
|
|
82
|
+
st.divider()
|
|
83
|
+
st.write('##### Solver')
|
|
84
|
+
choices = ['HiGHS', 'MOSEK']
|
|
85
|
+
kz.initKey('solver', choices[0])
|
|
86
|
+
ret = kz.getRadio('Linear programming solver', choices, 'solver')
|
|
83
87
|
|
|
84
88
|
st.divider()
|
|
89
|
+
st.write('##### Spending Profile')
|
|
85
90
|
col1, col2, col3 = st.columns(3, gap='medium', vertical_alignment='top')
|
|
86
91
|
with col1:
|
|
87
|
-
ret = kz.getRadio("
|
|
92
|
+
ret = kz.getRadio("Type of profile", profileChoices, 'spendingProfile', callback=owb.setProfile)
|
|
88
93
|
with col2:
|
|
89
94
|
if kz.getKey('status') == 'married':
|
|
90
95
|
helpmsg = 'Percentage of spending required for the surviving spouse.'
|
|
@@ -84,14 +84,6 @@ def prepareRun(plan):
|
|
|
84
84
|
st.error('Failed setting social security: %s' % e)
|
|
85
85
|
return
|
|
86
86
|
|
|
87
|
-
previousMAGI = kz.getPreviousMAGI()
|
|
88
|
-
if previousMAGI[0] > 0 or previousMAGI[1] > 0:
|
|
89
|
-
try:
|
|
90
|
-
plan.setPreviousMAGI(previousMAGI)
|
|
91
|
-
except Exception as e:
|
|
92
|
-
st.error('Failed setting previous MAGI: %s' % e)
|
|
93
|
-
return
|
|
94
|
-
|
|
95
87
|
if ni == 2:
|
|
96
88
|
benfrac = [kz.getKey('benf0'), kz.getKey('benf1'), kz.getKey('benf2')]
|
|
97
89
|
try:
|
|
@@ -605,6 +597,10 @@ def genDic(plan):
|
|
|
605
597
|
if key in optionKeys:
|
|
606
598
|
dic[key] = plan.solverOptions[key]
|
|
607
599
|
|
|
600
|
+
if 'previousMAGIs' in optionKeys:
|
|
601
|
+
dic['MAGI0'] = plan.solverOptions['previousMAGIs'][0]
|
|
602
|
+
dic['MAGI1'] = plan.solverOptions['previousMAGIs'][1]
|
|
603
|
+
|
|
608
604
|
if plan.objective == 'maxSpending':
|
|
609
605
|
dic['objective'] = 'Net spending'
|
|
610
606
|
else:
|
|
@@ -619,7 +615,7 @@ def genDic(plan):
|
|
|
619
615
|
|
|
620
616
|
# Initialize in both cases.
|
|
621
617
|
for k1 in range(plan.N_k):
|
|
622
|
-
dic['fxRate'+str(k1)] = 100*plan.rateValues[k1]
|
|
618
|
+
dic['fxRate'+str(k1)] = 100 * plan.rateValues[k1]
|
|
623
619
|
|
|
624
620
|
if plan.rateMethod in ['historical average', 'histochastic', 'historical']:
|
|
625
621
|
dic['yfrm'] = plan.rateFrm
|
|
@@ -632,8 +628,8 @@ def genDic(plan):
|
|
|
632
628
|
if plan.rateMethod in ['stochastic', 'histochastic']:
|
|
633
629
|
qq = 1
|
|
634
630
|
for k1 in range(plan.N_k):
|
|
635
|
-
dic['mean'+str(k1)] = 100*plan.rateValues[k1]
|
|
636
|
-
dic['stdev'+str(k1)] = 100*plan.rateStdev[k1]
|
|
631
|
+
dic['mean'+str(k1)] = 100 * plan.rateValues[k1]
|
|
632
|
+
dic['stdev'+str(k1)] = 100 * plan.rateStdev[k1]
|
|
637
633
|
for k2 in range(k1+1, plan.N_k):
|
|
638
634
|
dic['corr'+str(qq)] = plan.rateCorr[k1, k2]
|
|
639
635
|
qq += 1
|
|
@@ -336,6 +336,10 @@ def getSolveParameters():
|
|
|
336
336
|
if getKey('readRothX'):
|
|
337
337
|
options['maxRothConversion'] = 'file'
|
|
338
338
|
|
|
339
|
+
previousMAGIs = getPreviousMAGIs()
|
|
340
|
+
if previousMAGIs[0] > 0 or previousMAGIs[1] > 0:
|
|
341
|
+
options['previousMAGIs'] = previousMAGIs
|
|
342
|
+
|
|
339
343
|
return objective, options
|
|
340
344
|
|
|
341
345
|
|
|
@@ -385,14 +389,14 @@ def getAccountAllocationRatios():
|
|
|
385
389
|
return accounts
|
|
386
390
|
|
|
387
391
|
|
|
388
|
-
def
|
|
389
|
-
|
|
392
|
+
def getPreviousMAGIs():
|
|
393
|
+
backMAGIs = [0, 0]
|
|
390
394
|
for ii in range(2):
|
|
391
395
|
val = getKey('MAGI'+str(ii))
|
|
392
396
|
if val:
|
|
393
|
-
|
|
397
|
+
backMAGIs[ii] = val
|
|
394
398
|
|
|
395
|
-
return
|
|
399
|
+
return backMAGIs
|
|
396
400
|
|
|
397
401
|
|
|
398
402
|
def getFixedIncome(ni, what):
|
|
@@ -437,11 +441,11 @@ def getText(text, nkey, disabled=False, callback=setpull, placeholder=None):
|
|
|
437
441
|
placeholder=placeholder)
|
|
438
442
|
|
|
439
443
|
|
|
440
|
-
def getRadio(text, choices, nkey, callback=setpull, help=None):
|
|
444
|
+
def getRadio(text, choices, nkey, callback=setpull, disabled=False, help=None):
|
|
441
445
|
return st.radio(text, choices,
|
|
442
446
|
index=choices.index(getKey(nkey)),
|
|
443
447
|
on_change=callback, args=[nkey], key='_'+nkey,
|
|
444
|
-
horizontal=True, help=help)
|
|
448
|
+
disabled=disabled, horizontal=True, help=help)
|
|
445
449
|
|
|
446
450
|
|
|
447
451
|
def getToggle(text, nkey, callback=setpull, disabled=False, help=None):
|
|
@@ -454,6 +458,7 @@ def orangeDivider():
|
|
|
454
458
|
|
|
455
459
|
|
|
456
460
|
def caseHeader(txt):
|
|
457
|
-
st.html('<div style="text-align: right;color: orange;font-style: italic;">%s</div>' % currentCaseName())
|
|
461
|
+
# st.html('<div style="text-align: right;color: orange;font-style: italic;">%s</div>' % currentCaseName())
|
|
462
|
+
st.html('<div style="text-align: left;color: orange;font-style: italic;">%s</div>' % currentCaseName())
|
|
458
463
|
st.write('## ' + txt)
|
|
459
464
|
orangeDivider()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2025.1.29"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|