owlplanner 2025.12.20__py3-none-any.whl → 2026.2.2__py3-none-any.whl

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.
owlplanner/utils.py CHANGED
@@ -1,17 +1,28 @@
1
1
  """
2
+ Utility functions for data formatting and manipulation.
2
3
 
3
- Owl/utils
4
+ This module provides helper functions for formatting currency, percentages,
5
+ and other data transformations used throughout the retirement planner.
4
6
 
5
- This file contains functions for handling data.
7
+ Copyright (C) 2025-2026 The Owlplanner Authors
6
8
 
7
- Copyright © 2024 - Martin-D. Lacasse
9
+ This program is free software: you can redistribute it and/or modify
10
+ it under the terms of the GNU General Public License as published by
11
+ the Free Software Foundation, either version 3 of the License, or
12
+ (at your option) any later version.
8
13
 
9
- Disclaimers: This code is for educational purposes only and does not constitute financial advice.
14
+ This program is distributed in the hope that it will be useful,
15
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ GNU General Public License for more details.
10
18
 
19
+ You should have received a copy of the GNU General Public License
20
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
11
21
  """
12
22
 
13
23
  ######################################################################
14
24
  import numpy as np
25
+ import pandas as pd
15
26
 
16
27
 
17
28
  def d(value, f=0, latex=False) -> str:
@@ -70,6 +81,15 @@ def getUnits(units) -> int:
70
81
  return fac
71
82
 
72
83
 
84
+ def get_numeric_option(options, key, default, *, min_value=None) -> float:
85
+ value = options.get(key, default)
86
+ if not isinstance(value, (int, float)):
87
+ raise ValueError(f"{key} {value} is not a number.")
88
+ if min_value is not None and value < min_value:
89
+ raise ValueError(f"{key} must be >= {min_value}.")
90
+ return float(value)
91
+
92
+
73
93
  # Next two functions could be a one-line lambda functions.
74
94
  # e.g., krond = lambda a, b: 1 if a == b else 0
75
95
  def krond(a, b) -> int:
@@ -125,3 +145,158 @@ def parseDobs(dobs):
125
145
  tobs.append(ls[2])
126
146
 
127
147
  return np.array(yobs, dtype=np.int32), np.array(mobs, dtype=np.int32), np.array(tobs, dtype=np.int32)
