owlplanner 2025.2.9__tar.gz → 2025.2.10__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 (90) hide show
  1. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/PKG-INFO +1 -1
  2. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/pyproject.toml +1 -1
  3. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/src/owlplanner/abcapi.py +6 -6
  4. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/src/owlplanner/config.py +16 -16
  5. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/src/owlplanner/logging.py +1 -1
  6. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/src/owlplanner/plan.py +134 -178
  7. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/src/owlplanner/progress.py +1 -1
  8. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/src/owlplanner/rates.py +15 -15
  9. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/src/owlplanner/tax2025.py +1 -1
  10. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/src/owlplanner/timelists.py +5 -5
  11. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/src/owlplanner/utils.py +1 -1
  12. owlplanner-2025.2.10/src/owlplanner/version.py +1 -0
  13. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/requirements.txt +1 -1
  14. owlplanner-2025.2.9/src/owlplanner/version.py +0 -1
  15. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/.devcontainer/devcontainer.json +0 -0
  16. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/.flake8 +0 -0
  17. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/.github/workflows/github-actions-runtests.yml +0 -0
  18. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/.gitignore +0 -0
  19. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/INSTALL.md +0 -0
  20. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/LICENSE +0 -0
  21. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/README.md +0 -0
  22. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/AD-taxDef.png +0 -0
  23. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/AD-taxFree.png +0 -0
  24. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/AD-taxable.png +0 -0
  25. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/Hist_Bequest.png +0 -0
  26. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/Hist_Spending.png +0 -0
  27. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/MC-tutorial2a.png +0 -0
  28. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/MC-tutorial2b.png +0 -0
  29. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/OwlUI.png +0 -0
  30. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/allocations.png +0 -0
  31. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/owl.png +0 -0
  32. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/profile.png +0 -0
  33. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/ratesCorrelations.png +0 -0
  34. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/ratesPlot.png +0 -0
  35. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/savingsPlot.png +0 -0
  36. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/sourcesPlot.png +0 -0
  37. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/spendingPlot.png +0 -0
  38. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/taxIncomePlot.png +0 -0
  39. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/images/taxesPlot.png +0 -0
  40. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/owl.pdf +0 -0
  41. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/docs/owl.tex +0 -0
  42. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/examples/case_jack+jill.toml +0 -0
  43. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/examples/case_joe.toml +0 -0
  44. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/examples/case_john+sally.toml +0 -0
  45. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/examples/case_kim+sam-bequest.toml +0 -0
  46. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/examples/case_kim+sam-spending.toml +0 -0
  47. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/examples/jack+jill.xlsx +0 -0
  48. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/examples/joe.xlsx +0 -0
  49. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/examples/john+sally.xlsx +0 -0
  50. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/examples/template.xlsx +0 -0
  51. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/notebooks/john+sally.ipynb +0 -0
  52. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/notebooks/kim+sam.ipynb +0 -0
  53. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/notebooks/template.ipynb +0 -0
  54. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/notebooks/tutorial_1.ipynb +0 -0
  55. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/notebooks/tutorial_2.ipynb +0 -0
  56. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/notebooks/tutorial_3.ipynb +0 -0
  57. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/owlplanner.cmd +0 -0
  58. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/requirements.txt +0 -0
  59. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/src/owlplanner/__init__.py +0 -0
  60. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/src/owlplanner/data/__init__.py +0 -0
  61. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/src/owlplanner/data/rates.csv +0 -0
  62. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/test.py +0 -0
  63. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/tests/test_logger.py +0 -0
  64. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/tests/test_regressions.py +0 -0
  65. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/tests/test_repro.py +0 -0
  66. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/tests/test_toml_cases.py +0 -0
  67. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/tests/test_units.py +0 -0
  68. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/About_Owl.py +0 -0
  69. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/Asset_Allocation.py +0 -0
  70. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/Assets.py +0 -0
  71. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/Create_Case.py +0 -0
  72. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/Documentation.py +0 -0
  73. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/Fixed_Income.py +0 -0
  74. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/Graphs.py +0 -0
  75. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/Historical_Range.py +0 -0
  76. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/Logs.py +0 -0
  77. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/Monte_Carlo.py +0 -0
  78. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/Optimization_Parameters.py +0 -0
  79. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/Output_Files.py +0 -0
  80. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/Quick_Start.py +0 -0
  81. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/README.md +0 -0
  82. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/Rates_Selection.py +0 -0
  83. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/Settings.py +0 -0
  84. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/Wages_And_Contributions.py +0 -0
  85. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/Worksheets.py +0 -0
  86. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/main.py +0 -0
  87. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/owlbridge.py +0 -0
  88. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/plots.py +0 -0
  89. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/progress.py +0 -0
  90. {owlplanner-2025.2.9 → owlplanner-2025.2.10}/ui/sskeys.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.2.9
