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.
Files changed (89) hide show
  1. {owlplanner-2025.1.29 → owlplanner-2025.2}/PKG-INFO +1 -1
  2. {owlplanner-2025.1.29 → owlplanner-2025.2}/pyproject.toml +1 -1
  3. {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/plan.py +12 -10
  4. owlplanner-2025.2/src/owlplanner/version.py +1 -0
  5. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Documentation.py +0 -1
  6. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Optimization_Parameters.py +13 -8
  7. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/owlbridge.py +7 -11
  8. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/requirements.txt +1 -1
  9. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/sskeys.py +12 -7
  10. owlplanner-2025.1.29/src/owlplanner/version.py +0 -1
  11. {owlplanner-2025.1.29 → owlplanner-2025.2}/.devcontainer/devcontainer.json +0 -0
  12. {owlplanner-2025.1.29 → owlplanner-2025.2}/.flake8 +0 -0
  13. {owlplanner-2025.1.29 → owlplanner-2025.2}/.github/workflows/github-actions-runtests.yml +0 -0
  14. {owlplanner-2025.1.29 → owlplanner-2025.2}/.gitignore +0 -0
  15. {owlplanner-2025.1.29 → owlplanner-2025.2}/INSTALL.md +0 -0
  16. {owlplanner-2025.1.29 → owlplanner-2025.2}/LICENSE +0 -0
  17. {owlplanner-2025.1.29 → owlplanner-2025.2}/README.md +0 -0
  18. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/AD-taxDef.png +0 -0
  19. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/AD-taxFree.png +0 -0
  20. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/AD-taxable.png +0 -0
  21. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/Hist_Bequest.png +0 -0
  22. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/Hist_Spending.png +0 -0
  23. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/MC-tutorial2a.png +0 -0
  24. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/MC-tutorial2b.png +0 -0
  25. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/OwlUI.png +0 -0
  26. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/allocations.png +0 -0
  27. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/owl.png +0 -0
  28. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/profile.png +0 -0
  29. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/ratesCorrelations.png +0 -0
  30. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/ratesPlot.png +0 -0
  31. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/savingsPlot.png +0 -0
  32. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/sourcesPlot.png +0 -0
  33. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/spendingPlot.png +0 -0
  34. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/taxIncomePlot.png +0 -0
  35. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/images/taxesPlot.png +0 -0
  36. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/owl.pdf +0 -0
  37. {owlplanner-2025.1.29 → owlplanner-2025.2}/docs/owl.tex +0 -0
  38. {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/case_jack+jill.toml +0 -0
  39. {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/case_joe.toml +0 -0
  40. {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/case_john+sally.toml +0 -0
  41. {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/case_kim+sam-bequest.toml +0 -0
  42. {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/case_kim+sam-spending.toml +0 -0
  43. {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/jack+jill.xlsx +0 -0
  44. {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/joe.xlsx +0 -0
  45. {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/john+sally.xlsx +0 -0
  46. {owlplanner-2025.1.29 → owlplanner-2025.2}/examples/template.xlsx +0 -0
  47. {owlplanner-2025.1.29 → owlplanner-2025.2}/notebooks/john+sally.ipynb +0 -0
  48. {owlplanner-2025.1.29 → owlplanner-2025.2}/notebooks/kim+sam.ipynb +0 -0
  49. {owlplanner-2025.1.29 → owlplanner-2025.2}/notebooks/template.ipynb +0 -0
  50. {owlplanner-2025.1.29 → owlplanner-2025.2}/notebooks/tutorial_1.ipynb +0 -0
  51. {owlplanner-2025.1.29 → owlplanner-2025.2}/notebooks/tutorial_2.ipynb +0 -0
  52. {owlplanner-2025.1.29 → owlplanner-2025.2}/notebooks/tutorial_3.ipynb +0 -0
  53. {owlplanner-2025.1.29 → owlplanner-2025.2}/owlplanner.cmd +0 -0
  54. {owlplanner-2025.1.29 → owlplanner-2025.2}/requirements.txt +0 -0
  55. {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/__init__.py +0 -0
  56. {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/abcapi.py +0 -0
  57. {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/config.py +0 -0
  58. {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/data/__init__.py +0 -0
  59. {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/data/rates.csv +0 -0
  60. {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/logging.py +0 -0
  61. {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/progress.py +0 -0
  62. {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/rates.py +0 -0
  63. {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/tax2025.py +0 -0
  64. {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/timelists.py +0 -0
  65. {owlplanner-2025.1.29 → owlplanner-2025.2}/src/owlplanner/utils.py +0 -0
  66. {owlplanner-2025.1.29 → owlplanner-2025.2}/tests/test_logger.py +0 -0
  67. {owlplanner-2025.1.29 → owlplanner-2025.2}/tests/test_regressions.py +0 -0
  68. {owlplanner-2025.1.29 → owlplanner-2025.2}/tests/test_repro.py +0 -0
  69. {owlplanner-2025.1.29 → owlplanner-2025.2}/tests/test_toml_cases.py +0 -0
  70. {owlplanner-2025.1.29 → owlplanner-2025.2}/tests/test_units.py +0 -0
  71. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/About_Owl.py +0 -0
  72. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Asset_Allocation.py +0 -0
  73. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Assets.py +0 -0
  74. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Basic_Info.py +0 -0
  75. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Case_Results.py +0 -0
  76. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Case_Summary.py +0 -0
  77. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Case_Worksheets.py +0 -0
  78. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Fixed_Income.py +0 -0
  79. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Historical_Range.py +0 -0
  80. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Logs.py +0 -0
  81. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Monte_Carlo.py +0 -0
  82. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Quick_Start.py +0 -0
  83. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/README.md +0 -0
  84. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Rates_Selection.py +0 -0
  85. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Settings.py +0 -0
  86. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/Wages_And_Contributions.py +0 -0
  87. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/main.py +0 -0
  88. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/plots.py +0 -0
  89. {owlplanner-2025.1.29 → owlplanner-2025.2}/ui/progress.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.1.29
3
+ Version: 2025.2
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "owlplanner"
7
- version = "2025.01.29"
7
+ version = "2025.02"
8
8
  authors = [
9
9
  { name="Martin-D. Lacasse", email="martin.d.lacasse@gmail.com" },
10
10
  ]
@@ -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, col3 = st.columns(3, gap='large', vertical_alignment='top')
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
- with col3:
79
- if owb.hasMOSEK():
80
- choices = ['HiGHS', 'MOSEK']
81
- kz.initKey('solver', choices[0])
82
- ret = kz.getRadio('Solver', choices, 'solver')
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("Spending profile", profileChoices, 'spendingProfile', callback=owb.setProfile)
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
@@ -7,4 +7,4 @@ scipy
7
7
  streamlit
8
8
  toml
9
9
  # --extra-index-url https://test.pypi.org/simple
10
- owlplanner >= 2025.1.29
10
+ owlplanner >= 2025.02
@@ -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 getPreviousMAGI():
389
- backMAGI = [0, 0]
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
- backMAGI[ii] = val
397
+ backMAGIs[ii] = val
394
398
 
395
- return backMAGI
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