feat: 增加仅检查 MCP 工具并完善已提交 Alpha 指标展示
Deploy production / deploy (push) Successful in 57s

This commit is contained in:
yuxuanhui
2026-09-11 13:14:27 +08:00
parent f25161d624
commit 7e990b9a69
12 changed files with 311 additions and 63 deletions
+43 -33
View File
@@ -143,6 +143,48 @@ async def require_correlation(db, alpha_id):
raise HTTPException(409, "有本地比较基准时,请先取得有效、样本完整且低于阈值的本地自相关结果")
async def create_check_job(db, alpha_id, body):
"""Queue description writeback and checks only; caller owns commit and wake-up.
HTTP and MCP share locking, conflict checks and active-job deduplication.
No production submission operation is available in this flow.
"""
account = await db.scalar(select(Account).where(Account.id == 1).with_for_update())
if not account.password_encrypted or account.connection_status in ("disconnected", "error"):
raise HTTPException(409, "请先连接 WorldQuant")
alpha = await local_alpha(db, alpha_id)
context = source(alpha.raw)
if alpha.status != "UNSUBMITTED":
raise HTTPException(409, "仅对待提交 Alpha 写回 Description 并检查")
if fingerprint(context) != body.snapshot:
raise HTTPException(409, "Alpha 内容已变化,请重新载入并核对描述")
if set(body.descriptions) != set(context["sections"]):
raise HTTPException(422, "Description 必须匹配 Alpha 的 regular 或 selection/combo 部分")
await require_correlation(db, alpha_id)
texts = {}
for key, draft in body.descriptions.items():
if isinstance(draft, str):
texts[key] = draft
else:
original = context["sections"][key]["description"]
# Preserve existing formatting for older clients sending separate fields.
texts[key] = (
original if parse_description(original) == draft.model_dump() else draft.text()
)
payload = {"alpha_ids": [alpha_id], "expected": context, "descriptions": texts}
for job in (
await db.scalars(select(Job).where(Job.kind == "submission_check", Job.status.in_(ACTIVE)))
).all():
if job.payload.get("alpha_ids") == [alpha_id]:
if job.payload == payload:
return job
raise HTTPException(409, "此 Alpha 已有平台检查任务,请等待完成或取消后再修改描述")
job = Job(id=str(uuid4()), kind="submission_check", payload=payload, total=1)
db.add(job)
await db.flush()
return job
def router(runner, ai):
api = APIRouter(prefix="/api/v1/alphas", tags=["submission-check"], dependencies=[Depends(require_auth)])
generation_lock = asyncio.Lock()
@@ -248,39 +290,7 @@ def router(runner, ai):
@api.post("/{alpha_id}/submission-check", status_code=202, response_model=JobOutput)
async def check(alpha_id: str, body: CheckInput):
async with ai.sessions.begin() as db:
account = await db.scalar(select(Account).where(Account.id == 1).with_for_update())
if not account.password_encrypted or account.connection_status in ("disconnected", "error"):
raise HTTPException(409, "请先连接 WorldQuant")
alpha = await local_alpha(db, alpha_id)
context = source(alpha.raw)
if alpha.status != "UNSUBMITTED":
raise HTTPException(409, "仅对待提交 Alpha 写回 Description 并检查")
if fingerprint(context) != body.snapshot:
raise HTTPException(409, "Alpha 内容已变化,请重新载入并核对描述")
if set(body.descriptions) != set(context["sections"]):
raise HTTPException(422, "Description 必须匹配 Alpha 的 regular 或 selection/combo 部分")
await require_correlation(db, alpha_id)
texts = {}
for key, draft in body.descriptions.items():
if isinstance(draft, str):
texts[key] = draft
else:
original = context["sections"][key]["description"]
# Preserve existing formatting for older clients sending separate fields.
texts[key] = (
original if parse_description(original) == draft.model_dump() else draft.text()
)
payload = {"alpha_ids": [alpha_id], "expected": context, "descriptions": texts}
for job in (
await db.scalars(select(Job).where(Job.kind == "submission_check", Job.status.in_(ACTIVE)))
).all():
if job.payload.get("alpha_ids") == [alpha_id]:
if job.payload == payload:
return job
raise HTTPException(409, "此 Alpha 已有平台检查任务,请等待完成或取消后再修改描述")
job = Job(id=str(uuid4()), kind="submission_check", payload=payload, total=1)
db.add(job)
await db.flush()
job = await create_check_job(db, alpha_id, body)
runner.wake.set()
return job