owlplanner 2025.6.21__py3-none-any.whl → 2025.7.1__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/config.py CHANGED
@@ -65,7 +65,6 @@ def saveConfig(myplan, file, mylog):
65
65
  # Rates Selection.
66
66
  diconf["Rates Selection"] = {
67
67
  "Heirs rate on tax-deferred estate": float(100 * myplan.nu),
68
- "Long-term capital gain tax rate": float(100 * myplan.psi),
69
68
  "Dividend rate": float(100 * myplan.mu),
70
69
  "TCJA expiration year": myplan.yTCJA,
71
70
  "Method": myplan.rateMethod,
@@ -228,7 +227,6 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
228
227
 
229
228
  # Rates Selection.
230
229
  p.setDividendRate(float(diconf["Rates Selection"].get("Dividend rate", 1.8))) # Fix for mod.
231
- p.setLongTermCapitalTaxRate(float(diconf["Rates Selection"]["Long-term capital gain tax rate"]))
232
230
  p.setHeirsTaxRate(float(diconf["Rates Selection"]["Heirs rate on tax-deferred estate"]))
233
231
  p.yTCJA = int(diconf["Rates Selection"]["TCJA expiration year"])
234
232
 
owlplanner/plan.py CHANGED
@@ -283,7 +283,7 @@ class Plan(object):
283
283
  self.i_s = -1
284
284
 
285
285
  # Default parameters:
286
- self.psi = 0.15 # Long-term income tax rate on capital gains (decimal)
286
+ self.psi_n = np.zeros(self.N_n) # Long-term income tax rate on capital gains (decimal)
287
287
  self.chi = 0.6 # Survivor fraction
288
288
  self.mu = 0.018 # Dividend rate (decimal)
289
289
  self.nu = 0.30 # Heirs tax rate (decimal)
@@ -485,17 +485,6 @@ class Plan(object):
485
485
  self.caseStatus = "modified"
486
486
  self._adjustedParameters = False
487
487
 
488
- def setLongTermCapitalTaxRate(self, psi):
489
- """
490
- Set long-term income tax rate. Rate is in percent. Default 15%.
491
- """
492
- if not (0 <= psi <= 100):
493
- raise ValueError("Rate must be between 0 and 100.")
494
- psi /= 100
495
- self.mylog.vprint(f"Long-term capital gain income tax set to {u.pc(psi, f=0)}.")
496
- self.psi = psi
497
- self.caseStatus = "modified"
498
-
499
488
  def setBeneficiaryFractions(self, phi):
500
489
  """
501
490
  Set fractions of savings accounts that is left to surviving spouse.
@@ -1154,20 +1143,18 @@ class Plan(object):
1154
1143
  row.addElem(_q3(self.C["w"], i, 2, n, self.N_i, self.N_j, self.N_n), -1)
1155
1144
  for dn in range(1, 6):
1156
1145
  nn = n - dn
1157
- if nn < 0: # Past of future is in the past:
1146
+ if nn >= 0: # Past of future is now or in the future: use variables and parameters.
1147
+ Tau1 = 1 + np.sum(self.alpha_ijkn[i, 2, :, nn] * self.tau_kn[:, nn], axis=0)
1148
+ cgains *= Tau1
1149
+ row.addElem(_q2(self.C["x"], i, nn, self.N_i, self.N_n), -cgains)
1150
+ # If a contribution - it can be withdrawn but not the gains.
1151
+ rhs += (cgains - 1) * self.kappa_ijn[i, 2, nn]
1152
+ else: # Past of future is in the past:
1158
1153
  # Parameters are stored at the end of contributions and conversions arrays.
1159
1154
  cgains *= oldTau1
1160
- # If only an contribution - without conversion.
1155
+ # If a contribution, it has no penalty, but assume a conversion.
1161
1156
  # rhs += (cgains - 1) * self.kappa_ijn[i, 2, nn] + cgains * self.myRothX_in[i, nn]
1162
1157
  rhs += cgains * self.kappa_ijn[i, 2, nn] + cgains * self.myRothX_in[i, nn]
1163
- else: # Past of future is in the future: use variables and parameters.
1164
- ksum2 = np.sum(self.alpha_ijkn[i, 2, :, nn] * self.tau_kn[:, nn], axis=0)
1165
- Tau1 = 1 + ksum2
1166
- cgains *= Tau1
1167
- row.addElem(_q2(self.C["x"], i, nn, self.N_i, self.N_n), -cgains)
1168
- # If only a contribution - without conversion.
1169
- # rhs += (cgains - 1) * self.kappa_ijn[i, 2, nn]
1170
- rhs += cgains * self.kappa_ijn[i, 2, nn]
1171
1158
 
1172
1159
  self.A.addRow(row, rhs, np.inf)
1173
1160
 
@@ -1323,11 +1310,11 @@ class Plan(object):
1323
1310
  tau_0prev = np.roll(self.tau_kn[0, :], 1)
1324
1311
  tau_0prev[tau_0prev < 0] = 0
1325
1312
  for n in range(self.N_n):
1326
- rhs = -self.M_n[n]
1313
+ rhs = -self.M_n[n] - self.J_n[n]
1327
1314
  row = self.A.newRow({_q1(self.C["g"], n, self.N_n): 1})
1328
1315
  row.addElem(_q1(self.C["s"], n, self.N_n), 1)
1329
1316
  for i in range(self.N_i):
1330
- fac = self.psi * self.alpha_ijkn[i, 0, 0, n]
1317
+ fac = self.psi_n[n] * self.alpha_ijkn[i, 0, 0, n]
1331
1318
  rhs += (
1332
1319
  self.omega_in[i, n]
1333
1320
  + self.zetaBar_in[i, n]
@@ -1578,7 +1565,7 @@ class Plan(object):
1578
1565
 
1579
1566
  # Check objective and required options.
1580
1567
  knownObjectives = ["maxBequest", "maxSpending"]
1581
- knownSolvers = ["HiGHS", "PuLP/CBC", "MOSEK"]
1568
+ knownSolvers = ["HiGHS", "PuLP/CBC", "PuLP/HiGHS", "MOSEK"]
1582
1569
 
1583
1570
  knownOptions = [
1584
1571
  "bequest",
@@ -1637,18 +1624,22 @@ class Plan(object):
1637
1624
  raise ValueError(f"Slack value out of range {lambdha}.")
1638
1625
  self.lambdha = lambdha / 100
1639
1626
 
1627
+ # Ensure parameters are adjusted for inflation.
1640
1628
  self._adjustParameters()
1641
1629
 
1630
+ # Reset long-term capital gain tax rate to zero.
1631
+ self.psi_n[:] = 0
1632
+
1642
1633
  solver = myoptions.get("solver", self.defaultSolver)
1643
1634
  if solver not in knownSolvers:
1644
1635
  raise ValueError(f"Unknown solver {solver}.")
1645
1636
 
1646
1637
  if solver == "HiGHS":
1647
1638
  solverMethod = self._milpSolve
1648
- elif solver == "PuLP/CBC":
1649
- solverMethod = self._pulpSolve
1650
1639
  elif solver == "MOSEK":
1651
1640
  solverMethod = self._mosekSolve
1641
+ elif "PuLP" in solver:
1642
+ solverMethod = self._pulpSolve
1652
1643
  else:
1653
1644
  raise RuntimeError("Internal error in defining solverMethod.")
1654
1645
 
@@ -1710,7 +1701,7 @@ class Plan(object):
1710
1701
  old_x = xx
1711
1702
 
1712
1703
  if solverSuccess:
1713
- self.mylog.vprint(f"Self-consistent Medicare loop returned after {it} iterations.")
1704
+ self.mylog.vprint(f"Self-consistent Medicare loop returned after {it+1} iterations.")
1714
1705
  self.mylog.vprint(solverMsg)
1715
1706
  self.mylog.vprint(f"Objective: {u.d(solution * objFac)}")
1716
1707
  # self.mylog.vprint('Upper bound:', u.d(-solution.mip_dual_bound))
@@ -1806,7 +1797,13 @@ class Plan(object):
1806
1797
  # solver = pulp.getSolver("MOSEK")
1807
1798
  # prob.solve(solver)
1808
1799
 
1809
- prob.solve(pulp.PULP_CBC_CMD(msg=False))
1800
+ if "HiGHS" in options["solver"]:
1801
+ solver = pulp.getSolver("HiGHS", msg=False)
1802
+ else:
1803
+ solver = pulp.getSolver("PULP_CBC_CMD", msg=False)
1804
+
1805
+ prob.solve(solver)
1806
+
1810
1807
  # Filter out None values and convert to array.
1811
1808
  xx = np.array([0 if x[i].varValue is None else x[i].varValue for i in range(self.nvars)])
1812
1809
  solution = np.dot(c, xx)
@@ -1876,26 +1873,46 @@ class Plan(object):
1876
1873
 
1877
1874
  return solution, xx, solverSuccess, solverMsg
1878
1875
 
1876
+ def _computeNIIT(self):
1877
+ """
1878
+ Compute Wages (W), Dividends (Q), Interests (I), and exemption(e).
1879
+ For accounting for rent and trust income, one can easily add a column
1880
+ to the Wages and Contributions file and add yearly amount to Q_n + I_n below.
1881
+ """
1882
+ J_n = np.zeros(self.N_n)
1883
+ status = len(self.yobs) - 1
1884
+
1885
+ for n in range(self.N_n):
1886
+ if status and n == self.n_d:
1887
+ status -= 1
1888
+
1889
+ Gmax = tx.niitThreshold[status]
1890
+ if self.MAGI_n[n] > Gmax:
1891
+ J_n[n] = tx.niitRate * min(self.MAGI_n[n] - Gmax, self.I_n[n] + self.Q_n[n])
1892
+
1893
+ return J_n
1894
+
1879
1895
  def _estimateMedicare(self, x=None, withMedicare=True):
1880
1896
  """
1881
- Compute rough MAGI and Medicare costs.
1897
+ Compute MAGI, Medicare costs, long-term capital gain tax rate, and
1898
+ net investment income tax (NIIT).
1882
1899
  """
1883
- if withMedicare is False:
1900
+ if x is None or withMedicare is False:
1901
+ self.MAGI_n = np.zeros(self.N_n)
1902
+ self.J_n = np.zeros(self.N_n)
1884
1903
  self.M_n = np.zeros(self.N_n)
1904
+ self.psi_n = np.zeros(self.N_n)
1885
1905
  return
1886
1906
 
1887
- if x is None:
1888
- MAGI_n = np.zeros(self.N_n)
1889
- else:
1890
- self.F_tn = np.array(x[self.C["F"] : self.C["g"]])
1891
- self.F_tn = self.F_tn.reshape((self.N_t, self.N_n))
1892
- MAGI_n = np.sum(self.F_tn, axis=0) + np.array(x[self.C["e"] : self.C["F"]])
1907
+ self._aggregateResults(x, short=True)
1893
1908
 
1894
- self.M_n = tx.mediCosts(self.yobs, self.horizons, MAGI_n, self.prevMAGI, self.gamma_n[:-1], self.N_n)
1909
+ self.J_n = self._computeNIIT()
1910
+ self.M_n = tx.mediCosts(self.yobs, self.horizons, self.MAGI_n, self.prevMAGI, self.gamma_n[:-1], self.N_n)
1911
+ self.psi_n = tx.capitalGainTaxRate(self.N_i, self.MAGI_n, self.gamma_n[:-1], self.n_d, self.N_n)
1895
1912
 
1896
1913
  return None
1897
1914
 
1898
- def _aggregateResults(self, x):
1915
+ def _aggregateResults(self, x, short=False):
1899
1916
  """
1900
1917
  Utility function to aggregate results from solver.
1901
1918
  Process all results from solution vector.
@@ -1950,7 +1967,42 @@ class Plan(object):
1950
1967
  # self.z_inz = self.z_inz.reshape((Ni, Nn, Nz))
1951
1968
  # print(self.z_inz)
1952
1969
 
1953
- # Partial distribution at the passing of first spouse.
1970
+ self.G_n = np.sum(self.F_tn, axis=0)
1971
+
1972
+ tau_0 = np.array(self.tau_kn[0, :])
1973
+ tau_0[tau_0 < 0] = 0
1974
+ # Last year's rates.
1975
+ tau_0prev = np.roll(tau_0, 1)
1976
+ self.Q_n = np.sum(
1977
+ (
1978
+ self.mu
1979
+ * (self.b_ijn[:, 0, :-1] - self.w_ijn[:, 0, :] + self.d_in[:, :] + 0.5 * self.kappa_ijn[:, 0, :Nn])
1980
+ + tau_0prev * self.w_ijn[:, 0, :]
1981
+ )
1982
+ * self.alpha_ijkn[:, 0, 0, :-1],
1983
+ axis=0,
1984
+ )
1985
+ self.U_n = self.psi_n * self.Q_n
1986
+
1987
+ self.MAGI_n = self.G_n + self.e_n + self.Q_n
1988
+
1989
+ I_in = ((self.b_ijn[:, 0, :-1] + self.d_in - self.w_ijn[:, 0, :])
1990
+ * np.sum(self.alpha_ijkn[:, 0, 1:, :-1] * self.tau_kn[1:, :], axis=1))
1991
+ self.I_n = np.sum(I_in, axis=0)
1992
+
1993
+ # Stop after building minimu required for self-consistent loop.
1994
+ if short:
1995
+ return
1996
+
1997
+ self.T_tn = self.F_tn * self.theta_tn
1998
+ self.T_n = np.sum(self.T_tn, axis=0)
1999
+ self.P_n = np.zeros(Nn)
2000
+ # Add early withdrawal penalty if any.
2001
+ for i in range(Ni):
2002
+ self.P_n[0:self.n59[i]] += 0.1*(self.w_ijn[i, 1, 0:self.n59[i]] + self.w_ijn[i, 2, 0:self.n59[i]])
2003
+
2004
+ self.T_n += self.P_n
2005
+ # Compute partial distribution at the passing of first spouse.
1954
2006
  if Ni == 2 and n_d < Nn:
1955
2007
  nx = n_d - 1
1956
2008
  i_d = self.i_d
@@ -1976,30 +2028,6 @@ class Plan(object):
1976
2028
  self.rmd_in = self.rho_in * self.b_ijn[:, 1, :-1]
1977
2029
  self.dist_in = self.w_ijn[:, 1, :] - self.rmd_in
1978
2030
  self.dist_in[self.dist_in < 0] = 0
1979
- self.G_n = np.sum(self.F_tn, axis=0)
1980
- self.T_tn = self.F_tn * self.theta_tn
1981
- self.T_n = np.sum(self.T_tn, axis=0)
1982
- self.P_n = np.zeros(Nn)
1983
- # Add early withdrawal penalty if any.
1984
- for i in range(Ni):
1985
- self.P_n[0:self.n59[i]] += 0.1*(self.w_ijn[i, 1, 0:self.n59[i]] + self.w_ijn[i, 2, 0:self.n59[i]])
1986
-
1987
- self.T_n += self.P_n
1988
-
1989
- tau_0 = np.array(self.tau_kn[0, :])
1990
- tau_0[tau_0 < 0] = 0
1991
- # Last year's rates.
1992
- tau_0prev = np.roll(tau_0, 1)
1993
- self.Q_n = np.sum(
1994
- (
1995
- self.mu
1996
- * (self.b_ijn[:, 0, :-1] - self.w_ijn[:, 0, :] + self.d_in[:, :] + 0.5 * self.kappa_ijn[:, 0, :Nn])
1997
- + tau_0prev * self.w_ijn[:, 0, :]
1998
- )
1999
- * self.alpha_ijkn[:, 0, 0, :-1],
2000
- axis=0,
2001
- )
2002
- self.U_n = self.psi * self.Q_n
2003
2031
 
2004
2032
  # Make derivative variables.
2005
2033
  # Putting it all together in a dictionary.
@@ -2137,6 +2165,11 @@ class Plan(object):
2137
2165
  dic[" Total tax paid on gains and dividends"] = f"{u.d(taxPaidNow)}"
2138
2166
  dic["[Total tax paid on gains and dividends]"] = f"{u.d(taxPaid)}"
2139
2167
 
2168
+ taxPaid = np.sum(self.J_n, axis=0)
2169
+ taxPaidNow = np.sum(self.J_n / self.gamma_n[:-1], axis=0)
2170
+ dic[" Total net investment income tax paid"] = f"{u.d(taxPaidNow)}"
2171
+ dic["[Total net investment income tax paid]"] = f"{u.d(taxPaid)}"
2172
+
2140
2173
  taxPaid = np.sum(self.M_n, axis=0)
2141
2174
  taxPaidNow = np.sum(self.M_n / self.gamma_n[:-1], axis=0)
2142
2175
  dic[" Total Medicare premiums paid"] = f"{u.d(taxPaidNow)}"
@@ -2417,8 +2450,8 @@ class Plan(object):
2417
2450
  title = self._name + "\nFederal Income Tax"
2418
2451
  if tag:
2419
2452
  title += " - " + tag
2420
- # All taxes: ordinary income and dividends.
2421
- allTaxes = self.T_n + self.U_n
2453
+ # All taxes: ordinary income, dividends, and NIIT.
2454
+ allTaxes = self.T_n + self.U_n + self.J_n
2422
2455
  fig = self._plotter.plot_taxes(self.year_n, allTaxes, self.M_n, self.gamma_n,
2423
2456
  value, title, self.inames)
2424
2457
  if figure:
@@ -2490,7 +2523,7 @@ class Plan(object):
2490
2523
  "net spending": self.g_n,
2491
2524
  "taxable ord. income": self.G_n,
2492
2525
  "taxable gains/divs": self.Q_n,
2493
- "Tax bills + Med.": self.T_n + self.U_n + self.M_n,
2526
+ "Tax bills + Med.": self.T_n + self.U_n + self.M_n + self.J_n,
2494
2527
  }
