Fix amount parsing to handle both @ and @@ SATS notation
Pending expense entries use per-unit price notation (@ SATS) while migrated entries use total price notation (@@ SATS). Formats handled: - "50.00 EUR @@ 50000 SATS" - total price (multiply = amount) - "50.00 EUR @ 1000.5 SATS" - per-unit price (multiply for total) - "50.00 EUR" with metadata - legacy format - "50000 SATS" - old SATS-first format 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
7173e051fe
commit
913e4705b1
2 changed files with 144 additions and 51 deletions
|
|
@ -224,19 +224,53 @@ class FavaClient:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
import re
|
import re
|
||||||
# Try to extract EUR/USD amount first (new format)
|
|
||||||
|
# Try total price notation: "50.00 EUR @@ 50000 SATS"
|
||||||
|
total_price_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})\s+@@\s+(-?\d+)\s+SATS$', amount_str)
|
||||||
|
# Try per-unit price notation: "50.00 EUR @ 1000.5 SATS"
|
||||||
|
unit_price_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})\s+@\s+([\d.]+)\s+SATS$', amount_str)
|
||||||
|
|
||||||
|
if total_price_match:
|
||||||
|
fiat_amount = Decimal(total_price_match.group(1))
|
||||||
|
fiat_currency = total_price_match.group(2)
|
||||||
|
sats_amount = int(total_price_match.group(3))
|
||||||
|
|
||||||
|
if fiat_currency not in fiat_balances:
|
||||||
|
fiat_balances[fiat_currency] = Decimal(0)
|
||||||
|
fiat_balances[fiat_currency] += fiat_amount
|
||||||
|
|
||||||
|
total_sats += sats_amount
|
||||||
|
if account_name not in accounts_dict:
|
||||||
|
accounts_dict[account_name] = {"account": account_name, "sats": 0}
|
||||||
|
accounts_dict[account_name]["sats"] += sats_amount
|
||||||
|
|
||||||
|
elif unit_price_match:
|
||||||
|
fiat_amount = Decimal(unit_price_match.group(1))
|
||||||
|
fiat_currency = unit_price_match.group(2)
|
||||||
|
sats_per_unit = Decimal(unit_price_match.group(3))
|
||||||
|
sats_amount = int(fiat_amount * sats_per_unit)
|
||||||
|
|
||||||
|
if fiat_currency not in fiat_balances:
|
||||||
|
fiat_balances[fiat_currency] = Decimal(0)
|
||||||
|
fiat_balances[fiat_currency] += fiat_amount
|
||||||
|
|
||||||
|
total_sats += sats_amount
|
||||||
|
if account_name not in accounts_dict:
|
||||||
|
accounts_dict[account_name] = {"account": account_name, "sats": 0}
|
||||||
|
accounts_dict[account_name]["sats"] += sats_amount
|
||||||
|
|
||||||
|
# Try simple fiat format: "50.00 EUR" (check metadata for sats)
|
||||||
|
elif re.match(r'^(-?[\d.]+)\s+([A-Z]{3})$', amount_str):
|
||||||
fiat_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})$', amount_str)
|
fiat_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})$', amount_str)
|
||||||
if fiat_match and fiat_match.group(2) in ('EUR', 'USD', 'GBP'):
|
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_amount = Decimal(fiat_match.group(1))
|
||||||
fiat_currency = fiat_match.group(2)
|
fiat_currency = fiat_match.group(2)
|
||||||
|
|
||||||
if fiat_currency not in fiat_balances:
|
if fiat_currency not in fiat_balances:
|
||||||
fiat_balances[fiat_currency] = Decimal(0)
|
fiat_balances[fiat_currency] = Decimal(0)
|
||||||
|
|
||||||
fiat_balances[fiat_currency] += fiat_amount
|
fiat_balances[fiat_currency] += fiat_amount
|
||||||
|
|
||||||
# Also track SATS equivalent from metadata if available
|
# Also track SATS equivalent from metadata if available (legacy)
|
||||||
posting_meta = posting.get("meta", {})
|
posting_meta = posting.get("meta", {})
|
||||||
sats_equiv = posting_meta.get("sats-equivalent")
|
sats_equiv = posting_meta.get("sats-equivalent")
|
||||||
if sats_equiv:
|
if sats_equiv:
|
||||||
|
|
@ -347,19 +381,45 @@ class FavaClient:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
import re
|
import re
|
||||||
# Try to extract EUR/USD amount first (new format)
|
|
||||||
|
# Try total price notation: "50.00 EUR @@ 50000 SATS"
|
||||||
|
total_price_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})\s+@@\s+(-?\d+)\s+SATS$', amount_str)
|
||||||
|
# Try per-unit price notation: "50.00 EUR @ 1000.5 SATS"
|
||||||
|
unit_price_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})\s+@\s+([\d.]+)\s+SATS$', amount_str)
|
||||||
|
|
||||||
|
if total_price_match:
|
||||||
|
fiat_amount = Decimal(total_price_match.group(1))
|
||||||
|
fiat_currency = total_price_match.group(2)
|
||||||
|
sats_amount = int(total_price_match.group(3))
|
||||||
|
|
||||||
|
if fiat_currency not in user_data[user_id]["fiat_balances"]:
|
||||||
|
user_data[user_id]["fiat_balances"][fiat_currency] = Decimal(0)
|
||||||
|
user_data[user_id]["fiat_balances"][fiat_currency] += fiat_amount
|
||||||
|
user_data[user_id]["balance"] += sats_amount
|
||||||
|
|
||||||
|
elif unit_price_match:
|
||||||
|
fiat_amount = Decimal(unit_price_match.group(1))
|
||||||
|
fiat_currency = unit_price_match.group(2)
|
||||||
|
sats_per_unit = Decimal(unit_price_match.group(3))
|
||||||
|
sats_amount = int(fiat_amount * sats_per_unit)
|
||||||
|
|
||||||
|
if fiat_currency not in user_data[user_id]["fiat_balances"]:
|
||||||
|
user_data[user_id]["fiat_balances"][fiat_currency] = Decimal(0)
|
||||||
|
user_data[user_id]["fiat_balances"][fiat_currency] += fiat_amount
|
||||||
|
user_data[user_id]["balance"] += sats_amount
|
||||||
|
|
||||||
|
# Try simple fiat format: "50.00 EUR" (check metadata for sats)
|
||||||
|
elif re.match(r'^(-?[\d.]+)\s+([A-Z]{3})$', amount_str):
|
||||||
fiat_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})$', amount_str)
|
fiat_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})$', amount_str)
|
||||||
if fiat_match and fiat_match.group(2) in ('EUR', 'USD', 'GBP'):
|
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_amount = Decimal(fiat_match.group(1))
|
||||||
fiat_currency = fiat_match.group(2)
|
fiat_currency = fiat_match.group(2)
|
||||||
|
|
||||||
if fiat_currency not in user_data[user_id]["fiat_balances"]:
|
if fiat_currency not in user_data[user_id]["fiat_balances"]:
|
||||||
user_data[user_id]["fiat_balances"][fiat_currency] = Decimal(0)
|
user_data[user_id]["fiat_balances"][fiat_currency] = Decimal(0)
|
||||||
|
|
||||||
user_data[user_id]["fiat_balances"][fiat_currency] += fiat_amount
|
user_data[user_id]["fiat_balances"][fiat_currency] += fiat_amount
|
||||||
|
|
||||||
# Also track SATS equivalent from metadata if available
|
# Also track SATS equivalent from metadata if available (legacy)
|
||||||
posting_meta = posting.get("meta", {})
|
posting_meta = posting.get("meta", {})
|
||||||
sats_equiv = posting_meta.get("sats-equivalent")
|
sats_equiv = posting_meta.get("sats-equivalent")
|
||||||
if sats_equiv:
|
if sats_equiv:
|
||||||
|
|
|
||||||
45
views_api.py
45
views_api.py
|
|
@ -508,21 +508,38 @@ async def api_get_user_entries(
|
||||||
if isinstance(first_posting, dict):
|
if isinstance(first_posting, dict):
|
||||||
amount_str = first_posting.get("amount", "")
|
amount_str = first_posting.get("amount", "")
|
||||||
|
|
||||||
# Parse amount string: can be EUR/USD directly (new format) or "SATS {EUR}" (old format)
|
# Parse amount string: price notation, simple fiat, or legacy SATS format
|
||||||
if isinstance(amount_str, str) and amount_str:
|
if isinstance(amount_str, str) and amount_str:
|
||||||
import re
|
import re
|
||||||
# Try EUR/USD format first (new format: "37.22 EUR")
|
|
||||||
|
# Try total price notation: "50.00 EUR @@ 50000 SATS"
|
||||||
|
total_price_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})\s+@@\s+(\d+)\s+SATS$', amount_str)
|
||||||
|
# Try per-unit price notation: "50.00 EUR @ 1000.5 SATS"
|
||||||
|
unit_price_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})\s+@\s+([\d.]+)\s+SATS$', amount_str)
|
||||||
|
|
||||||
|
if total_price_match:
|
||||||
|
fiat_amount = abs(float(total_price_match.group(1)))
|
||||||
|
fiat_currency = total_price_match.group(2)
|
||||||
|
amount_sats = abs(int(total_price_match.group(3)))
|
||||||
|
elif unit_price_match:
|
||||||
|
fiat_amount = abs(float(unit_price_match.group(1)))
|
||||||
|
fiat_currency = unit_price_match.group(2)
|
||||||
|
sats_per_unit = float(unit_price_match.group(3))
|
||||||
|
amount_sats = abs(int(fiat_amount * sats_per_unit))
|
||||||
|
|
||||||
|
# Try simple fiat format: "50.00 EUR" (check metadata for sats)
|
||||||
|
elif re.match(r'^(-?[\d.]+)\s+([A-Z]{3})$', amount_str):
|
||||||
fiat_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})$', amount_str)
|
fiat_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})$', amount_str)
|
||||||
if fiat_match and fiat_match.group(2) in ('EUR', 'USD', 'GBP'):
|
if fiat_match and fiat_match.group(2) in ('EUR', 'USD', 'GBP'):
|
||||||
# Direct fiat amount (new approach)
|
|
||||||
fiat_amount = abs(float(fiat_match.group(1)))
|
fiat_amount = abs(float(fiat_match.group(1)))
|
||||||
fiat_currency = fiat_match.group(2)
|
fiat_currency = fiat_match.group(2)
|
||||||
|
|
||||||
# Get SATS from metadata
|
# Get SATS from metadata (legacy)
|
||||||
posting_meta = first_posting.get("meta", {})
|
posting_meta = first_posting.get("meta", {})
|
||||||
sats_equiv = posting_meta.get("sats-equivalent")
|
sats_equiv = posting_meta.get("sats-equivalent")
|
||||||
if sats_equiv:
|
if sats_equiv:
|
||||||
amount_sats = abs(int(sats_equiv))
|
amount_sats = abs(int(sats_equiv))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Old format: "36791 SATS {33.33 EUR, 2025-11-09}" or "36791 SATS"
|
# Old format: "36791 SATS {33.33 EUR, 2025-11-09}" or "36791 SATS"
|
||||||
sats_match = re.match(r'^(-?\d+)\s+SATS', amount_str)
|
sats_match = re.match(r'^(-?\d+)\s+SATS', amount_str)
|
||||||
|
|
@ -781,13 +798,29 @@ async def api_get_pending_entries(
|
||||||
if isinstance(amount_str, str) and amount_str:
|
if isinstance(amount_str, str) and amount_str:
|
||||||
import re
|
import re
|
||||||
|
|
||||||
# Try EUR/USD format first (new architecture): "50.00 EUR"
|
# Try total price notation: "50.00 EUR @@ 50000 SATS"
|
||||||
|
total_price_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})\s+@@\s+(\d+)\s+SATS$', amount_str)
|
||||||
|
# Try per-unit price notation: "50.00 EUR @ 1000.5 SATS"
|
||||||
|
unit_price_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})\s+@\s+([\d.]+)\s+SATS$', amount_str)
|
||||||
|
|
||||||
|
if total_price_match:
|
||||||
|
fiat_amount = abs(float(total_price_match.group(1)))
|
||||||
|
fiat_currency = total_price_match.group(2)
|
||||||
|
amount_sats = abs(int(total_price_match.group(3)))
|
||||||
|
elif unit_price_match:
|
||||||
|
fiat_amount = abs(float(unit_price_match.group(1)))
|
||||||
|
fiat_currency = unit_price_match.group(2)
|
||||||
|
sats_per_unit = float(unit_price_match.group(3))
|
||||||
|
amount_sats = abs(int(fiat_amount * sats_per_unit))
|
||||||
|
|
||||||
|
# Try simple fiat format: "50.00 EUR" (check metadata for sats)
|
||||||
|
elif re.match(r'^(-?[\d.]+)\s+([A-Z]{3})$', amount_str):
|
||||||
fiat_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})$', amount_str)
|
fiat_match = re.match(r'^(-?[\d.]+)\s+([A-Z]{3})$', amount_str)
|
||||||
if fiat_match and fiat_match.group(2) in ('EUR', 'USD', 'GBP'):
|
if fiat_match and fiat_match.group(2) in ('EUR', 'USD', 'GBP'):
|
||||||
fiat_amount = abs(float(fiat_match.group(1)))
|
fiat_amount = abs(float(fiat_match.group(1)))
|
||||||
fiat_currency = fiat_match.group(2)
|
fiat_currency = fiat_match.group(2)
|
||||||
|
|
||||||
# Extract sats equivalent from metadata
|
# Extract sats equivalent from metadata (legacy)
|
||||||
posting_meta = first_posting.get("meta", {})
|
posting_meta = first_posting.get("meta", {})
|
||||||
sats_equiv = posting_meta.get("sats-equivalent")
|
sats_equiv = posting_meta.get("sats-equivalent")
|
||||||
if sats_equiv:
|
if sats_equiv:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue