owlplanner 2025.2.22__tar.gz → 2025.2.23__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.22 → owlplanner-2025.2.23}/PKG-INFO +1 -1
  2. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/pyproject.toml +1 -1
  3. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/src/owlplanner/config.py +49 -49
  4. owlplanner-2025.2.23/src/owlplanner/version.py +1 -0
  5. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/Create_Case.py +4 -3
  6. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/owlbridge.py +2 -0
  7. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/requirements.txt +1 -1
  8. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/sskeys.py +13 -0
  9. owlplanner-2025.2.22/src/owlplanner/version.py +0 -1
  10. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/.devcontainer/devcontainer.json +0 -0
  11. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/.flake8 +0 -0
  12. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/.gitattributes +0 -0
  13. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/.github/workflows/github-actions-runtests.yml +0 -0
  14. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/.gitignore +0 -0
  15. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/INSTALL.md +0 -0
  16. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/LICENSE +0 -0
  17. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/README.md +0 -0
  18. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/USER_GUIDE.md +0 -0
  19. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docker/Dockerfile +0 -0
  20. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docker/README.md +0 -0
  21. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docker/docker-compose.yml +0 -0
  22. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docker/fastentrypoint.sh +0 -0
  23. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/AD-taxDef.png +0 -0
  24. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/AD-taxFree.png +0 -0
  25. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/AD-taxable.png +0 -0
  26. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/Hist_Bequest.png +0 -0
  27. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/Hist_Spending.png +0 -0
  28. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/MC-tutorial2a.png +0 -0
  29. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/MC-tutorial2b.png +0 -0
  30. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/OwlUI.png +0 -0
  31. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/allocations.png +0 -0
  32. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/owl.png +0 -0
  33. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/profile.png +0 -0
  34. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/ratesCorrelations.png +0 -0
  35. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/ratesPlot.png +0 -0
  36. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/savingsPlot.png +0 -0
  37. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/sourcesPlot.png +0 -0
  38. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/spendingPlot.png +0 -0
  39. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/taxIncomePlot.png +0 -0
  40. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/images/taxesPlot.png +0 -0
  41. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/owl.pdf +0 -0
  42. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/docs/owl.tex +0 -0
  43. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/examples/case_jack+jill.toml +0 -0
  44. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/examples/case_joe.toml +0 -0
  45. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/examples/case_john+sally.toml +0 -0
  46. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/examples/case_jon+jane.toml +0 -0
  47. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/examples/case_kim+sam-bequest.toml +0 -0
  48. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/examples/case_kim+sam-spending.toml +0 -0
  49. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/examples/jack+jill.xlsx +0 -0
  50. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/examples/joe.xlsx +0 -0
  51. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/examples/john+sally.xlsx +0 -0
  52. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/examples/jon+jane.xlsx +0 -0
  53. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/examples/template.xlsx +0 -0
  54. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/notebooks/john+sally.ipynb +0 -0
  55. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/notebooks/kim+sam.ipynb +0 -0
  56. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/notebooks/template.ipynb +0 -0
  57. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/notebooks/tutorial_1.ipynb +0 -0
  58. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/notebooks/tutorial_2.ipynb +0 -0
  59. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/notebooks/tutorial_3.ipynb +0 -0
  60. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/owlplanner.cmd +0 -0
  61. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/owlplanner.sh +0 -0
  62. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/requirements.txt +0 -0
  63. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/src/owlplanner/__init__.py +0 -0
  64. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/src/owlplanner/abcapi.py +0 -0
  65. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/src/owlplanner/data/__init__.py +0 -0
  66. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/src/owlplanner/data/rates.csv +0 -0
  67. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/src/owlplanner/logging.py +0 -0
  68. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/src/owlplanner/plan.py +0 -0
  69. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/src/owlplanner/progress.py +0 -0
  70. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/src/owlplanner/rates.py +0 -0
  71. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/src/owlplanner/tax2025.py +0 -0
  72. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/src/owlplanner/timelists.py +0 -0
  73. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/src/owlplanner/utils.py +0 -0
  74. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/tests/test_logger.py +0 -0
  75. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/tests/test_regressions.py +0 -0
  76. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/tests/test_repro.py +0 -0
  77. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/tests/test_toml_cases.py +0 -0
  78. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/tests/test_units.py +0 -0
  79. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ttt.py +0 -0
  80. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/About_Owl.py +0 -0
  81. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/Asset_Allocation.py +0 -0
  82. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/Assets.py +0 -0
  83. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/Documentation.py +0 -0
  84. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/Fixed_Income.py +0 -0
  85. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/Graphs.py +0 -0
  86. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/Historical_Range.py +0 -0
  87. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/Logs.py +0 -0
  88. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/Monte_Carlo.py +0 -0
  89. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/Optimization_Parameters.py +0 -0
  90. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/Output_Files.py +0 -0
  91. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/Quick_Start.py +0 -0
  92. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/README.md +0 -0
  93. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/Rates_Selection.py +0 -0
  94. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/Settings.py +0 -0
  95. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/Wages_And_Contributions.py +0 -0
  96. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/Worksheets.py +0 -0
  97. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/main+fonts.py +0 -0
  98. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/main.py +0 -0
  99. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/plots.py +0 -0
  100. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/progress.py +0 -0
  101. {owlplanner-2025.2.22 → owlplanner-2025.2.23}/ui/style.css +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.2.22
