owlplanner 2025.3.27__tar.gz → 2025.4.1__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 (112) hide show
  1. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/PKG-INFO +1 -1
  2. owlplanner-2025.4.1/Papers/bv_cvxbook.pdf +0 -0
  3. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/owl.pdf +0 -0
  4. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/owl.tex +1 -1
  5. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/pyproject.toml +1 -1
  6. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/requirements.txt +1 -0
  7. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/src/owlplanner/config.py +6 -0
  8. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/src/owlplanner/plan.py +21 -8
  9. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/src/owlplanner/tax2025.py +1 -1
  10. owlplanner-2025.4.1/src/owlplanner/version.py +1 -0
  11. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/About_Owl.py +5 -6
  12. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/Current_Assets.py +2 -1
  13. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/Documentation.py +16 -2
  14. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/Optimization_Parameters.py +7 -1
  15. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/requirements.txt +2 -1
  16. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/sskeys.py +6 -5
  17. owlplanner-2025.3.27/src/owlplanner/version.py +0 -1
  18. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/.devcontainer/devcontainer.json +0 -0
  19. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/.flake8 +0 -0
  20. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/.gitattributes +0 -0
  21. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/.github/workflows/github-actions-runtests.yml +0 -0
  22. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/.gitignore +0 -0
  23. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/INSTALL.md +0 -0
  24. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/LICENSE +0 -0
  25. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/Papers/FE00006821-Class-VI-Injection-Permit--Salient-Features-and-Regulatory-Challenges_Final.pdf +0 -0
  26. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/Papers/Kou-OptionPricingDouble-2004.pdf +0 -0
  27. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/Papers/Multi-Period Mean Expected-Shortfall Strategies Cut Your Losses and Ride Your Gains .pdf +0 -0
  28. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/Papers/Optimal Asset Allocation for Retirement Saving Deterministic Vs. Time Consistent Adaptive Strategies.pdf +0 -0
  29. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/Papers/Rule-based_strategies_for_dynamic_life_cycle_inves.pdf +0 -0
  30. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/Papers/s10436-006-0062-y.pdf +0 -0
  31. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/README.md +0 -0
  32. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/USER_GUIDE.md +0 -0
  33. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docker/Dockerfile +0 -0
  34. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docker/README.md +0 -0
  35. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docker/docker-compose.yml +0 -0
  36. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docker/fastentrypoint.sh +0 -0
  37. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/AD-taxDef.png +0 -0
  38. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/AD-taxFree.png +0 -0
  39. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/AD-taxable.png +0 -0
  40. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/Hist_Bequest.png +0 -0
  41. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/Hist_Spending.png +0 -0
  42. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/MC-tutorial2a.png +0 -0
  43. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/MC-tutorial2b.png +0 -0
  44. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/OwlUI.png +0 -0
  45. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/allocations.png +0 -0
  46. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/owl.png +0 -0
  47. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/profile.png +0 -0
  48. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/ratesCorrelations.png +0 -0
  49. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/ratesPlot.png +0 -0
  50. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/savingsPlot.png +0 -0
  51. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/sourcesPlot.png +0 -0
  52. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/spendingPlot.png +0 -0
  53. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/taxIncomePlot.png +0 -0
  54. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/docs/images/taxesPlot.png +0 -0
  55. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/examples/case_jack+jill.toml +0 -0
  56. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/examples/case_joe.toml +0 -0
  57. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/examples/case_john+sally.toml +0 -0
  58. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/examples/case_jon+jane.toml +0 -0
  59. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/examples/case_kim+sam-bequest.toml +0 -0
  60. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/examples/case_kim+sam-spending.toml +0 -0
  61. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/examples/jack+jill.xlsx +0 -0
  62. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/examples/joe.xlsx +0 -0
  63. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/examples/john+sally.xlsx +0 -0
  64. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/examples/jon+jane.xlsx +0 -0
  65. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/examples/template.xlsx +0 -0
  66. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/notebooks/john+sally.ipynb +0 -0
  67. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/notebooks/kim+sam.ipynb +0 -0
  68. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/notebooks/template.ipynb +0 -0
  69. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/notebooks/tutorial_1.ipynb +0 -0
  70. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/notebooks/tutorial_2.ipynb +0 -0
  71. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/notebooks/tutorial_3.ipynb +0 -0
  72. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/owlplanner.cmd +0 -0
  73. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/owlplanner.sh +0 -0
  74. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/src/owlplanner/__init__.py +0 -0
  75. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/src/owlplanner/abcapi.py +0 -0
  76. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/src/owlplanner/data/__init__.py +0 -0
  77. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/src/owlplanner/data/rates.csv +0 -0
  78. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/src/owlplanner/logging.py +0 -0
  79. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/src/owlplanner/progress.py +0 -0
  80. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/src/owlplanner/rates.py +0 -0
  81. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/src/owlplanner/timelists.py +0 -0
  82. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/src/owlplanner/utils.py +0 -0
  83. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/tests/test_logger.py +0 -0
  84. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/tests/test_regressions.py +0 -0
  85. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/tests/test_repro.py +0 -0
  86. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/tests/test_toml_cases.py +0 -0
  87. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/tests/test_units.py +0 -0
  88. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ttt.py +0 -0
  89. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ttt2.py +0 -0
  90. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ttt3.py +0 -0
  91. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/Asset_Allocation.py +0 -0
  92. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/Create_Case.py +0 -0
  93. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/Fixed_Income.py +0 -0
  94. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/Graphs.py +0 -0
  95. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/Historical_Range.py +0 -0
  96. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/Logs.py +0 -0
  97. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/Monte_Carlo.py +0 -0
  98. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/Output_Files.py +0 -0
  99. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/Quick_Start.py +0 -0
  100. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/README.md +0 -0
  101. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/Rates_Selection.py +0 -0
  102. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/Settings.py +0 -0
  103. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/Wages_And_Contributions.py +0 -0
  104. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/Worksheets.py +0 -0
  105. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/main+fonts.py +0 -0
  106. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/main.py +0 -0
  107. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/owlbridge.py +0 -0
  108. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/plots.py +0 -0
  109. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/progress.py +0 -0
  110. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/sskeys.py.color +0 -0
  111. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/style.css +0 -0
  112. {owlplanner-2025.3.27 → owlplanner-2025.4.1}/ui/tomlexamples.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.3.27
