import os import sqlite3 import json from contextlib import asynccontextmanager from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import HTMLResponse from pydantic import BaseModel from dotenv import load_dotenv load_dotenv() from src.agents.orchestrator import FinancialAgentOrchestrator orchestrator: FinancialAgentOrchestrator = None DB_PATH = "data/processed/filings.db" UI_HTML = r""" fin.agent — Financial Intelligence
Company Intelligence Reports
LIVE
All
Tech
Finance
Energy
20 companies
Recommended ▾
Apple Inc.
Technology / Consumer Electronics
NASDAQ FY2021-23 SEC 10-K
92
Strong match
Sections
4
Revenue · Risk · MD&A · Overview
Fiscal Years
3
2021 · 2022 · 2023
Index
100K+
Document chunks
Ask the Intelligence Agent
Revenue trends 2021–2023 Key risk factors Stock volatility Compare with peers MD&A summary Cybersecurity risks
Ctrl+Enter to run
Analysis Result
Sources retrieved
Risk Factor Analysis
Cross-company risk intelligence from SEC 10-K filings
Cybersecurity Regulatory Supply Chain Macro / Inflation Geopolitical Talent / HR
Query Log
All agent runs with quality scores and source counts
No queries yet.
Evaluation Report
Recall@K · Precision@K · Answer quality metrics
""" @asynccontextmanager async def lifespan(app: FastAPI): global orchestrator orchestrator = FinancialAgentOrchestrator() orchestrator.build_index() yield app = FastAPI( title="Financial Intelligence Agent", description="Multi-agent RAG system — 100K+ financial document chunks", version="2.0.0", lifespan=lifespan, ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) class QueryRequest(BaseModel): query: str class QueryResponse(BaseModel): query: str answer: str sources: list quality_score: float doc_count: int blocked: bool = False class SearchRequest(BaseModel): query: str ticker: str = "" section: str = "" @app.get("/", response_class=HTMLResponse) async def root(): return UI_HTML @app.post("/query", response_model=QueryResponse) async def query_endpoint(req: QueryRequest): if not req.query.strip(): raise HTTPException(status_code=400, detail="Query cannot be empty") result = orchestrator.answer(req.query) return QueryResponse( query=result["query"], answer=result["answer"], sources=result.get("sources", []), quality_score=result.get("quality_score", 0.0), doc_count=result.get("doc_count", 0), blocked=result.get("blocked", False), ) @app.post("/search") async def search_endpoint(req: SearchRequest): filters = {} if req.ticker: filters["ticker"] = [req.ticker] if req.section: filters["section"] = req.section docs = orchestrator.retriever.hybrid_search(req.query, k=10, filters=filters) results = [ { "ticker": d["metadata"].get("ticker", ""), "section": d["metadata"].get("section", ""), "year": d["metadata"].get("fiscal_year", ""), "text": d["text"][:300], "score": d.get("rerank_score", d.get("score", 0)), } for d in docs ] return {"results": results} @app.get("/prices/{ticker}") async def prices_endpoint(ticker: str): try: conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute( "SELECT date, close FROM stock_prices WHERE ticker=? ORDER BY date ASC", (ticker.upper(),), ) rows = c.fetchall() conn.close() if not rows: return {"dates": [], "closes": []} return { "ticker": ticker, "dates": [r[0] for r in rows], "closes": [r[1] for r in rows], } except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.get("/evaluate") async def evaluate_endpoint(): from pathlib import Path report = Path("evaluation/results/eval_report.json") if report.exists(): return json.loads(report.read_text()) try: from src.evaluation.eval import run_evaluation run_evaluation() if report.exists(): return json.loads(report.read_text()) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=404, detail="Run: python -m src.evaluation.eval") @app.get("/health") async def health(): return {"status": "ok", "version": "2.0.0"}