IBM Planning Analytics budget model with an offline rule evaluator
+----------------------------------------------------------------------+
| planning-analytics-model |
+----------------------------------------------------------------------+
| IBM Planning Analytics budget model as source |
+----------------------------------+-----------------------------------+
| DR what it gives you | CR what it needs |
+----------------------------------+-----------------------------------+
| runs TM1 rules with no server | PA model files in Git format |
| validates model structure | CSV data for cube loading |
| prints a P and L report | - |
+----------------------------------+-----------------------------------+
The PaciolisCube budget model. This repository was named PaciolisCube until September 2026; the old URL redirects.
An IBM Planning Analytics budgeting model published as source, with an offline engine that computes it and a test suite that asserts the answers.
Package lifecycle: source-only. Install from this repository; it is not published to PyPI.
Planning Analytics models usually live inside a server. You can read a rule file on GitHub, but you cannot run it, and nothing tells you whether a change to it broke a number. This repository holds a complete driver based budget for a fictional Australian mining services group in the layout IBM's own Git integration writes, plus a Python engine that parses the rules and evaluates them with no TM1 server anywhere. Continuous integration recomputes the whole model on every push and fails if a figure moves.
What is here
model/is the model: atm1project.jsonmanifest, 13 dimensions, 5 cubes, 4 rule files and 8 TurboIntegrator processes. Rules are plain.rulestext beside the cube JSON, scripts are plain.titext beside the process JSON, which is what the Planning Analytics Git integration reads and writes.pacioliscube/is the engine: a rule parser, a structural validator, a cell store with weighted consolidation, a CSV loader and a command line.examples/contains fictional entities, commercial rates and volumes, plus dated statutory rates and thresholds cited indocs/model-assumptions.md.docs/model-assumptions.mdtraces every statutory figure to its source, and says for each one whether it was read from the legislation or confirmed against the administering authority's own page.
Install
git clone https://github.com/ryanduguid/planning-analytics-model.git
cd planning-analytics-model
python -m pip install .
The runtime imports nothing outside the Python standard library. From a clone:
uv run --locked --extra dev pytest -q
Use
Check the model's structure:
pacioliscube validate model
0 errors, 0 warnings
Print a profit and loss from the shipped data:
pacioliscube report model --data examples --year FY2026-27 --version Budget
Profit and loss for FY2026-27, Budget
PnL at FY, Group, All Cost Centres
Revenue 52,764,000
Direct Costs (14,884,800)
Gross Margin 37,879,200
Employment Costs (11,428,418)
Overheads (6,384,000)
EBITDA 20,066,782
Depreciation (1,382,143)
EBIT 18,684,640
Read one cell, consolidated or leaf:
pacioliscube calculate model --data examples --cell "PnL:FY2026-27,Budget,FY,Group,All Cost Centres,EBIT,Amount"
Each report line is rounded to whole dollars on its own, so a subtotal can sit a
dollar away from the lines above it. calculate prints the unrounded figure.
The offline CSV loader refuses conflicting values for the same cell and names both rows. Repeated rows with equal decimal values remain valid, including when element names differ only in case or surrounding whitespace.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Clean |
| 1 | A usage or input error, including a report that does not fit the model |
| 2 | The model does not load, or validation reports an error |
| 3 | A calculation failed |
The model
Two entities, CivilCo (civil earthworks) and HaulCo (haulage), roll up to a
Group. The year ends 30 June, so Period runs July to June. FY2025-26 is held
as Actual and FY2026-27 as Budget.
Four cubes feed a fifth. Drivers holds statutory and planning rates and has no
rules at all. Workforce turns headcount and pay rates into base pay,
superannuation and payroll tax. Revenue turns billable hours and charge rates
into revenue, and litres into fuel cost. Capex spreads fleet additions over
each asset class life. All three feed PnL, which carries the statement from
revenue down to EBIT.
No rule file holds a statutory rate. Every one is read from the Drivers cube
with DB(), so changing the superannuation guarantee percentage means editing a
CSV, not a rule. A test fails the build if a number in any rule or script
carries a value the driver file ships.
What the tests actually check
The suite computes the model and asserts figures, rather than only checking that the source parses. Each expected number is written out longhand in the test, so a reviewer can follow the arithmetic without trusting the engine:
- Monthly base pay is headcount times the annual rate over twelve.
- Superannuation is capped at the maximum contribution base, and the cap is proved to bite by asserting the gap from the uncapped figure.
- Each year caps at its own base, so correcting one year cannot pass silently.
- Payroll tax is levied on pay grossed up by superannuation, not on pay alone.
- The payroll tax threshold credit reaches the designated group employer and no other cost centre.
- Depreciation is annual additions over the asset life in months, and an asset class with no life set charges nothing rather than raising.
- EBIT equals EBITDA less depreciation at the Group, across the full year.
- The
PnLwages line equalsWorkforcebase pay summed over roles.
Breaking any of those in the model source turns the suite red.
Native TM1 comparison defines eight synthetic cells, their independent expected values and the evidence to retain. Native comparison is pending because no TM1 server was available; passing offline tests does not establish agreement with a native server.
What it does not do
- It is not tax advice, and it is not a payroll or lodgement engine. The superannuation and payroll tax treatment here is a planning approximation.
- It is not a maintained rate table. The statutory figures are sample data
pinned at the date in
docs/model-assumptions.md, and they go stale. - It is not a full set of accounts. No balance sheet, no cash flow, no income tax, no GST, no foreign currency, no intercompany elimination.
- The engine covers the subset of the TM1 rules language this model uses. It is not a Planning Analytics reimplementation, and it will not run an arbitrary model.
Client data
No real data belongs in this repository. The .gitignore blocks the file names
ledgers and payroll exports arrive under. The examples combine invented business
data with dated, sourced statutory figures. See CONTRIBUTING.md.
Releases
The repository's GitHub Releases page is the canonical release history. A separate changelog is intentionally not maintained.
Author
Written by Ryan Duguid, a provisional member of Chartered Accountants ANZ, independently, in his own time and on his own equipment. Nothing here is the work of any employer, and no client data was used to build or test it.
Parts of this repository were written with AI assistance. Every statutory figure
is traced to its source in docs/model-assumptions.md, which records what was
read from the legislation, what was taken from the administering authority's own
page, and who confirmed the figures that could not be fetched.
Licence
MIT. See LICENSE.
Release files for pacioliscube 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pacioliscube-0.1.2.tar.gz | 77.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pacioliscube-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 109.4 kB
Release files / pacioliscube-0.1.2.tar.gz
| Download URL | pacioliscube-0.1.2.tar.gz |
|---|---|
| Size | 77.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
aff97c3756eebfb46e064685ed1a8c362f4c51dfe262c1ef833141d55810c59c
|
|
BLAKE2b-256 checksum How to use checksums |
5e1f79039b271c2fae5e6b31089cdf312c95b8a2a9aa16532e0aa9c1a54bd42a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.
Transparency logRelease files / pacioliscube-0.1.2-py3-none-any.whl
| Download URL | pacioliscube-0.1.2-py3-none-any.whl |
|---|---|
| Size | 32.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
08f9d24c3e9cef07129f0061a01b2eaa59efb0adff9058de0dda201e977f464b
|
|
BLAKE2b-256 checksum How to use checksums |
30931e5714a1b8f27c27487bcba3b5eaca6b67337879f148744c8af816aa5219
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.
Transparency log