owlplanner 2025.5.28__py3-none-any.whl → 2025.6.3__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/abcapi.py CHANGED
@@ -16,9 +16,9 @@ solvers for comparison.
16
16
  This approach has been successful with the MOSEK and the HiGHS solvers.
17
17
  A for matrix, B for bounds, C for constraints. Thus the name ABCAPI.
18
18
 
19
- Copyright (C) 2024 -- Martin-D. Lacasse
19
+ Copyright © 2024 - Martin-D. Lacasse
20
20
 
21
- Disclaimer: This program comes with no guarantee. Use at your own risk.
21
+ Disclaimers: This code is for educatonal purposes only and does not constitute financial advice.
22
22
 
23
23
  """
24
24
 
owlplanner/config.py CHANGED
@@ -4,9 +4,10 @@ Owl/conftoml
4
4
 
5
5
  This file contains utility functions to save case parameters.
6
6
 
7
- Copyright (C) 2024 -- Martin-D. Lacasse
7
+ Copyright © 2024 - Martin-D. Lacasse
8
+
9
+ Disclaimers: This code is for educatonal purposes only and does not constitute financial advice.
8
10
 
9
- Disclaimer: This program comes with no guarantee. Use at your own risk.
10
11
  """
11
12
 
12
13
  import toml as toml
owlplanner/mylogging.py CHANGED
@@ -4,9 +4,9 @@ Owl/logging
4
4
 
5
5
  This file contains routines for handling error messages.
6
6
 
7
- Copyright (C) 2024 -- Martin-D. Lacasse
7
+ Copyright © 2024 - Martin-D. Lacasse
8
8
 
9
- Disclaimer: This program comes with no guarantee. Use at your own risk.
9
+ Disclaimers: This code is for educatonal purposes only and does not constitute financial advice.
10
10
 
11
11
  """
12
12
 
owlplanner/plan.py CHANGED
@@ -8,9 +8,10 @@ A retirement planner using linear programming optimization.
8
8
  See companion PDF document for an explanation of the underlying
9
9
  mathematical model and a description of all variables and parameters.
10
10
 
11
- Copyright (C) 2024 -- Martin-D. Lacasse
11
+ Copyright © 2024 - Martin-D. Lacasse
12
+
13
+ Disclaimers: This code is for educatonal purposes only and does not constitute financial advice.
12
14
 
13
- Disclaimer: This program comes with no guarantee. Use at your own risk.
14
15
  """
15
16
 
16
17
  ###########################################################################
@@ -307,7 +308,7 @@ class Plan(object):
307
308
  self.kappa_ijn = np.zeros((self.N_i, self.N_j, self.N_n))
308
309
 
309
310
  # Previous 3 years for Medicare.
310
- self.prevMAGI = np.zeros((3))
311
+ self.prevMAGI = np.zeros((2))
311
312
 
312
313
  # Init previous balance to none.
313
314
  self.beta_ij = None
@@ -498,7 +499,7 @@ class Plan(object):
498
499
  def setBeneficiaryFractions(self, phi):
499
500
  """
500
501
  Set fractions of savings accounts that is left to surviving spouse.
501
- Default is [1, 1, 1] for taxable, tax-deferred, adn tax-exempt accounts.
502
+ Default is [1, 1, 1] for taxable, tax-deferred, and tax-free accounts.
502
503
  """
503
504
  if len(phi) != self.N_j:
504
505
  raise ValueError(f"Fractions must have {self.N_j} entries.")
@@ -901,7 +902,7 @@ class Plan(object):
901
902
  try:
902
903
  filename, self.timeLists = timelists.read(filename, self.inames, self.horizons, self.mylog)
903
904
  except Exception as e:
904
- raise Exception(f"Unsuccessful read of contributions: {e}") from e
905
+ raise Exception(f"Unsuccessful read of Wages and Contributions: {e}") from e
905
906
 
906
907
  self.timeListsFileName = filename
907
908
  self.setContributions()
@@ -909,6 +910,9 @@ class Plan(object):
909
910
  return True
910
911
 
