owlplanner 2025.2.20__tar.gz → 2025.2.22__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 (101) hide show
  1. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/PKG-INFO +1 -1
  2. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/owl.tex +1 -2
  3. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/examples/case_jack+jill.toml +1 -0
  4. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/examples/case_joe.toml +1 -0
  5. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/examples/case_john+sally.toml +1 -0
  6. owlplanner-2025.2.22/examples/case_jon+jane.toml +57 -0
  7. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/examples/case_kim+sam-bequest.toml +1 -0
  8. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/examples/case_kim+sam-spending.toml +1 -0
  9. owlplanner-2025.2.22/examples/jon+jane.xlsx +0 -0
  10. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/pyproject.toml +1 -1
  11. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/src/owlplanner/config.py +8 -1
  12. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/src/owlplanner/plan.py +10 -1
  13. owlplanner-2025.2.22/src/owlplanner/version.py +1 -0
  14. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/Create_Case.py +4 -1
  15. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/owlbridge.py +7 -1
  16. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/requirements.txt +1 -1
  17. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/sskeys.py +2 -1
  18. owlplanner-2025.2.20/src/owlplanner/version.py +0 -1
  19. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/.devcontainer/devcontainer.json +0 -0
  20. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/.flake8 +0 -0
  21. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/.gitattributes +0 -0
  22. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/.github/workflows/github-actions-runtests.yml +0 -0
  23. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/.gitignore +0 -0
  24. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/INSTALL.md +0 -0
  25. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/LICENSE +0 -0
  26. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/README.md +0 -0
  27. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/USER_GUIDE.md +0 -0
  28. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docker/Dockerfile +0 -0
  29. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docker/README.md +0 -0
  30. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docker/docker-compose.yml +0 -0
  31. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docker/fastentrypoint.sh +0 -0
  32. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/AD-taxDef.png +0 -0
  33. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/AD-taxFree.png +0 -0
  34. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/AD-taxable.png +0 -0
  35. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/Hist_Bequest.png +0 -0
  36. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/Hist_Spending.png +0 -0
  37. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/MC-tutorial2a.png +0 -0
  38. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/MC-tutorial2b.png +0 -0
  39. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/OwlUI.png +0 -0
  40. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/allocations.png +0 -0
  41. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/owl.png +0 -0
  42. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/profile.png +0 -0
  43. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/ratesCorrelations.png +0 -0
  44. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/ratesPlot.png +0 -0
  45. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/savingsPlot.png +0 -0
  46. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/sourcesPlot.png +0 -0
  47. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/spendingPlot.png +0 -0
  48. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/taxIncomePlot.png +0 -0
  49. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/images/taxesPlot.png +0 -0
  50. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/docs/owl.pdf +0 -0
  51. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/examples/jack+jill.xlsx +0 -0
  52. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/examples/joe.xlsx +0 -0
  53. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/examples/john+sally.xlsx +0 -0
  54. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/examples/template.xlsx +0 -0
  55. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/notebooks/john+sally.ipynb +0 -0
  56. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/notebooks/kim+sam.ipynb +0 -0
  57. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/notebooks/template.ipynb +0 -0
  58. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/notebooks/tutorial_1.ipynb +0 -0
  59. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/notebooks/tutorial_2.ipynb +0 -0
  60. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/notebooks/tutorial_3.ipynb +0 -0
  61. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/owlplanner.cmd +0 -0
  62. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/owlplanner.sh +0 -0
  63. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/requirements.txt +0 -0
  64. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/src/owlplanner/__init__.py +0 -0
  65. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/src/owlplanner/abcapi.py +0 -0
  66. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/src/owlplanner/data/__init__.py +0 -0
  67. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/src/owlplanner/data/rates.csv +0 -0
  68. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/src/owlplanner/logging.py +0 -0
  69. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/src/owlplanner/progress.py +0 -0
  70. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/src/owlplanner/rates.py +0 -0
  71. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/src/owlplanner/tax2025.py +0 -0
  72. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/src/owlplanner/timelists.py +0 -0
  73. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/src/owlplanner/utils.py +0 -0
  74. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/tests/test_logger.py +0 -0
  75. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/tests/test_regressions.py +0 -0
  76. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/tests/test_repro.py +0 -0
  77. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/tests/test_toml_cases.py +0 -0
  78. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/tests/test_units.py +0 -0
  79. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ttt.py +0 -0
  80. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/About_Owl.py +0 -0
  81. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/Asset_Allocation.py +0 -0
  82. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/Assets.py +0 -0
  83. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/Documentation.py +0 -0
  84. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/Fixed_Income.py +0 -0
  85. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/Graphs.py +0 -0
  86. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/Historical_Range.py +0 -0
  87. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/Logs.py +0 -0
  88. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/Monte_Carlo.py +0 -0
  89. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/Optimization_Parameters.py +0 -0
  90. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/Output_Files.py +0 -0
  91. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/Quick_Start.py +0 -0
  92. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/README.md +0 -0
  93. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/Rates_Selection.py +0 -0
  94. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/Settings.py +0 -0
  95. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/Wages_And_Contributions.py +0 -0
  96. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/Worksheets.py +0 -0
  97. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/main+fonts.py +0 -0
  98. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/main.py +0 -0
  99. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/plots.py +0 -0
  100. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/progress.py +0 -0
  101. {owlplanner-2025.2.20 → owlplanner-2025.2.22}/ui/style.css +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.2.20
