owlplanner 2025.2.8__py3-none-any.whl → 2025.2.9__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/progress.py CHANGED
@@ -11,10 +11,10 @@ class Progress(object):
11
11
  self.mylog = mylog
12
12
 
13
13
  def start(self):
14
- self.mylog.print('|--- progress ---|')
14
+ self.mylog.print("|--- progress ---|")
15
15
 
16
16
  def show(self, x):
17
- self.mylog.print('\r\r%s' % u.pc(x, f=0), end='')
17
+ self.mylog.print("\r\r%s" % u.pc(x, f=0), end="")
18
18
 
19
19
  def finish(self):
20
20
  self.mylog.print()
owlplanner/rates.py CHANGED
@@ -43,31 +43,31 @@ from owlplanner import utils as u
43
43
  FROM = 1928
44
44
  TO = 2024
45
45
 
46
- where = os.path.dirname(sys.modules['owlplanner'].__file__)
47
- file = os.path.join(where, 'data/rates.csv')
46
+ where = os.path.dirname(sys.modules["owlplanner"].__file__)
47
+ file = os.path.join(where, "data/rates.csv")
48
48
  try:
49
49
  df = pd.read_csv(file)
50
50
  except Exception as e:
51
- raise RuntimeError(f'Could not find rates data file: {e}')
51
+ raise RuntimeError(f"Could not find rates data file: {e}")
52
52
 
53
53
 
54
54
  # Annual rate of return (%) of S&P 500 since 1928, including dividends.
55
- SP500 = df['S&P 500']
55
+ SP500 = df["S&P 500"]
56
56
 
57
57
  # Annual rate of return (%) of Baa Corporate Bonds since 1928.
58
- BondsBaa = df['Bonds Baa']
58
+ BondsBaa = df["Bonds Baa"]
59
59
 
60
60
  # Annual rate of return (%) of Aaa Corporate Bonds since 1928.
61
- BondsAaa = df['Bonds Aaa']
61
+ BondsAaa = df["Bonds Aaa"]
62
62
 
63
63
  # Annual rate of return (%) for 10-y Treasury notes since 1928.
64
- TNotes = df['TNotes']
64
+ TNotes = df["TNotes"]
65
65
 
66
66
  # Annual rates of return for 3-month Treasury bills since 1928.
67
- TBills = df['TBills']
67
+ TBills = df["TBills"]
68
68
 
69
69
  # Inflation rate as U.S. CPI index (%) since 1928.
70
- Inflation = df['Inflation']
70
+ Inflation = df["Inflation"]
71
71
 
72
72
 
73
73
  def getRatesDistributions(frm, to, mylog=None):
@@ -87,10 +87,10 @@ def getRatesDistributions(frm, to, mylog=None):
87
87
  assert frm <= to, '"from" must be smaller than "to".'
88
88
 
89
89
  series = {
90
- 'SP500': SP500,
91
- 'BondsBaa': BondsBaa,
92
- 'T. Notes': TNotes,
93
- 'Inflation': Inflation,
90
+ "SP500": SP500,
91
+ "BondsBaa": BondsBaa,
92
+ "T. Notes": TNotes,
93
+ "Inflation": Inflation,
94
94
  }
95
95
 
96
96
  df = pd.DataFrame(series)
@@ -100,8 +100,8 @@ def getRatesDistributions(frm, to, mylog=None):
100
100
  stdev = df.std()
101
101
  covar = df.cov()
102
102
 
103
- mylog.print('means: (%)\n', means)
104
- mylog.print('standard deviation: (%)\n', stdev)
103
+ mylog.print("means: (%)\n", means)
104
+ mylog.print("standard deviation: (%)\n", stdev)
105
105
 
106
106
  # Convert to NumPy array and from percent to decimal.
107
107
  means = np.array(means) / 100.0
@@ -110,7 +110,7 @@ def getRatesDistributions(frm, to, mylog=None):
110
110
  # Build correlation matrix by dividing by the stdev for each column and row.
111
111
  corr = covar / stdev[:, None]
112
112
  corr = corr.T / stdev[:, None]
113
- mylog.print('correlation matrix: \n\t\t%s' % str(corr).replace('\n', '\n\t\t'))
113
+ mylog.print("correlation matrix: \n\t\t%s" % str(corr).replace("\n", "\n\t\t"))
114
114
 
