feat: 微服务架构拆分和前后端优化
后端: - 拆分出 agent_service, runtime_service, trading_service, news_service - Gateway 模块化拆分 (gateway_*.py) - 添加 domains/ 领域层 - 新增 control_client, runtime_client - 更新 start-dev.sh 支持 split 服务模式 前端: - 完善 API 服务层 (newsApi, tradingApi) - 更新 vite.config.js - Explain 组件优化 测试: - 添加多个服务 app 测试 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,13 +3,16 @@
|
||||
# pylint: disable=C0301
|
||||
"""Data fetching tools backed by the unified provider router."""
|
||||
import datetime
|
||||
import os
|
||||
|
||||
import httpx
|
||||
import pandas as pd
|
||||
import pandas_market_calendars as mcal
|
||||
from backend.data.provider_utils import normalize_symbol
|
||||
|
||||
from backend.data.cache import get_cache
|
||||
from backend.data.provider_router import get_provider_router
|
||||
from backend.data.schema import (
|
||||
from shared.schema import (
|
||||
CompanyNews,
|
||||
FinancialMetrics,
|
||||
InsiderTrade,
|
||||
@@ -23,6 +26,31 @@ _cache = get_cache()
|
||||
_router = get_provider_router()
|
||||
|
||||
|
||||
def _service_name() -> str:
|
||||
return str(os.getenv("SERVICE_NAME", "")).strip().lower()
|
||||
|
||||
|
||||
def _trading_service_url() -> str | None:
|
||||
value = str(os.getenv("TRADING_SERVICE_URL", "")).strip().rstrip("/")
|
||||
if not value or _service_name() == "trading_service":
|
||||
return None
|
||||
return value
|
||||
|
||||
|
||||
def _news_service_url() -> str | None:
|
||||
value = str(os.getenv("NEWS_SERVICE_URL", "")).strip().rstrip("/")
|
||||
if not value or _service_name() == "news_service":
|
||||
return None
|
||||
return value
|
||||
|
||||
|
||||
def _service_get_json(base_url: str, path: str, *, params: dict[str, object]) -> dict:
|
||||
with httpx.Client(base_url=base_url, timeout=30.0) as client:
|
||||
response = client.get(path, params=params)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def get_last_tradeday(date: str) -> str:
|
||||
"""
|
||||
Get the previous trading day for the specified date
|
||||
@@ -104,6 +132,24 @@ def get_prices(
|
||||
if cached_data := _cache.get_prices(cache_key):
|
||||
return [Price(**price) for price in cached_data]
|
||||
|
||||
service_url = _trading_service_url()
|
||||
if service_url:
|
||||
try:
|
||||
payload = _service_get_json(
|
||||
service_url,
|
||||
"/api/prices",
|
||||
params={
|
||||
"ticker": ticker,
|
||||
"start_date": start_date,
|
||||
"end_date": end_date,
|
||||
},
|
||||
)
|
||||
prices = [Price(**price) for price in payload.get("prices", [])]
|
||||
if prices:
|
||||
return prices
|
||||
except Exception as exc:
|
||||
logger.info("Trading service price lookup failed for %s: %s", ticker, exc)
|
||||
|
||||
try:
|
||||
prices, data_source = _router.get_prices(ticker, start_date, end_date)
|
||||
except Exception as exc:
|
||||
@@ -146,6 +192,28 @@ def get_financial_metrics(
|
||||
if cached_data := _cache.get_financial_metrics(cache_key):
|
||||
return [FinancialMetrics(**metric) for metric in cached_data]
|
||||
|
||||
service_url = _trading_service_url()
|
||||
if service_url:
|
||||
try:
|
||||
payload = _service_get_json(
|
||||
service_url,
|
||||
"/api/financials",
|
||||
params={
|
||||
"ticker": ticker,
|
||||
"end_date": end_date,
|
||||
"period": period,
|
||||
"limit": limit,
|
||||
},
|
||||
)
|
||||
metrics = [
|
||||
FinancialMetrics(**metric)
|
||||
for metric in payload.get("financial_metrics", [])
|
||||
]
|
||||
if metrics:
|
||||
return metrics
|
||||
except Exception as exc:
|
||||
logger.info("Trading service financial lookup failed for %s: %s", ticker, exc)
|
||||
|
||||
try:
|
||||
financial_metrics, data_source = _router.get_financial_metrics(
|
||||
ticker=ticker,
|
||||
@@ -183,6 +251,22 @@ def search_line_items(
|
||||
ticker = normalize_symbol(ticker)
|
||||
if not ticker:
|
||||
return []
|
||||
|
||||
service_url = _trading_service_url()
|
||||
if service_url:
|
||||
payload = _service_get_json(
|
||||
service_url,
|
||||
"/api/line-items",
|
||||
params={
|
||||
"ticker": ticker,
|
||||
"line_items": line_items,
|
||||
"end_date": end_date,
|
||||
"period": period,
|
||||
"limit": limit,
|
||||
},
|
||||
)
|
||||
return [LineItem(**item) for item in payload.get("search_results", [])]
|
||||
|
||||
return _router.search_line_items(
|
||||
ticker=ticker,
|
||||
line_items=line_items,
|
||||
@@ -213,6 +297,26 @@ def get_insider_trades(
|
||||
if cached_data := _cache.get_insider_trades(cache_key):
|
||||
return [InsiderTrade(**trade) for trade in cached_data]
|
||||
|
||||
service_url = _trading_service_url()
|
||||
if service_url:
|
||||
try:
|
||||
params = {"ticker": ticker, "end_date": end_date, "limit": limit}
|
||||
if start_date:
|
||||
params["start_date"] = start_date
|
||||
payload = _service_get_json(
|
||||
service_url,
|
||||
"/api/insider-trades",
|
||||
params=params,
|
||||
)
|
||||
trades = [
|
||||
InsiderTrade(**trade)
|
||||
for trade in payload.get("insider_trades", [])
|
||||
]
|
||||
if trades:
|
||||
return trades
|
||||
except Exception as exc:
|
||||
logger.info("Trading service insider lookup failed for %s: %s", ticker, exc)
|
||||
|
||||
try:
|
||||
all_trades, data_source = _router.get_insider_trades(
|
||||
ticker=ticker,
|
||||
@@ -248,6 +352,40 @@ def get_company_news(
|
||||
if cached_data := _cache.get_company_news(cache_key):
|
||||
return [CompanyNews(**news) for news in cached_data]
|
||||
|
||||
trading_service_url = _trading_service_url()
|
||||
if trading_service_url:
|
||||
try:
|
||||
params = {"ticker": ticker, "end_date": end_date, "limit": limit}
|
||||
if start_date:
|
||||
params["start_date"] = start_date
|
||||
payload = _service_get_json(
|
||||
trading_service_url,
|
||||
"/api/news",
|
||||
params=params,
|
||||
)
|
||||
news = [CompanyNews(**item) for item in payload.get("news", [])]
|
||||
if news:
|
||||
return news
|
||||
except Exception as exc:
|
||||
logger.info("Trading service news lookup failed for %s: %s", ticker, exc)
|
||||
|
||||
news_service_url = _news_service_url()
|
||||
if news_service_url:
|
||||
try:
|
||||
params = {"ticker": ticker, "end_date": end_date, "limit": limit}
|
||||
if start_date:
|
||||
params["start_date"] = start_date
|
||||
payload = _service_get_json(
|
||||
news_service_url,
|
||||
"/api/enriched-news",
|
||||
params=params,
|
||||
)
|
||||
news = [CompanyNews(**item) for item in payload.get("news", [])]
|
||||
if news:
|
||||
return news
|
||||
except Exception as exc:
|
||||
logger.info("News service lookup failed for %s: %s", ticker, exc)
|
||||
|
||||
try:
|
||||
all_news, data_source = _router.get_company_news(
|
||||
ticker=ticker,
|
||||
@@ -272,6 +410,19 @@ def get_market_cap(ticker: str, end_date: str) -> float | None:
|
||||
if not ticker:
|
||||
return None
|
||||
|
||||
service_url = _trading_service_url()
|
||||
if service_url:
|
||||
try:
|
||||
payload = _service_get_json(
|
||||
service_url,
|
||||
"/api/market-cap",
|
||||
params={"ticker": ticker, "end_date": end_date},
|
||||
)
|
||||
value = payload.get("market_cap")
|
||||
return float(value) if value is not None else None
|
||||
except Exception as exc:
|
||||
logger.info("Trading service market-cap lookup failed for %s: %s", ticker, exc)
|
||||
|
||||
def _metrics_lookup(symbol: str, date: str):
|
||||
for source in _router.api_sources():
|
||||
cache_key = f"{symbol}_ttm_{date}_10_{source}"
|
||||
|
||||
Reference in New Issue
Block a user