SPB Git

spb/air Public MIT

AIR — The Language of Accounting.

Python 100%
11.2 KB

# ⚙️ AIR

# The Language of Accounting

LLVM for the ledger — LLMs understand your documents, a deterministic compiler keeps your books.

CI Python License: MIT Tests Golden cases Amounts Double entry Ledger Offline first

by Simon-Pierre Boucher


# The idea

Every ERP reinvents the same accounting model. LLMs are brilliant at understanding ("this invoice = 3 chairs paid by Visa") and unreliable at applying hundreds of tax and accounting rules. AIR separates the two — exactly like LLVM separated language frontends from machine backends:

flowchart LR
    A[📄 Invoice / Email<br/>Bank / POS / API] --> B["🧠 LLM<br/><i>understanding</i>"]
    B -->|"AIR events<br/>(never journal entries)"| C["⚙️ AIC Compiler<br/><i>deterministic rules</i>"]
    P["📜 ALSL Policies<br/>taxes · thresholds · rounding<br/><i>versioned & cited</i>"] --> C
    C -->|"balanced entries<br/>+ full provenance"| D["📗 Native Ledger<br/>hash-chained, standalone"]
    C --> E["📤 CSV · QuickBooks<br/>Xero · Odoo · SAP*"]
    D --> F["📊 Trial balance · Balance sheet<br/>Income statement · GL"]
    style B fill:#f9e79f,stroke:#b7950b
    style C fill:#aed6f1,stroke:#2471a3
    style D fill:#a9dfbf,stroke:#1e8449
    style P fill:#f5b7b1,stroke:#c0392b

The one rule that never bends: an LLM (or any frontend) only ever produces AIR — a description of what happened economically. It never writes a journal entry, an account code, or a debit. The compiler does that, deterministically, with the double-entry invariant Assets = Liabilities + Equity verified after every pass and clang-style diagnostics when anything is wrong.

# What you get

🗣️ A universal language EconomicEvent (REA-based): sales, purchases, refunds, payments, FX — perspective-neutral, schema-validated, no debits anywhere
⚙️ A real compiler Pass pipeline (validation → classification → tax → FX → posting), pass manager with verify-after-every-pass, diagnostics with location + cause + help: fix
📜 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)
🏠 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
🔁 Git-style corrections Changed invoice? air recompile diffs by content fingerprint and posts reversal + replacement entries — history is never edited
🤖 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
🧾 LLM ingestion Invoice text → Claude (structured outputs) → schema-validated AIR → confidence routing → human approval inbox. Fully offline mock for tests
🚀 Optimizations 50 identical payments → 1 batch entry (fusion), refund↔sale netting, duplicate detection — provenance preserved through every transformation
🏦 Bank reconciliation camt.053 + MT940 (with statement integrity check) + CSV → matched against the books, differences reported on both sides
🔍 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

# Quickstart

bash
git clone https://github.com/spboucher-ai/air && cd air
python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
source .venv/bin/activate

# 1) Your books live in a managed AIR home — one command
air init --home books --policies alsl/policies/ca-qc-2026.yaml

# 2) Compile economic events into the books (document archived, ledger chained)
air compile tests/fixtures/demo_document.yaml --home books \
    --report trial-balance --report balance-sheet

# 3) A source invoice was corrected? Post only the delta (reversal + replacement)
air recompile old.yaml corrected.yaml --home books

# 4) Statements any time, any format
air report income-statement --home books --format markdown

# 5) Ingest a real document (offline extractor; add --llm for Claude)
air ingest tests/fixtures/invoice_high_confidence.txt --home books
air inbox --home books

# 6) Month end
air reconcile statement.mt940 --home books
air audit --home books

Full command reference: docs/cli-reference.md

# Sixty seconds of AIR

An economic event — what happened, nothing else:

yaml
events:
  - id: evt_01H8XGJWBW
    type: Sale
    date: 2026-07-20
    parties: {seller: "company:acme", buyer: "customer:cust_123"}
    items:
      - {sku: chair-std, qty: "3", unit_price: {amount: "333.33", currency: CAD}}
    payment: {method: card.visa, immediate: true}
    tax: {jurisdiction: CA-QC}          # ← where. Rates live in policies, never here

The compiler applies the cited CA-QC policy set (GST 5%, QST 9.975% on the pre-GST price, half-up per Excise Tax Act s.165.2(2)) and emits a balanced entry:

