import structlog logger = structlog.get_logger() PAIRS = [ ("GBP", "USD"), ("GBP", "EUR"), ("GBP", "JPY"), ("GBP", "CAD"), ("GBP", "AUD"), ("GBP", "CHF"), ("USD", "GBP"), ("EUR", "GBP"), ] async def fx_sync_job() -> None: from app.dependencies import get_session_factory session_factory = get_session_factory() if not session_factory: return try: import requests r = requests.get( "https://api.exchangerate-api.com/v4/latest/GBP", timeout=10, ) r.raise_for_status() data = r.json() rates = data.get("rates", {}) except Exception as exc: logger.error("fx_fetch_failed", error=str(exc)) return from datetime import datetime, timezone from decimal import Decimal import uuid as _uuid from sqlalchemy import select from app.db.models.currency import ExchangeRate # type: ignore[attr-defined] async with session_factory() as db: try: now = datetime.now(timezone.utc) for base, quote in PAIRS: if base == "GBP": rate_val = rates.get(quote) else: gbp_to_base = rates.get(base) if not gbp_to_base or gbp_to_base == 0: continue rate_val = 1 / gbp_to_base if not rate_val: continue result = await db.execute( select(ExchangeRate).where( ExchangeRate.base_currency == base, ExchangeRate.quote_currency == quote, ) ) existing = result.scalar_one_or_none() if existing: existing.rate = Decimal(str(round(rate_val, 8))) existing.fetched_at = now else: db.add(ExchangeRate( id=_uuid.uuid4(), base_currency=base, quote_currency=quote, rate=Decimal(str(round(rate_val, 8))), source="exchangerate-api", fetched_at=now, )) await db.commit() logger.info("fx_sync_done", pairs=len(PAIRS)) except Exception as exc: await db.rollback() logger.error("fx_sync_db_failed", error=str(exc))