148
+
149
+
150
+ def is_row_active(row):
151
+ """
152
+ Check if a DataFrame row should be processed based on 'active' column.
153
+
154
+ This function handles the common pattern of checking whether a row in a DataFrame
155
+ should be processed based on its 'active' column value. The logic is:
156
+ - If 'active' column doesn't exist, the row is considered active (default behavior)
157
+ - If 'active' value is NaN or None, the row is considered active (default behavior)
158
+ - If 'active' value is explicitly False (or falsy), the row is considered inactive
159
+ - Otherwise (True or truthy), the row is considered active
160
+
161
+ Parameters
162
+ ----------
163
+ row : pd.Series
164
+ A pandas Series representing a row from a DataFrame. The row should have
165
+ an 'active' column (or index entry) if the active/inactive status is to be checked.
166
+
167
+ Returns
168
+ -------
169
+ bool
170
+ True if the row should be processed (is active), False if it should be skipped (is inactive).
171
+ """
172
+ if "active" not in row.index:
173
+ return True # Default to active if column doesn't exist
174
+ active_value = row["active"]
175
+ if pd.isna(active_value) or active_value is None:
176
+ return True # NaN/None means active
177
+ return bool(active_value)
178
+
179
+
180
+ def is_dataframe_empty(df):
181
+ """
182
+ Check if a DataFrame is None or empty.
183
+
184
+ This function consolidates the common pattern of checking
185
+ `df is None or df.empty` throughout the codebase.
186
+
187
+ Parameters
188
+ ----------
189
+ df : pd.DataFrame or None
190
+ The DataFrame to check. Can be None or an empty DataFrame.
191
+
192
+ Returns
193
+ -------
194
+ bool
195
+ True if df is None or empty, False otherwise.
196
+ """
197
+ return df is None or df.empty
198
+
199
+
200
+ def ensure_dataframe(df, default_empty=None):
201
+ """
202
+ Ensure DataFrame is not None or empty, return default if needed.
203
+
204
+ This function checks if a DataFrame is None or empty and returns a default
205
+ value if so. This consolidates the common pattern of checking
206
+ `df is None or df.empty` throughout the codebase.
207
+
208
+ Parameters
209
+ ----------
210
+ df : pd.DataFrame or None
211
+ The DataFrame to check. Can be None or an empty DataFrame.
212
+ default_empty : any, optional
213
+ The value to return if df is None or empty. Default is None.
214
+ Common values are 0.0, np.zeros(N_n), or a default DataFrame.
215
+
216
+ Returns
217
+ -------
218
+ any
219
+ Returns default_empty if df is None or empty, otherwise returns df.
220
+ """
221
+ if is_dataframe_empty(df):
222
+ return default_empty
223
+ return df
224
+
225
+
226
+ def get_empty_array_or_value(N_n, default_value=0.0):
227
+ """
228
+ Return empty array or single value based on context.
229
+
230
+ This helper function returns either a numpy array of zeros with length N_n
231
+ if N_n is provided, or a single default value if N_n is None.
232
+
233
+ Parameters
234
+ ----------
235
+ N_n : int or None
236
+ Length of the array to create. If None, returns default_value instead.
237
+ default_value : float, optional
238
+ Default value to return if N_n is None. Default is 0.0.
239
+
240
+ Returns
241
+ -------
242
+ np.ndarray or float
243
+ Returns np.zeros(N_n) if N_n is not None, otherwise returns default_value.
244
+ """
245
+ if N_n is not None:
246
+ return np.zeros(N_n)
247
+ return default_value
248
+
249
+
250
+ def convert_to_bool(val):
251
+ """
252
+ Convert various input types to boolean.
253
+
254
+ Handles conversion from strings, numbers, booleans, and NaN values.
255
+ Excel may read booleans as strings ("True"/"False") or numbers (1/0),
256
+ so this function provides robust conversion.
257
+
258
+ Parameters
259
+ ----------
260
+ val : any
261
+ Value to convert to boolean. Can be:
262
+ - bool: returned as-is
263
+ - str: "True", "False", "1", "0", "yes", "no", etc.
264
+ - numeric: 1/0 or other numeric values
265
+ - None/NaN: defaults to True
266
+
267
+ Returns
268
+ -------
269
+ bool
270
+ Boolean value. NaN/None and unknown values default to True.
271
+ """
272
+ # Check for None first (before pd.isna which can fail on some types)
273
+ if val is None:
274
+ return True # Default to True for None
275
+
276
+ # Check for NaN, but handle cases where pd.isna might fail (e.g., empty lists)
277
+ try:
278
+ if pd.isna(val):
279
+ return True # Default to True for NaN
280
+ except (ValueError, TypeError):
281
+ # pd.isna can raise ValueError for empty arrays/lists
282
+ # or TypeError for unhashable types - treat as non-NaN and continue
283
+ pass
284
+ if isinstance(val, bool):
285
+ return val
286
+ if isinstance(val, str):
287
+ # Handle string representations
288
+ val_lower = val.lower().strip()
289
+ if val_lower in ("true", "1", "yes", "y"):
290
+ return True
291
+ elif val_lower in ("false", "0", "no", "n"):
292
+ return False
293
+ else:
294
+ # Unknown string, default to True
295
+ return True
296
+ # Handle numeric values (1/0)
297
+ try:
298
+ num_val = float(val)
299
+ return bool(num_val) if num_val != 0 else False
300
+ except (ValueError, TypeError):
301
+ # Can't convert, default to True
302
+ return True
owlplanner/version.py CHANGED
@@ -1 +1,20 @@
1
- __version__ = "2025.12.20"
1
+ """
2
+ Package version information.
3
+
4
+ Copyright (C) 2025-2026 The Owlplanner Authors
5
+
6
+ This program is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
18
+ """
19
+
20
+ __version__ = "2026.02.02"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.12.20
3
+ Version: 2026.2.2
4
4
  Summary: Owl - Optimal Wealth Lab: 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
@@ -683,6 +683,7 @@ License: GNU GENERAL PUBLIC LICENSE
683
683
  the library. If this is what you want to do, use the GNU Lesser General
684
684
  Public License instead of this License. But first, please read
685
685
  <https://www.gnu.org/licenses/why-not-lgpl.html>.
686
+ License-File: AUTHORS
686
687
  License-File: LICENSE
687
688
  Classifier: Development Status :: 5 - Production/Stable
688
689
  Classifier: Intended Audience :: End Users/Desktop
@@ -691,8 +692,10 @@ Classifier: Operating System :: OS Independent
691
692
  Classifier: Programming Language :: Python :: 3