3
+ Version: 2025.2.10
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.09"
7
+ version = "2025.02.10"
8
8
  authors = [
9
9
  { name="Martin-D. Lacasse", email="martin.d.lacasse@gmail.com" },
10
10
  ]
@@ -43,7 +43,7 @@ class Row(object):
43
43
  """
44
44
  Add an element at index ``ind`` of value ``val`` to the row.
45
45
  """
46
- assert 0 <= ind and ind < self.nvars, "Index %d out of range." % ind
46
+ assert 0 <= ind and ind < self.nvars, f"Index {ind} out of range."
47
47
  self.ind.append(ind)
48
48
  self.val.append(val)
49
49
 
@@ -154,7 +154,7 @@ class Bounds(object):
154
154
  self.integrality = []
155
155
 
156
156
  def setBinary(self, ii):
157
- assert 0 <= ii and ii < self.nvars, "Index %d out of range." % ii
157
+ assert 0 <= ii and ii < self.nvars, f"Index {ii} out of range."
158
158
  self.ind.append(ii)
159
159
  self.lb.append(0)
160
160
  self.ub.append(1)
@@ -162,21 +162,21 @@ class Bounds(object):
162
162
  self.integrality.append(ii)
163
163
 
164
164
  def set0_Ub(self, ii, ub):
165
- assert 0 <= ii and ii < self.nvars, "Index %d out of range." % ii
165
+ assert 0 <= ii and ii < self.nvars, f"Index {ii} out of range."
166
166
  self.ind.append(ii)
167
167
  self.lb.append(0)
168
168
  self.ub.append(ub)
169
169
  self.key.append("ra")
170
170
 
171
171
  def setLb_Inf(self, ii, lb):
172
- assert 0 <= ii and ii < self.nvars, "Index %d out of range." % ii
172
+ assert 0 <= ii and ii < self.nvars, f"Index {ii} out of range."
173
173
  self.ind.append(ii)
174
174
  self.lb.append(lb)
175
175
  self.ub.append(np.inf)
176
176
  self.key.append("lo")
177
177
 
178
178
  def setRange(self, ii, lb, ub):
179
- assert 0 <= ii and ii < self.nvars, "Index %d out of range." % ii
179
+ assert 0 <= ii and ii < self.nvars, f"Index {ii} out of range."
180
180
  self.ind.append(ii)
181
181
  self.lb.append(lb)
182
182
  self.ub.append(ub)
@@ -223,7 +223,7 @@ class Objective(object):
223
223
  self.val = []
224
224
 
225
225
  def setElem(self, ind, val):
226
- assert 0 <= ind and ind < self.nvars, "Index %d out of range." % ind
226
+ assert 0 <= ind and ind < self.nvars, f"Index {ind} out of range."
227
227
  self.ind.append(ind)
228
228
  self.val.append(val)
229
229
 
@@ -42,7 +42,7 @@ def saveConfig(plan, file, mylog):
42
42
  diconf["Assets"] = {}
43
43
  for j in range(plan.N_j):
44
44
  amounts = plan.beta_ij[:, j] / 1000
45
- diconf["Assets"]["%s savings balances" % accountTypes[j]] = amounts.tolist()
45
+ diconf["Assets"][f"{accountTypes[j]} savings balances"] = amounts.tolist()
46
46
  if plan.N_i == 2:
47
47
  diconf["Assets"]["Beneficiary fractions"] = plan.phi_j.tolist()
48
48
  diconf["Assets"]["Spousal surplus deposit fraction"] = plan.eta
@@ -113,23 +113,23 @@ def saveConfig(plan, file, mylog):
113
113
  filename = filename + ".toml"
114
114
  if not filename.startswith("case_"):
115
115
  filename = "case_" + filename
116
- mylog.vprint("Saving plan case file as '%s'." % filename)
116
+ mylog.vprint(f"Saving plan case file as '{filename}'.")
117
117
 
118
118
  try:
119
119
  with open(filename, "w") as casefile:
120
120
  toml.dump(diconf, casefile, encoder=toml.TomlNumpyEncoder())
121
121
  except Exception as e:
122
- raise RuntimeError("Failed to save case file %s: %s" % (filename, e))
122
+ raise RuntimeError(f"Failed to save case file {filename}: {e}")
123
123
  elif isinstance(file, StringIO):
124
124
  try:
125
125
  string = toml.dumps(diconf, encoder=toml.TomlNumpyEncoder())
126
126
  file.write(string)
127
127
  except Exception as e:
