ato-benchmark-compare 0.1.3__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.
Files changed (34) hide show
  1. ato_benchmark_compare-0.1.3/.gitignore +53 -0
  2. ato_benchmark_compare-0.1.3/CONTRIBUTING.md +63 -0
  3. ato_benchmark_compare-0.1.3/LICENSE +21 -0
  4. ato_benchmark_compare-0.1.3/NOTICE +10 -0
  5. ato_benchmark_compare-0.1.3/PKG-INFO +287 -0
  6. ato_benchmark_compare-0.1.3/README.md +256 -0
  7. ato_benchmark_compare-0.1.3/SECURITY.md +43 -0
  8. ato_benchmark_compare-0.1.3/atobenchmark/__init__.py +11 -0
  9. ato_benchmark_compare-0.1.3/atobenchmark/__main__.py +6 -0
  10. ato_benchmark_compare-0.1.3/atobenchmark/atomic_io.py +58 -0
  11. ato_benchmark_compare-0.1.3/atobenchmark/cli.py +353 -0
  12. ato_benchmark_compare-0.1.3/atobenchmark/csvsafe.py +35 -0
  13. ato_benchmark_compare-0.1.3/atobenchmark/data/benchmarks-2022-23.json +4640 -0
  14. ato_benchmark_compare-0.1.3/atobenchmark/data/benchmarks-2023-24.json +4640 -0
  15. ato_benchmark_compare-0.1.3/atobenchmark/dataset.py +238 -0
  16. ato_benchmark_compare-0.1.3/atobenchmark/mapping.py +315 -0
  17. ato_benchmark_compare-0.1.3/atobenchmark/money.py +93 -0
  18. ato_benchmark_compare-0.1.3/atobenchmark/pnl.py +344 -0
  19. ato_benchmark_compare-0.1.3/atobenchmark/py.typed +0 -0
  20. ato_benchmark_compare-0.1.3/atobenchmark/ratios.py +166 -0
  21. ato_benchmark_compare-0.1.3/atobenchmark/report.py +244 -0
  22. ato_benchmark_compare-0.1.3/docs/ato-source-notes-2026-08-13.md +112 -0
  23. ato_benchmark_compare-0.1.3/examples/bakery-mapping.csv +28 -0
  24. ato_benchmark_compare-0.1.3/examples/bakery-pnl.csv +35 -0
  25. ato_benchmark_compare-0.1.3/pyproject.toml +76 -0
  26. ato_benchmark_compare-0.1.3/tests/test_atomic_io.py +64 -0
  27. ato_benchmark_compare-0.1.3/tests/test_cli.py +403 -0
  28. ato_benchmark_compare-0.1.3/tests/test_dataset.py +196 -0
  29. ato_benchmark_compare-0.1.3/tests/test_guards.py +190 -0
  30. ato_benchmark_compare-0.1.3/tests/test_mapping.py +481 -0
  31. ato_benchmark_compare-0.1.3/tests/test_money.py +79 -0
  32. ato_benchmark_compare-0.1.3/tests/test_pnl.py +256 -0
  33. ato_benchmark_compare-0.1.3/tests/test_ratios.py +194 -0
  34. ato_benchmark_compare-0.1.3/tools/build_dataset.py +250 -0