115
115
  return means, stdev, corr, covar
116
116
 
@@ -121,9 +121,9 @@ def historicalValue(amount, year):
121
121
  valued at the beginning of the year specified.
122
122
  """
123
123
  thisyear = date.today().year
124
- assert TO == thisyear - 1, 'Rates file needs to be updated to be current to %d.' % thisyear
125
- assert year >= FROM, 'Only data from %d is available.' % FROM
126
- assert year <= thisyear, 'Year must be < %d for historical data.' % thisyear
124
+ assert TO == thisyear - 1, "Rates file needs to be updated to be current to %d." % thisyear
125
+ assert year >= FROM, "Only data from %d is available." % FROM
126
+ assert year <= thisyear, "Year must be < %d for historical data." % thisyear
127
127
 
128
128
  span = thisyear - year
129
129
  ub = len(Inflation)
@@ -173,7 +173,7 @@ class Rates(object):
173
173
  self.to = TO
174
174
 
175
175
  # Default values for rates.
176
- self.setMethod('default')
176
+ self.setMethod("default")
177
177
 
178
178
  def setMethod(self, method, frm=None, to=TO, values=None, stdev=None, corr=None):
179
179
  """
@@ -193,45 +193,45 @@ class Rates(object):
193
193
  For 4 assets, this represents a list of 6 off-diagonal values.