911
912
  def setContributions(self, timeLists=None):
913
+ """
914
+ If no argument is given, use the values that have been stored in self.timeLists.
915
+ """
912
916
  if timeLists is not None:
913
917
  timelists.check(timeLists, self.inames, self.horizons)
914
918
  self.timeLists = timeLists
@@ -1074,8 +1078,6 @@ class Plan(object):
1074
1078
  Utility function that builds constraint matrix and vectors.
1075
1079
  Refactored for clarity and maintainability.
1076
1080
  """
1077
- self._setup_constraint_shortcuts(options)
1078
-
1079
1081
  self.A = abc.ConstraintMatrix(self.nvars)
1080
1082
  self.B = abc.Bounds(self.nvars, self.nbins)
1081
1083
 
@@ -1098,12 +1100,6 @@ class Plan(object):
1098
1100
 
1099
1101
  return None
1100
1102
 
1101
- def _setup_constraint_shortcuts(self, options):
1102
- # Set up all the local variables as attributes for use in helpers.
1103
- oppCostX = options.get("oppCostX", 0.)
1104
- self.xnet = 1 - oppCostX / 100.
1105
- self.optionsUnits = u.getUnits(options.get("units", "k"))
1106
-
1107
1103
  def _add_rmd_inequalities(self):
1108
1104
  for i in range(self.N_i):
1109
1105
  if self.beta_ij[i, 1] > 0:
@@ -1579,14 +1575,18 @@ class Plan(object):
1579
1575
  if objective == "maxSpending" and "bequest" not in myoptions:
1580
1576
  self.mylog.vprint("Using bequest of $1.")
1581
1577
 
1582
- self.prevMAGI = np.zeros(3)
1578
+ self.optionsUnits = u.getUnits(myoptions.get("units", "k"))
1579
+
1580
+ oppCostX = options.get("oppCostX", 0.)
1581
+ self.xnet = 1 - oppCostX / 100.
1582
+
1583
+ self.prevMAGI = np.zeros(2)
1583
1584
  if "previousMAGIs" in myoptions:
1584
1585
  magi = myoptions["previousMAGIs"]
1585
- if len(magi) != 3:
1586
- raise ValueError("previousMAGIs must have 3 values.")
1586
+ if 3 < len(magi) < 2:
1587
+ raise ValueError("previousMAGIs must have 2 values.")
1587
1588
 
1588
- units = u.getUnits(options.get("units", "k"))
1589
- self.prevMAGI = units * np.array(magi)
1589
+ self.prevMAGI = self.optionsUnits * np.array(magi)
1590
1590
 
1591
1591
  lambdha = myoptions.get("spendingSlack", 0)
1592
1592
  if lambdha < 0 or lambdha > 50:
@@ -2058,7 +2058,7 @@ class Plan(object):
2058
2058
  dic = {}
2059
2059
  # Results
2060
2060
  dic["Plan name"] = self._name
2061
- dic["Net yearly spending basis"] = u.d(self.g_n[0] / self.xi_n[0])
2061
+ dic["Net yearly spending basis" + 26*" ."] = u.d(self.g_n[0] / self.xi_n[0])
2062
2062
  dic[f"Net spending for year {now}"] = u.d(self.g_n[0])
2063
2063
  dic[f"Net spending remaining in year {now}"] = u.d(self.g_n[0] * self.yearFracLeft)
2064
2064
 
@@ -1,5 +1,10 @@
1
1
  """
2
2
  Plotting backends for Owl.
3
+
4
+ Copyright &copy; 2025 - Martin-D. Lacasse
5
+
6
+ Disclaimers: This code is for educatonal purposes only and does not constitute financial advice.
7
+
3
8
  """
4
9
 
5
10
  from .factory import PlotFactory
@@ -1,5 +1,10 @@
1
1
  """
2
2
  Base classes for plot backends.
3
+
4
+ Copyright &copy; 2025 - Martin-D. Lacasse
5
+
6
+ Disclaimers: This code is for educatonal purposes only and does not constitute financial advice.
7
+
3
8
  """
4
9
 
5
10
  from abc import ABC, abstractmethod
@@ -1,5 +1,10 @@
1
1
  """
