pacioliscube 0.1.2__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 (100) hide show
  1. pacioliscube-0.1.2/.gitignore +58 -0
  2. pacioliscube-0.1.2/CONTRIBUTING.md +83 -0
  3. pacioliscube-0.1.2/LICENSE +22 -0
  4. pacioliscube-0.1.2/PKG-INFO +227 -0
  5. pacioliscube-0.1.2/README.md +197 -0
  6. pacioliscube-0.1.2/SECURITY.md +28 -0
  7. pacioliscube-0.1.2/docs/model-assumptions.md +291 -0
  8. pacioliscube-0.1.2/docs/mutation-baseline.md +68 -0
  9. pacioliscube-0.1.2/docs/native-comparison.md +60 -0
  10. pacioliscube-0.1.2/examples/capex.csv +21 -0
  11. pacioliscube-0.1.2/examples/drivers.csv +15 -0
  12. pacioliscube-0.1.2/examples/pnl-direct.csv +289 -0
  13. pacioliscube-0.1.2/examples/revenue.csv +289 -0
  14. pacioliscube-0.1.2/examples/workforce.csv +287 -0
  15. pacioliscube-0.1.2/model/cubes/Capex.json +12 -0
  16. pacioliscube-0.1.2/model/cubes/Capex.rules +31 -0
  17. pacioliscube-0.1.2/model/cubes/Drivers.json +9 -0
  18. pacioliscube-0.1.2/model/cubes/PnL.json +13 -0
  19. pacioliscube-0.1.2/model/cubes/PnL.rules +36 -0
  20. pacioliscube-0.1.2/model/cubes/Revenue.json +12 -0
  21. pacioliscube-0.1.2/model/cubes/Revenue.rules +16 -0
  22. pacioliscube-0.1.2/model/cubes/Workforce.json +13 -0
  23. pacioliscube-0.1.2/model/cubes/Workforce.rules +35 -0
  24. pacioliscube-0.1.2/model/dimensions/Account.hierarchies/Account.json +182 -0
  25. pacioliscube-0.1.2/model/dimensions/Account.json +6 -0
  26. pacioliscube-0.1.2/model/dimensions/CapexMeasure.hierarchies/CapexMeasure.json +18 -0
  27. pacioliscube-0.1.2/model/dimensions/CapexMeasure.json +6 -0
  28. pacioliscube-0.1.2/model/dimensions/CostCentre.hierarchies/CostCentre.json +65 -0
  29. pacioliscube-0.1.2/model/dimensions/CostCentre.json +6 -0
  30. pacioliscube-0.1.2/model/dimensions/DriverMeasure.hierarchies/DriverMeasure.json +34 -0
  31. pacioliscube-0.1.2/model/dimensions/DriverMeasure.json +6 -0
  32. pacioliscube-0.1.2/model/dimensions/Entity.hierarchies/Entity.json +29 -0
  33. pacioliscube-0.1.2/model/dimensions/Entity.json +6 -0
  34. pacioliscube-0.1.2/model/dimensions/Fleet.hierarchies/Fleet.json +47 -0
  35. pacioliscube-0.1.2/model/dimensions/Fleet.json +6 -0
  36. pacioliscube-0.1.2/model/dimensions/Period.hierarchies/Period.json +159 -0
  37. pacioliscube-0.1.2/model/dimensions/Period.json +6 -0
  38. pacioliscube-0.1.2/model/dimensions/PnLMeasure.hierarchies/PnLMeasure.json +10 -0
  39. pacioliscube-0.1.2/model/dimensions/PnLMeasure.json +6 -0
  40. pacioliscube-0.1.2/model/dimensions/RevenueMeasure.hierarchies/RevenueMeasure.json +30 -0
  41. pacioliscube-0.1.2/model/dimensions/RevenueMeasure.json +6 -0
  42. pacioliscube-0.1.2/model/dimensions/Role.hierarchies/Role.json +47 -0
  43. pacioliscube-0.1.2/model/dimensions/Role.json +6 -0
  44. pacioliscube-0.1.2/model/dimensions/Version.hierarchies/Version.json +14 -0
  45. pacioliscube-0.1.2/model/dimensions/Version.json +6 -0
  46. pacioliscube-0.1.2/model/dimensions/WorkforceMeasure.hierarchies/WorkforceMeasure.json +30 -0
  47. pacioliscube-0.1.2/model/dimensions/WorkforceMeasure.json +6 -0
  48. pacioliscube-0.1.2/model/dimensions/Year.hierarchies/Year.json +14 -0
  49. pacioliscube-0.1.2/model/dimensions/Year.json +6 -0
  50. pacioliscube-0.1.2/model/processes/ClearVersion.json +25 -0
  51. pacioliscube-0.1.2/model/processes/ClearVersion.ti +20 -0
  52. pacioliscube-0.1.2/model/processes/ExportPnL.json +61 -0
  53. pacioliscube-0.1.2/model/processes/ExportPnL.ti +27 -0
  54. pacioliscube-0.1.2/model/processes/LoadCapex.json +42 -0
  55. pacioliscube-0.1.2/model/processes/LoadCapex.ti +11 -0
  56. pacioliscube-0.1.2/model/processes/LoadDrivers.json +34 -0
  57. pacioliscube-0.1.2/model/processes/LoadDrivers.ti +11 -0
  58. pacioliscube-0.1.2/model/processes/LoadPnLDirect.json +46 -0
  59. pacioliscube-0.1.2/model/processes/LoadPnLDirect.ti +11 -0
  60. pacioliscube-0.1.2/model/processes/LoadRevenue.json +42 -0
  61. pacioliscube-0.1.2/model/processes/LoadRevenue.ti +11 -0
  62. pacioliscube-0.1.2/model/processes/LoadWorkforce.json +46 -0
  63. pacioliscube-0.1.2/model/processes/LoadWorkforce.ti +11 -0
  64. pacioliscube-0.1.2/model/processes/SeedBudget.json +52 -0
  65. pacioliscube-0.1.2/model/processes/SeedBudget.ti +34 -0
  66. pacioliscube-0.1.2/model/tm1project.json +38 -0
  67. pacioliscube-0.1.2/pacioliscube/__init__.py +11 -0
  68. pacioliscube-0.1.2/pacioliscube/cli.py +320 -0
  69. pacioliscube-0.1.2/pacioliscube/data.py +177 -0
  70. pacioliscube-0.1.2/pacioliscube/errors.py +31 -0
  71. pacioliscube-0.1.2/pacioliscube/evaluate.py +360 -0
  72. pacioliscube-0.1.2/pacioliscube/model.py +367 -0
  73. pacioliscube-0.1.2/pacioliscube/report.py +160 -0
  74. pacioliscube-0.1.2/pacioliscube/rules.py +364 -0
  75. pacioliscube-0.1.2/pacioliscube/validate.py +375 -0
  76. pacioliscube-0.1.2/pyproject.toml +88 -0
  77. pacioliscube-0.1.2/tests/conftest.py +109 -0
  78. pacioliscube-0.1.2/tests/fixtures/mini/cubes/Cost.json +4 -0
  79. pacioliscube-0.1.2/tests/fixtures/mini/cubes/Sales.json +5 -0
  80. pacioliscube-0.1.2/tests/fixtures/mini/cubes/Sales.rules +9 -0
  81. pacioliscube-0.1.2/tests/fixtures/mini/dimensions/Colour.hierarchies/Colour.json +14 -0
  82. pacioliscube-0.1.2/tests/fixtures/mini/dimensions/Colour.json +4 -0
  83. pacioliscube-0.1.2/tests/fixtures/mini/dimensions/Measure.hierarchies/Measure.json +10 -0
  84. pacioliscube-0.1.2/tests/fixtures/mini/dimensions/Measure.json +4 -0
  85. pacioliscube-0.1.2/tests/fixtures/mini/processes/Load.json +6 -0
  86. pacioliscube-0.1.2/tests/fixtures/mini/processes/Load.ti +3 -0
  87. pacioliscube-0.1.2/tests/fixtures/mini/tm1project.json +9 -0
  88. pacioliscube-0.1.2/tests/test_calculations.py +359 -0
  89. pacioliscube-0.1.2/tests/test_cli.py +743 -0
  90. pacioliscube-0.1.2/tests/test_data_loading.py +156 -0
  91. pacioliscube-0.1.2/tests/test_diff_coverage_policy.py +26 -0
  92. pacioliscube-0.1.2/tests/test_evaluate.py +263 -0
  93. pacioliscube-0.1.2/tests/test_model_content.py +64 -0
  94. pacioliscube-0.1.2/tests/test_model_dimensions.py +143 -0
  95. pacioliscube-0.1.2/tests/test_model_manifest.py +106 -0
  96. pacioliscube-0.1.2/tests/test_model_rules.py +266 -0
  97. pacioliscube-0.1.2/tests/test_package_lifecycle.py +15 -0
  98. pacioliscube-0.1.2/tests/test_properties.py +59 -0
  99. pacioliscube-0.1.2/tests/test_rules_parser.py +167 -0
  100. pacioliscube-0.1.2/tests/test_validate.py +275 -0