194
194
  """
195
195
  if method not in [
196
- 'default',
197
- 'optimistic',
198
- 'conservative',
199
- 'user',
200
- 'historical',
201
- 'historical average',
202
- 'mean',
203
- 'stochastic',
204
- 'histochastic',
196
+ "default",
197
+ "optimistic",
198
+ "conservative",
199
+ "user",
200
+ "historical",
201
+ "historical average",
202
+ "mean",
203
+ "stochastic",
204
+ "histochastic",
205
205
  ]:
206
- raise ValueError('Unknown rate selection method %s.' % method)
206
+ raise ValueError("Unknown rate selection method %s." % method)
207
207
 
208
208
  Nk = len(self._defRates)
209
209
  # First process fixed methods relying on values.
210
- if method == 'default':
210
+ if method == "default":
211
211
  self.means = self._defRates
212
212
  # self.mylog.vprint('Using default fixed rates values:', *[u.pc(k) for k in values])
213
213
  self._setFixedRates(self._defRates)
214
- elif method == 'optimistic':
214
+ elif method == "optimistic":
215
215
  self.means = self._defRates
216
- self.mylog.vprint('Using optimistic fixed rates values:', *[u.pc(k) for k in self.means])
216
+ self.mylog.vprint("Using optimistic fixed rates values:", *[u.pc(k) for k in self.means])
217
217
  self._setFixedRates(self._optimisticRates)
218
- elif method == 'conservative':
218
+ elif method == "conservative":
219
219
  self.means = self._conservRates
220
- self.mylog.vprint('Using conservative fixed rates values:', *[u.pc(k) for k in self.means])
220
+ self.mylog.vprint("Using conservative fixed rates values:", *[u.pc(k) for k in self.means])
221
221
  self._setFixedRates(self._conservRates)
222
- elif method == 'user':
223
- assert values is not None, 'Fixed values must be provided with the user option.'
224
- assert len(values) == Nk, 'values must have %d items.' % Nk
222
+ elif method == "user":
223
+ assert values is not None, "Fixed values must be provided with the user option."
224
+ assert len(values) == Nk, "values must have %d items." % Nk
225
225
  self.means = np.array(values, dtype=float)
226
226
  # Convert percent to decimal for storing.
227
227
  self.means /= 100.0
228
- self.mylog.vprint('Setting rates using fixed user values:', *[u.pc(k) for k in self.means])
228
+ self.mylog.vprint("Setting rates using fixed user values:", *[u.pc(k) for k in self.means])
229
229
  self._setFixedRates(self.means)
230
- elif method == 'stochastic':
231
- assert values is not None, 'Mean values must be provided with the stochastic option.'
232
- assert stdev is not None, 'Standard deviations must be provided with the stochastic option.'
233
- assert len(values) == Nk, 'values must have %d items.' % Nk
234
- assert len(stdev) == Nk, 'stdev must have %d items.' % Nk
230
+ elif method == "stochastic":
231
+ assert values is not None, "Mean values must be provided with the stochastic option."
232
+ assert stdev is not None, "Standard deviations must be provided with the stochastic option."
233
+ assert len(values) == Nk, "values must have %d items." % Nk
234
+ assert len(stdev) == Nk, "stdev must have %d items." % Nk
235
235
  self.means = np.array(values, dtype=float)
236
236
  self.stdev = np.array(stdev, dtype=float)
237
237
  # Convert percent to decimal for storing.
@@ -256,40 +256,40 @@ class Rates(object):
256
256
  x += 1
257
257
  corrarr = newcorr
258
258
  else:
259
- raise RuntimeError('Unable to process correlation shape of %s.' % corrarr.shape)
259
+ raise RuntimeError("Unable to process correlation shape of %s." % corrarr.shape)
260
260
 
261
261
  self.corr = corrarr
262
- assert np.array_equal(self.corr, self.corr.T), 'Correlation matrix must be symmetric.'
262
+ assert np.array_equal(self.corr, self.corr.T), "Correlation matrix must be symmetric."
263
263
  # Now build covariance matrix from stdev and correlation matrix.
264
264
  # Multiply each row by a vector element-wise. Then columns.
265
265
  covar = self.corr * self.stdev
266
266
  self.covar = covar.T * self.stdev
267
267
  self._rateMethod = self._stochRates
268
- self.mylog.vprint('Setting rates using stochastic method with means:', *[u.pc(k) for k in self.means])
269
- self.mylog.vprint('\t standard deviations:', *[u.pc(k) for k in self.stdev])
270
- self.mylog.vprint('\t and correlation matrix:\n\t\t', str(self.corr).replace('\n', '\n\t\t'))
268
+ self.mylog.vprint("Setting rates using stochastic method with means:", *[u.pc(k) for k in self.means])
269
+ self.mylog.vprint("\t standard deviations:", *[u.pc(k) for k in self.stdev])
270
+ self.mylog.vprint("\t and correlation matrix:\n\t\t", str(self.corr).replace("\n", "\n\t\t"))
271
271
  else:
272
272
  # Then methods relying on historical data range.
273
- assert frm is not None, 'From year must be provided with this option.'
273
+ assert frm is not None, "From year must be provided with this option."
274
274
  assert FROM <= frm and frm <= TO, 'Lower range "frm=%d" out of bounds.' % frm
275
275
  assert FROM <= to and to <= TO, 'Upper range "to=%d" out of bounds.' % to
276
- assert frm < to, 'Unacceptable range.'
276
+ assert frm < to, "Unacceptable range."
277
277
  self.frm = frm
278
278
  self.to = to
279
279
 
280
- if method == 'historical':
281
- self.mylog.vprint('Using historical rates representing data from %d to %d.' % (frm, to))
280
+ if method == "historical":
281
+ self.mylog.vprint("Using historical rates representing data from %d to %d." % (frm, to))
282
282
  self._rateMethod = self._histRates
283
- elif method == 'historical average' or method == 'means':
284
- self.mylog.vprint('Using average of rates from %d to %d.' % (frm, to))
283
+ elif method == "historical average" or method == "means":
284
+ self.mylog.vprint("Using average of rates from %d to %d." % (frm, to))
285
285
  self.means, self.stdev, self.corr, self.covar = getRatesDistributions(frm, to, self.mylog)
286
286
  self._setFixedRates(self.means)
287
- elif method == 'histochastic':
288
- self.mylog.vprint('Using histochastic rates derived from years %d to %d.' % (frm, to))
287
+ elif method == "histochastic":
288
+ self.mylog.vprint("Using histochastic rates derived from years %d to %d." % (frm, to))
289
289
  self._rateMethod = self._stochRates
290
290
  self.means, self.stdev, self.corr, self.covar = getRatesDistributions(frm, to, self.mylog)
291
291
  else:
292
- raise ValueError('Method $s not supported.' % method)
292
+ raise ValueError("Method $s not supported." % method)
293
293
 
294
294
  self.method = method
295
295
 
@@ -297,7 +297,7 @@ class Rates(object):
297
297
 
298
298
  def _setFixedRates(self, rates):
299
299
  Nk = len(self._defRates)
300
- assert len(rates) == Nk, 'Rate list provided must have %d entries.' % Nk
300
+ assert len(rates) == Nk, "Rate list provided must have %d entries." % Nk
301
301
  self._myRates = np.array(rates)
302
302
  self._rateMethod = self._fixedRates
303
303
 
@@ -369,7 +369,7 @@ def showRatesDistributions(frm=FROM, to=TO):
369
369
  """
