mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-08-17 12:28:07 +00:00
- Add missing numpy, scipy, dbnomics to requirements.txt (fixes ImportError on fresh install) - Sync claude.md with actual codebase: §3 file structure (37 services, 21 routers), §5 API endpoints (92 routes), §6 frontend pages (12), §13 TODO status - Update README.md with current architecture (92 API routes, 21 routers, 37 services), multi-asset overview, research grid, macro dashboard, screener+backtest, multi-jurisdiction filings, and 2026-03-26 changelog entry - Add new routers: dart, edinet, fmp, macro, research - Add new services: cache, dart_fetcher, dart_filing_service, economic_calendar, ecos_fetcher, edinet_filing_service, fmp_client, global_macro_quadrant, kpi_history_service, macro_cycle, macro_fetcher, oecd_cycle, peer_comparison_service, research_dashboard, smart_money_service, yield_fx_service - Add new frontend: macro page, screener+backtest, research grid components, overview (Equity/ETF/Commodity), filings (SEC/DART/EDINET), error boundaries - Remove 6 unused services: copilot_context, crypto_fetcher, fx_fetcher, gemini_analysis, market_data, technical_analysis - Remove obsolete docs: .agent/, AGENT.md, ATLAS_EVALUATION.md, docs/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
129 lines
3.8 KiB
Python
129 lines
3.8 KiB
Python
"""OECD leading indicator service via DBnomics."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from server.services.cache import cached
|
|
|
|
OECD_COUNTRIES = {
|
|
"USA": "United States",
|
|
"EA": "Euro Area",
|
|
"GBR": "United Kingdom",
|
|
"DEU": "Germany",
|
|
"FRA": "France",
|
|
"JPN": "Japan",
|
|
"KOR": "South Korea",
|
|
"CHN": "China",
|
|
"IND": "India",
|
|
"BRA": "Brazil",
|
|
}
|
|
|
|
_DATASET = "DSD_STES@DF_CLI"
|
|
|
|
|
|
def _fetch_cli(iso: str, limit: int = 36) -> List[Dict[str, Any]]:
|
|
"""Fetch CLI (normalized) for a country from DBnomics."""
|
|
code = f"{iso}.M.LI.IX._Z.NOR.IX._Z.H"
|
|
return _fetch_dbn(_DATASET, code, limit)
|
|
|
|
|
|
def _fetch_bci(iso: str) -> Optional[float]:
|
|
code = f"{iso}.M.BCICP.IX._Z.AA.IX._Z.H"
|
|
series = _fetch_dbn(_DATASET, code, 3)
|
|
return series[-1]["value"] if series else None
|
|
|
|
|
|
def _fetch_cci(iso: str) -> Optional[float]:
|
|
code = f"{iso}.M.CCICP.IX._Z.AA.IX._Z.H"
|
|
series = _fetch_dbn(_DATASET, code, 3)
|
|
return series[-1]["value"] if series else None
|
|
|
|
|
|
def _fetch_dbn(dataset: str, series_code: str, limit: int) -> List[Dict[str, Any]]:
|
|
"""Fetch from DBnomics, with REST fallback."""
|
|
try:
|
|
import dbnomics
|
|
df = dbnomics.fetch_series(
|
|
provider_code="OECD", dataset_code=dataset, series_code=series_code,
|
|
)
|
|
if df is None or df.empty:
|
|
return []
|
|
df = df.dropna(subset=["value"]).tail(limit)
|
|
return [
|
|
{"date": str(row["period"])[:7], "value": round(float(row["value"]), 4)}
|
|
for _, row in df.iterrows()
|
|
]
|
|
except Exception:
|
|
return _fetch_dbn_rest(dataset, series_code, limit)
|
|
|
|
|
|
def _fetch_dbn_rest(dataset: str, series_code: str, limit: int) -> List[Dict[str, Any]]:
|
|
"""REST fallback when the dbnomics package fails."""
|
|
import requests
|
|
url = f"https://api.db.nomics.world/v22/series/OECD/{dataset}/{series_code}?observations=1&limit=1"
|
|
try:
|
|
resp = requests.get(url, timeout=15)
|
|
data = resp.json()
|
|
docs = data.get("series", {}).get("docs", [])
|
|
if not docs:
|
|
return []
|
|
obs = docs[0]
|
|
periods = obs.get("period", [])
|
|
values = obs.get("value", [])
|
|
result = []
|
|
for p, v in zip(periods[-limit:], values[-limit:]):
|
|
if v is not None and str(v) != "NA":
|
|
try:
|
|
result.append({"date": str(p)[:7], "value": round(float(v), 4)})
|
|
except (TypeError, ValueError):
|
|
continue
|
|
return result
|
|
except Exception:
|
|
return []
|
|
|
|
|
|
def _direction(series: List[Dict[str, Any]]) -> str:
|
|
if len(series) < 2:
|
|
return "unknown"
|
|
last = series[-1]["value"]
|
|
prev = series[-2]["value"]
|
|
if last > 100 and last > prev:
|
|
return "expanding"
|
|
if last < 100 and last < prev:
|
|
return "contracting"
|
|
if last > prev:
|
|
return "recovering"
|
|
return "slowing"
|
|
|
|
|
|
@cached("oecd_cli_snapshot", ttl_seconds=21600)
|
|
def get_oecd_cli_snapshot() -> Dict[str, Any]:
|
|
"""Fetch OECD CLI for major economies via DBnomics."""
|
|
countries: List[Dict[str, Any]] = []
|
|
|
|
for iso, name in OECD_COUNTRIES.items():
|
|
series = _fetch_cli(iso, limit=36)
|
|
cli_current = series[-1]["value"] if series else None
|
|
cli_prev = series[-2]["value"] if len(series) >= 2 else None
|
|
|
|
bci = _fetch_bci(iso)
|
|
cci = _fetch_cci(iso)
|
|
|
|
countries.append({
|
|
"country": name,
|
|
"iso": iso,
|
|
"cli_current": cli_current,
|
|
"cli_prev": cli_prev,
|
|
"direction": _direction(series) if series else "unknown",
|
|
"bci": bci,
|
|
"cci": cci,
|
|
"series": series,
|
|
})
|
|
|
|
return {
|
|
"updated_at": datetime.utcnow().isoformat() + "Z",
|
|
"countries": countries,
|
|
}
|