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:
2026-03-23 17:45:39 +08:00
parent 0f1bc2bb39
commit 3448667b79
54 changed files with 5440 additions and 2947 deletions

View File

@@ -29,6 +29,7 @@ class NewsServiceClient:
ticker: str,
start_date: str | None = None,
end_date: str | None = None,
limit: int | None = None,
) -> dict:
"""Get enriched news for a ticker.
@@ -45,10 +46,44 @@ class NewsServiceClient:
params["start_date"] = start_date
if end_date:
params["end_date"] = end_date
if limit is not None:
params["limit"] = limit
response = await self._client.get("/api/enriched-news", params=params)
response.raise_for_status()
return response.json()
async def get_news_for_date(
self,
ticker: str,
date: str,
limit: int = 20,
) -> dict:
"""Get enriched news rows for a specific trade date."""
response = await self._client.get(
"/api/news-for-date",
params={"ticker": ticker, "date": date, "limit": limit},
)
response.raise_for_status()
return response.json()
async def get_news_timeline(
self,
ticker: str,
start_date: str,
end_date: str,
) -> dict:
"""Get aggregated news timeline for a ticker."""
response = await self._client.get(
"/api/news-timeline",
params={
"ticker": ticker,
"start_date": start_date,
"end_date": end_date,
},
)
response.raise_for_status()
return response.json()
async def get_similar_days(
self,
ticker: str,
@@ -70,61 +105,61 @@ class NewsServiceClient:
response.raise_for_status()
return response.json()
async def get_story(self, story_id: str) -> dict:
"""Get a specific news story by ID.
async def get_story(self, ticker: str, as_of_date: str) -> dict:
"""Get or build a ticker story as of one date.
Args:
story_id: The story identifier.
ticker: Stock ticker symbol.
as_of_date: Story date.
Returns:
Dictionary with story data.
"""
response = await self._client.get(f"/api/stories/{story_id}")
response = await self._client.get(
f"/api/stories/{ticker}",
params={"as_of_date": as_of_date},
)
response.raise_for_status()
return response.json()
async def post_enrich(self, news_items: list[dict]) -> dict:
"""Enrich news items with additional analysis.
Args:
news_items: List of news items to enrich.
Returns:
Dictionary with enriched news data.
"""
response = await self._client.post("/api/enrich", json=news_items)
response.raise_for_status()
return response.json()
async def get_categories(self) -> dict:
"""Get available news categories.
async def get_categories(
self,
ticker: str,
start_date: str | None = None,
end_date: str | None = None,
limit: int = 200,
) -> dict:
"""Get categories for a ticker window.
Returns:
Dictionary with available categories.
"""
response = await self._client.get("/api/categories")
params = {"ticker": ticker, "limit": limit}
if start_date:
params["start_date"] = start_date
if end_date:
params["end_date"] = end_date
response = await self._client.get("/api/categories", params=params)
response.raise_for_status()
return response.json()
async def search_news(
async def get_range_explain(
self,
query: str,
ticker: str | None = None,
limit: int = 10,
ticker: str,
start_date: str,
end_date: str,
article_ids: list[str] | None = None,
limit: int = 100,
) -> dict:
"""Search news articles.
Args:
query: Search query string.
ticker: Optional ticker to filter by.
limit: Maximum number of results.
Returns:
Dictionary with search results.
"""
params = {"query": query, "limit": limit}
if ticker:
params["ticker"] = ticker
response = await self._client.get("/api/search", params=params)
"""Get a range explanation for a ticker window."""
params: list[tuple[str, str | int]] = [
("ticker", ticker),
("start_date", start_date),
("end_date", end_date),
("limit", limit),
]
for article_id in article_ids or []:
params.append(("article_ids", article_id))
response = await self._client.get("/api/range-explain", params=params)
response.raise_for_status()
return response.json()