3
+ Version: 2025.2.23
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.02.22"
7
+ version = "2025.02.23"
8
8
  authors = [
9
9
  { name="Martin-D. Lacasse", email="martin.d.lacasse@gmail.com" },
10
10
  ]
@@ -19,7 +19,7 @@ from owlplanner import logging
19
19
  from owlplanner.rates import FROM, TO
20
20
 
21
21
 
22
- def saveConfig(plan, file, mylog):
22
+ def saveConfig(myplan, file, mylog):
23
23
  """
24
24
  Save case parameters and return a dictionary containing all parameters.
25
25
  """
@@ -27,87 +27,87 @@ def saveConfig(plan, file, mylog):
27
27
  accountTypes = ["taxable", "tax-deferred", "tax-free"]
28
28
 
29
29
  diconf = {}
30
- diconf["Plan Name"] = plan._name
31
- diconf["Description"] = plan._description
30
+ diconf["Plan Name"] = myplan._name
31
+ diconf["Description"] = myplan._description
32
32
 
33
33
  # Basic Info.
34
34
  diconf["Basic Info"] = {
35
- "Status": ["unknown", "single", "married"][plan.N_i],
36
- "Names": plan.inames,
37
- "Birth year": plan.yobs.tolist(),
38
- "Life expectancy": plan.expectancy.tolist(),
39
- "Start date": plan.startDate,
35
+ "Status": ["unknown", "single", "married"][myplan.N_i],
36
+ "Names": myplan.inames,
37
+ "Birth year": myplan.yobs.tolist(),
38
+ "Life expectancy": myplan.expectancy.tolist(),
39
+ "Start date": myplan.startDate,
40
40
  }
41
41
 
42
42
  # Assets.
43
43
  diconf["Assets"] = {}
44
- for j in range(plan.N_j):
45
- amounts = plan.beta_ij[:, j] / 1000
44
+ for j in range(myplan.N_j):
45
+ amounts = myplan.beta_ij[:, j] / 1000
46
46
  diconf["Assets"][f"{accountTypes[j]} savings balances"] = amounts.tolist()
47
- if plan.N_i == 2:
48
- diconf["Assets"]["Beneficiary fractions"] = plan.phi_j.tolist()
49
- diconf["Assets"]["Spousal surplus deposit fraction"] = plan.eta
47
+ if myplan.N_i == 2:
48
+ diconf["Assets"]["Beneficiary fractions"] = myplan.phi_j.tolist()
49
+ diconf["Assets"]["Spousal surplus deposit fraction"] = myplan.eta
50
50
 
51
51
  # Wages and Contributions.
52
- diconf["Wages and Contributions"] = {"Contributions file name": plan.timeListsFileName}
52
+ diconf["Wages and Contributions"] = {"Contributions file name": myplan.timeListsFileName}
53
53
 
54
54
  # Fixed Income.
55
55
  diconf["Fixed Income"] = {
56
- "Pension amounts": (plan.pensionAmounts / 1000).tolist(),
57
- "Pension ages": plan.pensionAges.tolist(),
58
- "Pension indexed": plan.pensionIndexed,
59
- "Social security amounts": (plan.ssecAmounts / 1000).tolist(),
60
- "Social security ages": plan.ssecAges.tolist(),
56
+ "Pension amounts": (myplan.pensionAmounts / 1000).tolist(),
57
+ "Pension ages": myplan.pensionAges.tolist(),
58
+ "Pension indexed": myplan.pensionIndexed,
59
+ "Social security amounts": (myplan.ssecAmounts / 1000).tolist(),
60
+ "Social security ages": myplan.ssecAges.tolist(),
61
61
  }
