# ============================================================================= # Projet : AIR — Accounting Intermediate Representation # Auteur : Simon-Pierre Boucher # Contact : contact@spboucher.ai # Fichier : journal.py # Description : Compiled output types — accounts, journal lines/entries, compiled journal. # ============================================================================= """Journal types: what the AIC compiler PRODUCES (never what the LLM writes). Every journal line carries a provenance node id, so any posted figure can be traced back through tax/FX derivations to the source economic event and its document. """ from __future__ import annotations import enum from dataclasses import dataclass, field from datetime import date from decimal import Decimal from core.money import Money from core.provenance import ProvenanceGraph class AccountType(str, enum.Enum): ASSET = "asset" LIABILITY = "liability" EQUITY = "equity" REVENUE = "revenue" EXPENSE = "expense" @property def normal_side(self) -> "Side": if self in (AccountType.ASSET, AccountType.EXPENSE): return Side.DEBIT return Side.CREDIT class Side(str, enum.Enum): DEBIT = "debit" CREDIT = "credit" @dataclass(frozen=True, slots=True) class Account: code: str name: str type: AccountType @dataclass(frozen=True, slots=True) class JournalLine: account: Account side: Side amount: Money # always >= 0; direction is carried by `side` memo: str = "" provenance_id: str | None = None def signed(self) -> Decimal: """Debit-positive signed amount (for balance math).""" return self.amount.amount if self.side is Side.DEBIT else -self.amount.amount @dataclass(frozen=True, slots=True) class JournalEntry: id: str date: date description: str lines: tuple[JournalLine, ...] source_event_id: str policy_set: str = "" policy_version: str = "" reverses: str | None = None # id of the entry this one reverses, if any @dataclass class CompiledJournal: """The result of one AIC compilation: entries + full provenance.""" entries: list[JournalEntry] = field(default_factory=list) provenance: ProvenanceGraph = field(default_factory=ProvenanceGraph) policy_set: str = "" policy_version: str = "" def accounts(self) -> dict[str, Account]: out: dict[str, Account] = {} for entry in self.entries: for line in entry.lines: out[line.account.code] = line.account return out