370
370
  import matplotlib.pyplot as plt
371
371
 
372
- title = 'Rates from ' + str(frm) + ' to ' + str(to)
372
+ title = "Rates from " + str(frm) + " to " + str(to)
373
373
  # Bring year values to indices.
374
374
  frm -= FROM
375
375
  to -= FROM
@@ -383,25 +383,25 @@ def showRatesDistributions(frm=FROM, to=TO):
383
383
  dat3 = np.array(Inflation[frm:to])
384
384
 
385
385
  fig.suptitle(title)
386
- ax[0].set_title('S&P500')
387
- label = '<>: ' + u.pc(np.mean(dat0), 2, 1)
386
+ ax[0].set_title("S&P500")
387
+ label = "<>: " + u.pc(np.mean(dat0), 2, 1)
388
388
  ax[0].hist(dat0, bins=nbins, label=label)
389
- ax[0].legend(loc='upper left', fontsize=8, framealpha=0.7)
389
+ ax[0].legend(loc="upper left", fontsize=8, framealpha=0.7)
390
390
 
391
- ax[1].set_title('BondsBaa')
392
- label = '<>: ' + u.pc(np.mean(dat1), 2, 1)
391
+ ax[1].set_title("BondsBaa")
392
+ label = "<>: " + u.pc(np.mean(dat1), 2, 1)
393
393
  ax[1].hist(dat1, bins=nbins, label=label)
394
- ax[1].legend(loc='upper left', fontsize=8, framealpha=0.7)
394
+ ax[1].legend(loc="upper left", fontsize=8, framealpha=0.7)
395
395
 
396
- ax[2].set_title('TNotes')
397
- label = '<>: ' + u.pc(np.mean(dat2), 2, 1)
396
+ ax[2].set_title("TNotes")
397
+ label = "<>: " + u.pc(np.mean(dat2), 2, 1)
398
398
  ax[2].hist(dat1, bins=nbins, label=label)
399
- ax[2].legend(loc='upper left', fontsize=8, framealpha=0.7)
399
+ ax[2].legend(loc="upper left", fontsize=8, framealpha=0.7)
400
400
 
401
- ax[3].set_title('Inflation')
402
- label = '<>: ' + u.pc(np.mean(dat3), 2, 1)
401
+ ax[3].set_title("Inflation")
402
+ label = "<>: " + u.pc(np.mean(dat3), 2, 1)
403
403
  ax[3].hist(dat3, bins=nbins, label=label)
404
- ax[3].legend(loc='upper left', fontsize=8, framealpha=0.7)
404
+ ax[3].legend(loc="upper left", fontsize=8, framealpha=0.7)
405
405
 
406
406
  plt.show()
407
407
 
owlplanner/tax2025.py CHANGED
@@ -22,7 +22,7 @@ from datetime import date
22
22
  ##############################################################################
23
23
  # Prepare the data.
24
24
 
25
- taxBracketNames = ['10%', '12/15%', '22/25%', '24/28%', '32/33%', '35%', '37/40%']
25
+ taxBracketNames = ["10%", "12/15%", "22/25%", "24/28%", "32/33%", "35%", "37/40%"]
26
26
 
27
27
  rates_2025 = np.array([0.10, 0.12, 0.22, 0.24, 0.32, 0.35, 0.370])
28
28
  rates_2026 = np.array([0.10, 0.15, 0.25, 0.28, 0.33, 0.35, 0.396])
@@ -154,7 +154,7 @@ def taxBrackets(N_i, n_d, N_n):
154
154
  Return dictionary containing future tax brackets
155
155
  unadjusted for inflation for plotting.
