From 476e9dec4b49180e0ab12665880a630e34f28c73 Mon Sep 17 00:00:00 2001 From: padreug Date: Mon, 10 Nov 2025 03:42:30 +0100 Subject: [PATCH] Supports new amount format and metadata tracking Updates the amount parsing logic to support a new format where fiat amounts (EUR/USD) are specified directly. Adds support for tracking SATS equivalents from metadata when the new format is used. Also tracks fiat amounts specified in metadata as a fallback for backward compatibility. Reverses the calculation of net balance to correctly reflect receivables and liabilities. --- fava_client.py | 64 ++++++++++++++++++++++++++++++++------------------ views_api.py | 13 +++++----- 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/fava_client.py b/fava_client.py index 591619d..4e38507 100644 --- a/fava_client.py +++ b/fava_client.py @@ -354,39 +354,57 @@ class FavaClient: "accounts": [] } - # Parse amount string: "36791 SATS {33.33 EUR, 2025-11-09}" + # Parse amount string: can be EUR/USD directly (new format) or "SATS {EUR}" (old format) amount_str = posting.get("amount", "") if not isinstance(amount_str, str) or not amount_str: continue import re - # Extract SATS amount (with sign) - sats_match = re.match(r'^(-?\d+)\s+SATS', amount_str) - if sats_match: - sats_amount = int(sats_match.group(1)) - - # For admin/castle view, use Beancount amounts as-is: - # Receivable (asset): positive in Beancount = user owes castle (positive) - # Payable (liability): negative in Beancount = castle owes user (negative) - user_data[user_id]["balance"] += sats_amount - - # Extract fiat from cost syntax: {33.33 EUR, ...} - cost_match = re.search(r'\{([\d.]+)\s+([A-Z]+)', amount_str) - if cost_match: - fiat_amount_unsigned = Decimal(cost_match.group(1)) - fiat_currency = cost_match.group(2) + # Try to extract EUR/USD amount first (new format) + fiat_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})$', amount_str) + if fiat_match and fiat_match.group(2) in ('EUR', 'USD', 'GBP'): + # Direct EUR/USD amount (new approach) + fiat_amount = Decimal(fiat_match.group(1)) + fiat_currency = fiat_match.group(2) if fiat_currency not in user_data[user_id]["fiat_balances"]: user_data[user_id]["fiat_balances"][fiat_currency] = Decimal(0) - # Apply the same sign as the SATS amount - # If SATS is negative, fiat should be negative too - if sats_match: - sats_amount_for_sign = int(sats_match.group(1)) - if sats_amount_for_sign < 0: - fiat_amount_unsigned = -fiat_amount_unsigned + user_data[user_id]["fiat_balances"][fiat_currency] += fiat_amount - user_data[user_id]["fiat_balances"][fiat_currency] += fiat_amount_unsigned + # Also track SATS equivalent from metadata if available + posting_meta = posting.get("meta", {}) + sats_equiv = posting_meta.get("sats-equivalent") + if sats_equiv: + sats_amount = int(sats_equiv) if fiat_amount > 0 else -int(sats_equiv) + user_data[user_id]["balance"] += sats_amount + + else: + # Old format: SATS with cost/price notation + sats_match = re.match(r'^(-?\d+)\s+SATS', amount_str) + if sats_match: + sats_amount = int(sats_match.group(1)) + user_data[user_id]["balance"] += sats_amount + + # Extract fiat from cost syntax or metadata (backward compatibility) + posting_meta = posting.get("meta", {}) + fiat_amount_total_str = posting_meta.get("fiat-amount-total") + fiat_currency_meta = posting_meta.get("fiat-currency") + + if fiat_amount_total_str and fiat_currency_meta: + fiat_total = Decimal(fiat_amount_total_str) + fiat_currency = fiat_currency_meta + + if fiat_currency not in user_data[user_id]["fiat_balances"]: + user_data[user_id]["fiat_balances"][fiat_currency] = Decimal(0) + + # Apply the same sign as the SATS amount + if sats_match: + sats_amount_for_sign = int(sats_match.group(1)) + if sats_amount_for_sign < 0: + fiat_total = -fiat_total + + user_data[user_id]["fiat_balances"][fiat_currency] += fiat_total return list(user_data.values()) diff --git a/views_api.py b/views_api.py index 6505dee..82f85e6 100644 --- a/views_api.py +++ b/views_api.py @@ -1104,12 +1104,13 @@ async def api_get_my_balance( all_balances = await fava.get_all_user_balances() # Calculate total: - # Positive balances = Castle owes users (liabilities) - # Negative balances = Users owe Castle (receivables) - # Net: positive means castle owes, negative means castle is owed - total_liabilities = sum(b["balance"] for b in all_balances if b["balance"] > 0) - total_receivables = sum(abs(b["balance"]) for b in all_balances if b["balance"] < 0) - net_balance = total_liabilities - total_receivables + # From get_user_balance(): positive = user owes castle, negative = castle owes user + # Positive balances = Users owe Castle (receivables for Castle) + # Negative balances = Castle owes users (liabilities for Castle) + # Net: positive means castle is owed money, negative means castle owes money + total_receivables = sum(b["balance"] for b in all_balances if b["balance"] > 0) + total_liabilities = sum(abs(b["balance"]) for b in all_balances if b["balance"] < 0) + net_balance = total_receivables - total_liabilities # Aggregate fiat balances from all users total_fiat_balances = {}