P0 修复: - runtimeStore: 添加缺失的 lastDayHistory 字段 - Gateway/RuntimeService: 状态同步改为内存优先,消除 glob 竞态 - App.jsx: 从 3075 行重构到 ~500 行,提取 8 个独立文件 P1 修复: - CORS: 4 个服务改为从环境变量读取允许 origins - MarketStore: 改为模块级单例模式 - Domain 层: 删除 trading thin wrapper,保留 news 真实逻辑 - 测试: 补齐 77 个 gateway/runtime 测试 新增文件: - backend/tests/test_gateway.py (43 tests) - frontend/src/hooks/useWebSocketHandler.js - frontend/src/hooks/useStockRequestCallbacks.js - frontend/src/hooks/useAgentCallbacks.js - frontend/src/hooks/useRuntimeCallbacks.js - frontend/src/hooks/useWatchlistCallbacks.js - frontend/src/components/TickerBar.jsx - frontend/src/components/HeaderRight.jsx - frontend/src/components/ChartTabs.jsx Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
155 lines
4.4 KiB
Python
155 lines
4.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""News and explain FastAPI surface."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from fastapi import Depends, FastAPI, Query
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
from backend.data.market_store import MarketStore
|
|
from backend.domains import news as news_domain
|
|
from backend.config.env_config import get_cors_origins
|
|
|
|
|
|
def get_market_store() -> MarketStore:
|
|
"""Create a market store dependency."""
|
|
return MarketStore()
|
|
|
|
|
|
def create_app() -> FastAPI:
|
|
"""Create the news/explain service app."""
|
|
app = FastAPI(
|
|
title="EvoTraders News Service",
|
|
description="Read-only news enrichment and explain service surface extracted from the monolith",
|
|
version="0.1.0",
|
|
)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=get_cors_origins(),
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
@app.get("/health")
|
|
async def health_check() -> dict[str, str]:
|
|
return {"status": "healthy", "service": "news-service"}
|
|
|
|
@app.get("/api/enriched-news")
|
|
async def api_get_enriched_news(
|
|
ticker: str = Query(..., min_length=1),
|
|
start_date: str | None = Query(None),
|
|
end_date: str | None = Query(None),
|
|
limit: int = Query(100, ge=1, le=1000),
|
|
store: MarketStore = Depends(get_market_store),
|
|
) -> dict[str, Any]:
|
|
return news_domain.get_enriched_news(
|
|
store,
|
|
ticker=ticker,
|
|
start_date=start_date,
|
|
end_date=end_date,
|
|
limit=limit,
|
|
)
|
|
|
|
@app.get("/api/news-for-date")
|
|
async def api_get_news_for_date(
|
|
ticker: str = Query(..., min_length=1),
|
|
date: str = Query(...),
|
|
limit: int = Query(20, ge=1, le=100),
|
|
store: MarketStore = Depends(get_market_store),
|
|
) -> dict[str, Any]:
|
|
return news_domain.get_news_for_date(
|
|
store,
|
|
ticker=ticker,
|
|
date=date,
|
|
limit=limit,
|
|
)
|
|
|
|
@app.get("/api/news-timeline")
|
|
async def api_get_news_timeline(
|
|
ticker: str = Query(..., min_length=1),
|
|
start_date: str = Query(...),
|
|
end_date: str = Query(...),
|
|
store: MarketStore = Depends(get_market_store),
|
|
) -> dict[str, Any]:
|
|
return news_domain.get_news_timeline(
|
|
store,
|
|
ticker=ticker,
|
|
start_date=start_date,
|
|
end_date=end_date,
|
|
)
|
|
|
|
@app.get("/api/categories")
|
|
async def api_get_categories(
|
|
ticker: str = Query(..., min_length=1),
|
|
start_date: str | None = Query(None),
|
|
end_date: str | None = Query(None),
|
|
limit: int = Query(200, ge=1, le=1000),
|
|
store: MarketStore = Depends(get_market_store),
|
|
) -> dict[str, Any]:
|
|
return news_domain.get_news_categories(
|
|
store,
|
|
ticker=ticker,
|
|
start_date=start_date,
|
|
end_date=end_date,
|
|
limit=limit,
|
|
)
|
|
|
|
@app.get("/api/similar-days")
|
|
async def api_get_similar_days(
|
|
ticker: str = Query(..., min_length=1),
|
|
date: str = Query(...),
|
|
n_similar: int = Query(5, ge=1, le=20),
|
|
store: MarketStore = Depends(get_market_store),
|
|
) -> dict[str, Any]:
|
|
return news_domain.get_similar_days_payload(
|
|
store,
|
|
ticker=ticker,
|
|
date=date,
|
|
n_similar=n_similar,
|
|
)
|
|
|
|
@app.get("/api/stories/{ticker}")
|
|
async def api_get_story(
|
|
ticker: str,
|
|
as_of_date: str = Query(...),
|
|
store: MarketStore = Depends(get_market_store),
|
|
) -> dict[str, Any]:
|
|
return news_domain.get_story_payload(
|
|
store,
|
|
ticker=ticker,
|
|
as_of_date=as_of_date,
|
|
)
|
|
|
|
@app.get("/api/range-explain")
|
|
async def api_get_range_explain(
|
|
ticker: str = Query(..., min_length=1),
|
|
start_date: str = Query(...),
|
|
end_date: str = Query(...),
|
|
article_ids: list[str] = Query(default=[]),
|
|
limit: int = Query(100, ge=1, le=500),
|
|
store: MarketStore = Depends(get_market_store),
|
|
) -> dict[str, Any]:
|
|
return news_domain.get_range_explain_payload(
|
|
store,
|
|
ticker=ticker,
|
|
start_date=start_date,
|
|
end_date=end_date,
|
|
article_ids=article_ids,
|
|
limit=limit,
|
|
)
|
|
|
|
return app
|
|
|
|
|
|
app = create_app()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
|
|
uvicorn.run(app, host="0.0.0.0", port=8002)
|