156
156
  """
157
- assert 0 < N_i and N_i <= 2, 'Cannot process %d individuals.' % N_i
157
+ assert 0 < N_i and N_i <= 2, "Cannot process %d individuals." % N_i
158
158
  # This 1 is the number of years left in TCJA from 2025.
159
159
  ytc = 1
160
160
  status = N_i - 1
@@ -218,7 +218,7 @@ def rho_in(yobs, N_n):
218
218
 
219
219
  N_i = len(yobs)
220
220
  if N_i == 2 and abs(yobs[0] - yobs[1]) > 10:
221
- raise RuntimeError('RMD: Unsupported age difference of more than 10 years.')
221
+ raise RuntimeError("RMD: Unsupported age difference of more than 10 years.")
222
222
 
223
223
  rho = np.zeros((N_i, N_n))
224
224
  thisyear = date.today().year
owlplanner/timelists.py CHANGED
@@ -21,15 +21,15 @@ import pandas as pd
21
21
 
22
22
  # Expected headers in each excel sheet, one per individual.
23
23
  timeHorizonItems = [
24
- 'year',
25
- 'anticipated wages',
26
- 'taxable ctrb',
27
- '401k ctrb',
28
- 'Roth 401k ctrb',
29
- 'IRA ctrb',
30
- 'Roth IRA ctrb',
31
- 'Roth conv',
32
- 'big-ticket items',
24
+ "year",
25
+ "anticipated wages",
26
+ "taxable ctrb",
27
+ "401k ctrb",
28
+ "Roth 401k ctrb",
29
+ "IRA ctrb",
30
+ "Roth IRA ctrb",
31
+ "Roth conv",
32
+ "big-ticket items",
33
33
  ]
34
34
 
35
35
 
@@ -44,18 +44,18 @@ def read(finput, inames, horizons, mylog):
44
44
  Returs a dictionary of dataframes by individual's names.
45
45
  """
46
46
 
47
- mylog.vprint('Reading wages, contributions, conversions, and big-ticket items over time...')
47
+ mylog.vprint("Reading wages, contributions, conversions, and big-ticket items over time...")
48
48
 
49
49
  if isinstance(finput, dict):
50
50
  timeLists = finput
51
- finput = 'dictionary of DataFrames'
52
- streamName = 'dictionary of DataFrames'
51
+ finput = "dictionary of DataFrames"
52
+ streamName = "dictionary of DataFrames"
53
53
  else:
54
54
  # Read all worksheets in memory but only process those with proper names.
55
55
  try:
56
56
  dfDict = pd.read_excel(finput, sheet_name=None)
57
57
  except Exception as e:
58
- raise Exception('Could not read file %r: %s.' % (finput, e))
58
+ raise Exception("Could not read file %r: %s." % (finput, e))
59
59
  streamName = "file '%s'" % finput
60
60
 
61
61
  timeLists = condition(dfDict, inames, horizons, mylog)
@@ -80,9 +80,9 @@ def condition(dfDict, inames, horizons, mylog):
80
80
 
81
81
  df = dfDict[iname]
82
82
 
83
- df = df.loc[:, ~df.columns.str.contains('^Unnamed')]
83
+ df = df.loc[:, ~df.columns.str.contains("^Unnamed")]
84
84
  for col in df.columns:
85
- if col == '' or col not in timeHorizonItems:
85
+ if col == "" or col not in timeHorizonItems:
86
86
  df.drop(col, axis=1)
87
87
 
88
88
  for item in timeHorizonItems:
@@ -90,34 +90,36 @@ def condition(dfDict, inames, horizons, mylog):
90
90
  raise ValueError(f"Item {item} not found for {iname}.")
91
91
 
92
92
  # Only consider lines in proper year range.
93
- df = df[df['year'] >= thisyear]
94
- df = df[df['year'] < endyear]
93
+ df = df[df["year"] >= thisyear]
94
+ df = df[df["year"] < endyear]
95
95
  missing = []
96
96
  for n in range(horizons[i]):
97
97
  year = thisyear + n
98
- if not (df[df['year'] == year]).any(axis=None):
98
+ if not (df[df["year"] == year]).any(axis=None):
99
99
  df.loc[len(df)] = [year, 0, 0, 0, 0, 0, 0, 0, 0]
100
100
  missing.append(year)
101
101
  else:
102
102
  for item in timeHorizonItems:
