mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-08-25 16:28:04 +00:00
feat: add Atlas Terminal — Next.js 14 + FastAPI full-stack migration
Complete migration from Streamlit to Next.js 14 App Router + FastAPI backend. Frontend (Next.js 14): - 10 pages: Overview, Research, Valuation, Technical, Markets, Earnings, News, Portfolio, Filings, Settings - Terminal Noir dark theme with custom Tailwind config - TradingView Lightweight Charts for candlestick/volume - Valuation: DCF, Sensitivity Matrix, Monte Carlo, Tornado, Reverse DCF - Financial Statements table with YoY growth badges and margin rows - SEC EDGAR inline filing viewer with section tabs - News split-view with iframe article embedding - Technical Analysis with RSI, MACD, Bollinger, Fibonacci, Moving Averages - Earnings beat/miss visualization - AI Copilot chat panel with Gemini integration Backend (FastAPI): - 13 routers: market_data, financials, valuation, technical, earnings, insider, edgar, news, portfolio, analysis, chat, estimates, fx - Services: DCF engine, Monte Carlo simulation, sensitivity analysis, risk metrics, SEC parser, technical indicators - yfinance + yahooquery data sources with fallback pattern - SQLite caching layer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
56a9561f71
commit
b2acda81ee
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
PostgreSQL cache repository — persistent cache with TTL.
|
||||
"""
|
||||
import json
|
||||
from typing import Optional, Any
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from server.db.pg_database import get_pg_pool
|
||||
|
||||
|
||||
async def pg_cache_get(key: str) -> Optional[Any]:
|
||||
"""Get a cached value. Returns None if expired or not found."""
|
||||
pool = await get_pg_pool()
|
||||
if not pool:
|
||||
return None
|
||||
async with pool.acquire() as conn:
|
||||
row = await conn.fetchrow(
|
||||
"SELECT value FROM cache WHERE key = $1 AND expires_at > NOW()",
|
||||
key,
|
||||
)
|
||||
if row and row["value"] is not None:
|
||||
return row["value"] # JSONB auto-deserializes
|
||||
return None
|
||||
|
||||
|
||||
async def pg_cache_set(key: str, value: Any, ttl_seconds: int = 86400) -> None:
|
||||
"""Set a cache value with TTL."""
|
||||
pool = await get_pg_pool()
|
||||
if not pool:
|
||||
return
|
||||
expires = datetime.now(timezone.utc) + timedelta(seconds=ttl_seconds)
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute(
|
||||
"""INSERT INTO cache (key, value, expires_at)
|
||||
VALUES ($1, $2::jsonb, $3)
|
||||
ON CONFLICT (key) DO UPDATE SET value = $2::jsonb, expires_at = $3""",
|
||||
key, json.dumps(value), expires,
|
||||
)
|
||||
|
||||
|
||||
async def pg_cache_delete(key: str) -> None:
|
||||
"""Delete a specific cache entry."""
|
||||
pool = await get_pg_pool()
|
||||
if not pool:
|
||||
return
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute("DELETE FROM cache WHERE key = $1", key)
|
||||
|
||||
|
||||
async def pg_cache_cleanup() -> int:
|
||||
"""Remove expired cache entries. Returns count of deleted rows."""
|
||||
pool = await get_pg_pool()
|
||||
if not pool:
|
||||
return 0
|
||||
async with pool.acquire() as conn:
|
||||
result = await conn.execute(
|
||||
"DELETE FROM cache WHERE expires_at < NOW()"
|
||||
)
|
||||
# Parse "DELETE N" result
|
||||
try:
|
||||
return int(result.split()[-1])
|
||||
except (IndexError, ValueError):
|
||||
return 0
|
||||
Reference in New Issue
Block a user