from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from routers import market_data, geopolitical, options, backtest, ai, portfolio, config, patterns, journal, cycle as cycle_router, profiles as profiles_router, reasoning as reasoning_router, knowledge as knowledge_router, options_vol as options_vol_router, analytics as analytics_router, risk as risk_router from routers import pattern_lab as pattern_lab_router from routers import specialist_desks as specialist_desks_router from routers import timeline as timeline_router from routers import instruments as instruments_router from routers import impact as impact_router from routers import cycle_actions as cycle_actions_router from routers import logs as logs_router from routers import var as var_router from routers import reports as reports_router from routers import institutional as institutional_router from services.database import init_db, get_config, cleanup_stale_running_cycles import os import logging import uvicorn class _DBLogHandler(logging.Handler): """Persist WARNING+ log records to system_logs table.""" def emit(self, record: logging.LogRecord): if record.levelno < logging.WARNING: return try: from services.database import log_system_event log_system_event( level=record.levelname, source=record.name, message=self.format(record), cycle_id=getattr(record, "cycle_id", None), ticker=getattr(record, "ticker", None), ) except Exception: pass # Never raise from inside a log handler app = FastAPI( title="GeoOptions Intelligence", description="Geopolitical Options Trading Cockpit API", version="2.0.0", ) app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:5173", "http://localhost:3000", "http://127.0.0.1:5173"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.on_event("startup") def startup(): # Attach DB log handler to root logger (captures WARNING+ from all modules) _db_handler = _DBLogHandler() _db_handler.setFormatter(logging.Formatter("%(message)s")) logging.getLogger().addHandler(_db_handler) _log = logging.getLogger(__name__) init_db() # Clean up cycles that were left in 'running' state by a previous crashed process stale = cleanup_stale_running_cycles() if stale: _log.warning(f"[Startup] Cleaned up {stale} stale 'running' cycle(s) — server likely reloaded mid-cycle") # Sync OpenAI key: env var → DB (useful on fresh VPS deployments) key = get_config("openai_api_key") or "" if not key: env_key = os.environ.get("OPENAI_API_KEY", "") if env_key: from services.database import set_config set_config("openai_api_key", env_key) key = env_key _log.info("[Startup] OPENAI_API_KEY synced from environment to DB config") if key: os.environ["OPENAI_API_KEY"] = key # Seed built-in patterns into DB (idempotent) from services.geo_analyzer import GEO_PATTERNS from services.database import seed_builtin_patterns seed_builtin_patterns(GEO_PATTERNS) # Bootstrap historical market events (macro/geopolitical + economic calendar) try: from services.macro_events_bootstrap import bootstrap_macro_events from services.eco_calendar_bootstrap import bootstrap_eco_events from services.impact_categories_bootstrap import bootstrap_impact_categories r1 = bootstrap_macro_events() r2 = bootstrap_eco_events() r3 = bootstrap_impact_categories() if r1.get("inserted", 0) or r2.get("inserted", 0) or r3.get("inserted", 0): _log.info(f"[Startup] Bootstrapped — macro: +{r1.get('inserted',0)}, eco: +{r2.get('inserted',0)}, categories: +{r3.get('inserted',0)}") except Exception as _e: _log.warning(f"[Startup] Event bootstrap failed: {_e}") # Start auto-cycle scheduler if enabled from services.auto_cycle import start_scheduler start_scheduler() # Start VaR + PnL snapshot schedulers from services.var_scheduler import start_var_scheduler, start_pnl_scheduler start_var_scheduler() start_pnl_scheduler() # Start institutional reports weekly scheduler (COT Fridays, EIA Wednesdays) from services.institutional_scheduler import start_institutional_scheduler start_institutional_scheduler() @app.on_event("shutdown") def shutdown(): from services.auto_cycle import stop_scheduler stop_scheduler() from services.var_scheduler import stop_var_scheduler, stop_pnl_scheduler stop_var_scheduler() stop_pnl_scheduler() from services.institutional_scheduler import stop_institutional_scheduler stop_institutional_scheduler() app.include_router(market_data.router) app.include_router(geopolitical.router) app.include_router(options.router) app.include_router(backtest.router) app.include_router(ai.router) app.include_router(portfolio.router) app.include_router(config.router) app.include_router(patterns.router) app.include_router(journal.router) app.include_router(cycle_router.router) app.include_router(profiles_router.router) app.include_router(reasoning_router.router) app.include_router(knowledge_router.router) app.include_router(options_vol_router.router) app.include_router(analytics_router.router) app.include_router(risk_router.router) app.include_router(logs_router.router) app.include_router(var_router.router) app.include_router(reports_router.router) app.include_router(institutional_router.router) app.include_router(pattern_lab_router.router) app.include_router(specialist_desks_router.router) app.include_router(timeline_router.router) app.include_router(instruments_router.router) app.include_router(impact_router.router) app.include_router(cycle_actions_router.router) @app.get("/") def root(): return {"app": "GeoOptions Intelligence Cockpit", "version": "2.0.0", "docs": "/docs"} @app.get("/api/health") def health(): return {"status": "ok", "version": "2.0.0"} if __name__ == "__main__": uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)