103
- if item != 'big-ticket items' and df[item].iloc[n] < 0:
104
- raise ValueError('Item %s for %s in year %d is < 0.'
105
- % (item, iname, df['year'].iloc[n])
106
- )
103
+ if item != "big-ticket items" and df[item].iloc[n] < 0:
104
+ raise ValueError("Item %s for %s in year %d is < 0." % (item, iname, df["year"].iloc[n]))
107
105
 
108
106
  if len(missing) > 0:
109
- mylog.vprint('Adding %d missing years for %s: %r.' % (len(missing), iname, missing))
107
+ mylog.vprint("Adding %d missing years for %s: %r." % (len(missing), iname, missing))
110
108
 
111
- df.sort_values('year', inplace=True)
109
+ df.sort_values("year", inplace=True)
112
110
  # Replace empty (NaN) cells with 0 value.
113
111
  df.fillna(0, inplace=True)
114
112
 
115
113
  timeLists[iname] = df
116
114
 
117
- if df['year'].iloc[-1] != endyear - 1:
118
- raise ValueError('Time horizon for', iname,
119
- 'is too short.\n\tIt should end in',
120
- endyear, 'but ends in', df['year'].iloc[-1]
121
- )
115
+ if df["year"].iloc[-1] != endyear - 1:
116
+ raise ValueError(
117
+ "Time horizon for",
118
+ iname,
119
+ "is too short.\n\tIt should end in",
120
+ endyear,
121
+ "but ends in",
122
+ df["year"].iloc[-1],
123
+ )
122
124
 
123
125
  return timeLists
owlplanner/utils.py CHANGED
@@ -20,12 +20,12 @@ def d(value, f=0, latex=False) -> str:
20
20
  Number of decimals controlled by `f` which defaults to 0.
21
21
  """
22
22
  if np.isnan(value):
23
- return 'NaN'
23
+ return "NaN"
24
24
 
25
25
  if latex:
26
- mystr = '\\${:,.' + str(f) + 'f}'
26
+ mystr = "\\${:,." + str(f) + "f}"
27
27
  else:
28
- mystr = '${:,.' + str(f) + 'f}'
28
+ mystr = "${:,." + str(f) + "f}"
29
29
 
30
30
  return mystr.format(value)
31
31
 
@@ -35,7 +35,7 @@ def pc(value, f=1, mul=100) -> str:
35
35
  Return a string formatting decimal value in percent.
36
36
  Number of decimals of percent controlled by `f` which defaults to 1.
37
37
  """
38
- mystr = '{:.' + str(f) + 'f}%'
38
+ mystr = "{:." + str(f) + "f}%"
39
39
 
40
40
  return mystr.format(mul * value)
41
41
 
@@ -58,14 +58,14 @@ def getUnits(units) -> int:
58
58
  Translate multiplication factor for units as expressed by an abbreviation
59
59
  expressed in a string. Returns an integer.
60
60
  """
61
- if units is None or units == 1 or units == '1' or units == 'one':
61
+ if units is None or units == 1 or units == "1" or units == "one":
62
62
  fac = 1
63
- elif units in {'k', 'K'}:
63
+ elif units in {"k", "K"}:
64
64
  fac = 1000
65
- elif units in {'m', 'M'}:
65
+ elif units in {"m", "M"}:
66
66
  fac = 1000000
67
67
  else:
68
- raise ValueError('Unknown units %r.' % units)
68
+ raise ValueError("Unknown units %r." % units)
69
69
 
70
70
  return fac
71
71
 
@@ -87,7 +87,7 @@ def roundCents(values, decimals=2):
87
87
  """
88
88
  multiplier = 10**decimals
89
89
 
90
- newvalues = values * multiplier + 0.5*np.sign(values)
90
+ newvalues = values * multiplier + 0.5 * np.sign(values)
91
91
 
92
92
  arr = np.fix(newvalues) / multiplier
93
93
  # Remove negative zero-like values.
owlplanner/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2025.02.08"
1
+ __version__ = "2025.02.09"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.2.8
3
+ Version: 2025.2.9
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
@@ -0,0 +1,17 @@
1
+ owlplanner/__init__.py,sha256=QqrdT0Qks20osBTg7h0vJHAxpP9lL7DA99xb0nYbtw4,254
2
+ owlplanner/abcapi.py,sha256=J2xlj6LXwIblot0g4UeO5BC-vIYXsrI72duH82H6Di8,6658
3
+ owlplanner/config.py,sha256=tKqqDxzygC283UPyZwa47oq0PBH0uEv_1bwiwAdRm4Q,11731
4
+ owlplanner/logging.py,sha256=0y6Mhj56vmilgnDvw0pA8ur15oz5O3IN7qNCaf8yUIk,2532
5
+ owlplanner/plan.py,sha256=5biGxkIfilBHOfoROpDs7m_BmzywJ6qldm_8aGAXbXU,114092
6
+ owlplanner/progress.py,sha256=uyCUmuvZzvg2UPonjmXmUsWnnP55Mm6n19Xg3WcjwHU,386
7
+ owlplanner/rates.py,sha256=CRQBaAXIB7hkxkLyDxxptBGuwnXwoIvK9_j4qTAc23Y,15627
8
+ owlplanner/tax2025.py,sha256=VNRo1CiskHLEY_m5-wPVBVK-eFoFIaDF2lFRW-bHdc4,7016
9
+ owlplanner/timelists.py,sha256=NgyPifalMBLfKi-2mtkAems4DnOJSkPtPPXR7LmbGTU,4052
10
+ owlplanner/utils.py,sha256=Wy3ngYuVmWB2CeJx-v6vdhlSeSszQTjtuPtbc7hTb_A,2353
11
+ owlplanner/version.py,sha256=WQPMjK8ZbcCw8owMuvICW0qcZSL2-1yIsSwBODMumNs,28
12
+ owlplanner/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ owlplanner/data/rates.csv,sha256=6fxg56BVVORrj9wJlUGFdGXKvOX5r7CSca8uhUbbuIU,3734
14
+ owlplanner-2025.2.9.dist-info/METADATA,sha256=cxhHp7ve0Rpmdr2pChd-y21DmNOejUweN_x8tR7N9K8,63946
15
+ owlplanner-2025.2.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ owlplanner-2025.2.9.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
17
+ owlplanner-2025.2.9.dist-info/RECORD,,
@@ -1,17 +0,0 @@
1
- owlplanner/__init__.py,sha256=QqrdT0Qks20osBTg7h0vJHAxpP9lL7DA99xb0nYbtw4,254
2
- owlplanner/abcapi.py,sha256=eemIsdbtzdWCIj5VuuswgphxXMcxJ_GZfUlDi6lttFM,6658
3
- owlplanner/config.py,sha256=1gofMnUUyOy4KXSWSPEij5yUKjvPu6kiPXcS_Nii4Rg,12220
4
- owlplanner/logging.py,sha256=pXg_mMgBll-kklqaDRLDNVUFo-5DAa-yqTKtiVrhNWw,2530
5
- owlplanner/plan.py,sha256=UBmOPVfpHPUZUo8Th6sROwTsCUi6PBuv11TD2x3XK70,114255
6
- owlplanner/progress.py,sha256=YZjL5_m4MMgKPlWlhhKacPLt54tVhVGF1eXxxZapMYs,386
7
- owlplanner/rates.py,sha256=aKOmau8i3uqxZGi7HQJpzooT3X-yAZhga5MZJ56pBzk,15627
8
- owlplanner/tax2025.py,sha256=b2RgM6TBQa8ggo6ODyh0p_J7j79UUm8z5NiENqa1l_k,7016
9
- owlplanner/timelists.py,sha256=WwymsYAGWcrEzMtc-wrLbn1EVA2fhqXGN4NHLJsH3Fs,4110
10
- owlplanner/utils.py,sha256=adIwqGVQFfvekke0JCxYJD3PKHbptVCj3NrQT2TQIB4,2351
11
- owlplanner/version.py,sha256=Voi7cBnkUuxoqJIUjmHB47rz-PDQTzK8NO_H0JraUMA,28
12
- owlplanner/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- owlplanner/data/rates.csv,sha256=6fxg56BVVORrj9wJlUGFdGXKvOX5r7CSca8uhUbbuIU,3734
14
- owlplanner-2025.2.8.dist-info/METADATA,sha256=c8WdLd8PAtMZtmRULpn-Q3BrV7edwrnpXCsRrbLbiz8,63946
15
- owlplanner-2025.2.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- owlplanner-2025.2.8.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
17
- owlplanner-2025.2.8.dist-info/RECORD,,