3
+ Version: 2025.2.22
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
@@ -607,8 +607,7 @@ add the market returns to the savings balances.
607
607
  if needed due to RMDs or receiving large sums of money,
608
608
  could be made at the end of the year. Let's formulate this approach
609
609
  in more detail and investigate for potential problems.
610
- Timing controls which terms get multiplied by the rate of return $(1 + \tau_{kn})$
611
- for a particular asset $k$.
610
+ Timing controls which terms get multiplied by the rate of return $(1 + \mathcal{T}_{ijn})$.
612
611
  Therefore, our current choice would yield
613
612
  \begin{eqnarray}
614
613
  \label{Eq:C3a}
@@ -1,4 +1,5 @@
1
1
  "Plan Name" = "jack+jill"
2
+ Description = "This example case is for Jack and Jill, a married couple."
2
3
 
3
4
  ["Basic Info"]
4
5
  Status = "married"
@@ -1,4 +1,5 @@
1
1
  "Plan Name" = "joe"
2
+ Description = "This example case is for Joe, a single individual."
2
3
 
3
4
  ["Basic Info"]
4
5
  Status = "single"
@@ -1,4 +1,5 @@
1
1
  "Plan Name" = "john+sally"
2
+ Description = "This example reproduces the case of John and Sally, discussed by Eric Sajdak."
2
3
 
3
4
  ["Basic Info"]
4
5
  Status = "married"
@@ -0,0 +1,57 @@
1
+ "Plan Name" = "Jon+Jane"
2
+ Description = "The case of Jon and Jane was the base case discussed by i-orp."
3
+
4
+ ["Basic Info"]
5
+ Status = "married"
6
+ Names = [ "Jon", "Jane",]
7
+ "Birth year" = [ 1965, 1968,]
8
+ "Life expectancy" = [ 92, 92,]
9
+ "Start date" = "01-01"
10
+
11
+ [Assets]
12
+ "taxable savings balances" = [ 50.0, 0.0,]
13
+ "tax-deferred savings balances" = [ 900.0, 500.0,]
14
+ "tax-free savings balances" = [ 0.0, 0.0,]
15
+ "Beneficiary fractions" = [ 1.0, 1.0, 1.0,]
16
+ "Spousal surplus deposit fraction" = 0.0
17
+
18
+ ["Wages and Contributions"]
19
+ "Contributions file name" = "edited values"
20
+
21
+ ["Fixed Income"]
22
+ "Pension amounts" = [ 0.0, 0.0,]
23
+ "Pension ages" = [ 65, 65,]
24
+ "Pension indexed" = [ false, false,]
25
+ "Social security amounts" = [ 21.0, 21.0,]
26
+ "Social security ages" = [ 65, 65,]
27
+
28
+ ["Rates Selection"]
29
+ "Heirs rate on tax-deferred estate" = 30.0
30
+ "Long-term capital gain tax rate" = 15.0
31
+ "Dividend tax rate" = 2.0
32
+ "TCJA expiration year" = 2026
33
+ Method = "user"
34
+ Values = [ 10.0, 0.0, 0.0, 3.5000000000000004,]
35
+ From = 1928
36
+ To = 2024
37
+
38
+ ["Asset Allocation"]
39
+ "Interpolation method" = "linear"
40
+ "Interpolation center" = 15.0
41
+ "Interpolation width" = 5.0
42
+ Type = "individual"
43
+ generic = [ [ [ 100, 0, 0, 0,], [ 100, 0, 0, 0,],], [ [ 100, 0, 0, 0,], [ 100, 0, 0, 0,],],]
44
+
45
+ ["Optimization Parameters"]
46
+ "Spending profile" = "flat"
47
+ "Surviving spouse spending percent" = 60
48
+ Objective = "maxSpending"
49
+
50
+ ["Solver Options"]
51
+ maxRothConversion = 0.0
52
+ noRothConversions = "None"
53
+ withMedicare = true
54
+ bequest = 30.0
55
+
56
+ [Results]
57
+ "Default plots" = "today"
@@ -1,4 +1,5 @@
1
1
  "Plan Name" = "kim+sam-bequest"
