Implement hybrid approach for balance assertions

Balance assertions now use a hybrid architecture where Beancount is the source
of truth for validation, while Castle stores metadata for UI convenience.

Backend changes:
- Add format_balance() function to beancount_format.py for formatting balance directives
- Update POST /api/v1/assertions to write balance directive to Beancount first (via Fava)
- Store metadata in Castle DB (created_by, tolerance, notes) for UI features
- Validate assertions immediately by querying Fava for actual balance

Frontend changes:
- Update dialog description to explain Beancount validation
- Update button tooltip to clarify balance assertions are written to Beancount
- Update empty state message to mention Beancount checkpoints

Benefits:
- Single source of truth (Beancount ledger file)
- Automatic validation by Beancount
- Best of both worlds: robust validation + friendly UI

See misc-docs/BALANCE-ASSERTIONS-HYBRID-APPROACH.md for full documentation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
padreug 2025-11-10 20:46:12 +01:00
parent 28832d6bfe
commit a3c3e44e5f
3 changed files with 69 additions and 6 deletions

View file

@ -104,6 +104,36 @@ def format_transaction(
}
def format_balance(
date_val: date,
account: str,
amount: int,
currency: str = "SATS"
) -> str:
"""
Format a balance assertion directive for Beancount.
Balance assertions verify that an account has an expected balance on a specific date.
They are checked automatically by Beancount when the file is loaded.
Args:
date_val: Date of the balance assertion
account: Account name (e.g., "Assets:Bitcoin:Lightning")
amount: Expected balance amount
currency: Currency code (default: "SATS")
Returns:
Beancount balance directive as a string
Example:
>>> format_balance(date(2025, 11, 10), "Assets:Bitcoin:Lightning", 1500000, "SATS")
'2025-11-10 balance Assets:Bitcoin:Lightning 1500000 SATS'
"""
date_str = date_val.strftime('%Y-%m-%d')
# Two spaces between account and amount (Beancount convention)
return f"{date_str} balance {account} {amount} {currency}"
def format_posting_with_cost(
account: str,
amount_sats: int,