ato-benchmark-compare 0.1.1__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 (32) hide show
  1. ato_benchmark_compare-0.1.1/.gitignore +53 -0
  2. ato_benchmark_compare-0.1.1/CONTRIBUTING.md +63 -0
  3. ato_benchmark_compare-0.1.1/LICENSE +32 -0
  4. ato_benchmark_compare-0.1.1/PKG-INFO +263 -0
  5. ato_benchmark_compare-0.1.1/README.md +235 -0
  6. ato_benchmark_compare-0.1.1/SECURITY.md +43 -0
  7. ato_benchmark_compare-0.1.1/atobenchmark/__init__.py +7 -0
  8. ato_benchmark_compare-0.1.1/atobenchmark/__main__.py +6 -0
  9. ato_benchmark_compare-0.1.1/atobenchmark/atomic_io.py +58 -0
  10. ato_benchmark_compare-0.1.1/atobenchmark/cli.py +339 -0
  11. ato_benchmark_compare-0.1.1/atobenchmark/csvsafe.py +35 -0
  12. ato_benchmark_compare-0.1.1/atobenchmark/data/benchmarks-2022-23.json +4640 -0
  13. ato_benchmark_compare-0.1.1/atobenchmark/data/benchmarks-2023-24.json +4640 -0
  14. ato_benchmark_compare-0.1.1/atobenchmark/dataset.py +234 -0
  15. ato_benchmark_compare-0.1.1/atobenchmark/mapping.py +182 -0
  16. ato_benchmark_compare-0.1.1/atobenchmark/money.py +93 -0
  17. ato_benchmark_compare-0.1.1/atobenchmark/pnl.py +293 -0
  18. ato_benchmark_compare-0.1.1/atobenchmark/ratios.py +156 -0
  19. ato_benchmark_compare-0.1.1/atobenchmark/report.py +244 -0
  20. ato_benchmark_compare-0.1.1/docs/ato-source-notes-2026-08-13.md +108 -0
  21. ato_benchmark_compare-0.1.1/examples/bakery-mapping.csv +28 -0
  22. ato_benchmark_compare-0.1.1/examples/bakery-pnl.csv +35 -0
  23. ato_benchmark_compare-0.1.1/pyproject.toml +58 -0
  24. ato_benchmark_compare-0.1.1/tests/test_atomic_io.py +64 -0
  25. ato_benchmark_compare-0.1.1/tests/test_cli.py +274 -0
  26. ato_benchmark_compare-0.1.1/tests/test_dataset.py +180 -0
  27. ato_benchmark_compare-0.1.1/tests/test_guards.py +190 -0
  28. ato_benchmark_compare-0.1.1/tests/test_mapping.py +147 -0
  29. ato_benchmark_compare-0.1.1/tests/test_money.py +79 -0
  30. ato_benchmark_compare-0.1.1/tests/test_pnl.py +175 -0
  31. ato_benchmark_compare-0.1.1/tests/test_ratios.py +177 -0
  32. ato_benchmark_compare-0.1.1/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,32 @@
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.
22
+
23
+ ---
24
+
25
+ The benchmark figures under atobenchmark/data are derived from Australian
26
+ Taxation Office data, "Small Business Benchmarks", published at
27
+ https://data.gov.au/data/dataset/small-business-benchmarks and licensed under
28
+ Creative Commons Attribution 2.5 Australia
29
+ (https://creativecommons.org/licenses/by/2.5/au/). The data has been converted
30
+ from the published spreadsheet into JSON and turnover band bounds have been made
31
+ adjoining; no published ratio has been altered. The Australian Taxation Office
32
+ has not endorsed this software.
@@ -0,0 +1,263 @@
1
+ Metadata-Version: 2.5
2
+ Name: ato-benchmark-compare
3
+ Version: 0.1.1
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
+ Keywords: accounting,ato,australia,benchmarks,small business,tax
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Financial and Insurance Industry
14
+ Classifier: Natural Language :: English
15
+ Classifier: Operating System :: OS Independent
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
+ Requires-Python: >=3.10
23
+ Provides-Extra: dev
24
+ Requires-Dist: build>=1.2; extra == 'dev'
25
+ Requires-Dist: openpyxl>=3.1; extra == 'dev'
26
+ Requires-Dist: pytest>=8; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # ato-benchmark-compare
30
+
31
+ [![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)
32
+ [![licence: MIT](https://img.shields.io/badge/licence-MIT-blue.svg)](LICENSE)
33
+ [![python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
34
+
35
+ Compare a set of profit and loss figures against the ATO small business benchmarks,
36
+ on your own machine, with the working shown.
37
+
38
+ The ATO publishes benchmark ranges for 100 industries and uses them to pick which
39
+ small businesses to look at more closely. Checking a client against them is a
40
+ sensible thing to do before lodgment, and it is usually done by hand: find the
41
+ industry page, work out which turnover range applies, add up the right accounts, and
42
+ divide. This does that, and it records which accounts went into which figure so the
43
+ answer can be checked by someone else later.
44
+
45
+ Australian tax rules only. Every figure comes from the ATO's own published dataset,
46
+ and the comparison is a comparison, not advice.
47
+
48
+ ## What it gets right
49
+
50
+ The arithmetic is not "expenses over income". The ATO defines these ratios narrowly,
51
+ and the differences change the answer:
52
+
53
+ - **Turnover** is the sales of goods and services label, not total income. It falls
54
+ back to total business income only when sales are blank, zero, or less than 50% of
55
+ total business income.
56
+ - **Total expenses** for the ratio is total expenses **less payments to associated
57
+ persons**. Wages to a spouse or an associated entity come out before the division.
58
+ - **Cost of sales** for the ratio **excludes salary and wages**, so the wages sitting
59
+ in a bakery's cost of sales are moved out of the numerator and stay in total
60
+ expenses.
61
+ - **The key range** is cost of sales to turnover where the ATO publishes one for that
62
+ industry, otherwise total expenses to turnover.
63
+ - **Turnover bands** are treated as adjoining. The ATO prints `$65,000 - $400,000`
64
+ then `$400,001 - $750,000`, which read literally leaves $400,000.50 in no band at
65
+ all.
66
+
67
+ Source: Australian Taxation Office, [How we calculate benchmark
68
+ 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)
69
+ (QC 37143).
70
+
71
+ ## Install
72
+
73
+ ```bash
74
+ git clone https://github.com/ryanduguid/ato-benchmark-compare.git
75
+ cd ato-benchmark-compare
76
+ pip install .
77
+ ```
78
+
79
+ Python 3.10 or later. The runtime has no dependencies at all: the benchmark data
80
+ ships inside the package and nothing is fetched at run time.
81
+
82
+ ## Use it
83
+
84
+ The flow is two commands, because the middle step is a person reading the ledger.
85
+
86
+ **1. Draft a mapping from the profit and loss.**
87
+
88
+ ```bash
89
+ ato-benchmark-compare map --profit-and-loss examples/bakery-pnl.csv --out mapping.csv
90
+ ```
91
+
92
+ That writes one row per account with a suggested bucket, the reason it was suggested,
93
+ and the amount it read. Suggestions come from account names alone.
94
+
95
+ **2. Review it.** Open `mapping.csv`, fix the buckets, and change the `source` column
96
+ to `reviewed`. `ato-benchmark-compare buckets` explains each bucket. This is the step
97
+ that decides whether the answer is worth anything: no account name tells you whether
98
+ wages went to an associate.
99
+
100
+ **3. Compare.**
101
+
102
+ ```bash
103
+ ato-benchmark-compare compare \
104
+ --profit-and-loss examples/bakery-pnl.csv \
105
+ --mapping examples/bakery-mapping.csv \
106
+ --industry "Bakeries and hot bread shops"
107
+ ```
108
+
109
+ ```
110
+ ATO small business benchmark comparison
111
+ =======================================
112
+ Business type: Bakeries and hot bread shops
113
+ Benchmark year: 2023-24
114
+ Turnover: $850,000.00 (sales of goods and services)
115
+ Turnover band: More than $750,000
116
+
117
+ Ratio This business ATO range Result
118
+ ---------------------------------------------------------------------------------
119
+ Cost of sales to turnover (key) 31.76% 29% to 36% within
120
+ Total expenses to turnover 83.17% 82% to 90% within
121
+ Labour to turnover 27.39% - no benchmark in this dataset
122
+ Rent to turnover 7.29% - no benchmark in this dataset
123
+ Motor vehicle expenses to turnover 1.12% - no benchmark in this dataset
124
+
125
+ Figures used
126
+ Sales of goods and services $850,000.00
127
+ Other business income $1,200.00
128
+ Total business income $851,200.00
129
+ Total expenses $751,950.00
130
+ Less payments to associates $45,000.00
131
+ Total expenses for the ratio $706,950.00
132
+ Cost of sales excluding wages $270,000.00
133
+ Labour $232,800.00
134
+ ```
135
+
136
+ Add `--json result.json` for the same result as structured data, including every
137
+ bucket total and the source metadata.
138
+
139
+ Other commands:
140
+
141
+ ```bash
142
+ ato-benchmark-compare industries --search cleaning # find the ATO business type
143
+ ato-benchmark-compare show "Bakeries and hot bread shops"
144
+ ato-benchmark-compare buckets
145
+ ```
146
+
147
+ ## Input formats
148
+
149
+ The format this tool guarantees is two columns, with an optional `section` column of
150
+ `income`, `cost_of_sales` or `expense`:
151
+
152
+ ```csv
153
+ account,amount
154
+ Sales,850000
155
+ Purchases,290000
156
+ ```
157
+
158
+ A report style export, with a title block, section headings and subtotal rows, is
159
+ also read. Subtotal rows are detected and written into the mapping marked `excluded`
160
+ rather than dropped, so a total can never be quietly added to the figures it totals,
161
+ and nothing vanishes without appearing in a file you can read. Amounts are taken from
162
+ the first column that parses as amounts; `--amount-column` takes a column number or a
163
+ column heading when a comparative export has more than one.
164
+
165
+ **The report style layout is inferred.** It was written against the shape these
166
+ exports normally take, not verified against a real export from any particular
167
+ accounting package. Check the mapping file against your own export the first time,
168
+ and use `--amount-column` if it picked the wrong period.
169
+
170
+ ## Buckets
171
+
172
+ | Bucket | What the ATO does with it |
173
+ | --- | --- |
174
+ | `turnover` | Sales of goods and services. The turnover denominator. |
175
+ | `other_income` | Business income that is not sales. Only reaches turnover through the fallback rule. |
176
+ | `cost_of_sales` | Cost of sales, excluding wages inside it. |
177
+ | `cost_of_sales_labour` | Wages inside cost of sales. Kept out of the cost of sales ratio, kept in total expenses and labour. |
178
+ | `salary_wages` | Salary and wages outside cost of sales. |
179
+ | `contractor_commission` | Contractor, subcontractor and commission expenses. |
180
+ | `associated_persons` | Payments to associated persons. Deducted from total expenses and from labour. |
181
+ | `rent` | Rent expenses. |
182
+ | `motor_vehicle` | Motor vehicle expenses. |
183
+ | `other_expense` | Every other expense, including superannuation and depreciation. |
184
+ | `excluded` | Outside the ATO calculation: income tax expense, subtotal rows. |
185
+
186
+ ## Exit codes
187
+
188
+ | Code | Meaning |
189
+ | --- | --- |
190
+ | 0 | Comparison produced, key ratio inside the ATO range |
191
+ | 1 | Could not produce a comparison, for example an account with no mapping entry |
192
+ | 2 | Comparison produced, key ratio outside the ATO range |
193
+ | 3 | Comparison produced, but accounts still carry suggested buckets |
194
+
195
+ ## What it does not do
196
+
197
+ - It is not tax advice, and sitting outside a range is not a finding that anything is
198
+ wrong. The ATO publishes ranges precisely because businesses differ.
199
+ - The bulk dataset the ATO publishes carries the two key ratios only. Labour, rent
200
+ and motor vehicle ratios are calculated and shown, but the ranges for them are on
201
+ the ATO's individual industry pages and are not in this dataset yet.
202
+ - Activity statement benchmarks are not covered. The ATO has not produced them since
203
+ 1 July 2017.
204
+ - It reads a profit and loss. It does not read a tax return, so it cannot see the
205
+ W1 label, the salary and wages code, or anything else that only exists at lodgment.
206
+ Pass `--w1` if you want the ATO's W1 rule applied to the labour ratio.
207
+
208
+ ## Client data
209
+
210
+ Nothing leaves the machine. There is no network call anywhere in the runtime.
211
+
212
+ The `.gitignore` blocks the file names a real ledger arrives under, including
213
+ `pnl*.csv`, `mapping*.csv`, spreadsheets, and `client-data/`. The example files are
214
+ invented. Do not commit a real one.
215
+
216
+ ## Benchmark data
217
+
218
+ | Year | Business types | Source |
219
+ | --- | --- | --- |
220
+ | 2023-24 | 100 | [ATO Small Business Benchmarks, data.gov.au](https://data.gov.au/data/dataset/small-business-benchmarks) |
221
+ | 2022-23 | 100 | same dataset |
222
+
223
+ Each shipped file records the resource URL, the date it was retrieved, and the SHA-256
224
+ of the ATO workbook it was built from. The comparison prints all of that, so a report
225
+ can be traced back to a specific published file.
226
+
227
+ To add a year when the ATO publishes one:
228
+
229
+ ```bash
230
+ uv run --with openpyxl python tools/build_dataset.py \
231
+ --xlsx small-business-benchmarks-2024-25-data.xlsx \
232
+ --year 2024-25 \
233
+ --resource-name "2024-25 Benchmarks" \
234
+ --resource-url https://data.gov.au/... \
235
+ --resource-last-modified 2027-03-15T00:00:00 \
236
+ --retrieved 2027-03-20 \
237
+ --out atobenchmark/data/benchmarks-2024-25.json
238
+ ```
239
+
240
+ The builder refuses a workbook whose columns are not where it expects them, rather
241
+ than quietly producing a dataset with the ratios in the wrong places.
242
+
243
+ ### Attribution
244
+
245
+ The benchmark figures are derived from Australian Taxation Office data, [Small
246
+ Business Benchmarks](https://data.gov.au/data/dataset/small-business-benchmarks),
247
+ used under [CC BY 2.5 AU](https://creativecommons.org/licenses/by/2.5/au/). The data
248
+ has been converted from the ATO's spreadsheet into JSON, and turnover band bounds
249
+ have been made adjoining as described above. No published ratio has been altered.
250
+ The ATO has not endorsed this tool and has nothing to do with it.
251
+
252
+ The code in this repository is MIT licensed.
253
+
254
+ ## Author
255
+
256
+ Written by Ryan Duguid, a provisional member of Chartered Accountants ANZ,
257
+ independently, in his own time and on his own equipment. Nothing here is the work of
258
+ any employer, and no client data was used to build or test it.
259
+
260
+ Parts of this repository were written with AI assistance. Every ATO rule it
261
+ implements was checked against the ATO's own published pages, and the shipped
262
+ benchmark figures were cross checked against the ATO's industry page for the same
263
+ industry.
@@ -0,0 +1,235 @@
1
+ # ato-benchmark-compare
2
+
3
+ [![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)
4
+ [![licence: MIT](https://img.shields.io/badge/licence-MIT-blue.svg)](LICENSE)
5
+ [![python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
6
+
7
+ Compare a set of profit and loss figures against the ATO small business benchmarks,
8
+ on your own machine, with the working shown.
9
+
10
+ The ATO publishes benchmark ranges for 100 industries and uses them to pick which
11
+ small businesses to look at more closely. Checking a client against them is a
12
+ sensible thing to do before lodgment, and it is usually done by hand: find the
13
+ industry page, work out which turnover range applies, add up the right accounts, and
14
+ divide. This does that, and it records which accounts went into which figure so the
15
+ answer can be checked by someone else later.
16
+
17
+ Australian tax rules only. Every figure comes from the ATO's own published dataset,
18
+ and the comparison is a comparison, not advice.
19
+
20
+ ## What it gets right
21
+
22
+ The arithmetic is not "expenses over income". The ATO defines these ratios narrowly,
23
+ and the differences change the answer:
24
+
25
+ - **Turnover** is the sales of goods and services label, not total income. It falls
26
+ back to total business income only when sales are blank, zero, or less than 50% of
27
+ total business income.
28
+ - **Total expenses** for the ratio is total expenses **less payments to associated
29
+ persons**. Wages to a spouse or an associated entity come out before the division.
30
+ - **Cost of sales** for the ratio **excludes salary and wages**, so the wages sitting
31
+ in a bakery's cost of sales are moved out of the numerator and stay in total
32
+ expenses.
33
+ - **The key range** is cost of sales to turnover where the ATO publishes one for that
34
+ industry, otherwise total expenses to turnover.
35
+ - **Turnover bands** are treated as adjoining. The ATO prints `$65,000 - $400,000`
36
+ then `$400,001 - $750,000`, which read literally leaves $400,000.50 in no band at
37
+ all.
38
+
39
+ Source: Australian Taxation Office, [How we calculate benchmark
40
+ 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)
41
+ (QC 37143).
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ git clone https://github.com/ryanduguid/ato-benchmark-compare.git
47
+ cd ato-benchmark-compare
48
+ pip install .
49
+ ```
50
+
51
+ Python 3.10 or later. The runtime has no dependencies at all: the benchmark data
52
+ ships inside the package and nothing is fetched at run time.
53
+
54
+ ## Use it
55
+
56
+ The flow is two commands, because the middle step is a person reading the ledger.
57
+
58
+ **1. Draft a mapping from the profit and loss.**
59
+
60
+ ```bash
61
+ ato-benchmark-compare map --profit-and-loss examples/bakery-pnl.csv --out mapping.csv
62
+ ```
63
+
64
+ That writes one row per account with a suggested bucket, the reason it was suggested,
65
+ and the amount it read. Suggestions come from account names alone.
66
+
67
+ **2. Review it.** Open `mapping.csv`, fix the buckets, and change the `source` column
68
+ to `reviewed`. `ato-benchmark-compare buckets` explains each bucket. This is the step
69
+ that decides whether the answer is worth anything: no account name tells you whether
70
+ wages went to an associate.
71
+
72
+ **3. Compare.**
73
+
74
+ ```bash
75
+ ato-benchmark-compare compare \
76
+ --profit-and-loss examples/bakery-pnl.csv \
77
+ --mapping examples/bakery-mapping.csv \
78
+ --industry "Bakeries and hot bread shops"
79
+ ```
80
+
81
+ ```
82
+ ATO small business benchmark comparison
83
+ =======================================
84
+ Business type: Bakeries and hot bread shops
85
+ Benchmark year: 2023-24
86
+ Turnover: $850,000.00 (sales of goods and services)
87
+ Turnover band: More than $750,000
88
+
89
+ Ratio This business ATO range Result
90
+ ---------------------------------------------------------------------------------
91
+ Cost of sales to turnover (key) 31.76% 29% to 36% within
92
+ Total expenses to turnover 83.17% 82% to 90% within
93
+ Labour to turnover 27.39% - no benchmark in this dataset
94
+ Rent to turnover 7.29% - no benchmark in this dataset
95
+ Motor vehicle expenses to turnover 1.12% - no benchmark in this dataset
96
+
97
+ Figures used
98
+ Sales of goods and services $850,000.00
99
+ Other business income $1,200.00
100
+ Total business income $851,200.00
101
+ Total expenses $751,950.00
102
+ Less payments to associates $45,000.00
103
+ Total expenses for the ratio $706,950.00
104
+ Cost of sales excluding wages $270,000.00
105
+ Labour $232,800.00
106
+ ```
107
+
108
+ Add `--json result.json` for the same result as structured data, including every
109
+ bucket total and the source metadata.
110
+
111
+ Other commands:
112
+
113
+ ```bash
114
+ ato-benchmark-compare industries --search cleaning # find the ATO business type
115
+ ato-benchmark-compare show "Bakeries and hot bread shops"
116
+ ato-benchmark-compare buckets
117
+ ```
118
+
119
+ ## Input formats
120
+
121
+ The format this tool guarantees is two columns, with an optional `section` column of
122
+ `income`, `cost_of_sales` or `expense`:
123
+
124
+ ```csv
125
+ account,amount
126
+ Sales,850000
127
+ Purchases,290000
128
+ ```
129
+
130
+ A report style export, with a title block, section headings and subtotal rows, is
131
+ also read. Subtotal rows are detected and written into the mapping marked `excluded`
132
+ rather than dropped, so a total can never be quietly added to the figures it totals,
133
+ and nothing vanishes without appearing in a file you can read. Amounts are taken from
134
+ the first column that parses as amounts; `--amount-column` takes a column number or a
135
+ column heading when a comparative export has more than one.
136
+
137
+ **The report style layout is inferred.** It was written against the shape these
138
+ exports normally take, not verified against a real export from any particular
139
+ accounting package. Check the mapping file against your own export the first time,
140
+ and use `--amount-column` if it picked the wrong period.
141
+
142
+ ## Buckets
143
+
144
+ | Bucket | What the ATO does with it |
145
+ | --- | --- |
146
+ | `turnover` | Sales of goods and services. The turnover denominator. |
147
+ | `other_income` | Business income that is not sales. Only reaches turnover through the fallback rule. |
148
+ | `cost_of_sales` | Cost of sales, excluding wages inside it. |
149
+ | `cost_of_sales_labour` | Wages inside cost of sales. Kept out of the cost of sales ratio, kept in total expenses and labour. |
150
+ | `salary_wages` | Salary and wages outside cost of sales. |
151
+ | `contractor_commission` | Contractor, subcontractor and commission expenses. |
152
+ | `associated_persons` | Payments to associated persons. Deducted from total expenses and from labour. |
153
+ | `rent` | Rent expenses. |
154
+ | `motor_vehicle` | Motor vehicle expenses. |
155
+ | `other_expense` | Every other expense, including superannuation and depreciation. |
156
+ | `excluded` | Outside the ATO calculation: income tax expense, subtotal rows. |
157
+
158
+ ## Exit codes
159
+
160
+ | Code | Meaning |
161
+ | --- | --- |
162
+ | 0 | Comparison produced, key ratio inside the ATO range |
163
+ | 1 | Could not produce a comparison, for example an account with no mapping entry |
164
+ | 2 | Comparison produced, key ratio outside the ATO range |
165
+ | 3 | Comparison produced, but accounts still carry suggested buckets |
166
+
167
+ ## What it does not do
168
+
169
+ - It is not tax advice, and sitting outside a range is not a finding that anything is
170
+ wrong. The ATO publishes ranges precisely because businesses differ.
171
+ - The bulk dataset the ATO publishes carries the two key ratios only. Labour, rent
172
+ and motor vehicle ratios are calculated and shown, but the ranges for them are on
173
+ the ATO's individual industry pages and are not in this dataset yet.
174
+ - Activity statement benchmarks are not covered. The ATO has not produced them since
175
+ 1 July 2017.
176
+ - It reads a profit and loss. It does not read a tax return, so it cannot see the
177
+ W1 label, the salary and wages code, or anything else that only exists at lodgment.
178
+ Pass `--w1` if you want the ATO's W1 rule applied to the labour ratio.
179
+
180
+ ## Client data
181
+
182
+ Nothing leaves the machine. There is no network call anywhere in the runtime.
183
+
184
+ The `.gitignore` blocks the file names a real ledger arrives under, including
185
+ `pnl*.csv`, `mapping*.csv`, spreadsheets, and `client-data/`. The example files are
186
+ invented. Do not commit a real one.
187
+
188
+ ## Benchmark data
189
+
190
+ | Year | Business types | Source |
191
+ | --- | --- | --- |
192
+ | 2023-24 | 100 | [ATO Small Business Benchmarks, data.gov.au](https://data.gov.au/data/dataset/small-business-benchmarks) |
193
+ | 2022-23 | 100 | same dataset |
194
+
195
+ Each shipped file records the resource URL, the date it was retrieved, and the SHA-256
196
+ of the ATO workbook it was built from. The comparison prints all of that, so a report
197
+ can be traced back to a specific published file.
198
+
199
+ To add a year when the ATO publishes one:
200
+
201
+ ```bash
202
+ uv run --with openpyxl python tools/build_dataset.py \
203
+ --xlsx small-business-benchmarks-2024-25-data.xlsx \
204
+ --year 2024-25 \
205
+ --resource-name "2024-25 Benchmarks" \
206
+ --resource-url https://data.gov.au/... \
207
+ --resource-last-modified 2027-03-15T00:00:00 \
208
+ --retrieved 2027-03-20 \
209
+ --out atobenchmark/data/benchmarks-2024-25.json
210
+ ```
211
+
212
+ The builder refuses a workbook whose columns are not where it expects them, rather
213
+ than quietly producing a dataset with the ratios in the wrong places.
214
+
215
+ ### Attribution
216
+
217
+ The benchmark figures are derived from Australian Taxation Office data, [Small
218
+ Business Benchmarks](https://data.gov.au/data/dataset/small-business-benchmarks),
219
+ used under [CC BY 2.5 AU](https://creativecommons.org/licenses/by/2.5/au/). The data
220
+ has been converted from the ATO's spreadsheet into JSON, and turnover band bounds
221
+ have been made adjoining as described above. No published ratio has been altered.
222
+ The ATO has not endorsed this tool and has nothing to do with it.
223
+
224
+ The code in this repository is MIT licensed.
225
+
226
+ ## Author
227
+
228
+ Written by Ryan Duguid, a provisional member of Chartered Accountants ANZ,
229
+ independently, in his own time and on his own equipment. Nothing here is the work of
230
+ any employer, and no client data was used to build or test it.
231
+
232
+ Parts of this repository were written with AI assistance. Every ATO rule it
233
+ implements was checked against the ATO's own published pages, and the shipped
234
+ benchmark figures were cross checked against the ATO's industry page for the same
235
+ industry.