owlplanner 2025.2.6__tar.gz → 2025.2.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/PKG-INFO +1 -1
  2. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/case_jack+jill.toml +1 -0
  3. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/case_joe.toml +1 -0
  4. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/case_john+sally.toml +1 -0
  5. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/case_kim+sam-bequest.toml +1 -0
  6. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/case_kim+sam-spending.toml +1 -0
  7. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/pyproject.toml +1 -1
  8. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/config.py +3 -1
  9. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/plan.py +13 -5
  10. owlplanner-2025.2.8/src/owlplanner/version.py +1 -0
  11. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/tests/test_toml_cases.py +21 -0
  12. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Asset_Allocation.py +7 -7
  13. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Fixed_Income.py +13 -4
  14. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/owlbridge.py +8 -7
  15. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/requirements.txt +1 -1
  16. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/sskeys.py +14 -26
  17. owlplanner-2025.2.6/src/owlplanner/version.py +0 -1
  18. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/.devcontainer/devcontainer.json +0 -0
  19. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/.flake8 +0 -0
  20. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/.github/workflows/github-actions-runtests.yml +0 -0
  21. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/.gitignore +0 -0
  22. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/INSTALL.md +0 -0
  23. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/LICENSE +0 -0
  24. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/README.md +0 -0
  25. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/AD-taxDef.png +0 -0
  26. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/AD-taxFree.png +0 -0
  27. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/AD-taxable.png +0 -0
  28. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/Hist_Bequest.png +0 -0
  29. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/Hist_Spending.png +0 -0
  30. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/MC-tutorial2a.png +0 -0
  31. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/MC-tutorial2b.png +0 -0
  32. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/OwlUI.png +0 -0
  33. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/allocations.png +0 -0
  34. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/owl.png +0 -0
  35. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/profile.png +0 -0
  36. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/ratesCorrelations.png +0 -0
  37. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/ratesPlot.png +0 -0
  38. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/savingsPlot.png +0 -0
  39. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/sourcesPlot.png +0 -0
  40. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/spendingPlot.png +0 -0
  41. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/taxIncomePlot.png +0 -0
  42. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/taxesPlot.png +0 -0
  43. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/owl.pdf +0 -0
  44. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/owl.tex +0 -0
  45. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/jack+jill.xlsx +0 -0
  46. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/joe.xlsx +0 -0
  47. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/john+sally.xlsx +0 -0
  48. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/template.xlsx +0 -0
  49. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/notebooks/john+sally.ipynb +0 -0
  50. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/notebooks/kim+sam.ipynb +0 -0
  51. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/notebooks/template.ipynb +0 -0
  52. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/notebooks/tutorial_1.ipynb +0 -0
  53. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/notebooks/tutorial_2.ipynb +0 -0
  54. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/notebooks/tutorial_3.ipynb +0 -0
  55. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/owlplanner.cmd +0 -0
  56. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/requirements.txt +0 -0
  57. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/__init__.py +0 -0
  58. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/abcapi.py +0 -0
  59. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/data/__init__.py +0 -0
  60. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/data/rates.csv +0 -0
  61. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/logging.py +0 -0
  62. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/progress.py +0 -0
  63. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/rates.py +0 -0
  64. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/tax2025.py +0 -0
  65. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/timelists.py +0 -0
  66. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/utils.py +0 -0
  67. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/tests/test_logger.py +0 -0
  68. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/tests/test_regressions.py +0 -0
  69. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/tests/test_repro.py +0 -0
  70. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/tests/test_units.py +0 -0
  71. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/About_Owl.py +0 -0
  72. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Assets.py +0 -0
  73. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Create_Case.py +0 -0
  74. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Documentation.py +0 -0
  75. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Graphs.py +0 -0
  76. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Historical_Range.py +0 -0
  77. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Logs.py +0 -0
  78. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Monte_Carlo.py +0 -0
  79. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Optimization_Parameters.py +0 -0
  80. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Output_Files.py +0 -0
  81. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Quick_Start.py +0 -0
  82. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/README.md +0 -0
  83. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Rates_Selection.py +0 -0
  84. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Settings.py +0 -0
  85. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Wages_And_Contributions.py +0 -0
  86. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Worksheets.py +0 -0
  87. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/main.py +0 -0
  88. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/plots.py +0 -0
  89. {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/progress.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.2.6
3
+ Version: 2025.2.8
4
4
  Summary: Owl: Retirement planner with great wisdom
5
5
  Project-URL: HomePage, https://github.com/mdlacasse/owl
6
6
  Project-URL: Repository, https://github.com/mdlacasse/owl
@@ -20,6 +20,7 @@ Names = [ "Jack", "Jill",]
20
20
  ["Fixed Income"]
21
21
  "Pension amounts" = [ 0.0, 10.5,]
22
22
  "Pension ages" = [ 65, 65,]
23
+ "Pension indexed" = [ false, false,]
23
24
  "Social security amounts" = [ 28.4, 19.7,]
24
25
  "Social security ages" = [ 70, 62,]
25
26
 
@@ -18,6 +18,7 @@ Names = [ "Joe",]
18
18
  ["Fixed Income"]
19
19
  "Pension amounts" = [ 18.0,]
20
20
  "Pension ages" = [ 65,]
21
+ "Pension indexed" = [ true,]
21
22
  "Social security amounts" = [ 28.4,]
22
23
  "Social security ages" = [ 67,]
23
24
 
@@ -20,6 +20,7 @@ Names = [ "John", "Sally",]
20
20
  ["Fixed Income"]
21
21
  "Pension amounts" = [ 0.0, 0.0,]
22
22
  "Pension ages" = [ 65, 65,]
23
+ "Pension indexed" = [ false, false,]
23
24
  "Social security amounts" = [ 36.0, 21.6,]
24
25
  "Social security ages" = [ 67, 67,]
25
26
 
@@ -20,6 +20,7 @@ Names = [ "Kim", "Sam",]
20
20
  ["Fixed Income"]
21
21
  "Pension amounts" = [ 0.0, 0.0,]
22
22
  "Pension ages" = [ 65, 65,]
23
+ "Pension indexed" = [ false, false,]
23
24
  "Social security amounts" = [ 45.0, 25.0,]
24
25
  "Social security ages" = [ 70, 68,]
25
26
 
@@ -20,6 +20,7 @@ Names = [ "Kim", "Sam",]
20
20
  ["Fixed Income"]
21
21
  "Pension amounts" = [ 0.0, 0.0,]
22
22
  "Pension ages" = [ 65, 65,]
23
+ "Pension indexed" = [ false, false,]
23
24
  "Social security amounts" = [ 45.0, 25.0,]
24
25
  "Social security ages" = [ 70, 68,]
25
26
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "owlplanner"
7
- version = "2025.02.06"
7
+ version = "2025.02.08"
8
8
  authors = [
9
9
  { name="Martin-D. Lacasse", email="martin.d.lacasse@gmail.com" },
10
10
  ]
@@ -52,6 +52,7 @@ def saveConfig(plan, file, mylog):
52
52
  # Fixed Income.
53
53
  diconf['Fixed Income'] = {'Pension amounts': (plan.pensionAmounts/1000).tolist(),
54
54
  'Pension ages': plan.pensionAges.tolist(),
55
+ 'Pension indexed': plan.pensionIndexed,
55
56
  'Social security amounts': (plan.ssecAmounts/1000).tolist(),
56
57
  'Social security ages': plan.ssecAges.tolist(),
57
58
  }
@@ -213,7 +214,8 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
213
214
  p.setSocialSecurity(ssecAmounts, ssecAges)
214
215
  pensionAmounts = np.array(diconf['Fixed Income']['Pension amounts'], dtype=np.float32)
215
216
  pensionAges = np.array(diconf['Fixed Income']['Pension ages'], dtype=np.int32)
216
- p.setPension(pensionAmounts, pensionAges)
217
+ pensionIndexed = diconf['Fixed Income']['Pension indexed']
218
+ p.setPension(pensionAmounts, pensionAges, pensionIndexed)
217
219
 
218
220
  # Rate Selection.
219
221
  p.setDividendRate(float(diconf['Rate Selection']['Dividend tax rate']))
@@ -286,6 +286,7 @@ class Plan(object):
286
286
  self.zeta_in = np.zeros((self.N_i, self.N_n))
287
287
  self.pensionAmounts = np.zeros(self.N_i)
288
288
  self.pensionAges = 65 * np.ones(self.N_i, dtype=np.int32)
289
+ self.pensionIndexed = [False, False]
289
290
  self.ssecAmounts = np.zeros(self.N_i)
290
291
  self.ssecAges = 67 * np.ones(self.N_i, dtype=np.int32)
291
292
 
@@ -481,13 +482,14 @@ class Plan(object):
481
482
  self.nu = nu
482
483
  self.caseStatus = 'modified'
483
484
 
484
- def setPension(self, amounts, ages, units='k'):
485
+ def setPension(self, amounts, ages, indexed=[False, False], units='k'):
485
486
  """
486
487
  Set value of pension for each individual and commencement age.
487
488
  Units are in $k, unless specified otherwise: 'k', 'M', or '1'.
488
489
  """
489
490
  assert len(amounts) == self.N_i, 'Amounts must have %d entries.' % self.N_i
490
491
  assert len(ages) == self.N_i, 'Ages must have %d entries.' % self.N_i
492
+ assert len(indexed) >= self.N_i, 'Indexed list must have at least %d entries.' % self.N_i
491
493
 
492
494
  fac = u.getUnits(units)
493
495
  amounts = u.rescale(amounts, fac)
@@ -508,7 +510,9 @@ class Plan(object):
508
510
 
509
511
  self.pensionAmounts = np.array(amounts)
510
512
  self.pensionAges = np.array(ages, dtype=np.int32)
513
+ self.pensionIndexed = indexed
511
514
  self.caseStatus = 'modified'
515
+ self._adjustedParameters = False
512
516
 
513
517
  def setSocialSecurity(self, amounts, ages, units='k'):
514
518
  """
@@ -1001,6 +1005,10 @@ class Plan(object):
1001
1005
  self.zetaBar_in = self.zeta_in * self.gamma_n[:-1]
1002
1006
  self.sigmaBar_n = self.sigma_n * self.gamma_n[:-1]
1003
1007
  self.xiBar_n = self.xi_n * self.gamma_n[:-1]
1008
+ self.piBar_in = self.pi_in
1009
+ for i in range(self.N_i):
1010
+ if self.pensionIndexed[i]:
1011
+ self.piBar_in[i] *= self.gamma_n[:-1]
1004
1012
 
1005
1013
  self._adjustedParameters = True
1006
1014
 
@@ -1269,7 +1277,7 @@ class Plan(object):
1269
1277
  rhs += (
1270
1278
  self.omega_in[i, n]
1271
1279
  + self.zetaBar_in[i, n]
1272
- + self.pi_in[i, n]
1280
+ + self.piBar_in[i, n]
1273
1281
  + self.Lambda_in[i, n]
1274
1282
  - 0.5 * fac * self.mu * self.kappa_ijn[i, 0, n]
1275
1283
  )
@@ -1299,7 +1307,7 @@ class Plan(object):
1299
1307
  row = A.newRow()
1300
1308
  row.addElem(_q1(Ce, n, Nn), 1)
1301
1309
  for i in range(Ni):
1302
- rhs += self.omega_in[i, n] + 0.85 * self.zetaBar_in[i, n] + self.pi_in[i, n]
1310
+ rhs += self.omega_in[i, n] + 0.85 * self.zetaBar_in[i, n] + self.piBar_in[i, n]
1303
1311
  # Taxable income from tax-deferred withdrawals.
1304
1312
  row.addElem(_q3(Cw, i, 1, n, Ni, Nj, Nn), -1)
1305
1313
  row.addElem(_q2(Cx, i, n, Ni, Nn), -1)
@@ -2000,7 +2008,7 @@ class Plan(object):
2000
2008
  sources = {}
2001
2009
  sources['wages'] = self.omega_in
2002
2010
  sources['ssec'] = self.zetaBar_in
2003
- sources['pension'] = self.pi_in
2011
+ sources['pension'] = self.piBar_in
2004
2012
  sources['txbl acc wdrwl'] = self.w_ijn[:, 0, :]
2005
2013
  sources['RMD'] = self.rmd_in
2006
2014
  sources['+dist'] = self.dist_in
@@ -2706,7 +2714,7 @@ class Plan(object):
2706
2714
  cashFlowDic = {
2707
2715
  'net spending': self.g_n,
2708
2716
  'all wages': np.sum(self.omega_in, axis=0),
2709
- 'all pensions': np.sum(self.pi_in, axis=0),
2717
+ 'all pensions': np.sum(self.piBar_in, axis=0),
2710
2718
  'all soc sec': np.sum(self.zetaBar_in, axis=0),
2711
2719
  "all BTI's": np.sum(self.Lambda_in, axis=0),
2712
2720
  'all wdrwls': np.sum(self.w_ijn, axis=(0, 1)),
@@ -0,0 +1 @@
1
+ __version__ = "2025.02.08"
@@ -2,6 +2,18 @@
2
2
  import owlplanner as owl
3
3
 
4
4
 
5
+ def getWac(exdir, base):
6
+ import os
7
+ wac = base.replace('case_', '')
8
+ wac = wac.replace('-spending', '')
9
+ wac = wac.replace('-bequest', '')
10
+ wac = exdir + wac + '.xlsx'
11
+ if os.path.exists(wac):
12
+ return wac
13
+ else:
14
+ return ''
15
+
16
+
5
17
  def test_allcases():
6
18
  exdir = './examples/'
7
19
  for case in ['case_john+sally',
@@ -10,6 +22,9 @@ def test_allcases():
10
22
  'case_kim+sam-spending',
11
23
  'case_kim+sam-bequest']:
12
24
  p = owl.readConfig(exdir + case)
25
+ wac = getWac(exdir, case)
26
+ if wac != '':
27
+ p.readContributions(wac)
13
28
  p.resolve()
14
29
 
15
30
 
@@ -17,6 +32,9 @@ def test_historical():
17
32
  exdir = './examples/'
18
33
  case = 'case_jack+jill'
19
34
  p = owl.readConfig(exdir + case)
35
+ wac = getWac(exdir, case)
36
+ if wac != '':
37
+ p.readContributions(wac)
20
38
  options = p.solverOptions
21
39
  objective = p.objective
22
40
  p.runHistoricalRange(objective, options, 1969, 2023)
@@ -26,6 +44,9 @@ def test_MC():
26
44
  exdir = './examples/'
27
45
  case = 'case_jack+jill'
28
46
  p = owl.readConfig(exdir + case)
47
+ wac = getWac(exdir, case)
48
+ if wac != '':
49
+ p.readContributions(wac)
29
50
  options = p.solverOptions
30
51
  objective = p.objective
31
52
  p.runMC(objective, options, 20)
@@ -5,7 +5,7 @@ import owlbridge as owb
5
5
 
6
6
 
7
7
  def getPercentInput(i, j, keybase, text, defval=0):
8
- nkey = keybase+str(j)+'_'+str(i)
8
+ nkey = f"{keybase}{j}_{i}"
9
9
  kz.initKey(nkey, defval)
10
10
  st.number_input(text, min_value=0, step=1, max_value=100,
11
11
  value=kz.getKey(nkey),
@@ -14,7 +14,7 @@ def getPercentInput(i, j, keybase, text, defval=0):
14
14
 
15
15
  ACC = ['taxable', 'tax-deferred', 'tax-free']
16
16
  ASSET = ['S&P 500', 'Corp Bonds Baa', 'T-Notes', 'Cash Assets']
17
- DEF = [60, 20, 10, 10]
17
+ DEFALLOC = [60, 20, 10, 10]
18
18
 
19
19
 
20
20
  def getIndividualAllocs(i, title, deco):
@@ -24,25 +24,25 @@ def getIndividualAllocs(i, title, deco):
24
24
  cols = st.columns(4, gap='large', vertical_alignment='top')
25
25
  for k1 in range(4):
26
26
  with cols[k1]:
27
- getPercentInput(i, k1, mydeco, ASSET[k1], DEF[k1])
27
+ getPercentInput(i, k1, mydeco, ASSET[k1], DEFALLOC[k1])
28
28
  checkIndividualAllocs(i, mydeco)
29
29
 
30
30
 
31
31
  def getAccountAllocs(i, j, title, deco):
32
32
  iname = kz.getKey('iname'+str(i))
33
- mydeco = f'j{j}_' + deco
33
+ mydeco = f"j{j}_" + deco
34
34
  st.write("###### %s's %s allocation for %s account (%%)" % (iname, title, ACC[j]))
35
35
  cols = st.columns(4, gap='large', vertical_alignment='top')
36
36
  for k1 in range(4):
37
37
  with cols[k1]:
38
- getPercentInput(i, k1, mydeco, ASSET[k1], DEF[k1])
38
+ getPercentInput(i, k1, mydeco, ASSET[k1], DEFALLOC[k1])
39
39
  checkAccountAllocs(i, mydeco)
40
40
 
41
41
 
42
42
  def checkAccountAllocs(i, deco):
43
43
  tot = 0
44
44
  for k1 in range(4):
45
- tot += int(kz.getKey(deco+str(k1)+'_'+str(i)))
45
+ tot += int(kz.getKey(f"{deco}{k1}_{i}"))
46
46
  if abs(100-tot) > 0:
47
47
  st.error('Percentages must add to 100%.')
48
48
  return False
@@ -52,7 +52,7 @@ def checkAccountAllocs(i, deco):
52
52
  def checkIndividualAllocs(i, deco):
53
53
  tot = 0
54
54
  for k1 in range(4):
55
- tot += int(kz.getKey(deco+str(k1)+'_'+str(i)))
55
+ tot += int(kz.getKey(f"{deco}{k1}_{i}"))
56
56
  if abs(100-tot) > 0:
57
57
  st.error('Percentages must add to 100%.')
58
58
  return False
@@ -3,24 +3,31 @@ import streamlit as st
3
3
  import sskeys as kz
4
4
 
5
5
 
6
- def getIntInput(i, key, text, defval=0):
6
+ def getIntInput(i, key, thing, defval=0):
7
7
  nkey = key+str(i)
8
8
  kz.initKey(nkey, defval)
9
9
  inamex = kz.getKey('iname'+str(i))
10
- st.number_input("%s's %s" % (inamex, text), min_value=0,
10
+ st.number_input(f"{inamex}'s {thing}", min_value=0,
11
11
  value=kz.getKey(nkey),
12
12
  on_change=kz.setpull, args=[nkey], key='_'+nkey)
13
13
 
14
14
 
15
- def getFloatInput(i, key, text, defval=0.):
15
+ def getFloatInput(i, key, thing, defval=0.):
16
16
  nkey = key+str(i)
17
17
  kz.initKey(nkey, defval)
18
18
  inamex = kz.getKey('iname'+str(i))
19
- st.number_input("%s's %s" % (inamex, text), min_value=0., help=kz.help1000,
19
+ st.number_input(f"{inamex}'s {thing}", min_value=0., help=kz.help1000,
20
20
  value=float(kz.getKey(nkey)), format='%.1f', step=10.,
21
21
  on_change=kz.setpull, args=[nkey], key='_'+nkey)
22
22
 
23
23
 
24
+ def getToggleInput(i, key, thing):
25
+ nkey = key+str(i)
26
+ kz.initKey(nkey, False)
27
+ defval = kz.getKey(nkey)
28
+ st.toggle(thing, on_change=kz.setpull, value=defval, args=[nkey], key='_'+nkey)
29
+
30
+
24
31
  ret = kz.titleBar('fixed')
25
32
  kz.caseHeader("Fixed Income")
26
33
 
@@ -44,8 +51,10 @@ else:
44
51
  with col1:
45
52
  getFloatInput(0, 'pAmt', 'pension annual amount (\\$k)')
46
53
  getIntInput(0, 'pAge', 'pension age', 65)
54
+ getToggleInput(0, 'pIdx', 'Inflafion adjusted')
47
55
 
48
56
  with col2:
49
57
  if kz.getKey('status') == 'married':
50
58
  getFloatInput(1, 'pAmt', 'pension annual amount (\\$k)')
51
59
  getIntInput(1, 'pAge', 'pension age', 65)
60
+ getToggleInput(1, 'pIdx', 'Inflafion adjusted')
@@ -69,14 +69,14 @@ def prepareRun(plan):
69
69
  st.error('Setting account balances failed: %s' % e)
70
70
  return
71
71
 
72
- amounts, ages = kz.getFixedIncome(ni, 'p')
72
+ amounts, ages, indexed = kz.getFixedIncome(ni, 'p')
73
73
  try:
74
- plan.setPension(amounts, ages)
74
+ plan.setPension(amounts, ages, indexed)
75
75
  except Exception as e:
76
76
  st.error('Failed setting pensions: %s' % e)
77
77
  return
78
78
 
79
- amounts, ages = kz.getFixedIncome(ni, 'ss')
79
+ amounts, ages, indexed = kz.getFixedIncome(ni, 'ss')
80
80
  try:
81
81
  plan.setSocialSecurity(amounts, ages)
82
82
  except Exception as e:
@@ -597,19 +597,20 @@ def genDic(plan):
597
597
  dic['ssAmt'+str(i)] = plan.ssecAmounts[i]/1000
598
598
  dic['pAge'+str(i)] = plan.pensionAges[i]
599
599
  dic['pAmt'+str(i)] = plan.pensionAmounts[i]/1000
600
+ dic['pIdx'+str(i)] = plan.pensionIndexed[i]
600
601
  for j1 in range(plan.N_j):
601
602
  dic[accName[j1]+str(i)] = plan.beta_ij[i, j1]/1000
602
603
 
603
604
  if plan.ARCoord == 'individual':
604
605
  for k1 in range(plan.N_k):
605
- dic['j3_init%'+str(k1)+'_'+str(i)] = int(plan.boundsAR['generic'][i][0][k1])
606
- dic['j3_fin%'+str(k1)+'_'+str(i)] = int(plan.boundsAR['generic'][i][1][k1])
606
+ dic[f"j3_init%{k1}_{i}"] = int(plan.boundsAR['generic'][i][0][k1])
607
+ dic[f"j3_fin%{k1}_{i}"] = int(plan.boundsAR['generic'][i][1][k1])
607
608
  elif plan.ARCoord == 'account':
608
609
  longAccName = ['taxable', 'tax-deferred', 'tax-free']
609
610
  for j2 in range(3):
610
611
  for k2 in range(plan.N_k):
611
- dic[f'j{j2}%d_init%'+str(k2)+'_'+str(i)] = int(plan.boundsAR[longAccName[j2]][i][0][k2])
612
- dic[f'j{j2}_fin%'+str(k2)+'_'+str(i)] = int(plan.boundsAR[longAccName[j2]][i][1][k2])
612
+ dic[f"j{j2}_init%{k2}_{i}"] = int(plan.boundsAR[longAccName[j2]][i][0][k2])
613
+ dic[f"j{j2}_fin%{k2}_{i}"] = int(plan.boundsAR[longAccName[j2]][i][1][k2])
613
614
  else:
614
615
  st.error("Only 'individual' and 'account' asset allocations are currently supported")
615
616
  return None
@@ -7,4 +7,4 @@ scipy
7
7
  streamlit
8
8
  toml
9
9
  # --extra-index-url https://test.pypi.org/simple
10
- owlplanner >= 2025.02.06
10
+ owlplanner >= 2025.02.08
@@ -339,44 +339,29 @@ def getSolveParameters():
339
339
 
340
340
  def getIndividualAllocationRatios():
341
341
  generic = []
342
- initial = []
343
- final = []
344
- for k1 in range(4):
345
- initial.append(int(getKey('j3_init%'+str(k1)+'_0')))
346
- final.append(int(getKey('j3_fin%'+str(k1)+'_0')))
347
- gen0 = [initial, final]
348
- generic = [gen0]
349
-
350
- if getKey('status') == 'married':
342
+ ni = 2 if getKey('status') == 'married' else 1
343
+ for i in range(ni):
351
344
  initial = []
352
345
  final = []
353
346
  for k1 in range(4):
354
- initial.append(int(getKey('j3_init%'+str(k1)+'_1')))
355
- final.append(int(getKey('j3_fin%'+str(k1)+'_1')))
356
- gen1 = [initial, final]
357
- generic.append(gen1)
347
+ initial.append(int(getKey(f"j3_init%{k1}_{i}")))
348
+ final.append(int(getKey(f"j3_fin%{k1}_{i}")))
349
+ gen = [initial, final]
350
+ generic.append(gen)
358
351
 
359
352
  return generic
360
353
 
361
354
 
362
355
  def getAccountAllocationRatios():
363
356
  accounts = [[], [], []]
364
- for j1 in range(3):
365
- initial = []
366
- final = []
367
- for k1 in range(4):
368
- initial.append(int(getKey(f'j{j1}_init%'+str(k1)+'_0')))
369
- final.append(int(getKey(f'j{j1}_fin%'+str(k1)+'_0')))
370
- tmp = [initial, final]
371
- accounts[j1].append(tmp)
372
-
373
- if getKey('status') == 'married':
357
+ ni = 2 if getKey('status') == 'married' else 1
358
+ for i in range(ni):
374
359
  for j1 in range(3):
375
360
  initial = []
376
361
  final = []
377
362
  for k1 in range(4):
378
- initial.append(int(getKey(f'j{j1}_init%'+str(k1)+'_1')))
379
- final.append(int(getKey(f'j{j1}_fin%'+str(k1)+'_1')))
363
+ initial.append(int(getKey(f"j{j1}_init%{k1}_{i}")))
364
+ final.append(int(getKey(f"j{j1}_fin%{k1}_{i}")))
380
365
  tmp = [initial, final]
381
366
  accounts[j1].append(tmp)
382
367
 
@@ -396,11 +381,14 @@ def getPreviousMAGIs():
396
381
  def getFixedIncome(ni, what):
397
382
  amounts = []
398
383
  ages = []
384
+ indexed = []
399
385
  for i in range(ni):
400
386
  amounts.append(getKey(what+'Amt'+str(i)))
401
387
  ages.append(getKey(what+'Age'+str(i)))
388
+ if what == 'p':
389
+ indexed.append(getKey(what+'Idx'+str(i)))
402
390
 
403
- return amounts, ages
391
+ return amounts, ages, indexed
404
392
 
405
393
 
406
394
  def getIntNum(text, nkey, disabled=False, callback=setpull, step=1, help=None, min_value=0, max_value=None):
@@ -1 +0,0 @@
1
- __version__ = "2025.02.06"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes