macrs 0.1.0__tar.gz
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.
- macrs-0.1.0/LICENSE +21 -0
- macrs-0.1.0/PKG-INFO +138 -0
- macrs-0.1.0/README.md +107 -0
- macrs-0.1.0/pyproject.toml +59 -0
- macrs-0.1.0/setup.cfg +4 -0
- macrs-0.1.0/src/macrs/__init__.py +40 -0
- macrs-0.1.0/src/macrs/cli.py +284 -0
- macrs-0.1.0/src/macrs/models.py +105 -0
- macrs-0.1.0/src/macrs/schedule.py +279 -0
- macrs-0.1.0/src/macrs/tables.py +565 -0
- macrs-0.1.0/src/macrs.egg-info/PKG-INFO +138 -0
- macrs-0.1.0/src/macrs.egg-info/SOURCES.txt +16 -0
- macrs-0.1.0/src/macrs.egg-info/dependency_links.txt +1 -0
- macrs-0.1.0/src/macrs.egg-info/entry_points.txt +2 -0
- macrs-0.1.0/src/macrs.egg-info/requires.txt +5 -0
- macrs-0.1.0/src/macrs.egg-info/top_level.txt +1 -0
- macrs-0.1.0/tests/test_cli.py +59 -0
- macrs-0.1.0/tests/test_schedule.py +181 -0
macrs-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SybilGambleyyu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
macrs-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: macrs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: IRS Publication 946 MACRS depreciation schedules with decimal precision and an audit trail
|
|
5
|
+
Author: SybilGambleyyu
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/SybilGambleyyu/macrs
|
|
8
|
+
Project-URL: Documentation, https://github.com/SybilGambleyyu/macrs#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/SybilGambleyyu/macrs
|
|
10
|
+
Project-URL: Issues, https://github.com/SybilGambleyyu/macrs/issues
|
|
11
|
+
Project-URL: IRS Pub 946, https://www.irs.gov/publications/p946
|
|
12
|
+
Keywords: macrs,depreciation,tax,irs,publication-946,fixed-assets,accounting,section-179,bonus-depreciation,schedule
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Office/Business :: Financial :: Accounting
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
28
|
+
Requires-Dist: build; extra == "dev"
|
|
29
|
+
Requires-Dist: twine; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# macrs
|
|
33
|
+
|
|
34
|
+
**IRS Publication 946 MACRS depreciation schedules** — decimal-precise, table-driven, with an audit trail.
|
|
35
|
+
|
|
36
|
+
Tax accountants, bookkeepers, and controllers still rebuild MACRS schedules in spreadsheets every year. Year-1 rates, mid-quarter vs half-year, and the residual last year are easy to get wrong. `macrs` encodes the IRS percentage tables from [Publication 946](https://www.irs.gov/publications/p946) and produces a full recovery schedule you can export, review, and reconcile.
|
|
37
|
+
|
|
38
|
+
> **Not tax advice.** Recovery period, convention, §179, and bonus depreciation eligibility depend on current law and facts. Always verify against the current IRS Publication 946 and your tax professional before filing.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install macrs
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Requires Python 3.10+.
|
|
47
|
+
|
|
48
|
+
## Quick start
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from macrs import AssetInput, build_schedule, schedule_for_class
|
|
52
|
+
|
|
53
|
+
# $10,000 office furniture, 7-year GDS, half-year (Pub 946 Table A-1)
|
|
54
|
+
sched = build_schedule(
|
|
55
|
+
AssetInput(cost="10000", recovery_years=7, convention="half_year",
|
|
56
|
+
placed_in_service_year=2024)
|
|
57
|
+
)
|
|
58
|
+
print(sched.lines[0].depreciation) # 1429.00 (14.29%)
|
|
59
|
+
print(sched.total_macrs_depreciation) # 10000.00
|
|
60
|
+
for reason in sched.reasons:
|
|
61
|
+
print(reason)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Named asset classes for common lookups:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
sched = schedule_for_class("12000", "computers", placed_in_service_year=2025)
|
|
68
|
+
# 5-year GDS half-year → year 1 = 2400.00
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## CLI
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# 7-year property, half-year
|
|
75
|
+
macrs schedule --cost 10000 --years 7 --year 2024 -v
|
|
76
|
+
|
|
77
|
+
# Computers shortcut with §179 and 60% bonus
|
|
78
|
+
macrs schedule --cost 8000 --class computers \
|
|
79
|
+
--section-179 1000 --bonus 60 --format json
|
|
80
|
+
|
|
81
|
+
# Mid-quarter test (>40% of personal property in Q4?)
|
|
82
|
+
macrs mq-test --q1 5000 --q2 3000 --q3 2000 --q4 12000
|
|
83
|
+
|
|
84
|
+
# List class shortcuts
|
|
85
|
+
macrs classes
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## What it covers
|
|
89
|
+
|
|
90
|
+
| Feature | Detail |
|
|
91
|
+
|---------|--------|
|
|
92
|
+
| GDS personal property | 3, 5, 7, 10, 15, 20-year — Table A-1 half-year |
|
|
93
|
+
| Mid-quarter | Tables A-2…A-5 (Q1–Q4) |
|
|
94
|
+
| Residential rental | 27.5-year mid-month — Table A-6 |
|
|
95
|
+
| Nonresidential real | 39-year mid-month — Table A-7a |
|
|
96
|
+
| §179 | Reduces basis before MACRS |
|
|
97
|
+
| Bonus depreciation | % of remaining basis after §179 |
|
|
98
|
+
| Business-use % | Scales qualified cost |
|
|
99
|
+
| Mid-quarter test | >40% Q4 personal property helper |
|
|
100
|
+
| Audit trail | `reasons` list cites table and steps |
|
|
101
|
+
| Export | text / CSV / JSON |
|
|
102
|
+
|
|
103
|
+
**Not yet covered** (by design for v0.1): ADS full straight-line tables for every period, short tax years, listed-property luxury auto limits, disposition / like-kind exchange, general asset accounts, and state decoupling. Open an issue if one of these is blocking you.
|
|
104
|
+
|
|
105
|
+
## Rates source
|
|
106
|
+
|
|
107
|
+
Percentage rates are taken from **IRS Publication 946 (2025), Appendix A**. The IRS publishes these tables for public use. This package does not modify them; it applies them with `decimal.Decimal` math and absorbs the final-year residual so remaining basis hits `$0.00`.
|
|
108
|
+
|
|
109
|
+
- Tables: https://www.irs.gov/publications/p946
|
|
110
|
+
- PDF: https://www.irs.gov/pub/irs-pdf/p946.pdf
|
|
111
|
+
|
|
112
|
+
## Example: mid-quarter vs half-year
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from macrs import mid_quarter_required, AssetInput, build_schedule
|
|
116
|
+
|
|
117
|
+
required, ratio, why = mid_quarter_required([2000, 1500, 1000, 8000])
|
|
118
|
+
print(required, ratio) # True, ~0.64
|
|
119
|
+
|
|
120
|
+
# If required, build each personal-property asset with mid_quarter:
|
|
121
|
+
a = AssetInput(cost="8000", recovery_years=5, convention="mid_quarter",
|
|
122
|
+
placed_in_service_month=11) # Q4
|
|
123
|
+
print(build_schedule(a).lines[0].depreciation) # 5% year-1 rate → 400.00
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Demo
|
|
127
|
+
|
|
128
|
+
Browser calculator: https://sybilgambleyyu.github.io/demo/macrs.html
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT. IRS tables are public government data; the software encoding is MIT-licensed.
|
|
133
|
+
|
|
134
|
+
## Related tools
|
|
135
|
+
|
|
136
|
+
- [cashapply](https://github.com/SybilGambleyyu/cashapply) — cash application / AR matching
|
|
137
|
+
- [courtdays](https://github.com/SybilGambleyyu/courtdays) — FRCP legal deadlines
|
|
138
|
+
- [loadcomb](https://github.com/SybilGambleyyu/loadcomb) — ASCE 7 load combinations
|
macrs-0.1.0/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# macrs
|
|
2
|
+
|
|
3
|
+
**IRS Publication 946 MACRS depreciation schedules** — decimal-precise, table-driven, with an audit trail.
|
|
4
|
+
|
|
5
|
+
Tax accountants, bookkeepers, and controllers still rebuild MACRS schedules in spreadsheets every year. Year-1 rates, mid-quarter vs half-year, and the residual last year are easy to get wrong. `macrs` encodes the IRS percentage tables from [Publication 946](https://www.irs.gov/publications/p946) and produces a full recovery schedule you can export, review, and reconcile.
|
|
6
|
+
|
|
7
|
+
> **Not tax advice.** Recovery period, convention, §179, and bonus depreciation eligibility depend on current law and facts. Always verify against the current IRS Publication 946 and your tax professional before filing.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install macrs
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Python 3.10+.
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from macrs import AssetInput, build_schedule, schedule_for_class
|
|
21
|
+
|
|
22
|
+
# $10,000 office furniture, 7-year GDS, half-year (Pub 946 Table A-1)
|
|
23
|
+
sched = build_schedule(
|
|
24
|
+
AssetInput(cost="10000", recovery_years=7, convention="half_year",
|
|
25
|
+
placed_in_service_year=2024)
|
|
26
|
+
)
|
|
27
|
+
print(sched.lines[0].depreciation) # 1429.00 (14.29%)
|
|
28
|
+
print(sched.total_macrs_depreciation) # 10000.00
|
|
29
|
+
for reason in sched.reasons:
|
|
30
|
+
print(reason)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Named asset classes for common lookups:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
sched = schedule_for_class("12000", "computers", placed_in_service_year=2025)
|
|
37
|
+
# 5-year GDS half-year → year 1 = 2400.00
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## CLI
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# 7-year property, half-year
|
|
44
|
+
macrs schedule --cost 10000 --years 7 --year 2024 -v
|
|
45
|
+
|
|
46
|
+
# Computers shortcut with §179 and 60% bonus
|
|
47
|
+
macrs schedule --cost 8000 --class computers \
|
|
48
|
+
--section-179 1000 --bonus 60 --format json
|
|
49
|
+
|
|
50
|
+
# Mid-quarter test (>40% of personal property in Q4?)
|
|
51
|
+
macrs mq-test --q1 5000 --q2 3000 --q3 2000 --q4 12000
|
|
52
|
+
|
|
53
|
+
# List class shortcuts
|
|
54
|
+
macrs classes
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## What it covers
|
|
58
|
+
|
|
59
|
+
| Feature | Detail |
|
|
60
|
+
|---------|--------|
|
|
61
|
+
| GDS personal property | 3, 5, 7, 10, 15, 20-year — Table A-1 half-year |
|
|
62
|
+
| Mid-quarter | Tables A-2…A-5 (Q1–Q4) |
|
|
63
|
+
| Residential rental | 27.5-year mid-month — Table A-6 |
|
|
64
|
+
| Nonresidential real | 39-year mid-month — Table A-7a |
|
|
65
|
+
| §179 | Reduces basis before MACRS |
|
|
66
|
+
| Bonus depreciation | % of remaining basis after §179 |
|
|
67
|
+
| Business-use % | Scales qualified cost |
|
|
68
|
+
| Mid-quarter test | >40% Q4 personal property helper |
|
|
69
|
+
| Audit trail | `reasons` list cites table and steps |
|
|
70
|
+
| Export | text / CSV / JSON |
|
|
71
|
+
|
|
72
|
+
**Not yet covered** (by design for v0.1): ADS full straight-line tables for every period, short tax years, listed-property luxury auto limits, disposition / like-kind exchange, general asset accounts, and state decoupling. Open an issue if one of these is blocking you.
|
|
73
|
+
|
|
74
|
+
## Rates source
|
|
75
|
+
|
|
76
|
+
Percentage rates are taken from **IRS Publication 946 (2025), Appendix A**. The IRS publishes these tables for public use. This package does not modify them; it applies them with `decimal.Decimal` math and absorbs the final-year residual so remaining basis hits `$0.00`.
|
|
77
|
+
|
|
78
|
+
- Tables: https://www.irs.gov/publications/p946
|
|
79
|
+
- PDF: https://www.irs.gov/pub/irs-pdf/p946.pdf
|
|
80
|
+
|
|
81
|
+
## Example: mid-quarter vs half-year
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from macrs import mid_quarter_required, AssetInput, build_schedule
|
|
85
|
+
|
|
86
|
+
required, ratio, why = mid_quarter_required([2000, 1500, 1000, 8000])
|
|
87
|
+
print(required, ratio) # True, ~0.64
|
|
88
|
+
|
|
89
|
+
# If required, build each personal-property asset with mid_quarter:
|
|
90
|
+
a = AssetInput(cost="8000", recovery_years=5, convention="mid_quarter",
|
|
91
|
+
placed_in_service_month=11) # Q4
|
|
92
|
+
print(build_schedule(a).lines[0].depreciation) # 5% year-1 rate → 400.00
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Demo
|
|
96
|
+
|
|
97
|
+
Browser calculator: https://sybilgambleyyu.github.io/demo/macrs.html
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT. IRS tables are public government data; the software encoding is MIT-licensed.
|
|
102
|
+
|
|
103
|
+
## Related tools
|
|
104
|
+
|
|
105
|
+
- [cashapply](https://github.com/SybilGambleyyu/cashapply) — cash application / AR matching
|
|
106
|
+
- [courtdays](https://github.com/SybilGambleyyu/courtdays) — FRCP legal deadlines
|
|
107
|
+
- [loadcomb](https://github.com/SybilGambleyyu/loadcomb) — ASCE 7 load combinations
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "macrs"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "IRS Publication 946 MACRS depreciation schedules with decimal precision and an audit trail"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "SybilGambleyyu" },
|
|
15
|
+
]
|
|
16
|
+
keywords = [
|
|
17
|
+
"macrs",
|
|
18
|
+
"depreciation",
|
|
19
|
+
"tax",
|
|
20
|
+
"irs",
|
|
21
|
+
"publication-946",
|
|
22
|
+
"fixed-assets",
|
|
23
|
+
"accounting",
|
|
24
|
+
"section-179",
|
|
25
|
+
"bonus-depreciation",
|
|
26
|
+
"schedule",
|
|
27
|
+
]
|
|
28
|
+
classifiers = [
|
|
29
|
+
"Development Status :: 4 - Beta",
|
|
30
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
31
|
+
"Intended Audience :: Developers",
|
|
32
|
+
"Programming Language :: Python :: 3",
|
|
33
|
+
"Programming Language :: Python :: 3.10",
|
|
34
|
+
"Programming Language :: Python :: 3.11",
|
|
35
|
+
"Programming Language :: Python :: 3.12",
|
|
36
|
+
"Programming Language :: Python :: 3.13",
|
|
37
|
+
"Topic :: Office/Business :: Financial :: Accounting",
|
|
38
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
39
|
+
]
|
|
40
|
+
dependencies = []
|
|
41
|
+
|
|
42
|
+
[project.optional-dependencies]
|
|
43
|
+
dev = ["pytest>=7.0", "build", "twine"]
|
|
44
|
+
|
|
45
|
+
[project.scripts]
|
|
46
|
+
macrs = "macrs.cli:main"
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
Homepage = "https://github.com/SybilGambleyyu/macrs"
|
|
50
|
+
Documentation = "https://github.com/SybilGambleyyu/macrs#readme"
|
|
51
|
+
Repository = "https://github.com/SybilGambleyyu/macrs"
|
|
52
|
+
Issues = "https://github.com/SybilGambleyyu/macrs/issues"
|
|
53
|
+
"IRS Pub 946" = "https://www.irs.gov/publications/p946"
|
|
54
|
+
|
|
55
|
+
[tool.setuptools.packages.find]
|
|
56
|
+
where = ["src"]
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
macrs-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""macrs — IRS Publication 946 MACRS depreciation schedules.
|
|
2
|
+
|
|
3
|
+
Deterministic, decimal-precise depreciation schedules for US tax depreciation
|
|
4
|
+
under the Modified Accelerated Cost Recovery System. Rates come from IRS
|
|
5
|
+
Publication 946 Appendix A tables.
|
|
6
|
+
|
|
7
|
+
Not tax advice. Verify recovery period, convention, section 179, and bonus
|
|
8
|
+
depreciation eligibility against current law and the current Publication 946
|
|
9
|
+
before filing.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .models import AssetInput, DepreciationSchedule, ScheduleLine, money
|
|
13
|
+
from .schedule import (
|
|
14
|
+
asset_from_class,
|
|
15
|
+
build_schedule,
|
|
16
|
+
lookup_asset_class,
|
|
17
|
+
mid_quarter_required,
|
|
18
|
+
month_to_quarter,
|
|
19
|
+
schedule_for_class,
|
|
20
|
+
)
|
|
21
|
+
from .tables import ASSET_CLASSES, TABLE_SOURCE, TABLE_URL
|
|
22
|
+
|
|
23
|
+
__version__ = "0.1.0"
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"ASSET_CLASSES",
|
|
27
|
+
"AssetInput",
|
|
28
|
+
"DepreciationSchedule",
|
|
29
|
+
"ScheduleLine",
|
|
30
|
+
"TABLE_SOURCE",
|
|
31
|
+
"TABLE_URL",
|
|
32
|
+
"asset_from_class",
|
|
33
|
+
"build_schedule",
|
|
34
|
+
"lookup_asset_class",
|
|
35
|
+
"mid_quarter_required",
|
|
36
|
+
"money",
|
|
37
|
+
"month_to_quarter",
|
|
38
|
+
"schedule_for_class",
|
|
39
|
+
"__version__",
|
|
40
|
+
]
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""Command-line interface for MACRS depreciation schedules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import csv
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
from decimal import Decimal
|
|
10
|
+
from typing import Optional, Sequence, TextIO
|
|
11
|
+
|
|
12
|
+
from . import __version__
|
|
13
|
+
from .models import AssetInput, money
|
|
14
|
+
from .schedule import (
|
|
15
|
+
build_schedule,
|
|
16
|
+
lookup_asset_class,
|
|
17
|
+
mid_quarter_required,
|
|
18
|
+
schedule_for_class,
|
|
19
|
+
)
|
|
20
|
+
from .tables import ASSET_CLASSES, GDS_PERSONAL_PERIODS, TABLE_SOURCE, TABLE_URL
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _print_schedule(sched, out: Optional[TextIO] = None, *, verbose: bool = False) -> None:
|
|
24
|
+
if out is None:
|
|
25
|
+
out = sys.stdout
|
|
26
|
+
a = sched.asset
|
|
27
|
+
print(f"# MACRS depreciation schedule", file=out)
|
|
28
|
+
if a.label:
|
|
29
|
+
print(f"# Asset: {a.label}", file=out)
|
|
30
|
+
if a.description:
|
|
31
|
+
print(f"# Class: {a.description}", file=out)
|
|
32
|
+
print(f"# Cost: {a.cost}", file=out)
|
|
33
|
+
print(f"# Recovery: {a.recovery_years}-year Convention: {sched.convention}", file=out)
|
|
34
|
+
print(f"# Table: {sched.table_id}", file=out)
|
|
35
|
+
print(f"# Source: {sched.table_source}", file=out)
|
|
36
|
+
print(f"# §179: {sched.section_179_deduction} Bonus: {sched.bonus_deduction}", file=out)
|
|
37
|
+
print(f"# MACRS basis: {sched.macrs_basis}", file=out)
|
|
38
|
+
print(f"# Year-1 total (§179+bonus+MACRS): {sched.total_first_year}", file=out)
|
|
39
|
+
print(file=out)
|
|
40
|
+
headers = [
|
|
41
|
+
"year",
|
|
42
|
+
"calendar",
|
|
43
|
+
"rate_%",
|
|
44
|
+
"depreciation",
|
|
45
|
+
"cumulative",
|
|
46
|
+
"remaining",
|
|
47
|
+
]
|
|
48
|
+
print("\t".join(headers), file=out)
|
|
49
|
+
for line in sched.lines:
|
|
50
|
+
print(
|
|
51
|
+
"\t".join(
|
|
52
|
+
[
|
|
53
|
+
str(line.recovery_year),
|
|
54
|
+
str(line.calendar_year or ""),
|
|
55
|
+
str(line.rate_percent),
|
|
56
|
+
str(line.depreciation),
|
|
57
|
+
str(line.cumulative_depreciation),
|
|
58
|
+
str(line.remaining_basis),
|
|
59
|
+
]
|
|
60
|
+
),
|
|
61
|
+
file=out,
|
|
62
|
+
)
|
|
63
|
+
print(file=out)
|
|
64
|
+
print(f"# Total MACRS depreciation: {sched.total_macrs_depreciation}", file=out)
|
|
65
|
+
if sched.warnings:
|
|
66
|
+
print("# Warnings:", file=out)
|
|
67
|
+
for w in sched.warnings:
|
|
68
|
+
print(f"# - {w}", file=out)
|
|
69
|
+
if verbose:
|
|
70
|
+
print("# Audit trail:", file=out)
|
|
71
|
+
for r in sched.reasons:
|
|
72
|
+
print(f"# - {r}", file=out)
|
|
73
|
+
print(
|
|
74
|
+
"# Disclaimer: Not tax advice. Verify against current IRS Publication 946.",
|
|
75
|
+
file=out,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def cmd_schedule(args: argparse.Namespace) -> int:
|
|
80
|
+
if args.asset_class:
|
|
81
|
+
sched = schedule_for_class(
|
|
82
|
+
args.cost,
|
|
83
|
+
args.asset_class,
|
|
84
|
+
convention=args.convention,
|
|
85
|
+
placed_in_service_month=args.month,
|
|
86
|
+
placed_in_service_year=args.year,
|
|
87
|
+
section_179=args.section_179,
|
|
88
|
+
bonus_percent=args.bonus,
|
|
89
|
+
business_use_percent=args.business_use,
|
|
90
|
+
label=args.label,
|
|
91
|
+
)
|
|
92
|
+
else:
|
|
93
|
+
asset = AssetInput(
|
|
94
|
+
cost=money(args.cost),
|
|
95
|
+
recovery_years=args.years,
|
|
96
|
+
convention=args.convention,
|
|
97
|
+
placed_in_service_month=args.month,
|
|
98
|
+
placed_in_service_year=args.year,
|
|
99
|
+
quarter=args.quarter,
|
|
100
|
+
section_179=money(args.section_179),
|
|
101
|
+
bonus_percent=Decimal(str(args.bonus)),
|
|
102
|
+
business_use_percent=Decimal(str(args.business_use)),
|
|
103
|
+
label=args.label,
|
|
104
|
+
)
|
|
105
|
+
sched = build_schedule(asset)
|
|
106
|
+
|
|
107
|
+
if args.format == "json":
|
|
108
|
+
payload = {
|
|
109
|
+
"cost": str(sched.asset.cost),
|
|
110
|
+
"recovery_years": sched.asset.recovery_years,
|
|
111
|
+
"convention": sched.convention,
|
|
112
|
+
"table_id": sched.table_id,
|
|
113
|
+
"table_source": sched.table_source,
|
|
114
|
+
"section_179": str(sched.section_179_deduction),
|
|
115
|
+
"bonus": str(sched.bonus_deduction),
|
|
116
|
+
"macrs_basis": str(sched.macrs_basis),
|
|
117
|
+
"total_first_year": str(sched.total_first_year),
|
|
118
|
+
"total_macrs": str(sched.total_macrs_depreciation),
|
|
119
|
+
"lines": sched.to_rows(),
|
|
120
|
+
"reasons": sched.reasons if args.verbose else [],
|
|
121
|
+
"warnings": sched.warnings,
|
|
122
|
+
"disclaimer": "Not tax advice. Verify against current IRS Publication 946.",
|
|
123
|
+
}
|
|
124
|
+
json.dump(payload, sys.stdout, indent=2)
|
|
125
|
+
print()
|
|
126
|
+
elif args.format == "csv":
|
|
127
|
+
w = csv.DictWriter(
|
|
128
|
+
sys.stdout,
|
|
129
|
+
fieldnames=[
|
|
130
|
+
"recovery_year",
|
|
131
|
+
"calendar_year",
|
|
132
|
+
"rate_percent",
|
|
133
|
+
"depreciation",
|
|
134
|
+
"cumulative_depreciation",
|
|
135
|
+
"remaining_basis",
|
|
136
|
+
"notes",
|
|
137
|
+
],
|
|
138
|
+
)
|
|
139
|
+
w.writeheader()
|
|
140
|
+
for row in sched.to_rows():
|
|
141
|
+
w.writerow(row)
|
|
142
|
+
else:
|
|
143
|
+
_print_schedule(sched, verbose=args.verbose)
|
|
144
|
+
return 0
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def cmd_classes(_: argparse.Namespace) -> int:
|
|
148
|
+
print(f"# Common asset classes (from {TABLE_SOURCE})")
|
|
149
|
+
print("key\tgds_years\tads_years\tkind\tdescription")
|
|
150
|
+
for key, meta in sorted(ASSET_CLASSES.items()):
|
|
151
|
+
print(
|
|
152
|
+
f"{key}\t{meta['gds_years']}\t{meta['ads_years']}\t"
|
|
153
|
+
f"{meta['kind']}\t{meta['description']}"
|
|
154
|
+
)
|
|
155
|
+
return 0
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def cmd_mq_test(args: argparse.Namespace) -> int:
|
|
159
|
+
qs = [args.q1, args.q2, args.q3, args.q4]
|
|
160
|
+
required, ratio, reasons = mid_quarter_required(qs)
|
|
161
|
+
if args.format == "json":
|
|
162
|
+
json.dump(
|
|
163
|
+
{
|
|
164
|
+
"mid_quarter_required": required,
|
|
165
|
+
"q4_ratio": str(ratio),
|
|
166
|
+
"quarters": [str(money(q)) for q in qs],
|
|
167
|
+
"reasons": reasons,
|
|
168
|
+
},
|
|
169
|
+
sys.stdout,
|
|
170
|
+
indent=2,
|
|
171
|
+
)
|
|
172
|
+
print()
|
|
173
|
+
else:
|
|
174
|
+
print(f"Mid-quarter required: {required}")
|
|
175
|
+
print(f"Q4 ratio: {ratio * 100}%")
|
|
176
|
+
for r in reasons:
|
|
177
|
+
print(f" - {r}")
|
|
178
|
+
return 0
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def cmd_rates(args: argparse.Namespace) -> int:
|
|
182
|
+
from .tables import personal_gds_rates
|
|
183
|
+
|
|
184
|
+
rates = personal_gds_rates(
|
|
185
|
+
args.years,
|
|
186
|
+
convention=args.convention,
|
|
187
|
+
quarter=args.quarter,
|
|
188
|
+
)
|
|
189
|
+
for i, r in enumerate(rates, 1):
|
|
190
|
+
print(f"{i}\t{r}")
|
|
191
|
+
return 0
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
195
|
+
p = argparse.ArgumentParser(
|
|
196
|
+
prog="macrs",
|
|
197
|
+
description=(
|
|
198
|
+
"MACRS depreciation schedules from IRS Publication 946 percentage tables. "
|
|
199
|
+
"Not tax advice."
|
|
200
|
+
),
|
|
201
|
+
)
|
|
202
|
+
p.add_argument("--version", action="version", version=f"macrs {__version__}")
|
|
203
|
+
sub = p.add_subparsers(dest="command", required=True)
|
|
204
|
+
|
|
205
|
+
s = sub.add_parser("schedule", help="Build a depreciation schedule")
|
|
206
|
+
s.add_argument("--cost", type=str, required=True, help="Asset cost / basis")
|
|
207
|
+
s.add_argument(
|
|
208
|
+
"--years",
|
|
209
|
+
type=int,
|
|
210
|
+
default=5,
|
|
211
|
+
help="Recovery period: 3,5,7,10,15,20 (personal); 27 residential; 39 nonresidential",
|
|
212
|
+
)
|
|
213
|
+
s.add_argument(
|
|
214
|
+
"--convention",
|
|
215
|
+
default="half_year",
|
|
216
|
+
choices=["half_year", "mid_quarter", "mid_month"],
|
|
217
|
+
help="Depreciation convention",
|
|
218
|
+
)
|
|
219
|
+
s.add_argument("--month", type=int, default=1, help="Month placed in service (1-12)")
|
|
220
|
+
s.add_argument("--year", type=int, default=None, help="Calendar year placed in service")
|
|
221
|
+
s.add_argument("--quarter", type=int, default=None, help="Quarter 1-4 (mid_quarter)")
|
|
222
|
+
s.add_argument("--section-179", type=str, default="0", dest="section_179")
|
|
223
|
+
s.add_argument("--bonus", type=str, default="0", help="Bonus depreciation percent 0-100")
|
|
224
|
+
s.add_argument(
|
|
225
|
+
"--business-use",
|
|
226
|
+
type=str,
|
|
227
|
+
default="100",
|
|
228
|
+
dest="business_use",
|
|
229
|
+
help="Business-use percent 0-100",
|
|
230
|
+
)
|
|
231
|
+
s.add_argument(
|
|
232
|
+
"--class",
|
|
233
|
+
dest="asset_class",
|
|
234
|
+
default=None,
|
|
235
|
+
help=f"Named asset class: {', '.join(sorted(ASSET_CLASSES))}",
|
|
236
|
+
)
|
|
237
|
+
s.add_argument("--label", default=None)
|
|
238
|
+
s.add_argument(
|
|
239
|
+
"--format",
|
|
240
|
+
choices=["text", "json", "csv"],
|
|
241
|
+
default="text",
|
|
242
|
+
)
|
|
243
|
+
s.add_argument("-v", "--verbose", action="store_true", help="Show audit trail")
|
|
244
|
+
s.set_defaults(func=cmd_schedule)
|
|
245
|
+
|
|
246
|
+
c = sub.add_parser("classes", help="List common asset class shortcuts")
|
|
247
|
+
c.set_defaults(func=cmd_classes)
|
|
248
|
+
|
|
249
|
+
m = sub.add_parser(
|
|
250
|
+
"mq-test",
|
|
251
|
+
help="Apply the >40%% fourth-quarter mid-quarter convention test",
|
|
252
|
+
)
|
|
253
|
+
m.add_argument("--q1", type=str, required=True)
|
|
254
|
+
m.add_argument("--q2", type=str, required=True)
|
|
255
|
+
m.add_argument("--q3", type=str, required=True)
|
|
256
|
+
m.add_argument("--q4", type=str, required=True)
|
|
257
|
+
m.add_argument("--format", choices=["text", "json"], default="text")
|
|
258
|
+
m.set_defaults(func=cmd_mq_test)
|
|
259
|
+
|
|
260
|
+
r = sub.add_parser("rates", help="Print raw IRS percentage rates for a class/convention")
|
|
261
|
+
r.add_argument("--years", type=int, required=True, choices=list(GDS_PERSONAL_PERIODS))
|
|
262
|
+
r.add_argument(
|
|
263
|
+
"--convention",
|
|
264
|
+
default="half_year",
|
|
265
|
+
choices=["half_year", "mid_quarter"],
|
|
266
|
+
)
|
|
267
|
+
r.add_argument("--quarter", type=int, default=None)
|
|
268
|
+
r.set_defaults(func=cmd_rates)
|
|
269
|
+
|
|
270
|
+
return p
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
274
|
+
parser = build_parser()
|
|
275
|
+
args = parser.parse_args(argv)
|
|
276
|
+
try:
|
|
277
|
+
return args.func(args)
|
|
278
|
+
except (ValueError, KeyError) as e:
|
|
279
|
+
print(f"error: {e}", file=sys.stderr)
|
|
280
|
+
return 2
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
if __name__ == "__main__":
|
|
284
|
+
raise SystemExit(main())
|