A local-first accounting and compliance cockpit for a UK owner-managed limited company — where a language model reviews the work but is never allowed to do it.
Compliance Studio pulls a company's complete accounting history from FreeAgent, cross-checks it against the public Companies House register, runs every statutory calculation deterministically, and then hands the whole picture to a locally hosted language model for review and explanation.
The model can analyse, explain, draft and suggest. It cannot file a return, move money, change FreeAgent, approve a dividend, or mark a compliance obligation complete.
Most "AI for X" projects put the model in the critical path and hope the prompt holds. In a regulated domain that is the wrong shape. Corporation tax is not a generation problem — it is arithmetic with a legal definition, and a plausible-sounding wrong answer is worse than no answer.
So the interesting engineering here is not the model call. It is the boundary:
| Concern | Owner | Why |
|---|---|---|
| Corporation tax, NI, Self Assessment, s455, marginal relief | Deterministic Python + versioned rule pack | Legally defined; must be reproducible and auditable |
| Deadline tracking, evidence retention, audit history | SQLite, append-only | Must survive restarts and be provable after the fact |
| Transaction categorisation | Deterministic engine, ranked by historical evidence | Every suggestion carries its confidence and its basis |
| Explanation, review, anomaly spotting, drafting | Local LLM | Genuinely open-ended; a wrong answer is cheap because it cannot act |
| Deciding, approving, filing, paying | The human director | Statutory responsibility is not delegable |
Everything below follows from that table.
flowchart TB
subgraph external["External systems"]
FA["FreeAgent API<br/>OAuth 2.0"]
CH["Companies House API<br/>read-only"]
end
subgraph local["Local machine — no data leaves it"]
SYNC["Sync layer<br/>full → incremental → deep resync"]
DB[("SQLite<br/>lifetime archive,<br/>evidence, audit log")]
CALC["Deterministic engine<br/>CT · NI · SA · s455 · dividends"]
RULES["Versioned tax-rule pack<br/>verified / expires dates"]
CAT["Categorisation engine<br/>rules → history → similarity"]
QUEUE["Action Queue<br/>priority · confidence · evidence"]
LLM["Local Ollama model<br/>review · explain · suggest"]
UI["PySide6 desktop UI<br/>24 workflow tabs"]
end
HUMAN(["Director<br/>approves, files, pays"])
FA -->|"pull"| SYNC
CH -->|"verify"| SYNC
SYNC --> DB
DB --> CALC
RULES --> CALC
DB --> CAT
CALC --> QUEUE
CAT --> QUEUE
QUEUE --> UI
DB -.->|"full context,<br/>untruncated"| LLM
CALC -.-> LLM
LLM -->|"advice only"| UI
UI --> HUMAN
HUMAN -->|"approves"| DB
classDef ext fill:#e8eef7,stroke:#5b7ba8,color:#1a2b42
classDef ai fill:#f5eee6,stroke:#b08a5b,color:#402c14
classDef human fill:#e6f2ea,stroke:#5b9e78,color:#123322
class FA,CH ext
class LLM ai
class HUMAN human
The dotted lines are the only paths the model touches, and they all point inward. There is no edge from the model to any external system, to the database, or to a filing.
The local adviser receives the complete current data pull with no application-level truncation, plus the lifetime history summary, the deterministic calculations, the evidence index and the Companies House cross-check findings. Context size is computed from the payload rather than capped at a convenient number.
That is a deliberate inversion of the usual trade-off. Because the model has no ability to act, there is no reason to be defensive about what it sees — and giving it partial data is how you get confidently wrong answers about a set of accounts.
The only supported endpoint is a local Ollama instance. There are no cloud providers, no external model API keys, and no remote fallback anywhere in the codebase. This is enforced by the test suite, not just by convention:
pass cloud LLM settings removed
pass local Ollama client only
pass context explicitly untruncated
For accounting data this is the difference between a tool a director can actually use and one their insurer would object to.
For each unexplained bank transaction the deterministic engine checks, in order: user-approved rules → exact prior payee matches → description and counterparty similarity → debit/credit direction → historical amount consistency → frequency and category consistency.
Every suggestion surfaces its confidence and the historical basis behind it. The model is asked to review the ranked shortlist; it never invents the category. Nothing is written back to FreeAgent automatically — the user promotes a suggestion to a deterministic rule, or does not.
Tax rates are not constants in the source. They live in a signed-off rule pack carrying a version, a verified-on date, an expiry date and source citations. The UI shows the pack's freshness state, and calculations built on an expired pack say so.
This is the compliance-domain answer to the grounding problem: the authority for a number is a dated, checkable artefact rather than whatever was in the training data.
The app distinguishes a figure it has reconciled from one it has estimated, and refuses to present the second as decision-ready. Statutory calculations use the legally relevant period with explicit carry-forwards; lifetime averages are used for forecasting and anomaly detection, and are never mixed into the tax arithmetic.
Reconciliation is only meaningful if you know what is missing, so every source stage reports its own completeness, record count and provenance:
FreeAgent — OAuth 2.0 with a local loopback callback. Full lifetime history sync (bank transactions, invoices, bills, expenses, credit notes, estimates, journals, contacts, projects, capital assets, recurring invoices, payroll years, annual trial balances), then incremental pulls with a periodic deep resynchronisation to catch retrospective corrections.
A notable piece of real-world API defensiveness: FreeAgent's Company endpoint advertises annual_accounting_periods that the Trial Balance endpoint will then refuse with HTTP 400 — No accounting period includes the specified dates. Compliance Studio probes candidate dates inside each declared period, indexes only what the API actually accepts, records the provenance, caches unsupported periods for 180 days, and reports the gap as non-blocking rather than failing the pull.
Companies House — read-only public register verification: company profile, accounts and confirmation-statement due dates, filing history, officers, PSCs, charges and insolvency data. Cross-checked against local settings, with material differences and overdue public filings raised into the Action Queue. Never used as a bookkeeping source or as an input to tax arithmetic.
Both integrations, the deterministic checks and the categorisation engine converge on one queue, where every row carries its priority, its due channel, its confidence and the evidence it needs:
- Accounting data never leaves the machine. Inference is local.
- On Windows, the FreeAgent OAuth secret and tokens and the Companies House API key are protected with DPAPI; other platforms use the OS keyring.
- Ordinary preferences live in
~/.compliance_studio_settings.json; records, snapshots, evidence and audit history live in~/.compliance_studio.db. Neither is in this repository. - Every source pull is snapshotted immutably with a content hash, and decisions (such as a dividend) are frozen against the exact snapshot and inputs that justified them.
- The audit log is append-only.
python compliance_studio.py --selftest65 v4 checks, 0 failure(s)
python compliance_studio.py --guitest24 GUI checks, 0 failure(s)
The headless suite covers history ingestion and old-date coverage, FreeAgent metadata/report mismatches, non-blocking trial-balance source gaps, deterministic categorisation, full raw-history context assembly, Companies House cross-checking, current statutory values, deadline persistence, CT bands and marginal relief, Self Assessment reconciliation, dividend snapshots, local-model context construction and SQLite persistence. The GUI suite builds all 24 tabs offscreen and asserts they render against the demo dataset.
Requires Python 3.10+.
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txtExplore the whole application on canned data, with no FreeAgent account and no API keys:
python compliance_studio.py --mock| Command | Effect |
|---|---|
python compliance_studio.py |
GUI against live FreeAgent data |
python compliance_studio.py --mock |
GUI on the demo dataset |
python compliance_studio.py --pull |
Headless live pull and text report |
python compliance_studio.py --ask "question" |
One local-model answer, headless |
python compliance_studio.py --selftest |
Regression suite |
python compliance_studio.py --guitest |
Offscreen GUI smoke test |
For the local adviser, install Ollama and pull a model with a large context window:
ollama pull qwen3:30bA full-history review can require a very large context and substantial RAM/VRAM. Ordinary questions use the current pull plus a lifetime summary and task-relevant history.
Connecting live data requires a FreeAgent developer application with the callback http://localhost:8582/callback, and optionally a Companies House Developer Hub API key. Both are entered in Settings and stored through the OS credential backend.
Everything in --mock mode is a fictional company (Example Consulting Limited, registration 99999999) with invented figures, contacts and transactions. No real company, client, bank or financial information appears anywhere in this repository.
This application does not submit filings, make payments, or alter FreeAgent. It is decision support for a director who remains responsible for approving actions and retaining evidence.
Treat a result as decision-ready only when its source stages are complete, the tax rules are verified and unexpired, the required inputs and evidence are present, the figure is reconciled rather than estimated, and the director has reviewed it.
This is not tax advice and is not a substitute for a qualified accountant. For unusual transactions, complex groups, overseas matters, share reorganisations, insolvency risk, large capital gains, R&D claims or disputed tax positions, obtain specialist advice.
MIT.






