""" Unified calendar sync — FXStreet (primary) with FF live fallback. Called by the background loop in main.py and manually via POST /api/eco/calendar-sync. """ import logging from datetime import datetime, timezone from typing import Any, Dict logger = logging.getLogger(__name__) _sync_status: Dict[str, Any] = { "running": False, "last_run": None, "last_result": None, "source": None, "next_run": None, } def calendar_sync(weeks_ahead: int = 8) -> Dict[str, Any]: """ Sync upcoming economic events: 1. Try FXStreet (no API key, ~300-400 events over weeks_ahead weeks) 2. Fall back to FF live JSON if FXStreet returns 403 or fails """ global _sync_status _sync_status["running"] = True _sync_status["last_run"] = datetime.now(timezone.utc).isoformat() result: Dict[str, Any] = {} source = "unknown" try: from services.fxstreet_calendar import fetch_upcoming as fxs_fetch r = fxs_fetch(weeks_ahead=weeks_ahead) if r.get("error"): logger.warning(f"[calendar_sync] FXStreet error: {r['error']} — falling back to FF live") from services.ff_calendar import sync_live r_ff = sync_live() result = {**r_ff, "_fallback": True, "_fxs_error": r["error"]} source = "ff_fallback" else: result = r source = "fxstreet" except Exception as e: logger.error(f"[calendar_sync] Exception: {e}") result = {"error": str(e)} source = "error" # After calendar upsert, log any forecast/actual changes to macro_series_log try: from services.database import get_conn from services.macro_series_log import scan_and_log_all _conn = get_conn() log_result = scan_and_log_all(_conn, source=source) _conn.close() result["_log"] = log_result except Exception as e: logger.warning(f"[calendar_sync] macro_series_log scan failed: {e}") _sync_status["running"] = False _sync_status["last_result"] = result _sync_status["source"] = source return result def get_sync_status() -> Dict[str, Any]: return dict(_sync_status) def set_next_run(ts: str): _sync_status["next_run"] = ts