PocketAccountant / src /engine /result.py
eldinosaur's picture
PocketAccountant: custom ledger UI + deterministic agent (engine, ledger, retrieval, classifier)
c55ab5e verified
Raw
History Blame Contribute Delete
1.6 kB
"""A structured calculation result.
Every engine function returns one of these instead of a bare number. It carries:
* ``amount`` — the headline figure (Decimal, rounded);
* ``label`` — what the figure is;
* ``breakdown`` — the step-by-step math, so the model can *explain* without doing
arithmetic;
* ``source`` / ``effective_year`` — provenance, so the UI can say "per the 2024
SAT tariff" and never present a number without its basis.
This is the contract between the deterministic engine and the LLM: the model reads
``breakdown`` and ``source`` and turns them into prose. It never recomputes.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from decimal import Decimal
from typing import List, Optional, Tuple
@dataclass
class CalcResult:
amount: Decimal
label: str
breakdown: List[Tuple[str, Decimal]] = field(default_factory=list)
source: Optional[str] = None
effective_year: Optional[int] = None
notes: List[str] = field(default_factory=list)
def explain(self) -> str:
"""A plain-text rendering of the math — handy for logs, tests and the UI."""
lines = [f"{self.label}: {self.amount}"]
for desc, val in self.breakdown:
lines.append(f" · {desc}: {val}")
if self.source:
year = f" ({self.effective_year})" if self.effective_year else ""
lines.append(f" source: {self.source}{year}")
for note in self.notes:
lines.append(f" note: {note}")
return "\n".join(lines)