3
+ Version: 2025.4.1
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
@@ -329,7 +329,7 @@ or an s-curve as in
329
329
  Income tax rate on long-term capital gain and qualified dividends, typically 15\%.
330
330
  \item [$\eta$]
331
331
  Spousal ratio for surplus deposits, which goes from 0 to 1, as the fraction
332
- that goes to the $i = 1$ second spouse's account. Therefore, a surplus $s_n$ in year $n$
332
+ that goes to the $i = 1$ spouse's account. Therefore, a surplus $s_n$ in year $n$
333
333
  will result in a deposit $d$ in the taxable account of individual $i$ as
334
334
  \begin{eqnarray}
335
335
  \label{Eq:eta}
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "owlplanner"
7
- version = "2025.03.27"
7
+ version = "2025.04.01"
8
8
  authors = [
9
9
  { name="Martin-D. Lacasse", email="martin.d.lacasse@gmail.com" },
10
10
  ]
@@ -3,6 +3,7 @@ matplotlib
3
3
  seaborn
4
4
  pandas
5
5
  openpyxl
6
+ odfpy
6
7
  scipy
7
8
  streamlit
8
9
  toml
@@ -12,6 +12,7 @@ Disclaimer: This program comes with no guarantee. Use at your own risk.
12
12
  import toml as toml
13
13
  from io import StringIO, BytesIO
14
14
  import numpy as np
15
+ from datetime import date
15
16
  import os
16
17
 
17
18
  from owlplanner import plan
@@ -302,6 +303,11 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
302
303
  if name != "None" and name not in p.inames:
303
304
  raise ValueError(f"Unknown name {name} for noRothConversions.")