2
2
  Factory for creating plot backends.
3
+
4
+ Copyright &copy; 2025 - Martin-D. Lacasse
5
+
6
+ Disclaimers: This code is for educatonal purposes only and does not constitute financial advice.
7
+
3
8
  """
4
9
 
5
10
  from .base import PlotBackend
@@ -1,5 +1,10 @@
1
1
  """
2
2
  Matplotlib implementation of plot backend.
3
+
4
+ Copyright &copy; 2025 - Martin-D. Lacasse
5
+
6
+ Disclaimers: This code is for educatonal purposes only and does not constitute financial advice.
7
+
3
8
  """
4
9
 
5
10
  import numpy as np
@@ -372,17 +377,19 @@ class MatplotlibBackend(PlotBackend):
372
377
  raise ValueError(f"Unknown coordination {ARCoord}.")
373
378
  figures = []
374
379
  assetDic = {"stocks": 0, "C bonds": 1, "T notes": 2, "common": 3}
380
+ blank = ["", ""]
375
381
  for i in range(count):
376
382
  y2stack = {}
377
383
  for acType in acList:
378
384
  stackNames = []
379
385
  for key in assetDic:
380
- aname = key + " / " + acType
386
+ # aname = key + " / " + acType
387
+ aname = key
381
388
  stackNames.append(aname)
382
389
  y2stack[aname] = np.zeros((count, len(year_n)))
383
390
  y2stack[aname][i][:] = alpha_ijkn[i, acList.index(acType), assetDic[key], : len(year_n)]
384
- t = title + f" - {acType}"
385
- fig, ax = self._stack_plot(year_n, inames, t, [i], y2stack, stackNames, "upper left", "percent")
391
+ t = title + f" - {acType} {inames[i]}"
392
+ fig, ax = self._stack_plot(year_n, blank, t, [i], y2stack, stackNames, "upper left", "percent")
386
393
  figures.append(fig)
387
394
 
388
395
  return figures
@@ -1,5 +1,10 @@
1
1
  """
2
2
  Plotly implementation of plot backend.
3
+
4
+ Copyright &copy; 2025 - Martin-D. Lacasse
5
+
6
+ Disclaimers: This code is for educatonal purposes only and does not constitute financial advice.
7
+
3
8
  """
4
9
 
5
10
  import numpy as np
@@ -815,7 +820,8 @@ class PlotlyBackend(PlotBackend):
815
820
  stack_data = []
816
821
  stack_names = []
817
822
  for key in assetDic:
818
- aname = f"{key} / {acType}"
823
+ # aname = f"{key} / {acType}"
824
+ aname = key
819
825
  stack_names.append(aname)
820
826
 
821
827
  # Get allocation data
@@ -834,7 +840,7 @@ class PlotlyBackend(PlotBackend):
834
840
  ))
835
841
 
836
842
  # Update layout
837
- plot_title = f"{title} - {acType}"
843
+ plot_title = f"{title} - {acType} {inames[i]}"
838
844
  fig.update_layout(
839
845
  title=plot_title,
840
846
  # xaxis_title="year",
owlplanner/progress.py CHANGED
@@ -1,6 +1,10 @@
1
1
  """
2
2
  A simple object to display progress.
3
3
 
4
+ Copyright &copy; 2024 - Martin-D. Lacasse
5
+
6
+ Disclaimers: This code is for educatonal purposes only and does not constitute financial advice.
7
+
4
8
  """
5
9
 
6
10
  from owlplanner import utils as u
owlplanner/rates.py CHANGED
@@ -21,11 +21,10 @@ Rate lists will need to be updated with values for current year.
21
21
  When doing so, the TO bound defined below will need to be adjusted
22
22
  to the last current data year.
23
23
 
24
- Copyright (C) 2024 -- Martin-D. Lacasse
24
+ Copyright &copy; 2024 - Martin-D. Lacasse
25
25
 
26
- Last updated: Jan 2025
26
+ Disclaimers: This code is for educatonal purposes only and does not constitute financial advice.
27
27
 
28
- Disclaimer: This program comes with no guarantee. Use at your own risk.
29
28
  """