2
+ Description = "Kim and Sam is an example case for optimizing bequest and Roth conversions."
2
3
 
3
4
  ["Basic Info"]
4
5
  Status = "married"
@@ -1,4 +1,5 @@
1
1
  "Plan Name" = "kim+sam-spending"
2
+ Description = "Kim and Sam is an example case for optimizing spending and Roth conversions."
2
3
 
3
4
  ["Basic Info"]
4
5
  Status = "married"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "owlplanner"
7
- version = "2025.02.20"
7
+ version = "2025.02.22"
8
8
  authors = [
9
9
  { name="Martin-D. Lacasse", email="martin.d.lacasse@gmail.com" },
10
10
  ]
@@ -28,6 +28,7 @@ def saveConfig(plan, file, mylog):
28
28
 
29
29
  diconf = {}
30
30
  diconf["Plan Name"] = plan._name
31
+ diconf["Description"] = plan._description
31
32
 
32
33
  # Basic Info.
33
34
  diconf["Basic Info"] = {
@@ -180,11 +181,12 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
180
181
  # status = diconf['Basic Info']['Status']
181
182
  yobs = diconf["Basic Info"]["Birth year"]
182
183
  expectancy = diconf["Basic Info"]["Life expectancy"]
183
- startDate = diconf["Basic Info"]["Start date"]
184
+ startDate = diconf["Basic Info"].get("Start date", "today")
184
185
  icount = len(yobs)
185
186
  s = ["", "s"][icount - 1]
186
187
  mylog.vprint(f"Plan for {icount} individual{s}: {inames}.")
187
188
  p = plan.Plan(inames, yobs, expectancy, name, startDate=startDate, verbose=True, logstreams=logstreams)
189
+ plan._description = diconf.get("Description", "")
188
190
 
189
191
  # Assets.
190
192
  balances = {}
@@ -295,6 +297,11 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
295
297
  # Solver Options.
296
298
  p.solverOptions = diconf["Solver Options"]
297
299
 
300
+ # Check consistency of noRothConversions.
301
+ name = p.solverOptions.get("noRothConversions", "None")
302
+ if name != "None" and name not in p.inames:
303
+ raise ValueError(f"Unknown name {name} for noRothConversions.")
304
+
298
305
  # Results.
299
306
  p.setDefaultPlots(diconf["Results"]["Default plots"])
300
307
 
@@ -239,6 +239,7 @@ class Plan(object):
239
239
  self.interpCenter = 15
240
240
  self.interpWidth = 5
241
241
 
242
+ self._description = ''
242
243
  self.defaultPlots = "nominal"
243
244
  self.defaultSolver = "HiGHS"
244
245
 
@@ -406,6 +407,12 @@ class Plan(object):
406
407
  self.mylog.vprint(f"Renaming plan {self._name} -> {newname}.")
407
408
  self._name = newname
408
409
 
410
+ def setDescription(self, description):
411
+ """
412
+ Set a text description of the plan.
413
+ """
414
+ self._description = description
415
+
409
416
  def setSpousalDepositFraction(self, eta):
410
417
  """
411
418
  Set spousal deposit and withdrawal fraction. Default 0.5.
@@ -2069,6 +2076,8 @@ class Plan(object):
2069
2076
  now = self.year_n[0]
2070
2077
  dic = {}
2071
2078
  # Results
2079
+ dic["Plan name"] = self._name
2080
+ dic["Brief description"] = self._description
2072
2081
  dic[f"Net yearly spending basis in {now}$"] = u.d(self.g_n[0] / self.xi_n[0])
2073
2082
  dic[f"Net yearly spending for year {now}"] = u.d(self.g_n[0] / self.yearFracLeft)
2074
2083
  dic[f"Net spending remaining in year {now}"] = u.d(self.g_n[0])
@@ -2895,7 +2904,7 @@ def _stackPlot(x, inames, title, irange, series, snames, location, yformat="\\$k
2895
2904
 
2896
2905
  if len(nonzeroSeries) == 0:
2897
2906
  # print('Nothing to plot for', title)
2898
- return None
2907
+ return None, None
2899
2908
 
2900
2909
  fig, ax = plt.subplots(figsize=(6, 4))
2901
2910
  plt.grid(visible="both")
@@ -0,0 +1 @@
1
+ __version__ = "2025.02.22"
@@ -40,7 +40,6 @@ else:
40
40
  on_change=kz.renameCase,
41
41
  args=["caseNewName"],
42
42
  key="_caseNewName",
43
- placeholder="Enter a name",
44
43
  help=helpmsg,
45
44
  )
46
45
 
@@ -61,6 +60,10 @@ else:
61
60
  horizontal=True,
62
61
  )
63
62
 
63
+ kz.initKey("description", "")
64
+ helpmsg = "Provide a short distinguishing description of the case."
65
+ description = kz.getText("Brief description", "description", help=helpmsg)
66
+
64
67
  col1, col2 = st.columns(2, gap="large", vertical_alignment="top")
65
68
  with col1:
66
69
  kz.initKey("iname0", "")
@@ -98,6 +98,7 @@ def prepareRun(plan):
98
98
  st.error(f"Failed setting beneficiary fractions: {e}")
99
99
  return
100
100
 
101
+ plan.setDescription(kz.getKey("description"))
101
102
  plan.setHeirsTaxRate(kz.getKey("heirsTx"))
102
103
  plan.setLongTermCapitalTaxRate(kz.getKey("gainTx"))
103
104
  plan.setDividendRate(kz.getKey("divRate"))
@@ -422,7 +423,11 @@ def plotSingleResults(plan):
422
423
  st.write("##### Assets Distribution")
423
424
  morecols = st.columns(3, gap="small")
424
425
  for fig in figs:
425
- morecols[c].pyplot(fig)
426
+ if fig:
427
+ morecols[c].pyplot(fig)
428
+ else:
429
+ morecols[c].write("#\n<div style='text-align: center'> This plot is empty </div>",
430
+ unsafe_allow_html=True)
426
431
  c = (c + 1) % 3
427
432
 
428
433
 
@@ -557,6 +562,7 @@ def genDic(plan):
557
562
  dic = {}
558
563
  dic["plan"] = plan
559
564
  dic["name"] = plan._name
565
+ dic["description"] = plan._description
560
566
  dic["summary"] = ""
561
567
  dic["casetoml"] = ""
562
568
  dic["caseStatus"] = "new"
@@ -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.20
10
+ owlplanner >= 2025.02.22
@@ -438,7 +438,7 @@ def getNum(
438
438
  )
439
439
 
440
440
 
441
- def getText(text, nkey, disabled=False, callback=setpull, placeholder=None):
441
+ def getText(text, nkey, disabled=False, callback=setpull, placeholder=None, help=None):
442
442
  return st.text_input(
443
443
  text,
444
444
  value=getKey(nkey),
@@ -447,6 +447,7 @@ def getText(text, nkey, disabled=False, callback=setpull, placeholder=None):
447
447
  args=[nkey],
448
448
  key="_" + nkey,
449
449
  placeholder=placeholder,
450
+ help=help,
450
451
  )
451
452
 
452
453
 
@@ -1 +0,0 @@
1
- __version__ = "2025.02.20"
File without changes
File without changes
File without changes
File without changes