692
693
  Classifier: Topic :: Office/Business :: Financial :: Investment
693
694
  Requires-Python: >=3.10
695
+ Requires-Dist: click>=8.3.1
694
696
  Requires-Dist: highspy
695
697
  Requires-Dist: jupyter>=1.1.1
698
+ Requires-Dist: loguru>=0.7.3
696
699
  Requires-Dist: matplotlib
697
700
  Requires-Dist: numpy
698
701
  Requires-Dist: odfpy
@@ -712,7 +715,7 @@ Description-Content-Type: text/markdown
712
715
 
713
716
  ## A retirement exploration tool based on linear programming
714
717
 
715
- <img align=right src="https://github.com/mdlacasse/Owl/blob/main/docs/images/owl.png?raw=true" width="250">
718
+ <img align="right" src="papers/images/owl.png" width="250">
716
719
 
717
720
  -------------------------------------------------------------------------------------
718
721
 
@@ -724,6 +727,10 @@ Users can select varying return rates to perform historical back testing,
724
727
  stochastic rates for performing Monte Carlo analyses,
725
728
  or fixed rates either derived from historical averages, or set by the user.
726
729
 
730
+ Owl is designed for US retirees as it considers US federal tax laws,
731
+ Medicare premiums, rules for 401k including required minimum distributions,
732
+ maturation rules for Roth accounts and conversions, social security rules, etc.
733
+
727
734
  There are three ways to run Owl:
728
735
 
729
736
  - **Streamlit Hub:** Run Owl remotely as hosted on the Streamlit Community Server at