304
305
 
306
+ # Rebase startRothConversions on year change.
307
+ thisyear = date.today().year
308
+ year = p.solverOptions.get("startRothConversions", thisyear)
309
+ diconf["Solver Options"]["startRothConversions"] = max(year, thisyear)
310
+
305
311
  # Results.
306
312
  p.setDefaultPlots(diconf["Results"]["Default plots"])
307
313
 
@@ -1143,6 +1143,17 @@ class Plan(object):
1143
1143
  # Should we adjust Roth conversion cap with inflation?
1144
1144
  B.set0_Ub(_q2(Cx, i, n, Ni, Nn), rhsopt)
1145
1145
 
1146
+ # Process startRothConversions option.
1147
+ if "startRothConversions" in options:
1148
+ rhsopt = options["startRothConversions"]
1149
+ thisyear = date.today().year
1150
+ yearn = max(rhsopt - thisyear, 0)
1151
+
1152
+ for i in range(Ni):
1153
+ nstart = min(yearn, self.horizons[i])
1154
+ for n in range(0, nstart):
1155
+ B.set0_Ub(_q2(Cx, i, n, Ni, Nn), zero)
1156
+
1146
1157
  # Process noRothConversions option. Also valid when N_i == 1, why not?
1147
1158
  if "noRothConversions" in options and options["noRothConversions"] != "None":
1148
1159
  rhsopt = options["noRothConversions"]
@@ -1197,7 +1208,8 @@ class Plan(object):
1197
1208
  # Account for time elapsed in the current year.
1198
1209
  spending *= units * self.yearFracLeft
1199
1210
  # self.mylog.vprint('Maximizing bequest with desired net spending of:', u.d(spending))
1200
- A.addNewRow({_q1(Cg, 0): 1}, spLo * spending, spHi * spending)
1211
+ # To allow slack in first year, Cg can be made Nn+1 and store basis in g[Nn].
1212
+ A.addNewRow({_q1(Cg, 0, Nn): 1}, spending, spending)
1201
1213
 
1202
1214
  # Set initial balances through constraints.
1203
1215
  for i in range(Ni):
@@ -1370,7 +1382,7 @@ class Plan(object):
1370
1382
  # Now build a solver-neutral objective vector.
1371
1383
  c = abc.Objective(self.nvars)
1372
1384
  if objective == "maxSpending":
1373
- # c.setElem(_q1(Cg, 0, Nn), -1)
1385
+ # c.setElem(_q1(Cg, 0, Nn), -1) # Only OK in implemention without slack.
1374
1386
  for n in range(Nn):
1375
1387
  c.setElem(_q1(Cg, n, Nn), -1/self.gamma_n[n])
1376
1388
  elif objective == "maxBequest":
@@ -1615,16 +1627,17 @@ class Plan(object):
1615
1627
  knownObjectives = ["maxBequest", "maxSpending"]
1616
1628
  knownSolvers = ["HiGHS", "MOSEK"]
1617
1629
  knownOptions = [
1618
- "units",
1619
- "maxRothConversion",
1620
- "netSpending",
1621
- "spendingSlack",
1622
1630
  "bequest",
1623
1631
  "bigM",
1632
+ "maxRothConversion",
1633
+ "netSpending",
1624
1634
  "noRothConversions",
1625
- "withMedicare",
1626
- "solver",
1627
1635
  "previousMAGIs",
1636
+ "solver",
1637
+ "spendingSlack",
1638
+ "startRothConversions",
1639
+ "units",
1640
+ "withMedicare",
1628
1641
  ]
1629
1642
  # We will modify options if required.
1630
1643
  if options is None:
@@ -36,7 +36,7 @@ rates_nonTCJA = np.array([0.10, 0.15, 0.25, 0.28, 0.33, 0.35, 0.396])
36
36
  taxBrackets_TCJA = np.array(
37
37
  [
38
38
  [11925, 48475, 103350, 197300, 250525, 626350, 9999999],
39
- [23850, 96950, 206700, 394600, 501050, 751700, 9999999],
39
+ [23850, 96950, 206700, 394600, 501050, 751600, 9999999],
40
40
  ]
41
41
  )