30
29
 
31
30
  ###################################################################
owlplanner/tax2025.py CHANGED
@@ -10,9 +10,10 @@ of all variables and parameters.
10
10
 
11
11
  Module to handle all tax calculations.
12
12
 
13
- Copyright (C) 2024 -- Martin-D. Lacasse
13
+ Copyright &copy; 2024 - Martin-D. Lacasse
14
+
15
+ Disclaimers: This code is for educatonal purposes only and does not constitute financial advice.
14
16
 
15
- Disclaimer: This program comes with no guarantee. Use at your own risk.
16
17
  """
17
18
 
18
19
  import numpy as np
@@ -93,9 +94,9 @@ def mediCosts(yobs, horizons, magi, prevmagi, gamma_n, Nn):
93
94
  status = 0 if Ni == 1 else 1 if n < horizons[0] and n < horizons[1] else 0
94
95
  for i in range(Ni):
95
96
  if thisyear + n - yobs[i] >= 65 and n < horizons[i]:
96
- # Start with the (indexed) basic Medicare part B premium.
97
+ # Start with the (inflation-adjusted) basic Medicare part B premium.
97
98
  costs[n] += gamma_n[n] * irmaaFees[0]
98
- if n < 3:
99
+ if n < 2:
99
100
  mymagi = prevmagi[n]
100
101
  else:
101
102
  mymagi = magi[n - 2]
owlplanner/timelists.py CHANGED
@@ -10,9 +10,10 @@ of all variables and parameters.
10
10
 
11
11
  Utility functions to read and check timelists.
12
12
 
13
- Copyright (C) 2024 -- Martin-D. Lacasse
13
+ Copyright &copy; 2024 - Martin-D. Lacasse
14
+
15
+ Disclaimers: This code is for educatonal purposes only and does not constitute financial advice.
14
16
 
15
- Disclaimer: This program comes with no guarantee. Use at your own risk.
16
17
  """
17
18
 
18
19
  from datetime import date
@@ -47,7 +48,7 @@ def read(finput, inames, horizons, mylog):
47
48
  mylog.vprint("Reading wages, contributions, conversions, and big-ticket items over time...")
48
49
 
49
50
  if isinstance(finput, dict):
50
- timeLists = finput
51
+ dfDict = finput
51
52
  finput = "dictionary of DataFrames"
52
53
  streamName = "dictionary of DataFrames"
53
54
  else:
@@ -58,7 +59,7 @@ def read(finput, inames, horizons, mylog):
58
59
  raise Exception(f"Could not read file {finput}: {e}.") from e
59
60
  streamName = f"file '{finput}'"
60
61
 
61
- timeLists = condition(dfDict, inames, horizons, mylog)
62
+ timeLists = condition(dfDict, inames, horizons, mylog)
62
63
 
63
64
  mylog.vprint(f"Successfully read time horizons from {streamName}.")
64
65
 
owlplanner/utils.py CHANGED
@@ -4,9 +4,9 @@ Owl/utils
4
4
 
5
5
  This file contains functions for handling data.
6
6
 
7
- Copyright (C) 2024 -- Martin-D. Lacasse
7
+ Copyright &copy; 2024 - Martin-D. Lacasse
8
8
 
9
- Disclaimer: This program comes with no guarantee. Use at your own risk.
9
+ Disclaimers: This code is for educatonal purposes only and does not constitute financial advice.
10
10
 