@@ -0,0 +1,53 @@
1
+ # Client and taxpayer data never belongs in this repository.
2
+ # Working files that could carry a real ledger are blocked by name and by folder.
3
+ profit-and-loss*.csv
4
+ p-and-l*.csv
5
+ pnl*.csv
6
+ mapping*.csv
7
+ !tests/fixtures/**
8
+ !examples/**
9
+ client-data/
10
+ clients/
11
+ ledgers/
12
+ exports/
13
+ *.aba
14
+ *.qbo
15
+ *.ofx
16
+ *.xlsx
17
+ *.xls
18
+ *.xlsm
19
+ !tests/fixtures/*.xlsx
20
+
21
+ # Credentials
22
+ .env
23
+ .env.*
24
+ !.env.example
25
+ *.pem
26
+ *.key
27
+ token.json
28
+
29
+ # Python
30
+ __pycache__/
31
+ *.py[cod]
32
+ *.egg-info/
33
+ .eggs/
34
+ build/
35
+ dist/
36
+ .venv/
37
+ venv/
38
+ .pytest_cache/
39
+ .ruff_cache/
40
+ .mypy_cache/
41
+ .coverage
42
+ coverage.xml
43
+ htmlcov/
44
+
45
+ # Editors and OS
46
+ .vscode/
47
+ .idea/
48
+ .DS_Store
49
+ Thumbs.db
50
+
51
+ # Agent scratch
52
+ .superpowers/
53
+ .claude/settings.local.json
@@ -0,0 +1,63 @@
1
+ # Contributing
2
+
3
+ This tool compares profit and loss figures against the ATO small business benchmarks
4
+ and shows which accounts produced each figure. A person reads the result and decides
5
+ what it means. Keep that boundary: nothing here should present a comparison as a
6
+ conclusion about whether a return is right, and nothing should lodge, submit or
7
+ transmit anything.
8
+
9
+ ## Data boundary
10
+
11
+ - Use invented data. The `.gitignore` blocks the file names a real ledger arrives
12
+ under, including `pnl*.csv`, `mapping*.csv`, `client-data/` and spreadsheet files,
13
+ with exceptions for `examples/` and `tests/fixtures/`. Put new fixtures in one of
14
+ those two directories.
15
+ - No client, taxpayer or employee data, no ABNs tied to a real business, no
16
+ screenshots of a live ledger, no credentials.
17
+ - Check what a fixture implies as well as what it says. An invented profit and loss
18
+ with a real client's account list is still that client's account list.
19
+
20
+ ## ATO rules and figures
21
+
22
+ - Trace every rule to a primary source and cite it in the pull request. The rules
23
+ currently implemented are recorded in `docs/ato-source-notes-2026-08-13.md` with
24
+ the page and QC number each came from.
25
+ - Do not hand edit anything under `atobenchmark/data`. Rebuild it with
26
+ `tools/build_dataset.py` from the ATO workbook so the SHA-256 in the file still
27
+ matches the file it came from.
28
+ - When the ATO publishes a new benchmark year, add it as a new dataset file rather
29
+ than replacing an existing one. A comparison run last year should still reproduce.
30
+ - Cross check at least one industry against the ATO's own industry page before
31
+ proposing a new dataset year. `tests/test_dataset.py` does this for bakeries.
32
+
33
+ ## Local verification
34
+
35
+ Python 3.10 or newer. The runtime imports nothing outside the standard library.
36
+ `uv` manages the development environment and the lock file is committed.
37
+
38
+ ```bash
39
+ uv sync --locked --extra dev --python 3.12
40
+ uv run --locked --extra dev --python 3.12 pytest
41
+ uv run --locked --extra dev --python 3.12 python -m build
42
+ uv run --locked --extra dev --with "pip-audit==2.10.1" pip-audit --local --strict
43
+ ```
44
+
45
+ CI repeats this on Ubuntu with Python 3.10 and 3.13, and on Windows with 3.12. Keep
46
+ runtime strings ASCII: on Windows, redirected stdout uses the machine's ANSI codepage
47
+ rather than UTF-8.
48
+
49
+ ## Pull requests
50
+
51
+ - Name the rule you changed and the test that pins it. Run that test against the old
52
+ code first. If it passes there too, it is not testing your change.
53
+ - When you change a rule, search for everything else that states it. The README
54
+ table, a docstring, the `buckets` command output and a warning string can all keep
55
+ asserting the old rule long after the code has moved.
56
+ - Amounts stay in `Decimal` end to end. A float anywhere in the ratio path will be
57
+ rejected: 0.31 as a float is not 0.31, and these comparisons are against published
58
+ figures at two decimal places.
59
+ - A displayed figure and its verdict must never disagree. 30.96% is below a 31% floor
60
+ and must not print as 31%.
61
+
62
+ For a suspected security vulnerability, follow [SECURITY.md](SECURITY.md) rather than
63
+ opening an issue.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ryan Duguid
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.
@@ -0,0 +1,10 @@
1
+ Third-party data notice
2
+
3
+ The benchmark figures under atobenchmark/data are derived from Australian
4
+ Taxation Office data, "Small Business Benchmarks", published at
5
+ https://data.gov.au/data/dataset/small-business-benchmarks and licensed under
6
+ Creative Commons Attribution 2.5 Australia
7
+ (https://creativecommons.org/licenses/by/2.5/au/). The data has been converted
8
+ from the published spreadsheet into JSON and turnover band bounds have been made
9
+ adjoining; no published ratio has been altered. The Australian Taxation Office
10
+ has not endorsed this software.
@@ -0,0 +1,287 @@
1
+ Metadata-Version: 2.5
2
+ Name: ato-benchmark-compare
3
+ Version: 0.1.3
4
+ Summary: Compare profit and loss figures against the ATO small business benchmarks
5
+ Project-URL: Homepage, https://github.com/ryanduguid/ato-benchmark-compare
6
+ Project-URL: Issues, https://github.com/ryanduguid/ato-benchmark-compare/issues
7
+ Author: Ryan Duguid
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ License-File: NOTICE
11
+ Keywords: accounting,ato,australia,benchmarks,small business,tax
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Financial and Insurance Industry
15
+ Classifier: Natural Language :: English
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
23
+ Requires-Python: >=3.10
24
+ Provides-Extra: dev
25
+ Requires-Dist: build>=1.2; extra == 'dev'
26
+ Requires-Dist: mypy>=1.14; extra == 'dev'
27
+ Requires-Dist: openpyxl>=3.1; extra == 'dev'
28
+ Requires-Dist: pytest>=8; extra == 'dev'
29
+ Requires-Dist: ruff>=0.11; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # ato-benchmark-compare
33
+
34
+ ![ato-benchmark-compare](assets/banner.svg)
35
+
36
+ [![tests](https://github.com/ryanduguid/ato-benchmark-compare/actions/workflows/ci.yml/badge.svg)](https://github.com/ryanduguid/ato-benchmark-compare/actions/workflows/ci.yml)
37
+ [![License: MIT](https://img.shields.io/badge/License-MIT-4F485E.svg?labelColor=04001F)](LICENSE)
38
+ [![python](https://img.shields.io/badge/python-3.10%2B-5C2D91.svg?logo=python&logoColor=white&labelColor=04001F)](https://www.python.org/)
39
+
40
+ Compare a set of profit and loss figures against the ATO small business benchmarks,
41
+ on your own machine, with the working shown.
42
+
43
+ The ATO publishes benchmark ranges for 100 industries and uses them to pick which
44
+ small businesses to look at more closely. Checking a client against them is a
45
+ sensible thing to do before lodgment, and it is usually done by hand: find the
46
+ industry page, work out which turnover range applies, add up the right accounts, and
47
+ divide. This does that, and it records which accounts went into which figure so the
48
+ answer can be checked by someone else later.
49
+
50
+ Australian tax rules only. Every figure comes from the ATO's own published dataset,
51
+ and the comparison is a comparison, not advice.
52
+
53
+ The published Python distribution and command remain `ato-benchmark-compare`, and the
54
+ import package remains `atobenchmark`.
55
+
56
+ ## What it gets right
57
+
58
+ The arithmetic is not "expenses over income". The ATO defines these ratios narrowly,
59
+ and the differences change the answer:
60
+
61
+ - **Turnover** is the sales of goods and services label, not total income. It falls
62
+ back to total business income only when sales are blank, zero, or less than 50% of
63
+ total business income.
64
+ - **Total expenses** for the ratio is total expenses **less payments to associated
65
+ persons**. Wages to a spouse or an associated entity come out before the division.
66
+ - **Cost of sales** for the ratio **excludes salary and wages**, so the wages sitting
67
+ in a bakery's cost of sales are moved out of the numerator and stay in total
68
+ expenses.
69
+ - **The key range** is cost of sales to turnover where the ATO publishes one for that
70
+ industry, otherwise total expenses to turnover.
71
+ - **Turnover bands** are treated as adjoining. The ATO prints `$65,000 - $400,000`
72
+ then `$400,001 - $750,000`, which read literally leaves $400,000.50 in no band at
73
+ all.
74
+
75
+ Source: Australian Taxation Office, [How we calculate benchmark
76
+ ratios](https://www.ato.gov.au/businesses-and-organisations/income-deductions-and-concessions/small-business-benchmarks/small-business-benchmarks-methodology-and-ratio-calculations/how-we-calculate-benchmark-ratios)
77
+ (QC 37143).
78
+
79
+ ## Install
80
+
81
+ ```bash
82
+ git clone https://github.com/ryanduguid/ato-benchmark-compare.git ato-benchmark-compare
83
+ cd ato-benchmark-compare
84
+ pip install .
85
+ ```
86
+
87
+ Python 3.10 or later. The runtime has no dependencies at all: the benchmark data
88
+ ships inside the package and nothing is fetched at run time.
89
+
90
+ ## Use it
91
+
92
+ The flow is two commands, because the middle step is a person reading the ledger.
93
+
94
+ **1. Draft a mapping from the profit and loss.**
95
+
96
+ ```bash
97
+ ato-benchmark-compare map --profit-and-loss examples/bakery-pnl.csv --out mapping.csv
98
+ ```
99
+
100
+ That writes one row per account with a suggested bucket, the reason it was suggested,
101
+ and the amount it read. Suggestions come from account names alone.
102
+
103
+ **2. Review it.** Open `mapping.csv`, fix the buckets, and change the `source` column
104
+ to `reviewed`. `ato-benchmark-compare buckets` explains each bucket. This is the step
105
+ that decides whether the answer is worth anything: no account name tells you whether
106
+ wages went to an associate.
107
+
108
+ The generated mapping includes an `account_key` immediately after `account`. It is a
109
+ SHA-256 digest of the tool's existing case-and-whitespace-insensitive account identity.
110
+ Leave both columns unchanged while reviewing `bucket`, `source` and `note`; `amount` is
111
+ shown for context and is not bound by the key. On the next run, the key lets the tool
112
+ recover a formula-guarded logical account without confusing `=cmd|calc` with the genuine
113
+ account `'=cmd|calc` when both look the same in a spreadsheet.
114
+
115
+ `account_key` is an identity integrity check, not authentication or tamper resistance.
116
+ Anyone who can edit the file can recompute it, and case- or whitespace-only account edits
117
+ remain valid by design. Older mappings without the column remain readable for ordinary,
118
+ unambiguous account names. A legacy formula-like name or one that could already contain a
119
+ spreadsheet guard must be regenerated; reapply the reviewed bucket, source and note values
120
+ to the new mapping. Profit and loss input parsing strips and normalises leading whitespace,
121
+ so leading tab, carriage-return and newline prefixes are not distinct raw-ledger identities.
122
+
123
+ **3. Compare.**
124
+
125
+ ```bash
126
+ ato-benchmark-compare compare \
127
+ --profit-and-loss examples/bakery-pnl.csv \
128
+ --mapping examples/bakery-mapping.csv \
129
+ --industry "Bakeries and hot bread shops"
130
+ ```
131
+
132
+ ```
133
+ ATO small business benchmark comparison
134
+ =======================================
135
+ Business type: Bakeries and hot bread shops
136
+ Benchmark year: 2023-24
137
+ Turnover: $850,000.00 (sales of goods and services)
138
+ Turnover band: More than $750,000
139
+
140
+ Ratio This business ATO range Result
141
+ ---------------------------------------------------------------------------------
142
+ Cost of sales to turnover (key) 31.76% 29% to 36% within
143
+ Total expenses to turnover 83.17% 82% to 90% within
144
+ Labour to turnover 32.68% - no benchmark in this dataset
145
+ Rent to turnover 7.29% - no benchmark in this dataset
146
+ Motor vehicle expenses to turnover 1.12% - no benchmark in this dataset
147
+
148
+ Figures used
149
+ Sales of goods and services $850,000.00
150
+ Other business income $1,200.00
151
+ Total business income $851,200.00
152
+ Total expenses $751,950.00
153
+ Less payments to associates $45,000.00
154
+ Total expenses for the ratio $706,950.00
155
+ Cost of sales excluding wages $270,000.00
156
+ Labour $277,800.00
157
+ ```
158
+
159
+ Add `--json result.json` for the same result as structured data, including every
160
+ bucket total and the source metadata.
161
+
162
+ Other commands:
163
+
164
+ ```bash
165
+ ato-benchmark-compare industries --search cleaning # find the ATO business type
166
+ ato-benchmark-compare show "Bakeries and hot bread shops"
167
+ ato-benchmark-compare buckets
168
+ ```
169
+
170
+ ## Input formats
171
+
172
+ The format this tool guarantees is two columns, with an optional `section` column of
173
+ `income`, `cost_of_sales` or `expense`:
174
+
175
+ ```csv
176
+ account,amount
177
+ Sales,850000
178
+ Purchases,290000
179
+ ```
180
+
181
+ A report style export, with a title block, section headings and subtotal rows, is
182
+ also read. Subtotal rows are detected and written into the mapping marked `excluded`
183
+ rather than dropped, so a total can never be quietly added to the figures it totals,
184
+ and nothing vanishes without appearing in a file you can read. Amounts are taken from
185
+ the first column that parses as amounts; `--amount-column` takes a column number or a
186
+ column heading when a comparative export has more than one.
187
+
188
+ **The report style layout is inferred.** It was written against the shape these
189
+ exports normally take, not verified against a real export from any particular
190
+ accounting package. Check the mapping file against your own export the first time,
191
+ and use `--amount-column` if it picked the wrong period.
192
+
193
+ ## Buckets
194
+
195
+ | Bucket | What the ATO does with it |
196
+ | --- | --- |
197
+ | `turnover` | Sales of goods and services. The turnover denominator. |
198
+ | `other_income` | Business income that is not sales. Only reaches turnover through the fallback rule. |
199
+ | `cost_of_sales` | Cost of sales, excluding wages inside it. |
200
+ | `cost_of_sales_labour` | Wages inside cost of sales. Kept out of the cost of sales ratio, kept in total expenses and labour. |
201
+ | `salary_wages` | Salary and wages outside cost of sales. |
202
+ | `contractor_commission` | Contractor, subcontractor and commission expenses. |
203
+ | `associated_persons` | Payments to associated persons. Deducted from total expenses. The wage buckets already exclude these, so labour deducts nothing further. |
204
+ | `rent` | Rent expenses. |
205
+ | `motor_vehicle` | Motor vehicle expenses. |
206
+ | `other_expense` | Every other expense, including superannuation and depreciation. |
207
+ | `excluded` | Outside the ATO calculation: income tax expense, subtotal rows. |
208
+
209
+ ## Exit codes
210
+
211
+ | Code | Meaning |
212
+ | --- | --- |
213
+ | 0 | Comparison produced, key ratio inside the ATO range |
214
+ | 1 | Could not produce a comparison, for example an account with no mapping entry |
215
+ | 2 | Comparison produced, key ratio outside the ATO range |
216
+ | 3 | Comparison produced, but accounts still carry suggested buckets |
217
+
218
+ ## What it does not do
219
+
220
+ - It is not tax advice, and sitting outside a range is not a finding that anything is
221
+ wrong. The ATO publishes ranges precisely because businesses differ.
222
+ - The bulk dataset the ATO publishes carries the two key ratios only. Labour, rent
223
+ and motor vehicle ratios are calculated and shown, but the ranges for them are on
224
+ the ATO's individual industry pages and are not in this dataset yet.
225
+ - Activity statement benchmarks are not covered. The ATO has not produced them since
226
+ 1 July 2017.
227
+ - It reads a profit and loss. It does not read a tax return, so it cannot see the
228
+ W1 label, the salary and wages code, or anything else that only exists at lodgment.
229
+ Pass `--w1` if you want the ATO's W1 rule applied to the labour ratio.
230
+
231
+ ## Client data
232
+
233
+ Nothing leaves the machine. There is no network call anywhere in the runtime.
234
+
235
+ The `.gitignore` blocks the file names a real ledger arrives under, including
236
+ `pnl*.csv`, `mapping*.csv`, spreadsheets, and `client-data/`. The example files are
237
+ invented. Do not commit a real one.
238
+
239
+ ## Benchmark data
240
+
241
+ | Year | Business types | Source |
242
+ | --- | --- | --- |
243
+ | 2023-24 | 100 | [ATO Small Business Benchmarks, data.gov.au](https://data.gov.au/data/dataset/small-business-benchmarks) |
244
+ | 2022-23 | 100 | same dataset |
245
+
246
+ Each shipped file records the resource URL, the date it was retrieved, and the SHA-256
247
+ of the ATO workbook it was built from. The comparison prints all of that, so a report
248
+ can be traced back to a specific published file.
249
+
250
+ To add a year when the ATO publishes one:
251
+
252
+ ```bash
253
+ uv run --with openpyxl python tools/build_dataset.py \
254
+ --xlsx small-business-benchmarks-2024-25-data.xlsx \
255
+ --year 2024-25 \
256
+ --resource-name "2024-25 Benchmarks" \
257
+ --resource-url https://data.gov.au/... \
258
+ --resource-last-modified 2027-03-15T00:00:00 \
259
+ --retrieved 2027-03-20 \
260
+ --out atobenchmark/data/benchmarks-2024-25.json
261
+ ```
262
+
263
+ The builder refuses a workbook whose columns are not where it expects them, rather
264
+ than quietly producing a dataset with the ratios in the wrong places.
265
+
266
+ ### Attribution
267
+
268
+ The benchmark figures are derived from Australian Taxation Office data, [Small
269
+ Business Benchmarks](https://data.gov.au/data/dataset/small-business-benchmarks),
270
+ used under [CC BY 2.5 AU](https://creativecommons.org/licenses/by/2.5/au/). The data
271
+ has been converted from the ATO's spreadsheet into JSON, and turnover band bounds
272
+ have been made adjoining as described above. No published ratio has been altered.
273
+ The ATO has not endorsed this tool and has nothing to do with it.
274
+
275
+ The code in this repository is MIT licensed. The data attribution is also
276
+ recorded in [NOTICE](NOTICE), which ships inside the wheel and the sdist.
277
+
278
+ ## Author
279
+
280
+ Written by Ryan Duguid, a provisional member of Chartered Accountants ANZ,
281
+ independently, in his own time and on his own equipment. Nothing here is the work of
282
+ any employer, and no client data was used to build or test it.
283
+
284
+ Every ATO rule it
285
+ implements was checked against the ATO's own published pages, and the shipped
286
+ benchmark figures were cross checked against the ATO's industry page for the same
287
+ industry.