@@ -0,0 +1,58 @@
1
+ # Client and taxpayer data never belongs in this repository.
2
+ # These patterns block the file names real ledgers and payroll exports arrive under.
3
+ # Character classes are used on extensions because git only folds extension case
4
+ # when core.ignorecase is set, which is not the case on every contributor's box.
5
+ [pP][nN][lL]*.[cC][sS][vV]
6
+ [tT][rR][iI][aA][lL]-[bB][aA][lL][aA][nN][cC][eE]*.[cC][sS][vV]
7
+ [pP][aA][yY][rR][oO][lL][lL]*.[cC][sS][vV]
8
+ client-data/
9
+ clients/
10
+ ledgers/
11
+ exports/
12
+ *.aba
13
+ *.qbo
14
+ *.ofx
15
+ *.qif
16
+ *.[xX][lL][sS]
17
+ *.[xX][lL][sS][xX]
18
+ *.[xX][lL][sS][mM]
19
+ *.[pP][dD][fF]
20
+
21
+ # The repository's own synthetic data is deliberately exempt.
22
+ !examples/**
23
+ !tests/fixtures/**
24
+
25
+ # Credentials
26
+ .env
27
+ .env.*
28
+ !.env.example
29
+ *.pem
30
+ *.key
31
+ token.json
32
+
33
+ # Python
34
+ __pycache__/
35
+ *.py[cod]
36
+ .venv/
37
+ venv/
38
+ build/
39
+ dist/
40
+ *.egg-info/
41
+ .pytest_cache/
42
+ .ruff_cache/
43
+ .coverage
44
+ coverage.xml
45
+
46
+ # Editors and OS
47
+ .vscode/
48
+ .idea/
49
+ .DS_Store
50
+ Thumbs.db
51
+
52
+ # Agent scratch
53
+ .superpowers/
54
+ .claude/settings.local.json
55
+
56
+ # Agent planning documents stay local: they carry paths from the box
57
+ # they were written on and are of no use to a reader.
58
+ docs/superpowers/
@@ -0,0 +1,83 @@
1
+ # Contributing
2
+
3
+ Issues and pull requests are welcome. A person reads every change before it
4
+ lands, and a change that alters a calculated number needs to show its working.
5
+
6
+ ## Data boundary
7
+
8
+ This repository must never carry real data. No client, taxpayer, employee or
9
+ payroll records, no credentials, no tokens, no organisation names from a live
10
+ engagement. Business data under `examples/` is invented. Statutory rates and
11
+ thresholds use the dated primary sources in `docs/model-assumptions.md`.
12
+
13
+ The `.gitignore` blocks the file names real ledgers and payroll exports arrive
14
+ under. It is a safety net, not permission to try. If you need a new fixture,
15
+ write one by hand.
16
+
17
+ ## Rates, thresholds and deadlines
18
+
19
+ Every statutory figure lives in `examples/drivers.csv` as data. None of them is
20
+ written into rule text, so changing a rate never means editing a rule.
21
+
22
+ If you change a rate, a threshold or an effective date, cite the primary source
23
+ in the pull request: the Australian Taxation Office page, the Revenue NSW page,
24
+ or the legislation itself. Update the table in `docs/model-assumptions.md` in
25
+ the same change, including the date you retrieved it. A secondary source, a blog
26
+ or an accounting firm summary is not enough on its own.
27
+
28
+ ## Tests
29
+
30
+ The suite asserts calculated numbers, not just that the model loads. A test that
31
+ pins a figure must compute the expected value from first principles in the test
32
+ body, so a reviewer can follow the arithmetic without trusting the engine.
33
+
34
+ Run that test against the old code first. If it passes there too, it is not
35
+ testing your change.
36
+
37
+ ```bash
38
+ uv run --locked --extra dev ruff check pacioliscube tests
39
+ uv run --locked --extra dev mypy pacioliscube
40
+ uv run --locked --extra dev pytest -q
41
+ ```
42
+
43
+ The same commands CI runs: ruff and mypy in the `lint` job, then the tests on
44
+ Python 3.10, 3.12 and 3.13 (and 3.12 on Windows). Install the git hooks once
45
+ with `uv tool install pre-commit && pre-commit install`; they run the pinned
46
+ ruff check on staged files. The packaging job additionally builds the wheel,
47
+ installs it into a clean environment and runs the real command line against the
48
+ real model tree, because a wheel that cannot find its own model is a broken
49
+ build artefact.
50
+
51
+ The bounded mutmut pilot over `pacioliscube/evaluate.py`, with its survivor
52
+ ledger and the decision to keep the pin, is recorded in
53
+ [docs/mutation-baseline.md](docs/mutation-baseline.md). It is a test-design
54
+ aid, not a CI gate.
55
+
56
+ ## Model changes
57
+
58
+ The model source under `model/` follows the layout IBM's own Git integration
59
+ writes: a `tm1project.json` manifest, `dimensions/`, `cubes/` with plain text
60
+ `.rules` beside the JSON, and `processes/` with plain text `.ti` beside the JSON.
61
+
62
+ Two rules hold for any change there:
63
+
64
+ 1. Every file in the tree must appear in the manifest, directly or through a
65
+ link from an object the manifest lists. The validator reports anything else,
66
+ because a deployment would leave it behind.
67
+ 2. Any cube with `SKIPCHECK` and rules needs a feeder pointing into it, from
68
+ that cube or from another. Unfed calculated cells are invisible in a real
69
+ database even though the rule is correct.
70
+
71
+ Run `pacioliscube validate` before you open the pull request.
72
+
73
+ ## Pull requests
74
+
75
+ Keep a pull request to one change. Describe what it does and, where it touches a
76
+ number, show the command output rather than saying the tests pass.
77
+
78
+ Prose in this repository is Australian English and carries no em dashes.
79
+
80
+ ## Security
81
+
82
+ Report a vulnerability privately through the process in
83
+ [SECURITY.md](SECURITY.md), not in a public issue.
@@ -0,0 +1,22 @@
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
+
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.5
2
+ Name: pacioliscube
3
+ Version: 0.1.2
4
+ Summary: An open IBM Planning Analytics budgeting model with an offline engine that computes and tests it.
5
+ Project-URL: Homepage, https://github.com/ryanduguid/planning-analytics-model
6
+ Project-URL: Issues, https://github.com/ryanduguid/planning-analytics-model/issues
7
+ Author: Ryan Duguid
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: australia,budgeting,fpa,planning-analytics,tm1
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Financial and Insurance Industry
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
19
+ Requires-Python: >=3.10
20
+ Provides-Extra: dev
21
+ Requires-Dist: build>=1.6.0; extra == 'dev'
22
+ Requires-Dist: coverage==7.16.0; extra == 'dev'
23
+ Requires-Dist: diff-cover==10.5.1; extra == 'dev'
24
+ Requires-Dist: hypothesis==6.167.1; extra == 'dev'
25
+ Requires-Dist: mutmut==3.7.0; extra == 'dev'
26
+ Requires-Dist: mypy==2.3.1; extra == 'dev'
27
+ Requires-Dist: pytest>=8; extra == 'dev'
28
+ Requires-Dist: ruff==0.16.6; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # IBM Planning Analytics budget model with an offline rule evaluator
32
+
33
+ ```
34
+ +----------------------------------------------------------------------+
35
+ | planning-analytics-model |
36
+ +----------------------------------------------------------------------+
37
+ | IBM Planning Analytics budget model as source |
38
+ +----------------------------------+-----------------------------------+
39
+ | DR what it gives you | CR what it needs |
40
+ +----------------------------------+-----------------------------------+
41
+ | runs TM1 rules with no server | PA model files in Git format |
42
+ | validates model structure | CSV data for cube loading |
43
+ | prints a P and L report | - |
44
+ +----------------------------------+-----------------------------------+
45
+ ```
46
+
47
+ The PaciolisCube budget model. This repository was named PaciolisCube until September 2026; the old URL redirects.
48
+
49
+ [![tests](https://github.com/ryanduguid/planning-analytics-model/actions/workflows/ci.yml/badge.svg)](https://github.com/ryanduguid/planning-analytics-model/actions/workflows/ci.yml)
50
+ [![licence: MIT](https://img.shields.io/badge/licence-MIT-5C2D91.svg?labelColor=04001F)](LICENSE)
51
+ [![python](https://img.shields.io/badge/python-3.10%2B-5C2D91.svg?labelColor=04001F)](https://www.python.org/)
52
+
53
+ An IBM Planning Analytics budgeting model published as source, with an offline
54
+ engine that computes it and a test suite that asserts the answers.
55
+
56
+ **Package lifecycle:** source-only. Install from this repository; it is not
57
+ published to PyPI.
58
+
59
+ Planning Analytics models usually live inside a server. You can read a rule file
60
+ on GitHub, but you cannot run it, and nothing tells you whether a change to it
61
+ broke a number. This repository holds a complete driver based budget for a
62
+ fictional Australian mining services group in the layout IBM's own Git
63
+ integration writes, plus a Python engine that parses the rules and evaluates
64
+ them with no TM1 server anywhere. Continuous integration recomputes the whole
65
+ model on every push and fails if a figure moves.
66
+
67
+ ## What is here
68
+
69
+ - `model/` is the model: a `tm1project.json` manifest, 13 dimensions, 5 cubes,
70
+ 4 rule files and 8 TurboIntegrator processes. Rules are plain `.rules` text
71
+ beside the cube JSON, scripts are plain `.ti` text beside the process JSON,
72
+ which is what the Planning Analytics Git integration reads and writes.
73
+ - `pacioliscube/` is the engine: a rule parser, a structural validator, a cell
74
+ store with weighted consolidation, a CSV loader and a command line.
75
+ - `examples/` contains fictional entities, commercial rates and volumes, plus
76
+ dated statutory rates and thresholds cited in `docs/model-assumptions.md`.
77
+ - `docs/model-assumptions.md` traces every statutory figure to its source, and
78
+ says for each one whether it was read from the legislation or confirmed
79
+ against the administering authority's own page.
80
+
81
+ ## Install
82
+
83
+ ```bash
84
+ git clone https://github.com/ryanduguid/planning-analytics-model.git
85
+ cd planning-analytics-model
86
+ python -m pip install .
87
+ ```
88
+
89
+ The runtime imports nothing outside the Python standard library. From a clone:
90
+
91
+ ```bash
92
+ uv run --locked --extra dev pytest -q
93
+ ```
94
+
95
+ ## Use
96
+
97
+ Check the model's structure:
98
+
99
+ ```bash
100
+ pacioliscube validate model
101
+ ```
102
+
103
+ ```
104
+ 0 errors, 0 warnings
105
+ ```
106
+
107
+ Print a profit and loss from the shipped data:
108
+
109
+ ```bash
110
+ pacioliscube report model --data examples --year FY2026-27 --version Budget
111
+ ```
112
+
113
+ ```
114
+ Profit and loss for FY2026-27, Budget
115
+ PnL at FY, Group, All Cost Centres
116
+
117
+ Revenue 52,764,000
118
+ Direct Costs (14,884,800)
119
+ Gross Margin 37,879,200
120
+ Employment Costs (11,428,418)
121
+ Overheads (6,384,000)
122
+ EBITDA 20,066,782
123
+ Depreciation (1,382,143)
124
+ EBIT 18,684,640
125
+ ```
126
+
127
+ Read one cell, consolidated or leaf:
128
+
129
+ ```bash
130
+ pacioliscube calculate model --data examples --cell "PnL:FY2026-27,Budget,FY,Group,All Cost Centres,EBIT,Amount"
131
+ ```
132
+
133
+ Each report line is rounded to whole dollars on its own, so a subtotal can sit a
134
+ dollar away from the lines above it. `calculate` prints the unrounded figure.
135
+
136
+ The offline CSV loader refuses conflicting values for the same cell and names
137
+ both rows. Repeated rows with equal decimal values remain valid, including when
138
+ element names differ only in case or surrounding whitespace.
139
+
140
+ ## Exit codes
141
+
142
+ | Code | Meaning |
143
+ | --- | --- |
144
+ | 0 | Clean |
145
+ | 1 | A usage or input error, including a report that does not fit the model |
146
+ | 2 | The model does not load, or validation reports an error |
147
+ | 3 | A calculation failed |
148
+
149
+ ## The model
150
+
151
+ Two entities, CivilCo (civil earthworks) and HaulCo (haulage), roll up to a
152
+ `Group`. The year ends 30 June, so `Period` runs July to June. FY2025-26 is held
153
+ as `Actual` and FY2026-27 as `Budget`.
154
+
155
+ Four cubes feed a fifth. `Drivers` holds statutory and planning rates and has no
156
+ rules at all. `Workforce` turns headcount and pay rates into base pay,
157
+ superannuation and payroll tax. `Revenue` turns billable hours and charge rates
158
+ into revenue, and litres into fuel cost. `Capex` spreads fleet additions over
159
+ each asset class life. All three feed `PnL`, which carries the statement from
160
+ revenue down to EBIT.
161
+
162
+ No rule file holds a statutory rate. Every one is read from the `Drivers` cube
163
+ with `DB()`, so changing the superannuation guarantee percentage means editing a
164
+ CSV, not a rule. A test fails the build if a number in any rule or script
165
+ carries a value the driver file ships.
166
+
167
+ ## What the tests actually check
168
+
169
+ The suite computes the model and asserts figures, rather than only checking that
170
+ the source parses. Each expected number is written out longhand in the test, so
171
+ a reviewer can follow the arithmetic without trusting the engine:
172
+
173
+ - Monthly base pay is headcount times the annual rate over twelve.
174
+ - Superannuation is capped at the maximum contribution base, and the cap is
175
+ proved to bite by asserting the gap from the uncapped figure.
176
+ - Each year caps at its own base, so correcting one year cannot pass silently.
177
+ - Payroll tax is levied on pay grossed up by superannuation, not on pay alone.
178
+ - The payroll tax threshold credit reaches the designated group employer and no
179
+ other cost centre.
180
+ - Depreciation is annual additions over the asset life in months, and an asset
181
+ class with no life set charges nothing rather than raising.
182
+ - EBIT equals EBITDA less depreciation at the Group, across the full year.
183
+ - The `PnL` wages line equals `Workforce` base pay summed over roles.
184
+
185
+ Breaking any of those in the model source turns the suite red.
186
+
187
+ [Native TM1 comparison](docs/native-comparison.md) defines eight synthetic cells,
188
+ their independent expected values and the evidence to retain. Native comparison
189
+ is pending because no TM1 server was available; passing offline tests does not
190
+ establish agreement with a native server.
191
+
192
+ ## What it does not do
193
+
194
+ - It is not tax advice, and it is not a payroll or lodgement engine. The
195
+ superannuation and payroll tax treatment here is a planning approximation.
196
+ - It is not a maintained rate table. The statutory figures are sample data
197
+ pinned at the date in `docs/model-assumptions.md`, and they go stale.
198
+ - It is not a full set of accounts. No balance sheet, no cash flow, no income
199
+ tax, no GST, no foreign currency, no intercompany elimination.
200
+ - The engine covers the subset of the TM1 rules language this model uses. It is
201
+ not a Planning Analytics reimplementation, and it will not run an arbitrary
202
+ model.
203
+
204
+ ## Client data
205
+
206
+ No real data belongs in this repository. The `.gitignore` blocks the file names
207
+ ledgers and payroll exports arrive under. The examples combine invented business
208
+ data with dated, sourced statutory figures. See [CONTRIBUTING.md](CONTRIBUTING.md).
209
+
210
+ ## Releases
211
+
212
+ The repository's [GitHub Releases](https://github.com/ryanduguid/planning-analytics-model/releases) page is the canonical release history. A separate changelog is intentionally not maintained.
213
+
214
+ ## Author
215
+
216
+ Written by Ryan Duguid, a provisional member of Chartered Accountants ANZ,
217
+ independently, in his own time and on his own equipment. Nothing here is the
218
+ work of any employer, and no client data was used to build or test it.
219
+
220
+ Parts of this repository were written with AI assistance. Every statutory figure
221
+ is traced to its source in `docs/model-assumptions.md`, which records what was
222
+ read from the legislation, what was taken from the administering authority's own
223
+ page, and who confirmed the figures that could not be fetched.
224
+
225
+ ## Licence
226
+
227
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,197 @@
1
+ # IBM Planning Analytics budget model with an offline rule evaluator
2
+
3
+ ```
4
+ +----------------------------------------------------------------------+
5
+ | planning-analytics-model |
6
+ +----------------------------------------------------------------------+
7
+ | IBM Planning Analytics budget model as source |
8
+ +----------------------------------+-----------------------------------+
9
+ | DR what it gives you | CR what it needs |
10
+ +----------------------------------+-----------------------------------+
11
+ | runs TM1 rules with no server | PA model files in Git format |
12
+ | validates model structure | CSV data for cube loading |
13
+ | prints a P and L report | - |
14
+ +----------------------------------+-----------------------------------+
15
+ ```
16
+
17
+ The PaciolisCube budget model. This repository was named PaciolisCube until September 2026; the old URL redirects.
18
+
19
+ [![tests](https://github.com/ryanduguid/planning-analytics-model/actions/workflows/ci.yml/badge.svg)](https://github.com/ryanduguid/planning-analytics-model/actions/workflows/ci.yml)
20
+ [![licence: MIT](https://img.shields.io/badge/licence-MIT-5C2D91.svg?labelColor=04001F)](LICENSE)
21
+ [![python](https://img.shields.io/badge/python-3.10%2B-5C2D91.svg?labelColor=04001F)](https://www.python.org/)
22
+
23
+ An IBM Planning Analytics budgeting model published as source, with an offline
24
+ engine that computes it and a test suite that asserts the answers.
25
+
26
+ **Package lifecycle:** source-only. Install from this repository; it is not
27
+ published to PyPI.
28
+
29
+ Planning Analytics models usually live inside a server. You can read a rule file
30
+ on GitHub, but you cannot run it, and nothing tells you whether a change to it
31
+ broke a number. This repository holds a complete driver based budget for a
32
+ fictional Australian mining services group in the layout IBM's own Git
33
+ integration writes, plus a Python engine that parses the rules and evaluates
34
+ them with no TM1 server anywhere. Continuous integration recomputes the whole
35
+ model on every push and fails if a figure moves.
36
+
37
+ ## What is here
38
+
39
+ - `model/` is the model: a `tm1project.json` manifest, 13 dimensions, 5 cubes,
40
+ 4 rule files and 8 TurboIntegrator processes. Rules are plain `.rules` text
41
+ beside the cube JSON, scripts are plain `.ti` text beside the process JSON,
42
+ which is what the Planning Analytics Git integration reads and writes.
43
+ - `pacioliscube/` is the engine: a rule parser, a structural validator, a cell
44
+ store with weighted consolidation, a CSV loader and a command line.
45
+ - `examples/` contains fictional entities, commercial rates and volumes, plus
46
+ dated statutory rates and thresholds cited in `docs/model-assumptions.md`.
47
+ - `docs/model-assumptions.md` traces every statutory figure to its source, and
48
+ says for each one whether it was read from the legislation or confirmed
49
+ against the administering authority's own page.
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ git clone https://github.com/ryanduguid/planning-analytics-model.git
55
+ cd planning-analytics-model
56
+ python -m pip install .
57
+ ```
58
+
59
+ The runtime imports nothing outside the Python standard library. From a clone:
60
+
61
+ ```bash
62
+ uv run --locked --extra dev pytest -q
63
+ ```
64
+
65
+ ## Use
66
+
67
+ Check the model's structure:
68
+
69
+ ```bash
70
+ pacioliscube validate model
71
+ ```
72
+
73
+ ```
74
+ 0 errors, 0 warnings
75
+ ```
76
+
77
+ Print a profit and loss from the shipped data:
78
+
79
+ ```bash
80
+ pacioliscube report model --data examples --year FY2026-27 --version Budget
81
+ ```
82
+
83
+ ```
84
+ Profit and loss for FY2026-27, Budget
85
+ PnL at FY, Group, All Cost Centres
86
+
87
+ Revenue 52,764,000
88
+ Direct Costs (14,884,800)
89
+ Gross Margin 37,879,200
90
+ Employment Costs (11,428,418)
91
+ Overheads (6,384,000)
92
+ EBITDA 20,066,782
93
+ Depreciation (1,382,143)
94
+ EBIT 18,684,640
95
+ ```
96
+
97
+ Read one cell, consolidated or leaf:
98
+
99
+ ```bash
100
+ pacioliscube calculate model --data examples --cell "PnL:FY2026-27,Budget,FY,Group,All Cost Centres,EBIT,Amount"
101
+ ```
102
+
103
+ Each report line is rounded to whole dollars on its own, so a subtotal can sit a
104
+ dollar away from the lines above it. `calculate` prints the unrounded figure.
105
+
106
+ The offline CSV loader refuses conflicting values for the same cell and names
107
+ both rows. Repeated rows with equal decimal values remain valid, including when
108
+ element names differ only in case or surrounding whitespace.
109
+
110
+ ## Exit codes
111
+
112
+ | Code | Meaning |
113
+ | --- | --- |
114
+ | 0 | Clean |
115
+ | 1 | A usage or input error, including a report that does not fit the model |
116
+ | 2 | The model does not load, or validation reports an error |
117
+ | 3 | A calculation failed |
118
+
119
+ ## The model
120
+
121
+ Two entities, CivilCo (civil earthworks) and HaulCo (haulage), roll up to a
122
+ `Group`. The year ends 30 June, so `Period` runs July to June. FY2025-26 is held
123
+ as `Actual` and FY2026-27 as `Budget`.
124
+
125
+ Four cubes feed a fifth. `Drivers` holds statutory and planning rates and has no
126
+ rules at all. `Workforce` turns headcount and pay rates into base pay,
127
+ superannuation and payroll tax. `Revenue` turns billable hours and charge rates
128
+ into revenue, and litres into fuel cost. `Capex` spreads fleet additions over
129
+ each asset class life. All three feed `PnL`, which carries the statement from
130
+ revenue down to EBIT.
131
+
132
+ No rule file holds a statutory rate. Every one is read from the `Drivers` cube
133
+ with `DB()`, so changing the superannuation guarantee percentage means editing a
134
+ CSV, not a rule. A test fails the build if a number in any rule or script
135
+ carries a value the driver file ships.
136
+
137
+ ## What the tests actually check
138
+
139
+ The suite computes the model and asserts figures, rather than only checking that
140
+ the source parses. Each expected number is written out longhand in the test, so
141
+ a reviewer can follow the arithmetic without trusting the engine:
142
+
143
+ - Monthly base pay is headcount times the annual rate over twelve.
144
+ - Superannuation is capped at the maximum contribution base, and the cap is
145
+ proved to bite by asserting the gap from the uncapped figure.
146
+ - Each year caps at its own base, so correcting one year cannot pass silently.
147
+ - Payroll tax is levied on pay grossed up by superannuation, not on pay alone.
148
+ - The payroll tax threshold credit reaches the designated group employer and no
149
+ other cost centre.
150
+ - Depreciation is annual additions over the asset life in months, and an asset
151
+ class with no life set charges nothing rather than raising.
152
+ - EBIT equals EBITDA less depreciation at the Group, across the full year.
153
+ - The `PnL` wages line equals `Workforce` base pay summed over roles.
154
+
155
+ Breaking any of those in the model source turns the suite red.
156
+
157
+ [Native TM1 comparison](docs/native-comparison.md) defines eight synthetic cells,
158
+ their independent expected values and the evidence to retain. Native comparison
159
+ is pending because no TM1 server was available; passing offline tests does not
160
+ establish agreement with a native server.
161
+
162
+ ## What it does not do
163
+
164
+ - It is not tax advice, and it is not a payroll or lodgement engine. The
165
+ superannuation and payroll tax treatment here is a planning approximation.
166
+ - It is not a maintained rate table. The statutory figures are sample data
167
+ pinned at the date in `docs/model-assumptions.md`, and they go stale.
168
+ - It is not a full set of accounts. No balance sheet, no cash flow, no income
169
+ tax, no GST, no foreign currency, no intercompany elimination.
170
+ - The engine covers the subset of the TM1 rules language this model uses. It is
171
+ not a Planning Analytics reimplementation, and it will not run an arbitrary
172
+ model.
173
+
174
+ ## Client data
175
+
176
+ No real data belongs in this repository. The `.gitignore` blocks the file names
177
+ ledgers and payroll exports arrive under. The examples combine invented business
178
+ data with dated, sourced statutory figures. See [CONTRIBUTING.md](CONTRIBUTING.md).
179
+
180
+ ## Releases
181
+
182
+ The repository's [GitHub Releases](https://github.com/ryanduguid/planning-analytics-model/releases) page is the canonical release history. A separate changelog is intentionally not maintained.
183
+
184
+ ## Author
185
+
186
+ Written by Ryan Duguid, a provisional member of Chartered Accountants ANZ,
187
+ independently, in his own time and on his own equipment. Nothing here is the
188
+ work of any employer, and no client data was used to build or test it.
189
+
190
+ Parts of this repository were written with AI assistance. Every statutory figure
191
+ is traced to its source in `docs/model-assumptions.md`, which records what was
192
+ read from the legislation, what was taken from the administering authority's own
193
+ page, and who confirmed the figures that could not be fetched.
194
+
195
+ ## Licence
196
+
197
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,28 @@
1
+ # Security
2
+
3
+ ## Supported version
4
+
5
+ The latest commit on the default branch. There is no backport branch.
6
+
7
+ ## Reporting a vulnerability
8
+
9
+ Use GitHub's private vulnerability reporting on this repository. Do not open a
10
+ public issue.
11
+
12
+ Expect an acknowledgement within seven days. If the report is valid, the fix and
13
+ a note of what was affected land on the default branch.
14
+
15
+ ## What this project does and does not do
16
+
17
+ The runtime imports nothing outside the Python standard library. It makes no
18
+ network calls, reads no credentials and writes no files unless you ask it to.
19
+ It reads two things: the model source under `model/` and whatever CSV files you
20
+ point it at.
21
+
22
+ Model source is data, not code. The loader refuses a link that climbs out of the
23
+ model root, so a manifest or an object file cannot reach an arbitrary path on
24
+ the machine that loads it. Rule text is parsed, never executed.
25
+
26
+ The TurboIntegrator processes under `model/processes/` are source for a Planning
27
+ Analytics server to run. Nothing here executes them. Read them before you deploy
28
+ them, the same as any script you did not write.