62
62
 
63
63
  # Rates Selection.
64
64
  diconf["Rates Selection"] = {
65
- "Heirs rate on tax-deferred estate": float(100 * plan.nu),
66
- "Long-term capital gain tax rate": float(100 * plan.psi),
67
- "Dividend tax rate": float(100 * plan.mu),
68
- "TCJA expiration year": plan.yTCJA,
69
- "Method": plan.rateMethod,
65
+ "Heirs rate on tax-deferred estate": float(100 * myplan.nu),
66
+ "Long-term capital gain tax rate": float(100 * myplan.psi),
67
+ "Dividend tax rate": float(100 * myplan.mu),
68
+ "TCJA expiration year": myplan.yTCJA,
69
+ "Method": myplan.rateMethod,
70
70
  }
71
- if plan.rateMethod in ["user", "stochastic"]:
72
- diconf["Rates Selection"]["Values"] = (100 * plan.rateValues).tolist()
73
- if plan.rateMethod in ["stochastic"]:
74
- diconf["Rates Selection"]["Standard deviations"] = (100 * plan.rateStdev).tolist()
75
- diconf["Rates Selection"]["Correlations"] = plan.rateCorr.tolist()
76
- if plan.rateMethod in ["historical average", "historical", "histochastic"]:
77
- diconf["Rates Selection"]["From"] = int(plan.rateFrm)
78
- diconf["Rates Selection"]["To"] = int(plan.rateTo)
71
+ if myplan.rateMethod in ["user", "stochastic"]:
72
+ diconf["Rates Selection"]["Values"] = (100 * myplan.rateValues).tolist()
73
+ if myplan.rateMethod in ["stochastic"]:
74
+ diconf["Rates Selection"]["Standard deviations"] = (100 * myplan.rateStdev).tolist()
75
+ diconf["Rates Selection"]["Correlations"] = myplan.rateCorr.tolist()
76
+ if myplan.rateMethod in ["historical average", "historical", "histochastic"]:
77
+ diconf["Rates Selection"]["From"] = int(myplan.rateFrm)
78
+ diconf["Rates Selection"]["To"] = int(myplan.rateTo)
79
79
  else:
80
80
  diconf["Rates Selection"]["From"] = int(FROM)
81
81
  diconf["Rates Selection"]["To"] = int(TO)
82
82
 
83
83
  # Asset Allocation.
84
84
  diconf["Asset Allocation"] = {
85
- "Interpolation method": plan.interpMethod,
86
- "Interpolation center": float(plan.interpCenter),
87
- "Interpolation width": float(plan.interpWidth),
88
- "Type": plan.ARCoord,
85
+ "Interpolation method": myplan.interpMethod,
86
+ "Interpolation center": float(myplan.interpCenter),
87
+ "Interpolation width": float(myplan.interpWidth),
88
+ "Type": myplan.ARCoord,
89
89
  }
90
- if plan.ARCoord == "account":
90
+ if myplan.ARCoord == "account":
91
91
  for accType in accountTypes:
92
- diconf["Asset Allocation"][accType] = plan.boundsAR[accType]
92
+ diconf["Asset Allocation"][accType] = myplan.boundsAR[accType]
93
93
  else:
94
- diconf["Asset Allocation"]["generic"] = plan.boundsAR["generic"]
94
+ diconf["Asset Allocation"]["generic"] = myplan.boundsAR["generic"]
95
95
 
96
96
  # Optimization Parameters.
97
97
  diconf["Optimization Parameters"] = {
98
- "Spending profile": plan.spendingProfile,
99
- "Surviving spouse spending percent": int(100 * plan.chi),
98
+ "Spending profile": myplan.spendingProfile,
99
+ "Surviving spouse spending percent": int(100 * myplan.chi),
100
100
  }