@@ -763,6 +770,7 @@ Follow these [instructions](INSTALL.md) to install from the source code and self
763
770
  It can also run on [MOSEK](https://mosek.com) if available on your computer.
764
771
  - Owl planner relies on the following [Python](https://python.org) packages:
765
772
  - [highspy](https://highs.dev),
773
+ [loguru](https://github.com/Delgan/loguru),
766
774
  [Matplotlib](https://matplotlib.org),
767
775
  [Numpy](https://numpy.org),
768
776
  [odfpy](https://https://pypi.org/project/odfpy),
@@ -789,7 +797,7 @@ your computer and can be used to reproduce a case at a later time.
789
797
 
790
798
  ---------------------------------------------------------------------
791
799
 
792
- Copyright &copy; 2024 - Martin-D. Lacasse
800
+ Copyright &copy; 2024-2026 - Martin-D. Lacasse
793
801
 
794
802
  Disclaimers: This code is for educatonal purposes only and does not constitute financial advice.
795
803
 
@@ -0,0 +1,35 @@
1
+ owlplanner/__init__.py,sha256=X9u8PrfeTyfjuksYhx0eNrIACgX5Tg3Le7pz8qKkJXk,1341
2
+ owlplanner/abcapi.py,sha256=5yWuiu5eBVO46V5vklO_zfCCrFZX9guRZ9XEemeJ878,7229
3
+ owlplanner/config.py,sha256=uMP68aG-YnSL0Mqw9tqrxxh62rkUHhwExWzS6rpOIeo,20619
4
+ owlplanner/debts.py,sha256=c-eLynjOFryYisae51Po6Iuh46_d_AGOUbOz3z96ZJY,9850
5
+ owlplanner/fixedassets.py,sha256=ItFeXjIdhMVNyXY0xBW6P9ODRajVg1kDRaid9-Eu_Ww,11510
6
+ owlplanner/mylogging.py,sha256=amQA0qWPsXMoJkYYF3nMaVtCDZMWxXJ1hbJS0TkMvf8,8269
7
+ owlplanner/plan.py,sha256=yYIdSfjJfsuV5aij0xfI8jZkRCRLWc-NV6wAycM4Ef0,157402
8
+ owlplanner/progress.py,sha256=ayqoRUMn6dtIxHDYaeX1mu7W2rAhkBYAi4Y2Y_NtdYA,2521
9
+ owlplanner/rates.py,sha256=pN6J1aZsj_OaCuLrU4vRlQJOCg3dKhamPuAmvI3Ekls,15158
10
+ owlplanner/socialsecurity.py,sha256=4vn76dUZ2P2fMKyy4eu2ramGNvPQymKzsNFZvwbFWOc,7595
11
+ owlplanner/tax2026.py,sha256=CN4nvdxz7ZqhvM57SwYjO4jF6VtDBRMgFpdj1zVU6yk,15907
12
+ owlplanner/timelists.py,sha256=4dBZVCgNIx0RwUfimp_k-m9KhRFj6BHX8Hon1vb8oo8,15032
13
+ owlplanner/utils.py,sha256=l2-3HmZutFlEfJUp7gFblY3PBNYm7HiPPCGl0wFkVmA,9461
14
+ owlplanner/version.py,sha256=8VDixj8vuzpLB0-WNUz9mZbrP9d1ufVm-oLGVHEzvPw,747
15
+ owlplanner/cli/README.md,sha256=R-jwwiX5ZW6gCKaRPWVCgwKVoN2MKozrLAkKvfUT4vA,2292
16
+ owlplanner/cli/_main.py,sha256=qAy_2oCLaa_EhvfGFnuh_sVXkCcgp70WMSL7_QKH5XI,1497
17
+ owlplanner/cli/cli_logging.py,sha256=CTBaAMpJIG7ge9Gz44YykBiea3hMxdAXHyiLj1UyvDU,1690
18
+ owlplanner/cli/cmd_list.py,sha256=lOpS_5LGR1IpJLOaHQOrwm1EtbGwv0tfIcZZ8QObmMM,2584
19
+ owlplanner/cli/cmd_run.py,sha256=aLy-X6x_3MSO_yBQ1R4MWKzczfQgWuRQUsNTrVUaWj8,3009
20
+ owlplanner/data/__init__.py,sha256=jpbWHd9n4F06pyfQj1kI3HQ3uiWXfZCjb5BYINOr9_M,850
21
+ owlplanner/data/awi.csv,sha256=w9YwPEqhxqo1Czumh3ihLXLPtk8kJQgqWKClMysOq-c,1625
22
+ owlplanner/data/bendpoints.csv,sha256=h0a7_XqTuyHWe5ZlS3mjIcAWqOMG_93GoiETS-1xxUY,746
23
+ owlplanner/data/newawi.csv,sha256=w9YwPEqhxqo1Czumh3ihLXLPtk8kJQgqWKClMysOq-c,1625
24
+ owlplanner/data/rates.csv,sha256=6Pr5cXFunZKRzB32--kWzhuRnTQMNFfZaww0QF1SIag,3898
25
+ owlplanner/plotting/__init__.py,sha256=UrKO_zHTxZ-Wb2sQbyk2vNHe20Owzt6M1KJ2M6Z5GGw,944
26
+ owlplanner/plotting/base.py,sha256=70-J5AWnHtWqrehVhCNojnTdfNT6PnEpxBKzDh3R57o,3298
27
+ owlplanner/plotting/factory.py,sha256=sVZ0uAuGKBVVh1qkF2IS7xNZGUPIQRIfqgUt2s2Ncjk,1694
28
+ owlplanner/plotting/matplotlib_backend.py,sha256=AILNOhXi_gsR__i1hKoHaEHvMUqQMyLeRAGCoCBfQBY,19222
29
+ owlplanner/plotting/plotly_backend.py,sha256=OqMJRxAhpwIbKSV-Od1-q-tzOs66McZPbKD32be2qx0,34523
30
+ owlplanner-2026.2.2.dist-info/METADATA,sha256=L4Dl5IrtC1_a9-2Ezk8FezJzf5YA6T8nAiYMgUtJTeg,46498
31
+ owlplanner-2026.2.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
32
+ owlplanner-2026.2.2.dist-info/entry_points.txt,sha256=m2Ql-6VQbotl65wiqx35d3TIh-Tm6Wdjrz3hNdqZKKg,52
33
+ owlplanner-2026.2.2.dist-info/licenses/AUTHORS,sha256=KD6uW6iEOHf51J8DEJ6az-gowFv3NWIpW4zRgqxwuAA,464
34
+ owlplanner-2026.2.2.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
35
+ owlplanner-2026.2.2.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ owlcli = owlplanner.cli._main:cli
@@ -0,0 +1,15 @@
1
+ # This is the list of Owlplanner's significant contributors.
2
+ #
3
+ # This does not necessarily list everyone who has contributed code.
4
+ # To see the full list of contributors, see the revision history in
5
+ # source control.
6
+ Martin-D. Lacasse (mdlacasse, original author)
7
+ Robert E. Anderson (NH-RedAnt)
8
+ Clark Jefcoat (hubcity)
9
+ kg333
10
+ John Leonard (jleonard99)
11
+ Benjamin Quinn (blquinn)
12
+ Dale Seng (sengsational)
13
+ Josh Williams (noimjosh)
14
+ Gene Wood (gene1wood)
15
+