owlplanner 2025.1.27__tar.gz → 2025.1.29__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.27 → owlplanner-2025.1.29}/PKG-INFO +1 -1
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/pyproject.toml +1 -1
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/src/owlplanner/plan.py +14 -31
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/src/owlplanner/rates.py +5 -5
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/src/owlplanner/tax2025.py +5 -5
- owlplanner-2025.1.29/src/owlplanner/version.py +1 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/Case_Worksheets.py +1 -2
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/Documentation.py +4 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/Optimization_Parameters.py +9 -1
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/Rates_Selection.py +3 -3
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/owlbridge.py +39 -148
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/requirements.txt +1 -1
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/sskeys.py +107 -3
- owlplanner-2025.1.27/src/owlplanner/version.py +0 -1
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/.devcontainer/devcontainer.json +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/.flake8 +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/.github/workflows/github-actions-runtests.yml +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/.gitignore +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/INSTALL.md +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/LICENSE +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/README.md +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/AD-taxDef.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/AD-taxFree.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/AD-taxable.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/Hist_Bequest.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/Hist_Spending.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/MC-tutorial2a.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/MC-tutorial2b.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/OwlUI.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/allocations.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/owl.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/profile.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/ratesCorrelations.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/ratesPlot.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/savingsPlot.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/sourcesPlot.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/spendingPlot.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/taxIncomePlot.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/images/taxesPlot.png +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/owl.pdf +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/docs/owl.tex +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/examples/case_jack+jill.toml +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/examples/case_joe.toml +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/examples/case_john+sally.toml +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/examples/case_kim+sam-bequest.toml +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/examples/case_kim+sam-spending.toml +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/examples/jack+jill.xlsx +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/examples/joe.xlsx +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/examples/john+sally.xlsx +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/examples/template.xlsx +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/notebooks/john+sally.ipynb +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/notebooks/kim+sam.ipynb +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/notebooks/template.ipynb +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/notebooks/tutorial_1.ipynb +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/notebooks/tutorial_2.ipynb +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/notebooks/tutorial_3.ipynb +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/owlplanner.cmd +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/requirements.txt +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/src/owlplanner/__init__.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/src/owlplanner/abcapi.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/src/owlplanner/config.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/src/owlplanner/data/__init__.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/src/owlplanner/data/rates.csv +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/src/owlplanner/logging.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/src/owlplanner/progress.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/src/owlplanner/timelists.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/src/owlplanner/utils.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/tests/test_logger.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/tests/test_regressions.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/tests/test_repro.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/tests/test_toml_cases.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/tests/test_units.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/About_Owl.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/Asset_Allocation.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/Assets.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/Basic_Info.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/Case_Results.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/Case_Summary.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/Fixed_Income.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/Historical_Range.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/Logs.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/Monte_Carlo.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/Quick_Start.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/README.md +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/Settings.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/Wages_And_Contributions.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/main.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/plots.py +0 -0
- {owlplanner-2025.1.27 → owlplanner-2025.1.29}/ui/progress.py +0 -0
|
@@ -295,6 +295,9 @@ class Plan(object):
|
|
|
295
295
|
self.myRothX_in = np.zeros((self.N_i, self.N_n))
|
|
296
296
|
self.kappa_ijn = np.zeros((self.N_i, self.N_j, self.N_n))
|
|
297
297
|
|
|
298
|
+
# Previous 2 years for Medicare.
|
|
299
|
+
self.prevMAGI = np.zeros((2))
|
|
300
|
+
|
|
298
301
|
# Scenario starts at the beginning of this year and ends at the end of the last year.
|
|
299
302
|
self.mylog.vprint('Preparing scenario of %d years for %d individual%s.'
|
|
300
303
|
% (self.N_n, self.N_i, ['', 's'][self.N_i - 1]))
|
|
@@ -394,6 +397,16 @@ class Plan(object):
|
|
|
394
397
|
|
|
395
398
|
return None
|
|
396
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
|
+
|
|
397
410
|
def rename(self, newname):
|
|
398
411
|
"""
|
|
399
412
|
Override name of the plan. Plan name is used
|
|
@@ -403,8 +416,6 @@ class Plan(object):
|
|
|
403
416
|
self.mylog.vprint('Renaming plan %s -> %s.' % (self._name, newname))
|
|
404
417
|
self._name = newname
|
|
405
418
|
|
|
406
|
-
return None
|
|
407
|
-
|
|
408
419
|
def setSpousalDepositFraction(self, eta):
|
|
409
420
|
"""
|
|
410
421
|
Set spousal deposit and withdrawal fraction. Default 0.5.
|
|
@@ -424,8 +435,6 @@ class Plan(object):
|
|
|
424
435
|
self.mylog.vprint('\t%s: %.1f, %s: %.1f' % (self.inames[0], (1 - eta), self.inames[1], eta))
|
|
425
436
|
self.eta = eta
|
|
426
437
|
|
|
427
|
-
return None
|
|
428
|
-
|
|
429
438
|
def setDefaultPlots(self, value):
|
|
430
439
|
"""
|
|
431
440
|
Set plots between nominal values or today's $.
|
|
@@ -434,8 +443,6 @@ class Plan(object):
|
|
|
434
443
|
self.defaultPlots = self._checkValue(value)
|
|
435
444
|
self.mylog.vprint('Setting plots default value to %s.' % value)
|
|
436
445
|
|
|
437
|
-
return None
|
|
438
|
-
|
|
439
446
|
def setDividendRate(self, mu):
|
|
440
447
|
"""
|
|
441
448
|
Set dividend rate on equities. Rate is in percent. Default 2%.
|
|
@@ -446,8 +453,6 @@ class Plan(object):
|
|
|
446
453
|
self.mu = mu
|
|
447
454
|
self.caseStatus = 'modified'
|
|
448
455
|
|
|
449
|
-
return None
|
|
450
|
-
|
|
451
456
|
def setLongTermCapitalTaxRate(self, psi):
|
|
452
457
|
"""
|
|
453
458
|
Set long-term income tax rate. Rate is in percent. Default 15%.
|
|
@@ -458,8 +463,6 @@ class Plan(object):
|
|
|
458
463
|
self.psi = psi
|
|
459
464
|
self.caseStatus = 'modified'
|
|
460
465
|
|
|
461
|
-
return None
|
|
462
|
-
|
|
463
466
|
def setBeneficiaryFractions(self, phi):
|
|
464
467
|
"""
|
|
465
468
|
Set fractions of savings accounts that is left to surviving spouse.
|
|
@@ -477,8 +480,6 @@ class Plan(object):
|
|
|
477
480
|
self.mylog.vprint('Consider changing spousal deposit fraction for better convergence.')
|
|
478
481
|
self.mylog.vprint('\tRecommended: setSpousalDepositFraction(%d)' % self.i_d)
|
|
479
482
|
|
|
480
|
-
return None
|
|
481
|
-
|
|
482
483
|
def setHeirsTaxRate(self, nu):
|
|
483
484
|
"""
|
|
484
485
|
Set the heirs tax rate on the tax-deferred portion of the estate.
|
|
@@ -490,8 +491,6 @@ class Plan(object):
|
|
|
490
491
|
self.nu = nu
|
|
491
492
|
self.caseStatus = 'modified'
|
|
492
493
|
|
|
493
|
-
return None
|
|
494
|
-
|
|
495
494
|
def setPension(self, amounts, ages, units='k'):
|
|
496
495
|
"""
|
|
497
496
|
Set value of pension for each individual and commencement age.
|
|
@@ -521,8 +520,6 @@ class Plan(object):
|
|
|
521
520
|
self.pensionAges = np.array(ages, dtype=np.int32)
|
|
522
521
|
self.caseStatus = 'modified'
|
|
523
522
|
|
|
524
|
-
return None
|
|
525
|
-
|
|
526
523
|
def setSocialSecurity(self, amounts, ages, units='k'):
|
|
527
524
|
"""
|
|
528
525
|
Set value of social security for each individual and commencement age.
|
|
@@ -560,8 +557,6 @@ class Plan(object):
|
|
|
560
557
|
self.caseStatus = 'modified'
|
|
561
558
|
self._adjustedParameters = False
|
|
562
559
|
|
|
563
|
-
return None
|
|
564
|
-
|
|
565
560
|
def setSpendingProfile(self, profile, percent=60, dip=15, increase=12, delay=0):
|
|
566
561
|
"""
|
|
567
562
|
Generate time series for spending profile. Surviving spouse fraction can be specified
|
|
@@ -589,8 +584,6 @@ class Plan(object):
|
|
|
589
584
|
self.smileDelay = delay
|
|
590
585
|
self.caseStatus = 'modified'
|
|
591
586
|
|
|
592
|
-
return None
|
|
593
|
-
|
|
594
587
|
def setRates(self, method, frm=None, to=None, values=None, stdev=None, corr=None):
|
|
595
588
|
"""
|
|
596
589
|
Generate rates for return and inflation based on the method and
|
|
@@ -632,8 +625,6 @@ class Plan(object):
|
|
|
632
625
|
self._adjustedParameters = False
|
|
633
626
|
self.caseStatus = 'modified'
|
|
634
627
|
|
|
635
|
-
return None
|
|
636
|
-
|
|
637
628
|
def regenRates(self):
|
|
638
629
|
"""
|
|
639
630
|
Regenerate the rates using the arguments specified during last setRates() call.
|
|
@@ -648,8 +639,6 @@ class Plan(object):
|
|
|
648
639
|
corr=self.rateCorr,
|
|
649
640
|
)
|
|
650
641
|
|
|
651
|
-
return None
|
|
652
|
-
|
|
653
642
|
def value(self, amount, year):
|
|
654
643
|
"""
|
|
655
644
|
Return value of amount deflated or inflated at the beginning
|
|
@@ -711,8 +700,6 @@ class Plan(object):
|
|
|
711
700
|
u.d(np.sum(taxable) + 0.7 * np.sum(taxDeferred) + np.sum(taxFree)),
|
|
712
701
|
)
|
|
713
702
|
|
|
714
|
-
return None
|
|
715
|
-
|
|
716
703
|
def setInterpolationMethod(self, method, center=15, width=5):
|
|
717
704
|
"""
|
|
718
705
|
Interpolate assets allocation ratios from initial value (today) to
|
|
@@ -739,8 +726,6 @@ class Plan(object):
|
|
|
739
726
|
|
|
740
727
|
self.mylog.vprint('Asset allocation interpolation method set to %s.' % method)
|
|
741
728
|
|
|
742
|
-
return None
|
|
743
|
-
|
|
744
729
|
def setAllocationRatios(self, allocType, taxable=None, taxDeferred=None, taxFree=None, generic=None):
|
|
745
730
|
"""
|
|
746
731
|
Single function for setting all types of asset allocations.
|
|
@@ -874,8 +859,6 @@ class Plan(object):
|
|
|
874
859
|
|
|
875
860
|
self.mylog.vprint('Interpolating assets allocation ratios using', self.interpMethod, 'method.')
|
|
876
861
|
|
|
877
|
-
return None
|
|
878
|
-
|
|
879
862
|
def readContributions(self, filename):
|
|
880
863
|
"""
|
|
881
864
|
Provide the name of the file containing the financial events
|
|
@@ -1897,7 +1880,7 @@ class Plan(object):
|
|
|
1897
1880
|
self.F_tn = self.F_tn.reshape((self.N_t, self.N_n))
|
|
1898
1881
|
MAGI_n = np.sum(self.F_tn, axis=0) + np.array(x[self.C['e']:self.C['F']])
|
|
1899
1882
|
|
|
1900
|
-
self.M_n = tx.mediCosts(self.yobs, self.horizons, MAGI_n, self.gamma_n[:-1], self.N_n)
|
|
1883
|
+
self.M_n = tx.mediCosts(self.yobs, self.horizons, MAGI_n, self.prevMAGI, self.gamma_n[:-1], self.N_n)
|
|
1901
1884
|
|
|
1902
1885
|
return None
|
|
1903
1886
|
|
|
@@ -31,8 +31,8 @@ Disclaimer: This program comes with no guarantee. Use at your own risk.
|
|
|
31
31
|
###################################################################
|
|
32
32
|
import numpy as np
|
|
33
33
|
import pandas as pd
|
|
34
|
-
import
|
|
35
|
-
import
|
|
34
|
+
import os
|
|
35
|
+
import sys
|
|
36
36
|
from datetime import date
|
|
37
37
|
|
|
38
38
|
from owlplanner import logging
|
|
@@ -43,10 +43,10 @@ from owlplanner import utils as u
|
|
|
43
43
|
FROM = 1928
|
|
44
44
|
TO = 2024
|
|
45
45
|
|
|
46
|
+
where = os.path.dirname(sys.modules['owlplanner'].__file__)
|
|
47
|
+
file = os.path.join(where, 'data/rates.csv')
|
|
46
48
|
try:
|
|
47
|
-
|
|
48
|
-
csvFile = io.BytesIO(csvBytes)
|
|
49
|
-
df = pd.read_csv(csvFile)
|
|
49
|
+
df = pd.read_csv(file)
|
|
50
50
|
except Exception as e:
|
|
51
51
|
raise RuntimeError(f'Could not find rates data file: {e}')
|
|
52
52
|
|
|
@@ -69,7 +69,7 @@ extra65Deduction_2025 = np.array([2000, 1600])
|
|
|
69
69
|
##############################################################################
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
def mediCosts(yobs, horizons, magi, gamma_n, Nn):
|
|
72
|
+
def mediCosts(yobs, horizons, magi, prevmagi, gamma_n, Nn):
|
|
73
73
|
"""
|
|
74
74
|
Compute Medicare costs directly.
|
|
75
75
|
"""
|
|
@@ -79,14 +79,14 @@ def mediCosts(yobs, horizons, magi, gamma_n, Nn):
|
|
|
79
79
|
for n in range(Nn):
|
|
80
80
|
for i in range(Ni):
|
|
81
81
|
if thisyear + n - yobs[i] >= 65 and n < horizons[i]:
|
|
82
|
-
#
|
|
82
|
+
# Start with the (indexed) basic Medicare part B premium.
|
|
83
83
|
costs[n] += gamma_n[n] * irmaaFees_2025[0]
|
|
84
84
|
if n < 2:
|
|
85
|
-
|
|
85
|
+
mymagi = prevmagi[n]
|
|
86
86
|
else:
|
|
87
|
-
|
|
87
|
+
mymagi = magi[n - 2]
|
|
88
88
|
for q in range(1, 6):
|
|
89
|
-
if
|
|
89
|
+
if mymagi > gamma_n[n] * irmaaBrackets_2025[Ni - 1][q]:
|
|
90
90
|
costs[n] += gamma_n[n] * irmaaFees_2025[q]
|
|
91
91
|
|
|
92
92
|
return costs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2025.1.29"
|
|
@@ -14,12 +14,11 @@ else:
|
|
|
14
14
|
else:
|
|
15
15
|
owb.showWorkbook()
|
|
16
16
|
st.divider()
|
|
17
|
-
# if not owb.isCaseUnsolved():
|
|
18
17
|
if kz.caseHasPlan():
|
|
19
18
|
download2 = st.download_button(
|
|
20
19
|
label="Download data as an Excel workbook...",
|
|
21
20
|
data=owb.saveWorkbook(),
|
|
22
21
|
file_name='Workbook_'+kz.getKey('name')+'.xlsx',
|
|
23
22
|
mime='application/vnd.ms-excel',
|
|
24
|
-
disabled=
|
|
23
|
+
disabled=kz.isCaseUnsolved()
|
|
25
24
|
)
|
|
@@ -249,6 +249,10 @@ the column `RothX` found in the
|
|
|
249
249
|
|
|
250
250
|
Calculations of Medicare and IRMAA can be turned on or off. This will typically speed up
|
|
251
251
|
the calculations by a factor of 2 to 3, which can be useful when running Monte Carlo simulations.
|
|
252
|
+
If the age of individuals makes them eligible for Medicare within the next two years,
|
|
253
|
+
additional cells will appear for entering the Modified Adjusted Gross Income (MAGI) for past years.
|
|
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.
|
|
252
256
|
|
|
253
257
|
The time profile of the net spending amount
|
|
254
258
|
can be selected to either be *flat* or follow a *smile* shape.
|
|
@@ -63,11 +63,19 @@ else:
|
|
|
63
63
|
|
|
64
64
|
st.divider()
|
|
65
65
|
kz.initKey('withMedicare', True)
|
|
66
|
-
col1, col2 = st.columns(
|
|
66
|
+
col1, col2, col3 = st.columns(3, gap='large', vertical_alignment='top')
|
|
67
67
|
with col1:
|
|
68
68
|
helpmsg = "Do or do not perform additional Medicare and IRMAA calculations."
|
|
69
69
|
ret = kz.getToggle('Medicare and IRMAA calculations', 'withMedicare', help=helpmsg)
|
|
70
70
|
with col2:
|
|
71
|
+
if kz.getKey('withMedicare'):
|
|
72
|
+
helpmsg = "MAGI in nominal $k for that previous year."
|
|
73
|
+
years = owb.backYearsMAGI()
|
|
74
|
+
for ii in range(2):
|
|
75
|
+
kz.initKey('MAGI'+str(ii), 0)
|
|
76
|
+
if years[ii] > 0:
|
|
77
|
+
ret = kz.getNum(f"MAGI for year {years[ii]} ($k)", 'MAGI'+str(ii), help=helpmsg)
|
|
78
|
+
with col3:
|
|
71
79
|
if owb.hasMOSEK():
|
|
72
80
|
choices = ['HiGHS', 'MOSEK']
|
|
73
81
|
kz.initKey('solver', choices[0])
|
|
@@ -198,17 +198,17 @@ else:
|
|
|
198
198
|
kz.initKey('divRate', 2)
|
|
199
199
|
helpmsg = 'Average annual dividend return rate on stock portfolio.'
|
|
200
200
|
ret = kz.getNum('Dividends return rate (%)', 'divRate', max_value=100., format='%.2f',
|
|
201
|
-
help=helpmsg,
|
|
201
|
+
help=helpmsg, step=1.)
|
|
202
202
|
|
|
203
203
|
st.write('#### Income taxes')
|
|
204
204
|
col1, col2 = st.columns(2, gap='large', vertical_alignment='top')
|
|
205
205
|
with col1:
|
|
206
206
|
kz.initKey('gainTx', 15)
|
|
207
207
|
ret = kz.getNum('Long-term capital gains tax rate (%)', 'gainTx', max_value=100.,
|
|
208
|
-
|
|
208
|
+
step=1.)
|
|
209
209
|
|
|
210
210
|
with col2:
|
|
211
211
|
kz.initKey('heirsTx', 30)
|
|
212
212
|
helpmsg = 'Marginal tax rate that heirs would have to pay on inherited tax-deferred balance.'
|
|
213
213
|
ret = kz.getNum('Heirs marginal tax rate (%)', 'heirsTx', max_value=100., help=helpmsg,
|
|
214
|
-
|
|
214
|
+
step=1.)
|
|
@@ -59,73 +59,39 @@ def _checkPlan(func):
|
|
|
59
59
|
return wrapper
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
amounts = []
|
|
64
|
-
ages = []
|
|
65
|
-
for i in range(ni):
|
|
66
|
-
amounts.append(kz.getKey(what+'Amt'+str(i)))
|
|
67
|
-
ages.append(kz.getKey(what+'Age'+str(i)))
|
|
68
|
-
|
|
69
|
-
return amounts, ages
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def getAccountBalances(ni):
|
|
73
|
-
bal = [[], [], []]
|
|
74
|
-
accounts = ['txbl', 'txDef', 'txFree']
|
|
75
|
-
for j, acc in enumerate(accounts):
|
|
76
|
-
for i in range(ni):
|
|
77
|
-
bal[j].append(kz.getKey(acc+str(i)))
|
|
78
|
-
|
|
79
|
-
return bal
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def getSolveParameters():
|
|
83
|
-
maximize = kz.getKey('objective')
|
|
84
|
-
if maximize is None:
|
|
85
|
-
return None
|
|
86
|
-
if 'spending' in maximize:
|
|
87
|
-
objective = 'maxSpending'
|
|
88
|
-
else:
|
|
89
|
-
objective = 'maxBequest'
|
|
90
|
-
|
|
91
|
-
options = {}
|
|
92
|
-
optList = ['netSpending', 'maxRothConversion', 'noRothConversions',
|
|
93
|
-
'withMedicare', 'bequest', 'solver']
|
|
94
|
-
for opt in optList:
|
|
95
|
-
val = kz.getKey(opt)
|
|
96
|
-
if val is not None:
|
|
97
|
-
options[opt] = val
|
|
98
|
-
|
|
99
|
-
if kz.getKey('readRothX'):
|
|
100
|
-
options['maxRothConversion'] = 'file'
|
|
101
|
-
|
|
102
|
-
return objective, options
|
|
103
|
-
|
|
104
|
-
|
|
62
|
+
# _checkPlan
|
|
105
63
|
def prepareRun(plan):
|
|
106
64
|
ni = 2 if kz.getKey('status') == 'married' else 1
|
|
107
65
|
|
|
108
|
-
bal = getAccountBalances(ni)
|
|
66
|
+
bal = kz.getAccountBalances(ni)
|
|
109
67
|
try:
|
|
110
68
|
plan.setAccountBalances(taxable=bal[0], taxDeferred=bal[1], taxFree=bal[2])
|
|
111
69
|
except Exception as e:
|
|
112
70
|
st.error('Setting account balances failed: %s' % e)
|
|
113
71
|
return
|
|
114
72
|
|
|
115
|
-
amounts, ages = getFixedIncome(ni, 'p')
|
|
73
|
+
amounts, ages = kz.getFixedIncome(ni, 'p')
|
|
116
74
|
try:
|
|
117
75
|
plan.setPension(amounts, ages)
|
|
118
76
|
except Exception as e:
|
|
119
77
|
st.error('Failed setting pensions: %s' % e)
|
|
120
78
|
return
|
|
121
79
|
|
|
122
|
-
amounts, ages = getFixedIncome(ni, 'ss')
|
|
80
|
+
amounts, ages = kz.getFixedIncome(ni, 'ss')
|
|
123
81
|
try:
|
|
124
82
|
plan.setSocialSecurity(amounts, ages)
|
|
125
83
|
except Exception as e:
|
|
126
84
|
st.error('Failed setting social security: %s' % e)
|
|
127
85
|
return
|
|
128
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
|
+
|
|
129
95
|
if ni == 2:
|
|
130
96
|
benfrac = [kz.getKey('benf0'), kz.getKey('benf1'), kz.getKey('benf2')]
|
|
131
97
|
try:
|
|
@@ -141,26 +107,19 @@ def prepareRun(plan):
|
|
|
141
107
|
st.error('Failed setting beneficiary fractions: %s' % e)
|
|
142
108
|
return
|
|
143
109
|
|
|
110
|
+
plan.setHeirsTaxRate(kz.getKey('heirsTx'))
|
|
111
|
+
plan.setLongTermCapitalTaxRate(kz.getKey('gainTx'))
|
|
112
|
+
plan.setDividendRate(kz.getKey('divRate'))
|
|
113
|
+
|
|
144
114
|
setRates()
|
|
145
115
|
setContributions()
|
|
146
116
|
|
|
147
117
|
|
|
148
|
-
def isCaseUnsolved():
|
|
149
|
-
if kz.getKey('plan') is None:
|
|
150
|
-
return True
|
|
151
|
-
return kz.getKey('caseStatus') != 'solved'
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
@_checkPlan
|
|
155
|
-
def caseStatus(plan):
|
|
156
|
-
return plan.caseStatus
|
|
157
|
-
|
|
158
|
-
|
|
159
118
|
@_checkPlan
|
|
160
119
|
def runPlan(plan):
|
|
161
120
|
prepareRun(plan)
|
|
162
121
|
|
|
163
|
-
objective, options = getSolveParameters()
|
|
122
|
+
objective, options = kz.getSolveParameters()
|
|
164
123
|
try:
|
|
165
124
|
plan.solve(objective, options=options)
|
|
166
125
|
except Exception as e:
|
|
@@ -183,7 +142,7 @@ def runHistorical(plan):
|
|
|
183
142
|
hyfrm = kz.getKey('hyfrm')
|
|
184
143
|
hyto = kz.getKey('hyto')
|
|
185
144
|
|
|
186
|
-
objective, options = getSolveParameters()
|
|
145
|
+
objective, options = kz.getSolveParameters()
|
|
187
146
|
try:
|
|
188
147
|
mybar = progress.Progress(None)
|
|
189
148
|
fig, summary = plan.runHistoricalRange(objective, options, hyfrm, hyto, figure=True, progcall=mybar)
|
|
@@ -207,7 +166,7 @@ def runMC(plan):
|
|
|
207
166
|
|
|
208
167
|
N = kz.getKey('MC_cases')
|
|
209
168
|
|
|
210
|
-
objective, options = getSolveParameters()
|
|
169
|
+
objective, options = kz.getSolveParameters()
|
|
211
170
|
try:
|
|
212
171
|
mybar = progress.Progress(None)
|
|
213
172
|
fig, summary = plan.runMC(objective, options, N, figure=True, progcall=mybar)
|
|
@@ -337,6 +296,9 @@ def setContributions(plan):
|
|
|
337
296
|
"""
|
|
338
297
|
Set from UI -> Plan.
|
|
339
298
|
"""
|
|
299
|
+
if kz.getKey('timeList0') is None:
|
|
300
|
+
return
|
|
301
|
+
|
|
340
302
|
dicDf = {kz.getKey('iname0'): kz.getKey('timeList0')}
|
|
341
303
|
if kz.getKey('status') == 'married':
|
|
342
304
|
dicDf[kz.getKey('iname1')] = kz.getKey('timeList1')
|
|
@@ -383,66 +345,20 @@ def resetContributions(plan):
|
|
|
383
345
|
def setAllocationRatios(plan):
|
|
384
346
|
if kz.getKey('allocType') == 'individual':
|
|
385
347
|
try:
|
|
386
|
-
generic = getIndividualAllocationRatios()
|
|
348
|
+
generic = kz.getIndividualAllocationRatios()
|
|
387
349
|
plan.setAllocationRatios('individual', generic=generic)
|
|
388
350
|
except Exception as e:
|
|
389
351
|
st.error('Setting asset allocations failed: %s' % e)
|
|
390
352
|
return
|
|
391
353
|
elif kz.getKey('allocType') == 'account':
|
|
392
354
|
try:
|
|
393
|
-
acc = getAccountAllocationRatios()
|
|
355
|
+
acc = kz.getAccountAllocationRatios()
|
|
394
356
|
plan.setAllocationRatios('account', taxable=acc[0], taxDeferred=acc[1], taxFree=acc[2])
|
|
395
357
|
except Exception as e:
|
|
396
358
|
st.error('Setting asset allocations failed: %s' % e)
|
|
397
359
|
return
|
|
398
360
|
|
|
399
361
|
|
|
400
|
-
def getIndividualAllocationRatios():
|
|
401
|
-
generic = []
|
|
402
|
-
initial = []
|
|
403
|
-
final = []
|
|
404
|
-
for k1 in range(4):
|
|
405
|
-
initial.append(int(kz.getKey('j3_init%'+str(k1)+'_0')))
|
|
406
|
-
final.append(int(kz.getKey('j3_fin%'+str(k1)+'_0')))
|
|
407
|
-
gen0 = [initial, final]
|
|
408
|
-
generic = [gen0]
|
|
409
|
-
|
|
410
|
-
if kz.getKey('status') == 'married':
|
|
411
|
-
initial = []
|
|
412
|
-
final = []
|
|
413
|
-
for k1 in range(4):
|
|
414
|
-
initial.append(int(kz.getKey('j3_init%'+str(k1)+'_1')))
|
|
415
|
-
final.append(int(kz.getKey('j3_fin%'+str(k1)+'_1')))
|
|
416
|
-
gen1 = [initial, final]
|
|
417
|
-
generic.append(gen1)
|
|
418
|
-
|
|
419
|
-
return generic
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
def getAccountAllocationRatios():
|
|
423
|
-
accounts = [[], [], []]
|
|
424
|
-
for j1 in range(3):
|
|
425
|
-
initial = []
|
|
426
|
-
final = []
|
|
427
|
-
for k1 in range(4):
|
|
428
|
-
initial.append(int(kz.getKey(f'j{j1}_init%'+str(k1)+'_0')))
|
|
429
|
-
final.append(int(kz.getKey(f'j{j1}_fin%'+str(k1)+'_0')))
|
|
430
|
-
tmp = [initial, final]
|
|
431
|
-
accounts[j1].append(tmp)
|
|
432
|
-
|
|
433
|
-
if kz.getKey('status') == 'married':
|
|
434
|
-
for j1 in range(3):
|
|
435
|
-
initial = []
|
|
436
|
-
final = []
|
|
437
|
-
for k1 in range(4):
|
|
438
|
-
initial.append(int(kz.getKey(f'j{j1}_init%'+str(k1)+'_1')))
|
|
439
|
-
final.append(int(kz.getKey(f'j{j1}_fin%'+str(k1)+'_1')))
|
|
440
|
-
tmp = [initial, final]
|
|
441
|
-
accounts[j1].append(tmp)
|
|
442
|
-
|
|
443
|
-
return accounts
|
|
444
|
-
|
|
445
|
-
|
|
446
362
|
@_checkPlan
|
|
447
363
|
def plotSingleResults(plan):
|
|
448
364
|
c = 0
|
|
@@ -507,24 +423,6 @@ def setProfile(plan, key, pull=True):
|
|
|
507
423
|
plan.setSpendingProfile(profile, survivor, dip, increase, delay)
|
|
508
424
|
|
|
509
425
|
|
|
510
|
-
@_checkPlan
|
|
511
|
-
def setHeirsTaxRate(plan, key):
|
|
512
|
-
val = kz.setpull(key)
|
|
513
|
-
plan.setHeirsTaxRate(val)
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
@_checkPlan
|
|
517
|
-
def setLongTermCapitalTaxRate(plan, key):
|
|
518
|
-
val = kz.setpull(key)
|
|
519
|
-
plan.setLongTermCapitalTaxRate(val)
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
@_checkPlan
|
|
523
|
-
def setDividendRate(plan, key):
|
|
524
|
-
val = kz.setpull(key)
|
|
525
|
-
plan.setDividendRate(val)
|
|
526
|
-
|
|
527
|
-
|
|
528
426
|
@_checkPlan
|
|
529
427
|
def setDefaultPlots(plan, key):
|
|
530
428
|
val = kz.storepull(key)
|
|
@@ -609,7 +507,7 @@ def saveContributions(plan):
|
|
|
609
507
|
@_checkPlan
|
|
610
508
|
def saveCaseFile(plan):
|
|
611
509
|
stringBuffer = StringIO()
|
|
612
|
-
if getSolveParameters() is None:
|
|
510
|
+
if kz.getSolveParameters() is None:
|
|
613
511
|
return ''
|
|
614
512
|
plan.saveConfig(stringBuffer)
|
|
615
513
|
encoded_data = stringBuffer.getvalue().encode('utf-8')
|
|
@@ -632,23 +530,7 @@ def createCaseFromFile(file):
|
|
|
632
530
|
|
|
633
531
|
return name, mydic
|
|
634
532
|
|
|
635
|
-
|
|
636
|
-
# 'timeList', 'plots', 'interpMethod', 'interpCenter', 'interpWidth',
|
|
637
|
-
# 'objective', 'withMedicare', 'bequest', 'netSpending',
|
|
638
|
-
# 'noRothConversions', 'maxRothConversion',
|
|
639
|
-
# 'rateType', 'fixedType', 'varyingType', 'yfrm', 'yto',
|
|
640
|
-
# 'divRate', 'heirsTx', 'gainTx', 'spendingProfile', 'survivor',
|
|
641
|
-
# 'surplusFraction', ]
|
|
642
|
-
# keynamesJ = ['benf', ]
|
|
643
|
-
# keynamesK = ['fxRate', 'mean', 'stdev']
|
|
644
|
-
# keynamesI = ['iname', 'yob', 'life', 'txbl', 'txDef', 'txFree',
|
|
645
|
-
# 'ssAge', 'ssAmt', 'pAge', 'pAmt', 'df',
|
|
646
|
-
# 'jX_init%0_', 'jX_init%1_', 'jX_init%2_', 'jX_init%3_',
|
|
647
|
-
# 'jX_fin%0_', 'jX_fin%1_', 'jX_fin%2_', 'jX_fin%3_']
|
|
648
|
-
# keynames6 = ['corr']
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
# @_checkPlan
|
|
533
|
+
|
|
652
534
|
def genDic(plan):
|
|
653
535
|
accName = ['txbl', 'txDef', 'txFree']
|
|
654
536
|
dic = {}
|
|
@@ -744,8 +626,8 @@ def genDic(plan):
|
|
|
744
626
|
dic['yto'] = plan.rateTo
|
|
745
627
|
else:
|
|
746
628
|
dic['yfrm'] = FROM
|
|
747
|
-
# Rates
|
|
748
|
-
dic['yto'] = date.today().year -
|
|
629
|
+
# Rates availability are trailing by 1 year.
|
|
630
|
+
dic['yto'] = date.today().year - 1
|
|
749
631
|
|
|
750
632
|
if plan.rateMethod in ['stochastic', 'histochastic']:
|
|
751
633
|
qq = 1
|
|
@@ -759,8 +641,17 @@ def genDic(plan):
|
|
|
759
641
|
return plan._name, dic
|
|
760
642
|
|
|
761
643
|
|
|
762
|
-
|
|
763
|
-
|
|
644
|
+
@_checkPlan
|
|
645
|
+
def backYearsMAGI(plan):
|
|
646
|
+
thisyear = date.today().year
|
|
647
|
+
backyears = [0, 0]
|
|
648
|
+
for i in range(plan.N_i):
|
|
649
|
+
if thisyear - plan.yobs[i] >= 65:
|
|
650
|
+
backyears[0] = thisyear - 2
|
|
651
|
+
elif thisyear - plan.yobs[i] >= 64:
|
|
652
|
+
backyears[1] = thisyear - 1
|
|
653
|
+
|
|
654
|
+
return backyears
|
|
764
655
|
|
|
765
656
|
|
|
766
657
|
def version():
|
|
@@ -98,10 +98,9 @@ def updateContributions():
|
|
|
98
98
|
|
|
99
99
|
|
|
100
100
|
def switchToCase(key):
|
|
101
|
-
import owlbridge as owb
|
|
102
101
|
# Catch case where switch happens while editing W&W tables.
|
|
103
102
|
if getGlobalKey('currentPageName') == 'Wages And Contributions':
|
|
104
|
-
|
|
103
|
+
updateContributions()
|
|
105
104
|
ss.currentCase = ss['_'+key]
|
|
106
105
|
|
|
107
106
|
|
|
@@ -110,6 +109,12 @@ def isIncomplete():
|
|
|
110
109
|
or (getKey('status') == 'married' and getKey('iname1') in [None, '']))
|
|
111
110
|
|
|
112
111
|
|
|
112
|
+
def isCaseUnsolved():
|
|
113
|
+
if caseHasNoPlan():
|
|
114
|
+
return True
|
|
115
|
+
return getKey('caseStatus') != 'solved'
|
|
116
|
+
|
|
117
|
+
|
|
113
118
|
def caseHasNoPlan():
|
|
114
119
|
return getKey('plan') is None
|
|
115
120
|
|
|
@@ -214,7 +219,7 @@ def createNewCase(case):
|
|
|
214
219
|
st.error("Case name '%s' already exists." % casename)
|
|
215
220
|
return
|
|
216
221
|
|
|
217
|
-
ss.cases[casename] = {'name': casename, 'caseStatus': '', 'summary': '', 'logs': None}
|
|
222
|
+
ss.cases[casename] = {'name': casename, 'caseStatus': 'unknown', 'summary': '', 'logs': None}
|
|
218
223
|
setCurrentCase(ss._newcase)
|
|
219
224
|
|
|
220
225
|
|
|
@@ -301,6 +306,105 @@ def getDict(key=ss.currentCase):
|
|
|
301
306
|
return ss.cases[key]
|
|
302
307
|
|
|
303
308
|
|
|
309
|
+
def getAccountBalances(ni):
|
|
310
|
+
bal = [[], [], []]
|
|
311
|
+
accounts = ['txbl', 'txDef', 'txFree']
|
|
312
|
+
for j, acc in enumerate(accounts):
|
|
313
|
+
for i in range(ni):
|
|
314
|
+
bal[j].append(getKey(acc+str(i)))
|
|
315
|
+
|
|
316
|
+
return bal
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def getSolveParameters():
|
|
320
|
+
maximize = getKey('objective')
|
|
321
|
+
if maximize is None:
|
|
322
|
+
return None
|
|
323
|
+
if 'spending' in maximize:
|
|
324
|
+
objective = 'maxSpending'
|
|
325
|
+
else:
|
|
326
|
+
objective = 'maxBequest'
|
|
327
|
+
|
|
328
|
+
options = {}
|
|
329
|
+
optList = ['netSpending', 'maxRothConversion', 'noRothConversions',
|
|
330
|
+
'withMedicare', 'bequest', 'solver']
|
|
331
|
+
for opt in optList:
|
|
332
|
+
val = getKey(opt)
|
|
333
|
+
if val is not None:
|
|
334
|
+
options[opt] = val
|
|
335
|
+
|
|
336
|
+
if getKey('readRothX'):
|
|
337
|
+
options['maxRothConversion'] = 'file'
|
|
338
|
+
|
|
339
|
+
return objective, options
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def getIndividualAllocationRatios():
|
|
343
|
+
generic = []
|
|
344
|
+
initial = []
|
|
345
|
+
final = []
|
|
346
|
+
for k1 in range(4):
|
|
347
|
+
initial.append(int(getKey('j3_init%'+str(k1)+'_0')))
|
|
348
|
+
final.append(int(getKey('j3_fin%'+str(k1)+'_0')))
|
|
349
|
+
gen0 = [initial, final]
|
|
350
|
+
generic = [gen0]
|
|
351
|
+
|
|
352
|
+
if getKey('status') == 'married':
|
|
353
|
+
initial = []
|
|
354
|
+
final = []
|
|
355
|
+
for k1 in range(4):
|
|
356
|
+
initial.append(int(getKey('j3_init%'+str(k1)+'_1')))
|
|
357
|
+
final.append(int(getKey('j3_fin%'+str(k1)+'_1')))
|
|
358
|
+
gen1 = [initial, final]
|
|
359
|
+
generic.append(gen1)
|
|
360
|
+
|
|
361
|
+
return generic
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def getAccountAllocationRatios():
|
|
365
|
+
accounts = [[], [], []]
|
|
366
|
+
for j1 in range(3):
|
|
367
|
+
initial = []
|
|
368
|
+
final = []
|
|
369
|
+
for k1 in range(4):
|
|
370
|
+
initial.append(int(getKey(f'j{j1}_init%'+str(k1)+'_0')))
|
|
371
|
+
final.append(int(getKey(f'j{j1}_fin%'+str(k1)+'_0')))
|
|
372
|
+
tmp = [initial, final]
|
|
373
|
+
accounts[j1].append(tmp)
|
|
374
|
+
|
|
375
|
+
if getKey('status') == 'married':
|
|
376
|
+
for j1 in range(3):
|
|
377
|
+
initial = []
|
|
378
|
+
final = []
|
|
379
|
+
for k1 in range(4):
|
|
380
|
+
initial.append(int(getKey(f'j{j1}_init%'+str(k1)+'_1')))
|
|
381
|
+
final.append(int(getKey(f'j{j1}_fin%'+str(k1)+'_1')))
|
|
382
|
+
tmp = [initial, final]
|
|
383
|
+
accounts[j1].append(tmp)
|
|
384
|
+
|
|
385
|
+
return accounts
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def getPreviousMAGI():
|
|
389
|
+
backMAGI = [0, 0]
|
|
390
|
+
for ii in range(2):
|
|
391
|
+
val = getKey('MAGI'+str(ii))
|
|
392
|
+
if val:
|
|
393
|
+
backMAGI[ii] = val
|
|
394
|
+
|
|
395
|
+
return backMAGI
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def getFixedIncome(ni, what):
|
|
399
|
+
amounts = []
|
|
400
|
+
ages = []
|
|
401
|
+
for i in range(ni):
|
|
402
|
+
amounts.append(getKey(what+'Amt'+str(i)))
|
|
403
|
+
ages.append(getKey(what+'Age'+str(i)))
|
|
404
|
+
|
|
405
|
+
return amounts, ages
|
|
406
|
+
|
|
407
|
+
|
|
304
408
|
def getIntNum(text, nkey, disabled=False, callback=setpull, step=1, help=None, min_value=0, max_value=None):
|
|
305
409
|
return st.number_input(text,
|
|
306
410
|
value=int(getKey(nkey)),
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2025.1.27"
|
|
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
|