42
42
 
@@ -0,0 +1 @@
1
+ __version__ = "2025.04.01"
@@ -30,6 +30,7 @@ Copyright © 2024 - Martin-D. Lacasse
30
30
  [Seaborn](https://seaborn.pydata.org),
31
31
  [Scipy](https://scipy.org),
32
32
  [openpyxl](https://openpyxl.readthedocs.io),
33
+ [odfpy](https://https://pypi.org/project/odfpy),
33
34
  [toml](https://toml.io),
34
35
  and [Streamlit](https://streamlit.io) for the front-end
35
36
  - Contributors: Josh (noimjosh@gmail.com) for Docker image code.
@@ -41,12 +42,10 @@ or directly by [email](mailto://martin.d.lacasse@gmail.com).
41
42
 
42
43
  #### :orange[Privacy]
43
44
  - This app does not store or forward any information. All data entered is lost
44
- after a session is closed. However, you can choose to download selected data to your own
45
- computer before closing the session.
46
- Source code is publicly available and can be inspected in the repository.
45
+ after a session is closed. You can choose to download selected parts of your
46
+ own data to your computer before closing the session.
47
47
 
48
- #### :orange[Disclaimers]
49
- - I am not a financial planner. You make your own decisions.
50
- - This program comes with no guarantee. Use at your own risk.
48
+ #### :orange[Disclaimer]
49
+ - This program is provided for educational purposes and comes with no guarantee. Use at your own risk.
51
50
  """
52
51
  )
@@ -51,7 +51,8 @@ else:
51
51
  col1, col2, col3 = st.columns(3, gap="large", vertical_alignment="top")
52
52
  with col1:
53
53
  kz.initKey("surplusFraction", 0.5)
54
- helpmsg = "When beneficiary fractions are not all 1, set surplus deposits to all go to survivor's account."
54
+ helpmsg = ("When beneficiary fractions not all 1, "
55
+ "set surplus deposits to all go to account of first spouse to pass.")
55
56
  ret = kz.getNum(
56
57
  f"Fraction deposited in {iname1}'s taxable account",
57
58
  "surplusFraction",
@@ -55,7 +55,8 @@ are entered and reported in unit dollars.
55
55
 
56
56
  If you are accessing Owl through the Chrome browser,
57
57
  the performance manager might be configured to disable hidden or inactive tabs.
58
- This will cause your Owl session to inadvertently reset, and losing the state of the calculator.
58
+ This could cause your Owl session to inadvertently reset when idling for too long,
59
+ and losing the state of the calculator.
59
60
  To avoid this, configure Chrome to keep the page active using
60
61
  `More Tools` -> `Performance` -> `Always keep these sites active` and
61
62
  add the site *owlplanner.streamlit.app*.
@@ -80,8 +81,13 @@ and life expectancies are required. A starting date for the plan determines when
80
81
  starts in the first year. Plan still ends at the end of the year when all individuals
81
82
  have passed according to the specified life expectancies.
82
83
 
84
+ A typical workflow will involve creating
85
+ a base case, and duplicating it a few times with slight parameter changes
86
+ for investigating the resulting effects.
87
+ It is recommended to rename the case to reflect the change in parameters.
83
88
  When duplicating a scenario, make sure to visit all pages in the **Case Setup** section
84
- and verify that all parameters are as intended.
89
+ and verify that all parameters are as intended. When all cases were succussfully run,
90
+ results of the different cases can be compared side-by-side in the `Output Files` section.
85
91
 
86
92
  ##### Initializing the life parameters for the realization
87
93
  Start with the `Case selector` box and choose one of `New case...` or `Upload case file...`.
@@ -291,6 +297,14 @@ and a time delay, in years from today, before the non-flat behavior starts to ac
291
297
  Values default to 15%, 12%, and 0 year respectively, but they are fully configurable
292
298
  for experimentation and to fit your anticipated lifestyle.
293
299
 
300
+ A slack variable can also be adjusted. This variable allows the net spending to deviate from
301
+ the desired profile in order to maximize the objective. This is provided mostly for educational purpose
302
+ as maximizing the total net spending will involve leaving the savings invested for as long as possible,
303
+ and therefore this will favor smaller spending early in the plan and larger towards the end.
304
+ This tension between maximizing a dollar amount and the utility of money then becomes evident.
305
+ While the health of the individuals and therefore the utility of money is higher at the beginning
306
+ of retirement, maximizing the total spending or bequest will pull in an opposite direction.
307
+
294
308
  For married couples, the survivor's
295
309
  net spending percentage is also configurable. A value of 60% is typically used.
296
310
  The selected profile multiplies
@@ -1,4 +1,5 @@
1
1
  import streamlit as st
2
+ from datetime import date
2
3
 
3
4
  import sskeys as kz
4
5
  import owlbridge as owb
@@ -49,10 +50,15 @@ else:
49
50
  kz.initKey("readRothX", False)
50
51
  fromFile = kz.getKey("readRothX")
51
52
  kz.initKey("maxRothConversion", 50)
52
- ret = kz.getNum("Maximum Roth conversion (\\$k)", "maxRothConversion", disabled=fromFile, help=helpmsg)
53
+ ret = kz.getNum("Maximum annual Roth conversion (\\$k)", "maxRothConversion", disabled=fromFile, help=helpmsg)
53
54
  ret = kz.getToggle("Convert as in wages and contributions tables", "readRothX")
54
55
 
55
56
  with col2:
57
+ helpmsg = "Do not perform Roth conversions before that year."
58
+ thisyear = date.today().year
59
+ kz.initKey("startRothConversions", thisyear)
60
+ ret = kz.getIntNum("Year to start Roth conversions", "startRothConversions", min_value=thisyear,
61
+ disabled=fromFile, help=helpmsg)
56
62
  if kz.getKey("status") == "married":
57
63
  iname1 = kz.getKey("iname1")
58
64
  choices = ["None", iname0, iname1]
@@ -3,8 +3,9 @@ matplotlib
3
3
  seaborn
4
4
  pandas
5
5
  openpyxl
6
+ odfpy
6
7
  scipy
7
8
  streamlit
8
9
  toml
9
10
  # --extra-index-url https://test.pypi.org/simple
10
- owlplanner >= 2025.03.27
11
+ owlplanner >= 2025.04.01
@@ -360,7 +360,7 @@ def getSolveParameters():
360
360
  objective = "maxBequest"
361
361
 
362
362
  options = {}
363
- optList = ["netSpending", "maxRothConversion", "noRothConversions",
363
+ optList = ["netSpending", "maxRothConversion", "noRothConversions", "startRothConversions",
364
364
  "withMedicare", "bequest", "solver", "spendingSlack"]
365
365
  for opt in optList:
366
366
  val = getKey(opt)
@@ -510,8 +510,8 @@ def getToggle(text, nkey, callback=setpull, disabled=False, help=None):
510
510
  )
511
511
 
512
512
 
513
- def divider(color):
514
- st.html("<style> hr {border-color: %s;}</style><hr>" % color)
513
+ def divider(color, width="auto"):
514
+ st.html("<style> hr {border-color: %s;width: %s}</style><hr>" % (color, width))
515
515
 
516
516
 
517
517
  def getColors():
@@ -545,7 +545,7 @@ def titleBar(txt, choices=None):
545
545
  /* background-color: %s; */
546
546
  color: %s;
547
547
  top: 2.875rem;
548
- z-index: 999;
548
+ z-index: 100;
549
549
  }
550
550
  </style>""" % (bc, fc), unsafe_allow_html=True
551
551
  )
@@ -565,5 +565,6 @@ def titleBar(txt, choices=None):
565
565
  args=[nkey],
566
566
  )
567
567
 
568
- divider("white")
568
+ divider("white", "99%")
569
+
569
570
  return ret
@@ -1 +0,0 @@
1
- __version__ = "2025.03.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