text
DR  1000 Cash                 1149.74
    CR  4000 Sales revenue                999.99
    CR  2310 GST payable                   50.00      tax:GST@0.05~half_up
    CR  2320 QST payable                   99.75      tax:QST@0.09975~half_up

Same input + same policies = byte-identical output, always (property-tested, and CI compiles the demo twice and diffs the results). When something is wrong, you get a compiler error, not a wrong number:

text
error[AIR-E400]: no tax policy in set 'ca-qc' matches jurisdiction 'CA-BC'
  --> event evt_x, field tax.jurisdiction
  pass: tax
  help: add an ALSL tax policy for this jurisdiction or mark the event tax.exempt: true

# Agents keep books through syscalls — and only syscalls

python
from sdk.syscalls import AirKernel

k = AirKernel("books", actor="agent:alice")
k.create_economic_event({...})     # schema-validated draft
k.post()                           # deterministic compile → books (idempotent)
k.reverse("je_evt_x")              # corrections are contra entries, never edits
k.close_period("2026-01")          # posting into it now fails with AIR-E700
k.merge()                          # post with netting + payment fusion
k.reconcile("statement.mt940")     # bank matching
text
$ air audit --home books
#0000 OK  agent:alice  CreateEconomicEvent  {"event_id": "evt_x", "type": "Sale"}
#0001 OK  agent:alice  Post                 {"drafts": 1}
#0008 ERR agent:alice  Post                 {"drafts": 1}  <- error[AIR-E700]: period 2026-01 is closed
10 syscalls, hash chain VALID

Refused actions are audited like successful ones. Editing any record breaks the chain.

# The seven guarantees

  1. Determinism — no LLM, network, or clock inside the compiler.
  2. Double entry — the invariant is verified after every pass; violations abort compilation.
  3. Traceability — provenance graph from every posted line to its source (accounting SSA).
  4. No floats — exact decimals end to end; floats rejected at every boundary.
  5. No rules in code — every rate/threshold lives in cited, versioned ALSL policies.
  6. Append-only — reversals, never edits; ledger and audit log are hash-chained.
  7. Human in the loop — low-confidence or schema-invalid extractions always route to a person.

# Repository map

Path Role LLVM analogy
schemas/ AIR JSON Schema the IR definition
core/ events, Money, provenance, invariants IR + verifier
aic/ pass manager, passes, diagnostics, incremental opt/llc
alsl/ rule language + cited policy sets TableGen
backends/ native, CSV, QuickBooks (offline-tested) targets
kernel/ ledger, reporting, workspace, audit, reconciliation runtime
sdk/ CLI, agent syscalls, demo agent libclang
ingestion/ extractors, confidence routing, approval queue frontend
docs/spec/ the AIR/ALSL specification LangRef
docs/adr/ architecture decision records
docs/research/ 9 cited research reports (tax law, formats, APIs)
tests/ golden cases, property tests, unit + live tests lit

# Documentation

  • 📖 The AIR Specification — the language, the compiler, diagnostics, syscalls, reconciliation
  • 🧭 CLI Reference — every command with examples
  • 🏛️ ADRs — why Python, why REA events, why rounding is policy
  • 🔬 Research — GST/QST from official sources, camt.053/MT940 specs, ERP API surveys, LLVM architecture lessons
  • 🤝 Contributing — the non-negotiable rules CI enforces

# Development

bash
.venv/bin/python -m pytest tests/ -q --ignore=tests/test_llm_live.py   # 99 tests, fully offline
python3 scripts/check_headers.py                                       # author-header gate

# opt-in live LLM tests (cost a few cents)
ANTHROPIC_API_KEY=sk-ant-... .venv/bin/python -m pytest tests/test_llm_live.py -v

Everything — including the QuickBooks backend and the LLM ingestion — develops and tests fully offline: mock transports simulate the real APIs' documented behaviors (idempotency replay, duplicate errors), so no account or key is ever required to work on AIR.

# Roadmap

  • Phases 0–6: research → core → compiler → backends → ingestion → agent SDK → optimizations & reconciliation (complete)
  • beancount/hledger export backend
  • Xero & Odoo backends (offline mock pattern)
  • Reference-first bank matching (EndToEndId)
  • More jurisdictions as cited ALSL policy sets (US sales tax, EU VAT)
  • AIR v0.2: REA commitments (pending deliveries, IFRS 15 performance obligations)

AIRbecause your books deserve a compiler.

MIT © 2026 Simon-Pierre Boucher