101
- if plan.spendingProfile == "smile":
102
- diconf["Optimization Parameters"]["Smile dip"] = int(plan.smileDip)
103
- diconf["Optimization Parameters"]["Smile increase"] = int(plan.smileIncrease)
104
- diconf["Optimization Parameters"]["Smile delay"] = int(plan.smileDelay)
101
+ if myplan.spendingProfile == "smile":
102
+ diconf["Optimization Parameters"]["Smile dip"] = int(myplan.smileDip)
103
+ diconf["Optimization Parameters"]["Smile increase"] = int(myplan.smileIncrease)
104
+ diconf["Optimization Parameters"]["Smile delay"] = int(myplan.smileDelay)
105
105
 
106
- diconf["Optimization Parameters"]["Objective"] = plan.objective
107
- diconf["Solver Options"] = plan.solverOptions
106
+ diconf["Optimization Parameters"]["Objective"] = myplan.objective
107
+ diconf["Solver Options"] = myplan.solverOptions
108
108
 
109
109
  # Results.
110
- diconf["Results"] = {"Default plots": plan.defaultPlots}
110
+ diconf["Results"] = {"Default plots": myplan.defaultPlots}
111
111
 
112
112
  if isinstance(file, str):
113
113
  filename = file
@@ -186,7 +186,7 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
186
186
  s = ["", "s"][icount - 1]
187
187
  mylog.vprint(f"Plan for {icount} individual{s}: {inames}.")
188
188
  p = plan.Plan(inames, yobs, expectancy, name, startDate=startDate, verbose=True, logstreams=logstreams)
189
- plan._description = diconf.get("Description", "")
189
+ p._description = diconf.get("Description", "")
190
190
 
191
191
  # Assets.
192
192
  balances = {}
@@ -0,0 +1 @@
1
+ __version__ = "2025.02.23"
@@ -17,7 +17,7 @@ if ret == kz.newCase:
17
17
  key="_newcase",
18
18
  on_change=kz.createNewCase,
19
19
  args=["newcase"],
20
- placeholder="Enter a name...",
20
+ placeholder="Enter a case name...",
21
21
  )
22
22
  elif ret == kz.loadCaseFile:
23
23
  # "<a href="Documentation" target="_self">Documentation</a>", unsafe_allow_html=True)
@@ -61,8 +61,9 @@ else:
61
61
  )
62
62
 
63
63
  kz.initKey("description", "")
64
- helpmsg = "Provide a short distinguishing description of the case."
65
- description = kz.getText("Brief description", "description", help=helpmsg)
64
+ helpmsg = "Provide a short distinguishing description for the case."
65
+ description = kz.getLongText("Brief description", "description", help=helpmsg,
66
+ placeholder="Enter a brief description...")
66
67
 
67
68
  col1, col2 = st.columns(2, gap="large", vertical_alignment="top")
68
69
  with col1:
@@ -20,6 +20,7 @@ def hasMOSEK():
20
20
  def createPlan():
21
21
  name = kz.currentCaseName()
22
22
  inames = [kz.getKey("iname0")]
23
+ description = kz.getKey("description")
23
24
  yobs = [kz.getKey("yob0")]
24
25
  life = [kz.getKey("life0")]
25
26
  startDate = kz.getKey("startDate")
@@ -37,6 +38,7 @@ def createPlan():
37
38
  st.error(f"Failed creation of plan '{name}': {e}")
38
39
  return
39
40
 
41
+ plan.setDescription(description)
40
42
  val = kz.getKey("plots")
41
43
  if val is not None:
42
44
  plan.setDefaultPlots(val)
@@ -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.22
10
+ owlplanner >= 2025.02.23
@@ -451,6 +451,19 @@ def getText(text, nkey, disabled=False, callback=setpull, placeholder=None, help
451
451
  )
452
452
 
453
453
 
454
+ def getLongText(text, nkey, disabled=False, callback=setpull, placeholder=None, help=None):
455
+ return st.text_area(
456
+ text,
457
+ value=getKey(nkey),
458
+ disabled=disabled,
459
+ on_change=callback,
460
+ args=[nkey],
461
+ key="_" + nkey,
462
+ placeholder=placeholder,
463
+ help=help,
464
+ )
465
+
466
+
454
467
  def getRadio(text, choices, nkey, callback=setpull, disabled=False, help=None):
455
468
  return st.radio(
456
469
  text,
@@ -1 +0,0 @@
1
- __version__ = "2025.02.22"
File without changes
File without changes
File without changes
File without changes