2495
2528
 
2496
2529
  fillsheet(ws, incomeDic, "currency")
@@ -2504,7 +2537,7 @@ class Plan(object):
2504
2537
  "all BTI's": np.sum(self.Lambda_in, axis=0),
2505
2538
  "all wdrwls": np.sum(self.w_ijn, axis=(0, 1)),
2506
2539
  "all deposits": -np.sum(self.d_in, axis=0),
2507
- "ord taxes": -self.T_n,
2540
+ "ord taxes": -self.T_n - self.J_n,
2508
2541
  "div taxes": -self.U_n,
2509
2542
  "Medicare": -self.M_n,
2510
2543
  }
owlplanner/tax2025.py CHANGED
@@ -33,7 +33,7 @@ rates_nonTCJA = np.array([0.10, 0.15, 0.25, 0.28, 0.33, 0.35, 0.396])
33
33
  ###############################################################################
34
34
  # Single [0] and married filing jointly [1].
35
35
 
36
- # These are current.
36
+ # These are 2025 current.
37
37
  taxBrackets_TCJA = np.array(
38
38
  [
39
39
  [11925, 48475, 103350, 197300, 250525, 626350, 9999999],
@@ -65,24 +65,57 @@ irmaaFees = 12 * np.array([185.00, 74.00, 111.00, 110.90, 111.00, 37.00])
65
65
  # These are speculated.
66
66
  taxBrackets_nonTCJA = np.array(
67
67
  [
68
- [12150, 49550, 119950, 250200, 544000, 546200, 9999999],
69
- [24350, 99100, 199850, 304600, 543950, 614450, 9999999],
68
+ [12150, 49550, 119950, 250200, 544000, 546200, 9999999], # Single
69
+ [24350, 99100, 199850, 304600, 543950, 614450, 9999999], # MFJ
70
70
  ]
71
71
  )
72
72
 
73
- # These are current.
74
- stdDeduction_TCJA = np.array([15000, 30000])
75
- # These are speculated.
76
- stdDeduction_nonTCJA = np.array([8300, 16600])
73
+ # These are 2025 current (adjusted for inflation).
74
+ stdDeduction_TCJA = np.array([15000, 30000]) # Single, MFJ
75
+ # These are speculated (adjusted for inflation).
76
+ stdDeduction_nonTCJA = np.array([8300, 16600]) # Single, MFJ
77
77
 
78
- # These are current.
79
- extra65Deduction = np.array([2000, 1600])
78
+ # These are current (adjusted for inflation).
79
+ extra65Deduction = np.array([2000, 1600]) # Single, MFJ
80
+
81
+ # Thresholds for capital gains (adjusted for inflation).
82
+ capGainRates = np.array(
83
+ [
84
+ [48350, 533400],
85
+ [96700, 600050],
86
+ ]
87
+ )
88
+
89
+ # Thresholds for net investment income tax (not adjusted for inflation).
90
+ niitThreshold = np.array([200000, 250000])
91
+ niitRate = 0.038
80
92
 
81
93
  ###############################################################################
82
94
  # End of section where rates need to be actualized every year.
83
95
  ###############################################################################
84
96
 
85
97
 
98
+ def capitalGainTaxRate(Ni, magi_n, gamma_n, nd, Nn):
99
+ """
100
+ Return an array of decimal rates for capital gains.
101
+ Parameter nd is the index year of first passing of a spouse, if applicable,
102
+ nd == Nn for single individuals.
103
+ """
104
+ status = Ni - 1
105
+ cgRate_n = np.zeros(Nn)
106
+
107
+ for n in range(Nn):
108
+ if n == nd:
109
+ status -= 1
110
+
111
+ if magi_n[n] > gamma_n[n] * capGainRates[status][1]:
112
+ cgRate_n[n] = 0.20
113
+ elif magi_n[n] > gamma_n[n] * capGainRates[status][0]:
114
+ cgRate_n[n] = 0.15
115
+
116
+ return cgRate_n
117
+
118
+
86
119
  def mediCosts(yobs, horizons, magi, prevmagi, gamma_n, Nn):
87
120
  """
88
121
  Compute Medicare costs directly.
owlplanner/timelists.py CHANGED
@@ -42,7 +42,7 @@ def read(finput, inames, horizons, mylog):
42
42
  year, anticipated wages, taxable ctrb, 401k ctrb, Roth 401k ctrb,
43
43
  IRA ctrb, Roth IRA ctrb, Roth conv, and big-ticket items.
44
44
  Supports xls, xlsx, xlsm, xlsb, odf, ods, and odt file extensions.
45
- Returs a dictionary of dataframes by individual's names.
45
+ Return a dictionary of dataframes by individual's names.
46
46
  """
47
47
 
48
48
  mylog.vprint("Reading wages, contributions, conversions, and big-ticket items over time...")
@@ -93,6 +93,7 @@ def _condition(dfDict, inames, horizons, mylog):
93
93
  # Only consider lines in proper year range. Go back 5 years for Roth maturation.
94
94
  df = df[df["year"] >= (thisyear - 5)]
95
95
  df = df[df["year"] < endyear]
96
+ df = df.drop_duplicates("year")
96
97
  missing = []
97
98
  for n in range(-5, horizons[i]):
98
99
  year = thisyear + n
owlplanner/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2025.06.21"
1
+ __version__ = "2025.07.01"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.6.21
3
+ Version: 2025.7.1
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
@@ -1,14 +1,14 @@
1
1
  owlplanner/__init__.py,sha256=hJ2i4m2JpHPAKyQLjYOXpJzeEsgcTcKD-Vhm0AIjjWg,592
2
2
  owlplanner/abcapi.py,sha256=8VCXS7nH_QZYxCUU3lwO0_UPR9Q5fuYQ6DHDLvHVLPg,6878
3
- owlplanner/config.py,sha256=onGIMqW2WwB9_CUZauDL6LtHGvc8O1cPUKKcK7Oh70M,12617
3
+ owlplanner/config.py,sha256=-sSz37hwlnmI9_oXZn-R1rpmY0Vyk5L4X--NxGpgEMA,12446
4
4
  owlplanner/mylogging.py,sha256=RKUr-y-1XvKZzLMcfdtm4IM30LuRpJwb2qUeXmAWqME,2557
5
- owlplanner/plan.py,sha256=VVHyKJiooLXXVLiRFJcauZ3oOYU62CCBe4DlpA08P38,109765
5
+ owlplanner/plan.py,sha256=rivQ9lSFJx6Eahx83VyTOm6n4uxjbr6LiwiyChRhAnc,111133
6
6
  owlplanner/progress.py,sha256=2DOjOLo6Mo7m21wY-9iZhoUksAyi4VCbb6UL2RegNCw,529
7
7
  owlplanner/rates.py,sha256=7jXcuHbkJ3AVIeBYZdwme18rdYslIzCuT-c0cLzvKUU,14819
8
- owlplanner/tax2025.py,sha256=HVYJq8po28jL5Z_il39ZY7qvf2riUEfxio15Zp7TGj8,7890
9
- owlplanner/timelists.py,sha256=95rKYknGMi1bonDVIc3xNmiwG0zTSejKyQy_uWCLSiA,4024
8
+ owlplanner/tax2025.py,sha256=3uDJfKiSRFUp5WDcouAnTEQqEY7LnDKxqxDSKiTsOSQ,8927
9
+ owlplanner/timelists.py,sha256=4pRumdoFlEmmh07wpGhDqauHl2doLG5JcRkvi41fvR4,4065
10
10
  owlplanner/utils.py,sha256=6Ky8ZKfNE9x--3znsZ8VZaT2PptDinszRxWsOCPanu8,2512
11
- owlplanner/version.py,sha256=Q6lAE5sS9Y-tuy3jNnv43HcB70y7l1kmDPNzx4CR9tc,28
11
+ owlplanner/version.py,sha256=J00-UKwWWUboYr10jyhzSLOwp2iWtGoC1DRPgnuINV0,28
12
12
  owlplanner/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  owlplanner/data/rates.csv,sha256=6fxg56BVVORrj9wJlUGFdGXKvOX5r7CSca8uhUbbuIU,3734
14
14
  owlplanner/plotting/__init__.py,sha256=uhxqtUi0OI-QWNOO2LkXgQViW_9yM3rYb-204Wit974,250
@@ -16,7 +16,7 @@ owlplanner/plotting/base.py,sha256=UimGKpMTV-dVm3BX5Apr_Ltorc7dlDLCRPRQ3RF_v7c,2
16
16
  owlplanner/plotting/factory.py,sha256=EDopIAPQr9zHRgemObko18FlCeRNhNCoLNNFAOq-X6s,1030
17
17
  owlplanner/plotting/matplotlib_backend.py,sha256=AOEkapD94U5hGNoS0EdbRoe8mgdMHH4oOvkXADZS914,17957
18
18
  owlplanner/plotting/plotly_backend.py,sha256=AO33GxBHGYG5osir_H1iRRtGxdhs4AjfLV2d_xm35nY,33138
19
- owlplanner-2025.6.21.dist-info/METADATA,sha256=bZI1gHxPSbxJvJP2DuSqiB6Y33x4tdiVgHlKK73wGD4,54045
20
- owlplanner-2025.6.21.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
- owlplanner-2025.6.21.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
22
- owlplanner-2025.6.21.dist-info/RECORD,,
19
+ owlplanner-2025.7.1.dist-info/METADATA,sha256=_T6vNe7aESAIt668fK9IVb0VDEv5P3Z16bYPyTvr9QY,54044
20
+ owlplanner-2025.7.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
+ owlplanner-2025.7.1.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
22
+ owlplanner-2025.7.1.dist-info/RECORD,,