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.
- pacioliscube-0.1.2/.gitignore +58 -0
- pacioliscube-0.1.2/CONTRIBUTING.md +83 -0
- pacioliscube-0.1.2/LICENSE +22 -0
- pacioliscube-0.1.2/PKG-INFO +227 -0
- pacioliscube-0.1.2/README.md +197 -0
- pacioliscube-0.1.2/SECURITY.md +28 -0
- pacioliscube-0.1.2/docs/model-assumptions.md +291 -0
- pacioliscube-0.1.2/docs/mutation-baseline.md +68 -0
- pacioliscube-0.1.2/docs/native-comparison.md +60 -0
- pacioliscube-0.1.2/examples/capex.csv +21 -0
- pacioliscube-0.1.2/examples/drivers.csv +15 -0
- pacioliscube-0.1.2/examples/pnl-direct.csv +289 -0
- pacioliscube-0.1.2/examples/revenue.csv +289 -0
- pacioliscube-0.1.2/examples/workforce.csv +287 -0
- pacioliscube-0.1.2/model/cubes/Capex.json +12 -0
- pacioliscube-0.1.2/model/cubes/Capex.rules +31 -0
- pacioliscube-0.1.2/model/cubes/Drivers.json +9 -0
- pacioliscube-0.1.2/model/cubes/PnL.json +13 -0
- pacioliscube-0.1.2/model/cubes/PnL.rules +36 -0
- pacioliscube-0.1.2/model/cubes/Revenue.json +12 -0
- pacioliscube-0.1.2/model/cubes/Revenue.rules +16 -0
- pacioliscube-0.1.2/model/cubes/Workforce.json +13 -0
- pacioliscube-0.1.2/model/cubes/Workforce.rules +35 -0
- pacioliscube-0.1.2/model/dimensions/Account.hierarchies/Account.json +182 -0
- pacioliscube-0.1.2/model/dimensions/Account.json +6 -0
- pacioliscube-0.1.2/model/dimensions/CapexMeasure.hierarchies/CapexMeasure.json +18 -0
- pacioliscube-0.1.2/model/dimensions/CapexMeasure.json +6 -0
- pacioliscube-0.1.2/model/dimensions/CostCentre.hierarchies/CostCentre.json +65 -0
- pacioliscube-0.1.2/model/dimensions/CostCentre.json +6 -0
- pacioliscube-0.1.2/model/dimensions/DriverMeasure.hierarchies/DriverMeasure.json +34 -0
- pacioliscube-0.1.2/model/dimensions/DriverMeasure.json +6 -0
- pacioliscube-0.1.2/model/dimensions/Entity.hierarchies/Entity.json +29 -0
- pacioliscube-0.1.2/model/dimensions/Entity.json +6 -0
- pacioliscube-0.1.2/model/dimensions/Fleet.hierarchies/Fleet.json +47 -0
- pacioliscube-0.1.2/model/dimensions/Fleet.json +6 -0
- pacioliscube-0.1.2/model/dimensions/Period.hierarchies/Period.json +159 -0
- pacioliscube-0.1.2/model/dimensions/Period.json +6 -0
- pacioliscube-0.1.2/model/dimensions/PnLMeasure.hierarchies/PnLMeasure.json +10 -0
- pacioliscube-0.1.2/model/dimensions/PnLMeasure.json +6 -0
- pacioliscube-0.1.2/model/dimensions/RevenueMeasure.hierarchies/RevenueMeasure.json +30 -0
- pacioliscube-0.1.2/model/dimensions/RevenueMeasure.json +6 -0
- pacioliscube-0.1.2/model/dimensions/Role.hierarchies/Role.json +47 -0
- pacioliscube-0.1.2/model/dimensions/Role.json +6 -0
- pacioliscube-0.1.2/model/dimensions/Version.hierarchies/Version.json +14 -0
- pacioliscube-0.1.2/model/dimensions/Version.json +6 -0
- pacioliscube-0.1.2/model/dimensions/WorkforceMeasure.hierarchies/WorkforceMeasure.json +30 -0
- pacioliscube-0.1.2/model/dimensions/WorkforceMeasure.json +6 -0
- pacioliscube-0.1.2/model/dimensions/Year.hierarchies/Year.json +14 -0
- pacioliscube-0.1.2/model/dimensions/Year.json +6 -0
- pacioliscube-0.1.2/model/processes/ClearVersion.json +25 -0
- pacioliscube-0.1.2/model/processes/ClearVersion.ti +20 -0
- pacioliscube-0.1.2/model/processes/ExportPnL.json +61 -0
- pacioliscube-0.1.2/model/processes/ExportPnL.ti +27 -0
- pacioliscube-0.1.2/model/processes/LoadCapex.json +42 -0
- pacioliscube-0.1.2/model/processes/LoadCapex.ti +11 -0
- pacioliscube-0.1.2/model/processes/LoadDrivers.json +34 -0
- pacioliscube-0.1.2/model/processes/LoadDrivers.ti +11 -0
- pacioliscube-0.1.2/model/processes/LoadPnLDirect.json +46 -0
- pacioliscube-0.1.2/model/processes/LoadPnLDirect.ti +11 -0
- pacioliscube-0.1.2/model/processes/LoadRevenue.json +42 -0
- pacioliscube-0.1.2/model/processes/LoadRevenue.ti +11 -0
- pacioliscube-0.1.2/model/processes/LoadWorkforce.json +46 -0
- pacioliscube-0.1.2/model/processes/LoadWorkforce.ti +11 -0
- pacioliscube-0.1.2/model/processes/SeedBudget.json +52 -0
- pacioliscube-0.1.2/model/processes/SeedBudget.ti +34 -0
- pacioliscube-0.1.2/model/tm1project.json +38 -0
- pacioliscube-0.1.2/pacioliscube/__init__.py +11 -0
- pacioliscube-0.1.2/pacioliscube/cli.py +320 -0
- pacioliscube-0.1.2/pacioliscube/data.py +177 -0
- pacioliscube-0.1.2/pacioliscube/errors.py +31 -0
- pacioliscube-0.1.2/pacioliscube/evaluate.py +360 -0
- pacioliscube-0.1.2/pacioliscube/model.py +367 -0
- pacioliscube-0.1.2/pacioliscube/report.py +160 -0
- pacioliscube-0.1.2/pacioliscube/rules.py +364 -0
- pacioliscube-0.1.2/pacioliscube/validate.py +375 -0
- pacioliscube-0.1.2/pyproject.toml +88 -0
- pacioliscube-0.1.2/tests/conftest.py +109 -0
- pacioliscube-0.1.2/tests/fixtures/mini/cubes/Cost.json +4 -0
- pacioliscube-0.1.2/tests/fixtures/mini/cubes/Sales.json +5 -0
- pacioliscube-0.1.2/tests/fixtures/mini/cubes/Sales.rules +9 -0
- pacioliscube-0.1.2/tests/fixtures/mini/dimensions/Colour.hierarchies/Colour.json +14 -0
- pacioliscube-0.1.2/tests/fixtures/mini/dimensions/Colour.json +4 -0
- pacioliscube-0.1.2/tests/fixtures/mini/dimensions/Measure.hierarchies/Measure.json +10 -0
- pacioliscube-0.1.2/tests/fixtures/mini/dimensions/Measure.json +4 -0
- pacioliscube-0.1.2/tests/fixtures/mini/processes/Load.json +6 -0
- pacioliscube-0.1.2/tests/fixtures/mini/processes/Load.ti +3 -0
- pacioliscube-0.1.2/tests/fixtures/mini/tm1project.json +9 -0
- pacioliscube-0.1.2/tests/test_calculations.py +359 -0
- pacioliscube-0.1.2/tests/test_cli.py +743 -0
- pacioliscube-0.1.2/tests/test_data_loading.py +156 -0
- pacioliscube-0.1.2/tests/test_diff_coverage_policy.py +26 -0
- pacioliscube-0.1.2/tests/test_evaluate.py +263 -0
- pacioliscube-0.1.2/tests/test_model_content.py +64 -0
- pacioliscube-0.1.2/tests/test_model_dimensions.py +143 -0
- pacioliscube-0.1.2/tests/test_model_manifest.py +106 -0
- pacioliscube-0.1.2/tests/test_model_rules.py +266 -0
- pacioliscube-0.1.2/tests/test_package_lifecycle.py +15 -0
- pacioliscube-0.1.2/tests/test_properties.py +59 -0
- pacioliscube-0.1.2/tests/test_rules_parser.py +167 -0
- 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
|
+
[](https://github.com/ryanduguid/planning-analytics-model/actions/workflows/ci.yml)
|
|
50
|
+
[](LICENSE)
|
|
51
|
+
[](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
|
+
[](https://github.com/ryanduguid/planning-analytics-model/actions/workflows/ci.yml)
|
|
20
|
+
[](LICENSE)
|
|
21
|
+
[](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.
|