diff --git a/fava_client.py b/fava_client.py index 76aa022..e300867 100644 --- a/fava_client.py +++ b/fava_client.py @@ -111,13 +111,16 @@ class FavaClient: """ Get balance for a specific account (excluding pending transactions). + Uses sum(weight) for efficient SATS aggregation from price notation. + Args: account_name: Full account name (e.g., "Assets:Receivable:User-abc123") Returns: Dict with: - - sats: int (balance in satoshis) - - positions: dict (currency → amount with cost basis) + - sats: int (balance in satoshis from weight column) + - fiat: Decimal (balance in fiat currency from number column) + - fiat_currency: str (currency code, defaults to EUR) Note: Excludes pending transactions (flag='!') from balance calculation. @@ -125,12 +128,13 @@ class FavaClient: Example: balance = await fava_client.get_account_balance("Assets:Receivable:User-abc") - # Returns: { - # "sats": 200000, - # "positions": {"SATS": {"{100.00 EUR}": 200000}} - # } + # Returns: {"sats": 200000, "fiat": Decimal("150.00"), "fiat_currency": "EUR"} """ - query = f"SELECT sum(position) WHERE account = '{account_name}' AND flag != '!'" + from decimal import Decimal + + # Use sum(weight) for SATS and sum(number) for fiat + # Note: BQL doesn't support != operator, so use flag = '*' to exclude pending + query = f"SELECT sum(number), sum(weight) WHERE account = '{account_name}' AND flag = '*'" try: async with httpx.AsyncClient(timeout=self.timeout) as client: @@ -141,26 +145,26 @@ class FavaClient: response.raise_for_status() data = response.json() - if not data['data']['rows']: - return {"sats": 0, "positions": {}} + if not data['data']['rows'] or not data['data']['rows'][0]: + return {"sats": 0, "fiat": Decimal(0), "fiat_currency": "EUR"} - # Fava returns: [[account, {"SATS": {cost: amount}}]] - positions = data['data']['rows'][0][1] if data['data']['rows'] else {} + row = data['data']['rows'][0] + fiat_sum = row[0] if len(row) > 0 else 0 + weight_sum = row[1] if len(row) > 1 else {} - # Sum up all SATS positions + # Parse fiat amount + fiat_amount = Decimal(str(fiat_sum)) if fiat_sum else Decimal(0) + + # Parse SATS from weight column total_sats = 0 - if isinstance(positions, dict) and "SATS" in positions: - sats_positions = positions["SATS"] - if isinstance(sats_positions, dict): - # Sum all amounts (with different cost bases) - total_sats = sum(int(amount) for amount in sats_positions.values()) - elif isinstance(sats_positions, (int, float)): - # Simple number (no cost basis) - total_sats = int(sats_positions) + if isinstance(weight_sum, dict) and "SATS" in weight_sum: + sats_value = weight_sum["SATS"] + total_sats = int(Decimal(str(sats_value))) return { "sats": total_sats, - "positions": positions + "fiat": fiat_amount, + "fiat_currency": "EUR" # Default, could be extended to detect currency } except httpx.HTTPStatusError as e: diff --git a/views_api.py b/views_api.py index 86b96e6..7589d54 100644 --- a/views_api.py +++ b/views_api.py @@ -309,7 +309,8 @@ async def api_get_account_balance(account_id: str) -> dict: return { "account_id": account_id, "balance": balance_data["sats"], # Balance in satoshis - "positions": balance_data["positions"] # Full Beancount positions with cost basis + "fiat": float(balance_data.get("fiat", 0)), # Fiat amount + "fiat_currency": balance_data.get("fiat_currency", "EUR") }