11
11
  """
12
12
 
owlplanner/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2025.05.28"
1
+ __version__ = "2025.06.03"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.5.28
3
+ Version: 2025.6.3
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
@@ -748,13 +748,6 @@ This is exactly where this tool fits it. Given your savings capabilities and spe
748
748
  it can generate different future realizations of
749
749
  your strategy under different market assumptions, helping to better understand your financial situation.
750
750
 
751
- Disclaimers: I am not a financial planner. You make your own decisions.
752
- This program comes with no guarantee. Use at your own risk.
753
-
754
- More disclaimers: While some output of the code has been verified with other approaches,
755
- this code is still under development and I cannot guarantee the accuracy of the results.
756
- Use at your own risk.
757
-
758
751
  -------------------------------------------------------------------------------------
759
752
  ## Purpose and vision
760
753
  The goal of Owl is to create a free and open-source ecosystem that has cutting-edge optimization capabilities,
@@ -839,7 +832,7 @@ an Excel spreadsheet that contains future wages, contributions
839
832
  to savings accounts, and planned *big-ticket items* such as the purchase of a lake house,
840
833
  the sale of a boat, large gifts, or inheritance.
841
834
 
842
- Three types of savings accounts are considered: taxable, tax-deferred, and tax-exempt,
835
+ Three types of savings accounts are considered: taxable, tax-deferred, and tax-free,
843
836
  which are all tracked separately for married individuals. Asset transition to the surviving spouse
844
837
  is done according to beneficiary fractions for each type of savings account.
845
838
  Tax status covers married filing jointly and single, depending on the number of individuals reported.
@@ -898,8 +891,10 @@ assets to support, even with no estate being left.
898
891
 
899
892
  Copyright &copy; 2024 - Martin-D. Lacasse
900
893
 
901
- Disclaimers: I am not a financial planner. You make your own decisions.
902
- This program comes with no guarantee. Use at your own risk.
894
+ Disclaimers: This code is for educatonal purposes only and does not constitute financial advice.
895
+
896
+ Code output has been verified with analytical solutions and other approaches.
897
+ Nevertheless, accuracy of results are not guaranteed.
903
898
 
904
899
  --------------------------------------------------------
905
900
 
@@ -0,0 +1,22 @@
1
+ owlplanner/__init__.py,sha256=hJ2i4m2JpHPAKyQLjYOXpJzeEsgcTcKD-Vhm0AIjjWg,592
2
+ owlplanner/abcapi.py,sha256=8VCXS7nH_QZYxCUU3lwO0_UPR9Q5fuYQ6DHDLvHVLPg,6878
3
+ owlplanner/config.py,sha256=onGIMqW2WwB9_CUZauDL6LtHGvc8O1cPUKKcK7Oh70M,12617
4
+ owlplanner/mylogging.py,sha256=RKUr-y-1XvKZzLMcfdtm4IM30LuRpJwb2qUeXmAWqME,2557
5
+ owlplanner/plan.py,sha256=FgQB0kEV5BXsQdFzcVZwukpqJUy1qR2OYP8wS_owtfo,107055
6
+ owlplanner/progress.py,sha256=2DOjOLo6Mo7m21wY-9iZhoUksAyi4VCbb6UL2RegNCw,529
7
+ owlplanner/rates.py,sha256=7jXcuHbkJ3AVIeBYZdwme18rdYslIzCuT-c0cLzvKUU,14819
8
+ owlplanner/tax2025.py,sha256=HVYJq8po28jL5Z_il39ZY7qvf2riUEfxio15Zp7TGj8,7890
9
+ owlplanner/timelists.py,sha256=HjoUJQU_ZbfdUy79DefQo5EAXpkfcuG_dOIO9qCljyM,3971
10
+ owlplanner/utils.py,sha256=6Ky8ZKfNE9x--3znsZ8VZaT2PptDinszRxWsOCPanu8,2512
11
+ owlplanner/version.py,sha256=s0nlL6F_JwS9jBAKvGDeVQurTia-o1wmPv43QaJETt4,28
12
+ owlplanner/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ owlplanner/data/rates.csv,sha256=6fxg56BVVORrj9wJlUGFdGXKvOX5r7CSca8uhUbbuIU,3734
14
+ owlplanner/plotting/__init__.py,sha256=uhxqtUi0OI-QWNOO2LkXgQViW_9yM3rYb-204Wit974,250
15
+ owlplanner/plotting/base.py,sha256=UimGKpMTV-dVm3BX5Apr_Ltorc7dlDLCRPRQ3RF_v7c,2578
16
+ owlplanner/plotting/factory.py,sha256=EDopIAPQr9zHRgemObko18FlCeRNhNCoLNNFAOq-X6s,1030
17
+ owlplanner/plotting/matplotlib_backend.py,sha256=AOEkapD94U5hGNoS0EdbRoe8mgdMHH4oOvkXADZS914,17957
18
+ owlplanner/plotting/plotly_backend.py,sha256=AO33GxBHGYG5osir_H1iRRtGxdhs4AjfLV2d_xm35nY,33138
19
+ owlplanner-2025.6.3.dist-info/METADATA,sha256=WhszwtjjNOjd2rfx3QHl6_E52ypcg1DpdJ6JJz3G1vY,53785
20
+ owlplanner-2025.6.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
+ owlplanner-2025.6.3.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
22
+ owlplanner-2025.6.3.dist-info/RECORD,,
@@ -1,22 +0,0 @@
1
- owlplanner/__init__.py,sha256=hJ2i4m2JpHPAKyQLjYOXpJzeEsgcTcKD-Vhm0AIjjWg,592
2
- owlplanner/abcapi.py,sha256=m0vtoEzz9HJV7fOK_d7OnK7ha2Qbf7wLLPCJ9YZzR1k,6851
3
- owlplanner/config.py,sha256=v6T6A_90rVyl4sfX8KLpI8wkzt9HCjUiGDsPS-4VTec,12588
4
- owlplanner/mylogging.py,sha256=tYMw04O-XYSzjTj36fmKJGLcE1VkK6k6oJNeqtKXzuc,2530
5
- owlplanner/plan.py,sha256=BnojjOQzzFdcT4dL8EALzc_vzXO2qQJJXjY98nRZIyA,107114
6
- owlplanner/progress.py,sha256=8jlCvvtgDI89zXVNMBg1-lnEyhpPvKQS2X5oAIpoOVQ,384
7
- owlplanner/rates.py,sha256=MiaibxJY82JGpAhGyF2BJTm5-rmVAUuG8KLApVQhjvU,14816
8
- owlplanner/tax2025.py,sha256=wmlZpYeeGNrbyn5g7wOFqhWbggppodtHqc-ex5XRooI,7850
9
- owlplanner/timelists.py,sha256=wNYnJqxJ6QqE6jHh5lfFqYngfw5wUFrI15LSsM5ae8s,3949
10
- owlplanner/utils.py,sha256=WpJgn79YZfH8UCkcmhd-AZlxlGuz1i1-UDBRXImsY6I,2485
11
- owlplanner/version.py,sha256=5-OFo8vhU5vD37pZxgc1DJNjT8dReNh-zTUao6lJAKE,28
12
- owlplanner/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- owlplanner/data/rates.csv,sha256=6fxg56BVVORrj9wJlUGFdGXKvOX5r7CSca8uhUbbuIU,3734
14
- owlplanner/plotting/__init__.py,sha256=VnF6ui78YrTrg1dA6hBIdI02ahzEaHVR3ZEdDe_i880,103
15
- owlplanner/plotting/base.py,sha256=LP1TByl1tO4m087O6VpbZ_TTMnErHJGLTxXZXC9cuKQ,2431
16
- owlplanner/plotting/factory.py,sha256=i1k8m_ISnJw06f_JWlMvOQ7Q0PgV_BoLm05uLwFPvOQ,883
17
- owlplanner/plotting/matplotlib_backend.py,sha256=iJm3IBeMA5VUYG_zZxKPIzt4Izv2QWtWvlP656zwJVk,17738
18
- owlplanner/plotting/plotly_backend.py,sha256=5nqEUJXwLPW1vL9hQijxIUK57sWHvya6ZqIIYof-OjE,32944
19
- owlplanner-2025.5.28.dist-info/METADATA,sha256=MfSczDcOlFEnxDNOr1HD8NU_fqwvilnF4tTwngbQejE,54024
20
- owlplanner-2025.5.28.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
- owlplanner-2025.5.28.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
22
- owlplanner-2025.5.28.dist-info/RECORD,,