mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-05-19 11:28:49 +02:00
Convert all currencies to CRC and poll rates every 6h
All checks were successful
Deploy to VPS / deploy (push) Successful in 14s
All checks were successful
Deploy to VPS / deploy (push) Successful in 14s
Budget/transactions/salarios totals summed Transaction.amount directly,
so USD/EUR entries were treated as CRC and effectively disappeared from
the dashboard (the analytics fix in 9a80f2a only covered analytics).
Adds a shared get_converted_amount_expr() helper driven by the full
Currency enum — USD/EUR via ExchangeRate-API, BTC/XMR via CoinGecko —
and wires it into every func.sum(Transaction.amount) site.
Also starts a background task in the FastAPI lifespan that force-refreshes
every currency 4x/day, persisting USD to the DB and updating in-memory
caches for the rest. Failures are swallowed per-currency so a CoinGecko
outage cannot take out USD/EUR, and the last-known rate is always retained.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ from app.models.models import (
|
||||
TransactionSource,
|
||||
TransactionType,
|
||||
)
|
||||
from app.services.exchange_rate import get_converted_amount_expr
|
||||
|
||||
MIN_YEAR = 2026
|
||||
MAX_YEAR = 2030
|
||||
@@ -104,13 +105,15 @@ def compute_actuals_by_source(
|
||||
prev_start, prev_end = get_cycle_range(prev_cc_y, prev_cc_m)
|
||||
cal_start, cal_end = get_month_range(year, month)
|
||||
|
||||
amount_crc = get_converted_amount_expr(session)
|
||||
|
||||
results = {}
|
||||
for source in TransactionSource:
|
||||
if source == TransactionSource.CREDIT_CARD:
|
||||
start, end = cc_start, cc_end
|
||||
# Normal transactions in this cycle (not deferred)
|
||||
compra_normal = session.exec(
|
||||
select(func.coalesce(func.sum(Transaction.amount), 0)).where(
|
||||
select(func.coalesce(func.sum(amount_crc), 0)).where(
|
||||
Transaction.date >= start,
|
||||
Transaction.date < end,
|
||||
Transaction.source == source,
|
||||
@@ -120,7 +123,7 @@ def compute_actuals_by_source(
|
||||
).one()
|
||||
# Deferred from previous cycle
|
||||
compra_deferred = session.exec(
|
||||
select(func.coalesce(func.sum(Transaction.amount), 0)).where(
|
||||
select(func.coalesce(func.sum(amount_crc), 0)).where(
|
||||
Transaction.date >= prev_start,
|
||||
Transaction.date < prev_end,
|
||||
Transaction.source == source,
|
||||
@@ -131,7 +134,7 @@ def compute_actuals_by_source(
|
||||
compra = float(compra_normal) + float(compra_deferred)
|
||||
|
||||
dev_normal = session.exec(
|
||||
select(func.coalesce(func.sum(Transaction.amount), 0)).where(
|
||||
select(func.coalesce(func.sum(amount_crc), 0)).where(
|
||||
Transaction.date >= start,
|
||||
Transaction.date < end,
|
||||
Transaction.source == source,
|
||||
@@ -140,7 +143,7 @@ def compute_actuals_by_source(
|
||||
)
|
||||
).one()
|
||||
dev_deferred = session.exec(
|
||||
select(func.coalesce(func.sum(Transaction.amount), 0)).where(
|
||||
select(func.coalesce(func.sum(amount_crc), 0)).where(
|
||||
Transaction.date >= prev_start,
|
||||
Transaction.date < prev_end,
|
||||
Transaction.source == source,
|
||||
@@ -180,7 +183,7 @@ def compute_actuals_by_source(
|
||||
else:
|
||||
# Cash / Transfer: calendar month, no deferred logic
|
||||
compra = session.exec(
|
||||
select(func.coalesce(func.sum(Transaction.amount), 0)).where(
|
||||
select(func.coalesce(func.sum(amount_crc), 0)).where(
|
||||
Transaction.date >= cal_start,
|
||||
Transaction.date < cal_end,
|
||||
Transaction.source == source,
|
||||
@@ -188,7 +191,7 @@ def compute_actuals_by_source(
|
||||
)
|
||||
).one()
|
||||
devolucion = session.exec(
|
||||
select(func.coalesce(func.sum(Transaction.amount), 0)).where(
|
||||
select(func.coalesce(func.sum(amount_crc), 0)).where(
|
||||
Transaction.date >= cal_start,
|
||||
Transaction.date < cal_end,
|
||||
Transaction.source == source,
|
||||
@@ -230,6 +233,8 @@ def compute_actuals_by_category(
|
||||
prev_start, prev_end = get_cycle_range(prev_cc_y, prev_cc_m)
|
||||
cal_start, cal_end = get_month_range(year, month)
|
||||
|
||||
amount_crc = get_converted_amount_expr(session)
|
||||
|
||||
totals: dict[int, float] = {}
|
||||
|
||||
def _merge_rows(rows: list) -> None:
|
||||
@@ -245,7 +250,7 @@ def compute_actuals_by_category(
|
||||
select(
|
||||
Transaction.category_id,
|
||||
Transaction.transaction_type,
|
||||
func.sum(Transaction.amount),
|
||||
func.sum(amount_crc),
|
||||
)
|
||||
.where(
|
||||
Transaction.date >= cc_start,
|
||||
@@ -265,7 +270,7 @@ def compute_actuals_by_category(
|
||||
select(
|
||||
Transaction.category_id,
|
||||
Transaction.transaction_type,
|
||||
func.sum(Transaction.amount),
|
||||
func.sum(amount_crc),
|
||||
)
|
||||
.where(
|
||||
Transaction.date >= prev_start,
|
||||
@@ -285,7 +290,7 @@ def compute_actuals_by_category(
|
||||
select(
|
||||
Transaction.category_id,
|
||||
Transaction.transaction_type,
|
||||
func.sum(Transaction.amount),
|
||||
func.sum(amount_crc),
|
||||
)
|
||||
.where(
|
||||
Transaction.date >= cal_start,
|
||||
@@ -310,6 +315,8 @@ def compute_cc_by_category(
|
||||
prev_cc_y, prev_cc_m = get_previous_cycle(cc_cycle_y, cc_cycle_m)
|
||||
prev_start, prev_end = get_cycle_range(prev_cc_y, prev_cc_m)
|
||||
|
||||
amount_crc = get_converted_amount_expr(session)
|
||||
|
||||
totals: dict[int | None, float] = {}
|
||||
|
||||
def _merge(rows: list) -> None:
|
||||
@@ -325,7 +332,7 @@ def compute_cc_by_category(
|
||||
select(
|
||||
Transaction.category_id,
|
||||
Transaction.transaction_type,
|
||||
func.sum(Transaction.amount),
|
||||
func.sum(amount_crc),
|
||||
)
|
||||
.where(
|
||||
Transaction.date >= cc_start,
|
||||
@@ -343,7 +350,7 @@ def compute_cc_by_category(
|
||||
select(
|
||||
Transaction.category_id,
|
||||
Transaction.transaction_type,
|
||||
func.sum(Transaction.amount),
|
||||
func.sum(amount_crc),
|
||||
)
|
||||
.where(
|
||||
Transaction.date >= prev_start,
|
||||
@@ -449,6 +456,8 @@ def compute_monthly_projection(
|
||||
prev_start, prev_end = get_cycle_range(prev_cc_y, prev_cc_m)
|
||||
cal_start, cal_end = get_month_range(year, month)
|
||||
|
||||
amount_crc = get_converted_amount_expr(session)
|
||||
|
||||
def _sum_uncategorized(rows: list) -> float:
|
||||
total = 0.0
|
||||
for tx_type, amount in rows:
|
||||
@@ -461,7 +470,7 @@ def compute_monthly_projection(
|
||||
# CC uncategorized: this cycle (not deferred)
|
||||
uncovered_actual += _sum_uncategorized(
|
||||
session.exec(
|
||||
select(Transaction.transaction_type, func.sum(Transaction.amount))
|
||||
select(Transaction.transaction_type, func.sum(amount_crc))
|
||||
.where(
|
||||
Transaction.date >= cc_start,
|
||||
Transaction.date < cc_end,
|
||||
@@ -476,7 +485,7 @@ def compute_monthly_projection(
|
||||
# CC uncategorized: deferred from previous cycle
|
||||
uncovered_actual += _sum_uncategorized(
|
||||
session.exec(
|
||||
select(Transaction.transaction_type, func.sum(Transaction.amount))
|
||||
select(Transaction.transaction_type, func.sum(amount_crc))
|
||||
.where(
|
||||
Transaction.date >= prev_start,
|
||||
Transaction.date < prev_end,
|
||||
@@ -491,7 +500,7 @@ def compute_monthly_projection(
|
||||
# Non-CC uncategorized: calendar month
|
||||
uncovered_actual += _sum_uncategorized(
|
||||
session.exec(
|
||||
select(Transaction.transaction_type, func.sum(Transaction.amount))
|
||||
select(Transaction.transaction_type, func.sum(amount_crc))
|
||||
.where(
|
||||
Transaction.date >= cal_start,
|
||||
Transaction.date < cal_end,
|
||||
|
||||
Reference in New Issue
Block a user