128
- raise RuntimeError("Failed to save case to StringIO: %s", e)
128
+ raise RuntimeError(f"Failed to save case to StringIO: {e}")
129
129
  elif file is None:
130
130
  pass
131
131
  else:
132
- raise ValueError("Argument %s has unknown type" % type(file))
132
+ raise ValueError(f"Argument {type(file)} has unknown type")
133
133
 
134
134
  return diconf
135
135
 
@@ -151,27 +151,27 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
151
151
  if not filename.endswith(".toml"):
152
152
  filename = filename + ".toml"
153
153
 
154
- mylog.vprint("Reading plan from case file '%s'." % filename)
154
+ mylog.vprint(f"Reading plan from case file '{filename}'.")
155
155
 
156
156
  try:
157
157
  with open(filename, "r") as f:
158
158
  diconf = toml.load(f)
159
159
  except Exception as e:
160
- raise FileNotFoundError("File %s not found: %s" % (filename, e))
160
+ raise FileNotFoundError(f"File {filename} not found: {e}")
161
161
  elif isinstance(file, BytesIO):
162
162
  try:
163
163
  string = file.getvalue().decode("utf-8")
164
164
  diconf = toml.loads(string)
165
165
  except Exception as e:
166
- raise RuntimeError("Cannot read from BytesIO: %s" % e)
166
+ raise RuntimeError(f"Cannot read from BytesIO: {e}")
167
167
  elif isinstance(file, StringIO):
168
168
  try:
169
169
  string = file.getvalue()
170
170
  diconf = toml.loads(string)
171
171
  except Exception as e:
172
- raise RuntimeError("Cannot read from StringIO: %s" % e)
172
+ raise RuntimeError(f"Cannot read from StringIO: {e}")
173
173
  else:
174
- raise ValueError("%s not a valid type" % type(file))
174
+ raise ValueError(f"Type {type(file)} not a valid type")
175
175
 
176
176
  # Basic Info.
177
177
  name = diconf["Plan Name"]
@@ -181,14 +181,14 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
181
181
  expectancy = diconf["Basic Info"]["Life expectancy"]
182
182
  startDate = diconf["Basic Info"]["Start date"]
183
183
  icount = len(yobs)
184
-
185
- mylog.vprint("Plan for %d individual%s: %s." % (icount, ["", "s"][icount - 1], inames))
184
+ s = ["", "s"][icount - 1]
185
+ mylog.vprint(f"Plan for {icount} individual{s}: {inames}.")
186
186
  p = plan.Plan(inames, yobs, expectancy, name, startDate=startDate, verbose=True, logstreams=logstreams)
187
187
 
188
188
  # Assets.
189
189
  balances = {}
190
190
  for acc in accountTypes:
191
- balances[acc] = diconf["Assets"]["%s savings balances" % acc]
191
+ balances[acc] = diconf["Assets"][f"{acc} savings balances"]
192
192
  p.setAccountBalances(
193
193
  taxable=balances["taxable"], taxDeferred=balances["tax-deferred"], taxFree=balances["tax-free"]
194
194
  )
@@ -207,11 +207,11 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
207
207
  elif dirname != "" and os.path.exists(dirname + "/" + timeListsFileName):
208
208
  myfile = dirname + "/" + timeListsFileName
209
209
  else:
210
- raise FileNotFoundError("File '%s' not found." % timeListsFileName)
210
+ raise FileNotFoundError(f"File '{timeListsFileName}' not found.")
211
211
  p.readContributions(myfile)
212
212
  else:
213
213
  p.timeListsFileName = timeListsFileName
214
- mylog.vprint("Ignoring to read contributions file %s." % timeListsFileName)
214
+ mylog.vprint(f"Ignoring to read contributions file {timeListsFileName}.")
215
215
 
216
216
  # Fixed Income.
217
217
  ssecAmounts = np.array(diconf["Fixed Income"]["Social security amounts"], dtype=np.float32)
@@ -272,7 +272,7 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
272
272
  generic=boundsAR["generic"],
273
273
  )
274
274
  else:
275
- raise ValueError("Unknown asset allocation type %s." % allocType)
275
+ raise ValueError(f"Unknown asset allocation type {allocType}.")
276
276
 
277
277
  # Optimization Parameters.
278
278
  p.objective = diconf["Optimization Parameters"]["Objective"]
@@ -27,7 +27,7 @@ class Logger(object):
27
27
  self._logstreams = 2 * logstreams
28
28
  self.vprint("Using logstream as stream logger.")
29
29
  else:
30
- raise ValueError("Log streams %r must be a list." % logstreams)
30
+ raise ValueError(f"Log streams {logstreams} must be a list.")
31
31
 
32
32
  def setVerbose(self, verbose=True):
33
33
  """