chore: baseline — ORM migration + systemd deploy + MCP server + daily check

- ORM migration: models/ops 重构
- deploy: 标准化 systemd timer/service 部署体系
- MCP server: 5 个只读工具(任务/状态/数据集)
- daily check: 数据一致性巡检
- watch: task_watch 后台监控
- kline_daily: 麦蕊优先,时间门禁15:00
- 删除: task_industry_sector, task_sector_features
This commit is contained in:
gao
2026-07-21 16:37:00 +08:00
parent 91e83a3b0b
commit 8f016f25df
94 changed files with 4117 additions and 1176 deletions
+53 -10
View File
@@ -34,6 +34,7 @@ if str(_PROJECT_ROOT) not in sys.path:
# ── MCP server bootstrap ────────────────────────────────────────────
from mcp.server import Server
from mcp.server.sse import SseServerTransport
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
@@ -125,9 +126,10 @@ def list_datasets() -> list[dict]:
from sqlalchemy import func, select
from app.core.db.models import (
KlineStock, KlineIndex, Kline5Min, Moneyflow, Share, TickTrade,
SectorIndices, SectorFeaturesDaily, MarketRegimeDaily,
LonghubangDaily, LonghubangSeat, Stock, Industry,
MarketRegimeDaily,
LonghubangDaily, LonghubangSeat, Stock,
NodeCategory, Node, StockNodeMap, KlineStockMADaily,
KlineStockMACDDaily, KlineStockKDJDaily, KlineStockBOLLDaily,
)
from app.core.db.orm import SessionLocal
@@ -140,15 +142,15 @@ def list_datasets() -> list[dict]:
("moneyflow", Moneyflow),
("share", Share),
("tick_trade", TickTrade),
("sector_indices", SectorIndices),
("sector_features_daily", SectorFeaturesDaily),
("market_regime_daily", MarketRegimeDaily),
("longhubang_daily", LonghubangDaily),
("longhubang_seat", LonghubangSeat),
("industry", Industry),
("node_categories", NodeCategory),
("nodes", Node),
("stock_node_map", StockNodeMap),
("kline_stock_macd_daily", KlineStockMACDDaily),
("kline_stock_kdj_daily", KlineStockKDJDaily),
("kline_stock_boll_daily", KlineStockBOLLDaily),
]
out = []
with SessionLocal() as s:
@@ -175,9 +177,10 @@ def get_dataset_info(table_name: str) -> dict:
allowed = {
"stocks", "kline_stock", "kline_index", "kline_5min",
"kline_stock_ma_daily", "moneyflow", "share", "tick_trade",
"sector_indices", "sector_features_daily", "market_regime_daily",
"longhubang_daily", "longhubang_seat", "industry",
"market_regime_daily",
"longhubang_daily", "longhubang_seat",
"node_categories", "nodes", "stock_node_map",
"kline_stock_macd_daily", "kline_stock_kdj_daily", "kline_stock_boll_daily",
"dataset_registry", "sync_history", "config",
}
if table_name not in allowed:
@@ -291,11 +294,51 @@ async def call_tool(name: str, arguments: dict):
# ── main ────────────────────────────────────────────────────────────
async def main():
# 启动时注册数据源(让 datasource.health_check 之类方法可用)
async def main_sse(host: str = "127.0.0.1", port: int = 8101):
"""启动 MCP 的 SSE (HTTP) 服务,供持久运行。
客户端连接: http://{host}:{port}/sse
"""
sse_transport = SseServerTransport("/messages")
async def app(scope, receive, send):
if scope["type"] != "http":
return
path = scope.get("path", "")
if path == "/sse":
async with sse_transport.connect_sse(scope, receive, send) as (read, write):
await server.run(read, write, server.create_initialization_options())
elif path == "/messages" and scope.get("method") == "POST":
await sse_transport.handle_post_message(scope, receive, send)
import uvicorn
from app.core.utils.logging import get_logger
logger = get_logger("mcp")
logger.info(f"MCP SSE server listening on http://{host}:{port}/sse")
config = uvicorn.Config(app, host=host, port=port, log_level="info")
srv = uvicorn.Server(config)
await srv.serve()
def main():
from app.core.datasource.registry import build_default_registry
build_default_registry()
import sys
if "--sse" in sys.argv:
host = "127.0.0.1"
port = 8101
for i, arg in enumerate(sys.argv):
if arg == "--host" and i + 1 < len(sys.argv):
host = sys.argv[i + 1]
if arg == "--port" and i + 1 < len(sys.argv):
port = int(sys.argv[i + 1])
asyncio.run(main_sse(host, port))
else:
asyncio.run(_stdio_main())
async def _stdio_main():
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
@@ -305,4 +348,4 @@ async def main():
if __name__ == "__main__":
asyncio.run(main())
main()