Files
Personal-Finance-Management/app/services/fx_service.py
T
2026-05-31 11:17:53 -04:00

87 lines
2.3 KiB
Python

"""
FX Service — fetches and caches daily USD→VND exchange rate.
Primary source: open.er-api.com (free, no key)
Fallback: last known rate from DB
"""
import requests
from datetime import date, datetime
from app.extensions import db
from app.models.fx_rate import FxRate
ER_API_URL = 'https://open.er-api.com/v6/latest/USD'
REQUEST_TIMEOUT = 8 # seconds
def get_today_rate():
"""
Return today's USD→VND rate as a dict:
{ 'rate': 25450.00, 'date': date(...), 'source': '...', 'is_stale': False }
"""
today = date.today()
# 1. Check cache
cached = FxRate.query.filter_by(date=today).first()
if cached:
return {
'rate': float(cached.usd_to_vnd),
'date': cached.date,
'source': cached.source,
'is_stale': False,
}
# 2. Fetch from API
rate, source = _fetch_from_api()
if rate:
record = FxRate(
date=today,
usd_to_vnd=rate,
source=source,
fetched_at=datetime.utcnow(),
)
db.session.add(record)
try:
db.session.commit()
except Exception:
db.session.rollback()
return {'rate': rate, 'date': today, 'source': source, 'is_stale': False}
# 3. Fallback — last known rate
last = FxRate.query.order_by(FxRate.date.desc()).first()
if last:
return {
'rate': float(last.usd_to_vnd),
'date': last.date,
'source': last.source,
'is_stale': True,
}
# 4. Nothing available
return None
def _fetch_from_api():
"""Try open.er-api.com. Returns (rate, source) or (None, None)."""
try:
resp = requests.get(ER_API_URL, timeout=REQUEST_TIMEOUT)
if resp.status_code == 200:
data = resp.json()
vnd = data.get('rates', {}).get('VND')
if vnd:
return float(vnd), 'exchangerate-api'
except Exception:
pass
return None, None
def get_rate_history(days=30):
"""Return list of FxRate records for last N days, oldest first."""
from datetime import timedelta
since = date.today() - timedelta(days=days)
return (FxRate.query
.filter(FxRate.date >= since)
.order_by(FxRate.date.asc())
.all())