""" 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())