absfuyu 5.6.1__py3-none-any.whl → 6.1.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.
Potentially problematic release.
This version of absfuyu might be problematic. Click here for more details.
- absfuyu/__init__.py +5 -3
- absfuyu/__main__.py +2 -2
- absfuyu/cli/__init__.py +13 -2
- absfuyu/cli/audio_group.py +98 -0
- absfuyu/cli/color.py +2 -2
- absfuyu/cli/config_group.py +2 -2
- absfuyu/cli/do_group.py +2 -2
- absfuyu/cli/game_group.py +20 -2
- absfuyu/cli/tool_group.py +68 -4
- absfuyu/config/__init__.py +3 -3
- absfuyu/core/__init__.py +10 -6
- absfuyu/core/baseclass.py +104 -34
- absfuyu/core/baseclass2.py +43 -2
- absfuyu/core/decorator.py +2 -2
- absfuyu/core/docstring.py +4 -2
- absfuyu/core/dummy_cli.py +3 -3
- absfuyu/core/dummy_func.py +2 -2
- absfuyu/dxt/__init__.py +2 -2
- absfuyu/dxt/base_type.py +93 -0
- absfuyu/dxt/dictext.py +188 -6
- absfuyu/dxt/dxt_support.py +2 -2
- absfuyu/dxt/intext.py +72 -4
- absfuyu/dxt/listext.py +495 -23
- absfuyu/dxt/strext.py +2 -2
- absfuyu/extra/__init__.py +2 -2
- absfuyu/extra/audio/__init__.py +8 -0
- absfuyu/extra/audio/_util.py +57 -0
- absfuyu/extra/audio/convert.py +192 -0
- absfuyu/extra/audio/lossless.py +281 -0
- absfuyu/extra/beautiful.py +2 -2
- absfuyu/extra/da/__init__.py +39 -3
- absfuyu/extra/da/dadf.py +436 -29
- absfuyu/extra/da/dadf_base.py +2 -2
- absfuyu/extra/da/df_func.py +89 -5
- absfuyu/extra/da/mplt.py +2 -2
- absfuyu/extra/ggapi/__init__.py +8 -0
- absfuyu/extra/ggapi/gdrive.py +223 -0
- absfuyu/extra/ggapi/glicense.py +148 -0
- absfuyu/extra/ggapi/glicense_df.py +186 -0
- absfuyu/extra/ggapi/gsheet.py +88 -0
- absfuyu/extra/img/__init__.py +30 -0
- absfuyu/extra/img/converter.py +402 -0
- absfuyu/extra/img/dup_check.py +291 -0
- absfuyu/extra/pdf.py +4 -6
- absfuyu/extra/rclone.py +253 -0
- absfuyu/extra/xml.py +90 -0
- absfuyu/fun/__init__.py +2 -20
- absfuyu/fun/rubik.py +2 -2
- absfuyu/fun/tarot.py +2 -2
- absfuyu/game/__init__.py +2 -2
- absfuyu/game/game_stat.py +2 -2
- absfuyu/game/schulte.py +78 -0
- absfuyu/game/sudoku.py +2 -2
- absfuyu/game/tictactoe.py +2 -2
- absfuyu/game/wordle.py +6 -4
- absfuyu/general/__init__.py +2 -2
- absfuyu/general/content.py +2 -2
- absfuyu/general/human.py +2 -2
- absfuyu/general/resrel.py +213 -0
- absfuyu/general/shape.py +3 -8
- absfuyu/general/tax.py +344 -0
- absfuyu/logger.py +806 -59
- absfuyu/numbers/__init__.py +13 -0
- absfuyu/numbers/number_to_word.py +321 -0
- absfuyu/numbers/shorten_number.py +303 -0
- absfuyu/numbers/time_duration.py +217 -0
- absfuyu/pkg_data/__init__.py +2 -2
- absfuyu/pkg_data/deprecated.py +2 -2
- absfuyu/pkg_data/logo.py +1462 -0
- absfuyu/sort.py +4 -4
- absfuyu/tools/__init__.py +2 -2
- absfuyu/tools/checksum.py +119 -4
- absfuyu/tools/converter.py +2 -2
- absfuyu/tools/generator.py +24 -7
- absfuyu/tools/inspector.py +2 -2
- absfuyu/tools/keygen.py +2 -2
- absfuyu/tools/obfuscator.py +2 -2
- absfuyu/tools/passwordlib.py +2 -2
- absfuyu/tools/shutdownizer.py +3 -8
- absfuyu/tools/sw.py +213 -10
- absfuyu/tools/web.py +10 -13
- absfuyu/typings.py +5 -8
- absfuyu/util/__init__.py +31 -2
- absfuyu/util/api.py +7 -4
- absfuyu/util/cli.py +119 -0
- absfuyu/util/gui.py +91 -0
- absfuyu/util/json_method.py +2 -2
- absfuyu/util/lunar.py +2 -2
- absfuyu/util/package.py +124 -0
- absfuyu/util/path.py +313 -4
- absfuyu/util/performance.py +2 -2
- absfuyu/util/shorten_number.py +206 -13
- absfuyu/util/text_table.py +2 -2
- absfuyu/util/zipped.py +2 -2
- absfuyu/version.py +22 -19
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/METADATA +37 -8
- absfuyu-6.1.2.dist-info/RECORD +105 -0
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/WHEEL +1 -1
- absfuyu/extra/data_analysis.py +0 -21
- absfuyu-5.6.1.dist-info/RECORD +0 -79
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/entry_points.txt +0 -0
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/licenses/LICENSE +0 -0
absfuyu/general/tax.py
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Tax calculator
|
|
3
|
+
-----------------------
|
|
4
|
+
Tax calculator
|
|
5
|
+
|
|
6
|
+
Version: 6.1.1
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module level
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = ["PersonalIncomeTaxCalculator"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Library
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import Literal, TypedDict, overload
|
|
19
|
+
|
|
20
|
+
from absfuyu.core.baseclass import BaseClass, BaseDataclass
|
|
21
|
+
|
|
22
|
+
# Class
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
type TaxLevel = list[tuple[float | int | None, float]]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class TaxLevelResult(BaseDataclass):
|
|
29
|
+
"""
|
|
30
|
+
Result for a single tax level.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
lower : float
|
|
35
|
+
Lower bound
|
|
36
|
+
|
|
37
|
+
upper : float | None
|
|
38
|
+
Upper bound. ``None`` means +infinite
|
|
39
|
+
|
|
40
|
+
rate : float
|
|
41
|
+
Tax percentage in decimal (0.1 for 10%)
|
|
42
|
+
|
|
43
|
+
amount_taxed : float
|
|
44
|
+
Amount to calculate in current tax level
|
|
45
|
+
|
|
46
|
+
tax : float
|
|
47
|
+
Tax amount in current tax level
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
lower: float
|
|
51
|
+
upper: float | None
|
|
52
|
+
rate: float
|
|
53
|
+
amount_taxed: float
|
|
54
|
+
tax: float
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TaxCalculationResult(TypedDict):
|
|
58
|
+
"""
|
|
59
|
+
Tax calculation result.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
gross_income : float
|
|
64
|
+
The input gross income for the calculation.
|
|
65
|
+
|
|
66
|
+
deductions : float, optional
|
|
67
|
+
Amount to subtract from gross income before tax.
|
|
68
|
+
|
|
69
|
+
taxable_income : float
|
|
70
|
+
Gross income minus deductions.
|
|
71
|
+
|
|
72
|
+
per_tax_level : list[TaxLevelResult]
|
|
73
|
+
Detailed breakdown of income taxed at each tax level.
|
|
74
|
+
|
|
75
|
+
gross_tax : float
|
|
76
|
+
Total tax before credits.
|
|
77
|
+
|
|
78
|
+
tax_credits : float, optional
|
|
79
|
+
Amount subtracted from the computed tax after calculation.
|
|
80
|
+
|
|
81
|
+
net_tax : float
|
|
82
|
+
Total tax after applying credits.
|
|
83
|
+
|
|
84
|
+
effective_rate : float
|
|
85
|
+
Ratio of net tax to gross income (net_tax / gross_income).
|
|
86
|
+
|
|
87
|
+
marginal_rate : float
|
|
88
|
+
Tax rate applied to the last amount of money of taxable income.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
gross_income: float
|
|
92
|
+
deductions: float
|
|
93
|
+
taxable_income: float
|
|
94
|
+
per_tax_level: list[TaxLevelResult]
|
|
95
|
+
gross_tax: float
|
|
96
|
+
tax_credits: float
|
|
97
|
+
net_tax: float
|
|
98
|
+
effective_rate: float
|
|
99
|
+
marginal_rate: float
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class TaxCalculationResultRaw(TypedDict):
|
|
103
|
+
"""
|
|
104
|
+
Tax calculation result.
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
gross_income : float
|
|
109
|
+
The input gross income for the calculation.
|
|
110
|
+
|
|
111
|
+
deductions : float, optional
|
|
112
|
+
Amount to subtract from gross income before tax.
|
|
113
|
+
|
|
114
|
+
taxable_income : float
|
|
115
|
+
Gross income minus deductions.
|
|
116
|
+
|
|
117
|
+
per_tax_level : list[TaxLevelResult]
|
|
118
|
+
Detailed breakdown of income taxed at each tax level.
|
|
119
|
+
|
|
120
|
+
gross_tax : float
|
|
121
|
+
Total tax before credits.
|
|
122
|
+
|
|
123
|
+
tax_credits : float, optional
|
|
124
|
+
Amount subtracted from the computed tax after calculation.
|
|
125
|
+
|
|
126
|
+
net_tax : float
|
|
127
|
+
Total tax after applying credits.
|
|
128
|
+
|
|
129
|
+
effective_rate : float
|
|
130
|
+
Ratio of net tax to gross income (net_tax / gross_income).
|
|
131
|
+
|
|
132
|
+
marginal_rate : float
|
|
133
|
+
Tax rate applied to the last amount of money of taxable income.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
gross_income: float
|
|
137
|
+
deductions: float
|
|
138
|
+
taxable_income: float
|
|
139
|
+
per_tax_level: list[dict[str, float | None]]
|
|
140
|
+
gross_tax: float
|
|
141
|
+
tax_credits: float
|
|
142
|
+
net_tax: float
|
|
143
|
+
effective_rate: float
|
|
144
|
+
marginal_rate: float
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class PersonalIncomeTaxCalculator(BaseClass):
|
|
148
|
+
"""
|
|
149
|
+
Progressive personal income tax calculator.
|
|
150
|
+
|
|
151
|
+
Parameters
|
|
152
|
+
----------
|
|
153
|
+
tax_levels : list[tuple[float | int | None, float]], optional
|
|
154
|
+
Ordered list of tax levels.
|
|
155
|
+
Each tax level is represented as a tuple of (upper_bound, rate),
|
|
156
|
+
where ``upper_bound`` is the cumulative upper limit of the tax level.
|
|
157
|
+
Use ``None`` for the last tax level (no upper limit).
|
|
158
|
+
Example: ``[(10000, 0.10), (50000, 0.2), (None, 0.3)]``.
|
|
159
|
+
Set to ``None`` to have 0% tax rate, by default ``None``
|
|
160
|
+
|
|
161
|
+
deductions : float, optional
|
|
162
|
+
Amount to subtract from gross income before tax, by default 0.0
|
|
163
|
+
|
|
164
|
+
tax_credits : float, optional
|
|
165
|
+
Amount subtracted from the computed tax after calculation, by default 0.0
|
|
166
|
+
|
|
167
|
+
Attributes
|
|
168
|
+
----------
|
|
169
|
+
gross_income : float
|
|
170
|
+
The input gross income for the calculation.
|
|
171
|
+
|
|
172
|
+
taxable_income : float
|
|
173
|
+
Gross income minus deductions.
|
|
174
|
+
|
|
175
|
+
per_tax_level : list[TaxLevelResult]
|
|
176
|
+
Detailed breakdown of income taxed at each tax level.
|
|
177
|
+
|
|
178
|
+
gross_tax : float
|
|
179
|
+
Total tax before credits.
|
|
180
|
+
|
|
181
|
+
net_tax : float
|
|
182
|
+
Total tax after applying credits.
|
|
183
|
+
|
|
184
|
+
effective_rate : float
|
|
185
|
+
Ratio of net tax to gross income.
|
|
186
|
+
|
|
187
|
+
marginal_rate : float
|
|
188
|
+
Tax rate applied to the last amount of money of taxable income.
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
Example:
|
|
192
|
+
--------
|
|
193
|
+
>>> tax_levels = [(100, 0.05), (None, 0.1)]
|
|
194
|
+
>>> cal = PersonalIncomeTaxCalculator(tax_levels)
|
|
195
|
+
>>> cal.calculate(500)
|
|
196
|
+
>>> cal.to_dict(raw=True)
|
|
197
|
+
{...}
|
|
198
|
+
|
|
199
|
+
>>> cal.interpret_result()
|
|
200
|
+
===== Tax information =====
|
|
201
|
+
Taxable income: 500.00 (500.00 - 0.00)
|
|
202
|
+
- Level 1: 0.00 - 100.00 @ 5.0%: 100.00 -> tax 5.00
|
|
203
|
+
- Level 2: 100.00 - 500.00 @ 10.0%: 400.00 -> tax 40.00
|
|
204
|
+
Net tax: 45.00 (45.00 - 0.00)
|
|
205
|
+
Effective rate: 9.00%
|
|
206
|
+
Marginal rate: 10.0%
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
def __init__(self, tax_levels: TaxLevel | None = None, deductions: float = 0.0, tax_credits: float = 0.0) -> None:
|
|
210
|
+
"""
|
|
211
|
+
Progressive personal income tax calculator.
|
|
212
|
+
|
|
213
|
+
Parameters
|
|
214
|
+
----------
|
|
215
|
+
tax_levels : list[tuple[float | int | None, float]], optional
|
|
216
|
+
Ordered list of tax levels.
|
|
217
|
+
Each tax level is represented as a tuple of (upper_bound, rate),
|
|
218
|
+
where ``upper_bound`` is the cumulative upper limit of the tax level.
|
|
219
|
+
Use ``None`` for the last tax level (no upper limit).
|
|
220
|
+
Example: ``[(10000, 0.10), (50000, 0.2), (None, 0.3)]``.
|
|
221
|
+
Set to ``None`` to have 0% tax rate, by default ``None``
|
|
222
|
+
|
|
223
|
+
deductions : float, optional
|
|
224
|
+
Amount to subtract from gross income before tax, by default 0.0
|
|
225
|
+
|
|
226
|
+
tax_credits : float, optional
|
|
227
|
+
Amount subtracted from the computed tax after calculation, by default 0.0
|
|
228
|
+
"""
|
|
229
|
+
self.tax_levels = [] if tax_levels is None else tax_levels
|
|
230
|
+
self.deductions = deductions
|
|
231
|
+
self.tax_credits = tax_credits
|
|
232
|
+
|
|
233
|
+
# Results populated after calculation
|
|
234
|
+
self.gross_income: float = 0.0
|
|
235
|
+
self.taxable_income: float = 0.0
|
|
236
|
+
self.per_tax_level: list[TaxLevelResult] = []
|
|
237
|
+
self.gross_tax: float = 0.0
|
|
238
|
+
self.net_tax: float = 0.0
|
|
239
|
+
self.effective_rate: float = 0.0
|
|
240
|
+
self.marginal_rate: float = 0.0
|
|
241
|
+
|
|
242
|
+
def calculate(self, gross_income: float) -> None:
|
|
243
|
+
"""
|
|
244
|
+
Compute tax for a given gross income.
|
|
245
|
+
|
|
246
|
+
Parameters
|
|
247
|
+
----------
|
|
248
|
+
gross_income : float
|
|
249
|
+
Total gross income (unless tax levels are defined otherwise).
|
|
250
|
+
"""
|
|
251
|
+
if gross_income < 0:
|
|
252
|
+
raise ValueError("gross_income must be non-negative")
|
|
253
|
+
|
|
254
|
+
self.gross_income = gross_income
|
|
255
|
+
self.taxable_income = max(0.0, gross_income - max(0.0, self.deductions))
|
|
256
|
+
|
|
257
|
+
self.per_tax_level = []
|
|
258
|
+
prev_upper = 0.0
|
|
259
|
+
remaining = self.taxable_income
|
|
260
|
+
self.gross_tax = 0.0
|
|
261
|
+
self.marginal_rate = 0.0
|
|
262
|
+
|
|
263
|
+
for upper, rate in self.tax_levels:
|
|
264
|
+
lower = prev_upper
|
|
265
|
+
if upper is None:
|
|
266
|
+
amount_taxed = remaining
|
|
267
|
+
else:
|
|
268
|
+
band = upper - prev_upper
|
|
269
|
+
amount_taxed = min(band, max(0.0, remaining))
|
|
270
|
+
tax = max(0.0, amount_taxed) * rate
|
|
271
|
+
self.per_tax_level.append(TaxLevelResult(lower, upper, rate, amount_taxed, tax))
|
|
272
|
+
self.gross_tax += tax
|
|
273
|
+
remaining -= amount_taxed
|
|
274
|
+
prev_upper = upper if upper is not None else prev_upper
|
|
275
|
+
if remaining <= 1e-9:
|
|
276
|
+
self.marginal_rate = rate
|
|
277
|
+
break
|
|
278
|
+
self.marginal_rate = rate
|
|
279
|
+
|
|
280
|
+
self.net_tax = max(0.0, self.gross_tax - max(0.0, self.tax_credits))
|
|
281
|
+
self.effective_rate = self.net_tax / gross_income if gross_income > 0 else 0.0
|
|
282
|
+
|
|
283
|
+
@overload
|
|
284
|
+
def to_dict(self) -> TaxCalculationResult: ... # type: ignore
|
|
285
|
+
|
|
286
|
+
@overload
|
|
287
|
+
def to_dict(self, *, raw: Literal[True] = ...) -> TaxCalculationResultRaw: ...
|
|
288
|
+
|
|
289
|
+
def to_dict(self, *, raw: bool = False) -> TaxCalculationResult | TaxCalculationResultRaw:
|
|
290
|
+
"""
|
|
291
|
+
Returns calculation result in dict format
|
|
292
|
+
|
|
293
|
+
Parameters
|
|
294
|
+
----------
|
|
295
|
+
raw : bool, optional
|
|
296
|
+
Convert every value to dict, by default ``False``
|
|
297
|
+
|
|
298
|
+
Returns
|
|
299
|
+
-------
|
|
300
|
+
TaxCalculationResult
|
|
301
|
+
Tax calculation result
|
|
302
|
+
"""
|
|
303
|
+
result: TaxCalculationResult = {
|
|
304
|
+
"gross_income": self.gross_income,
|
|
305
|
+
"deductions": self.deductions,
|
|
306
|
+
"taxable_income": self.taxable_income,
|
|
307
|
+
"per_tax_level": self.per_tax_level,
|
|
308
|
+
"gross_tax": self.gross_tax,
|
|
309
|
+
"tax_credits": self.tax_credits,
|
|
310
|
+
"net_tax": self.net_tax,
|
|
311
|
+
"effective_rate": self.effective_rate,
|
|
312
|
+
"marginal_rate": self.marginal_rate,
|
|
313
|
+
}
|
|
314
|
+
if raw:
|
|
315
|
+
result_raw: TaxCalculationResultRaw = result # type: ignore
|
|
316
|
+
result_raw["per_tax_level"] = [x.to_dict() for x in result["per_tax_level"]]
|
|
317
|
+
return result
|
|
318
|
+
return result
|
|
319
|
+
|
|
320
|
+
def interpret_result(self) -> str:
|
|
321
|
+
result = self.to_dict()
|
|
322
|
+
text = ["===== Tax information ====="]
|
|
323
|
+
|
|
324
|
+
# text.append(f"Gross income: {result['gross_income']:,}")
|
|
325
|
+
# text.append(f"Deduction: {result['deductions']:,}")
|
|
326
|
+
# text.append(f"Taxable income: {result['taxable_income']:,}")
|
|
327
|
+
text.append(
|
|
328
|
+
f"Taxable income: {result['taxable_income']:,.2f} ({result['gross_income']:,.2f} - {result['deductions']:,.2f})"
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
for idx, tax_level in enumerate(result["per_tax_level"], start=1):
|
|
332
|
+
upper = f"{tax_level.upper:,.2f}" if tax_level.upper is not None else f"{self.gross_income:,.2f}"
|
|
333
|
+
text.append(
|
|
334
|
+
f"- Level {idx}: {tax_level.lower:,.2f} - {upper} @ {tax_level.rate*100:.1f}%: {tax_level.amount_taxed:,.2f} -> tax {tax_level.tax:,.2f}"
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# text.append(f"Gross tax: {result['gross_tax']:,}")
|
|
338
|
+
# text.append(f"Tax credits: {result['tax_credits']:,}")
|
|
339
|
+
# text.append(f"Net tax: {result['net_tax']:,}")
|
|
340
|
+
text.append(f"Net tax: {result['net_tax']:,.2f} ({result['gross_tax']:,.2f} - {result['tax_credits']:,.2f})")
|
|
341
|
+
|
|
342
|
+
text.append(f"Effective rate: {result['effective_rate']*100:.2f}%")
|
|
343
|
+
text.append(f"Marginal rate: {result['marginal_rate']*100:.1f}%")
|
|
344
|
+
return "\n".join(text)
|