SPB Git

spb/air Public MIT

AIR — The Language of Accounting.

Python 100%
11.2 KB · 234 lines markdown
Rendered Raw Blame History
1<!--2Projet : AIR — Accounting Intermediate Representation3Auteur : Simon-Pierre Boucher4Contact : contact@spboucher.ai5Fichier : README.md6-->78<div align="center">910# ⚙️ AIR1112### The Language of Accounting1314**LLVM for the ledger** — LLMs understand your documents, a deterministic compiler keeps your books.1516[![CI](https://github.com/spboucher-ai/air/actions/workflows/ci.yml/badge.svg)](https://github.com/spboucher-ai/air/actions/workflows/ci.yml)17[![Python](https://img.shields.io/badge/python-3.11%2B-3776AB?logo=python&logoColor=white)](pyproject.toml)18[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)19[![Tests](https://img.shields.io/badge/tests-99%20offline%20%2B%202%20live-brightgreen)](tests/)20[![Golden cases](https://img.shields.io/badge/golden%20cases-21-gold)](tests/golden/cases/)21[![Amounts](https://img.shields.io/badge/floats-forbidden-red)](docs/adr/0003-monetary-amounts.md)22[![Double entry](https://img.shields.io/badge/A%20%3D%20L%20%2B%20E-verified%20after%20every%20pass-blueviolet)](core/invariant.py)23[![Ledger](https://img.shields.io/badge/ledger-append--only%20%7C%20hash--chained-orange)](kernel/ledger.py)24[![Offline first](https://img.shields.io/badge/works%20offline-no%20ERP%2C%20no%20API%20key%20required-informational)](docs/cli-reference.md)2526*by [Simon-Pierre Boucher](mailto:contact@spboucher.ai)*2728</div>2930---3132## The idea3334Every ERP reinvents the same accounting model. LLMs are brilliant at *understanding*35("this invoice = 3 chairs paid by Visa") and unreliable at *applying* hundreds of36tax and accounting rules. **AIR separates the two** — exactly like LLVM separated37language frontends from machine backends:3839```mermaid40flowchart LR41    A[📄 Invoice / Email<br/>Bank / POS / API] --> B["🧠 LLM<br/><i>understanding</i>"]42    B -->|"AIR events<br/>(never journal entries)"| C["⚙️ AIC Compiler<br/><i>deterministic rules</i>"]43    P["📜 ALSL Policies<br/>taxes · thresholds · rounding<br/><i>versioned & cited</i>"] --> C44    C -->|"balanced entries<br/>+ full provenance"| D["📗 Native Ledger<br/>hash-chained, standalone"]45    C --> E["📤 CSV · QuickBooks<br/>Xero · Odoo · SAP*"]46    D --> F["📊 Trial balance · Balance sheet<br/>Income statement · GL"]47    style B fill:#f9e79f,stroke:#b7950b48    style C fill:#aed6f1,stroke:#2471a349    style D fill:#a9dfbf,stroke:#1e844950    style P fill:#f5b7b1,stroke:#c0392b51```5253> **The one rule that never bends:** an LLM (or any frontend) only ever produces54> **AIR** — a description of *what happened economically*. It never writes a journal55> entry, an account code, or a debit. The compiler does that, deterministically,56> with the double-entry invariant `Assets = Liabilities + Equity` verified after57> **every** pass and clang-style diagnostics when anything is wrong.5859## What you get6061| | |62|---|---|63| 🗣️ **A universal language** | `EconomicEvent` (REA-based): sales, purchases, refunds, payments, FX — perspective-neutral, schema-validated, no debits anywhere |64| ⚙️ **A real compiler** | Pass pipeline (validation → classification → tax → FX → posting), pass manager with verify-after-every-pass, diagnostics with location + cause + `help:` fix |65| 📜 **Rules as data (ALSL)** | GST 5%, QST 9.975%, HST, capitalization thresholds, rounding modes — all in versioned YAML policy sets with **mandatory source citations** (the loader rejects uncited rates) |66| 🏠 **Standalone books** | No ERP required: managed AIR home with an append-only, tamper-evident ledger, content-addressed document archive, and all financial statements in text/markdown/CSV/JSON |67| 🔁 **Git-style corrections** | Changed invoice? `air recompile` diffs by content fingerprint and posts reversal + replacement entries — history is never edited |68| 🤖 **An agent SDK** | AI agents get syscalls (`CreateEconomicEvent`, `Post`, `Reverse`, `ClosePeriod`, `Merge`, `Reconcile`…) — never the ledger. Every call, including refusals, lands in a hash-chained audit log |69| 🧾 **LLM ingestion** | Invoice text → Claude (structured outputs) → schema-validated AIR → confidence routing → human approval inbox. Fully offline mock for tests |70| 🚀 **Optimizations** | 50 identical payments → 1 batch entry (fusion), refund↔sale netting, duplicate detection — provenance preserved through every transformation |71| 🏦 **Bank reconciliation** | camt.053 + MT940 (with statement integrity check) + CSV → matched against the books, differences reported on both sides |72| 🔍 **Total traceability** | Every posted cent walks back through the provenance graph (accounting SSA) to its source event, document, OCR/LLM confidence, policy version, and rounding mode |7374## Quickstart7576```bash77git clone https://github.com/spboucher-ai/air && cd air78python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"79source .venv/bin/activate8081# 1) Your books live in a managed AIR home — one command82air init --home books --policies alsl/policies/ca-qc-2026.yaml8384# 2) Compile economic events into the books (document archived, ledger chained)85air compile tests/fixtures/demo_document.yaml --home books \86    --report trial-balance --report balance-sheet8788# 3) A source invoice was corrected? Post only the delta (reversal + replacement)89air recompile old.yaml corrected.yaml --home books9091# 4) Statements any time, any format92air report income-statement --home books --format markdown9394# 5) Ingest a real document (offline extractor; add --llm for Claude)95air ingest tests/fixtures/invoice_high_confidence.txt --home books96air inbox --home books9798# 6) Month end99air reconcile statement.mt940 --home books100air audit --home books101```102103Full command reference: **[docs/cli-reference.md](docs/cli-reference.md)**104105## Sixty seconds of AIR106107An economic event — *what happened*, nothing else:108109```yaml110events:111  - id: evt_01H8XGJWBW112    type: Sale113    date: 2026-07-20114    parties: {seller: "company:acme", buyer: "customer:cust_123"}115    items:116      - {sku: chair-std, qty: "3", unit_price: {amount: "333.33", currency: CAD}}117    payment: {method: card.visa, immediate: true}118    tax: {jurisdiction: CA-QC}          # ← where. Rates live in policies, never here119```120121The compiler applies the cited CA-QC policy set (GST 5%, QST 9.975% on the122pre-GST price, half-up per Excise Tax Act s.165.2(2)) and emits a balanced entry:123124```125DR  1000 Cash                 1149.74126    CR  4000 Sales revenue                999.99127    CR  2310 GST payable                   50.00      tax:GST@0.05~half_up128    CR  2320 QST payable                   99.75      tax:QST@0.09975~half_up129```130131Same input + same policies = **byte-identical output, always** (property-tested,132and CI compiles the demo twice and `diff`s the results). When something is wrong,133you get a compiler error, not a wrong number:134135```136error[AIR-E400]: no tax policy in set 'ca-qc' matches jurisdiction 'CA-BC'137  --> event evt_x, field tax.jurisdiction138  pass: tax139  help: add an ALSL tax policy for this jurisdiction or mark the event tax.exempt: true140```141142## Agents keep books through syscalls — and only syscalls143144```python145from sdk.syscalls import AirKernel146147k = AirKernel("books", actor="agent:alice")148k.create_economic_event({...})     # schema-validated draft149k.post()                           # deterministic compile → books (idempotent)150k.reverse("je_evt_x")              # corrections are contra entries, never edits151k.close_period("2026-01")          # posting into it now fails with AIR-E700152k.merge()                          # post with netting + payment fusion153k.reconcile("statement.mt940")     # bank matching154```155156```157$ air audit --home books158#0000 OK  agent:alice  CreateEconomicEvent  {"event_id": "evt_x", "type": "Sale"}159#0001 OK  agent:alice  Post                 {"drafts": 1}160#0008 ERR agent:alice  Post                 {"drafts": 1}  <- error[AIR-E700]: period 2026-01 is closed16110 syscalls, hash chain VALID162```163164Refused actions are audited like successful ones. Editing any record breaks the chain.165166## The seven guarantees1671681. **Determinism** — no LLM, network, or clock inside the compiler.1692. **Double entry** — the invariant is verified after *every* pass; violations abort compilation.1703. **Traceability** — provenance graph from every posted line to its source (accounting SSA).1714. **No floats** — exact decimals end to end; floats rejected at every boundary.1725. **No rules in code** — every rate/threshold lives in cited, versioned ALSL policies.1736. **Append-only** — reversals, never edits; ledger and audit log are hash-chained.1747. **Human in the loop** — low-confidence or schema-invalid extractions always route to a person.175176## Repository map177178| Path | Role | LLVM analogy |179|---|---|---|180| [`schemas/`](schemas/) | AIR JSON Schema | the IR definition |181| [`core/`](core/) | events, Money, provenance, invariants | IR + verifier |182| [`aic/`](aic/) | pass manager, passes, diagnostics, incremental | opt/llc |183| [`alsl/`](alsl/) | rule language + cited policy sets | TableGen |184| [`backends/`](backends/) | native, CSV, QuickBooks (offline-tested) | targets |185| [`kernel/`](kernel/) | ledger, reporting, workspace, audit, reconciliation | runtime |186| [`sdk/`](sdk/) | CLI, agent syscalls, demo agent | libclang |187| [`ingestion/`](ingestion/) | extractors, confidence routing, approval queue | frontend |188| [`docs/spec/`](docs/spec/) | **the AIR/ALSL specification** | LangRef |189| [`docs/adr/`](docs/adr/) | architecture decision records | — |190| [`docs/research/`](docs/research/) | 9 cited research reports (tax law, formats, APIs) | — |191| [`tests/`](tests/) | golden cases, property tests, unit + live tests | lit |192193## Documentation194195- 📖 **[The AIR Specification](docs/spec/air-spec-v0.1.md)** — the language, the compiler, diagnostics, syscalls, reconciliation196- 🧭 **[CLI Reference](docs/cli-reference.md)** — every command with examples197- 🏛️ **[ADRs](docs/adr/)** — why Python, why REA events, why rounding is policy198- 🔬 **[Research](docs/research/)** — GST/QST from official sources, camt.053/MT940 specs, ERP API surveys, LLVM architecture lessons199- 🤝 **[Contributing](CONTRIBUTING.md)** — the non-negotiable rules CI enforces200201## Development202203```bash204.venv/bin/python -m pytest tests/ -q --ignore=tests/test_llm_live.py   # 99 tests, fully offline205python3 scripts/check_headers.py                                       # author-header gate206207# opt-in live LLM tests (cost a few cents)208ANTHROPIC_API_KEY=sk-ant-... .venv/bin/python -m pytest tests/test_llm_live.py -v209```210211Everything — including the QuickBooks backend and the LLM ingestion — develops212and tests **fully offline**: mock transports simulate the real APIs' documented213behaviors (idempotency replay, duplicate errors), so no account or key is ever214required to work on AIR.215216## Roadmap217218- [x] Phases 0–6: research → core → compiler → backends → ingestion → agent SDK → optimizations & reconciliation *(complete)*219- [ ] beancount/hledger export backend220- [ ] Xero & Odoo backends (offline mock pattern)221- [ ] Reference-first bank matching (`EndToEndId`)222- [ ] More jurisdictions as cited ALSL policy sets (US sales tax, EU VAT)223- [ ] AIR v0.2: REA commitments (pending deliveries, IFRS 15 performance obligations)224225---226227<div align="center">228229**AIR***because your books deserve a compiler.*230231MIT © 2026 [Simon-Pierre Boucher](mailto:contact@spboucher.ai)232233</div>234