diff --git a/backend/app/submission.py b/backend/app/submission.py index 74969b8..d170583 100644 --- a/backend/app/submission.py +++ b/backend/app/submission.py @@ -359,4 +359,7 @@ async def run_check(runner, job_id, payload): setattr(alpha, key, value) db.add(JobItem(job_id=job_id, alpha_id=alpha_id)) job.processed = 1 - job.checkpoint = {"alpha_id": alpha_id, "phase": "checked", "checked_at": now().isoformat()} + job.checkpoint = { + "alpha_id": alpha_id, "phase": "checked", "checked_at": now().isoformat(), + "review_snapshot": fingerprint(source(alpha.raw)), + } diff --git a/backend/tests/test_submission_repeat.py b/backend/tests/test_submission_repeat.py new file mode 100644 index 0000000..ba05b27 --- /dev/null +++ b/backend/tests/test_submission_repeat.py @@ -0,0 +1,23 @@ +"""A completed check can be repeated using its resulting review snapshot.""" + +from tests.test_submission import FIELDS, Description, setup + + +async def test_repeat_check_uses_resulting_snapshot_without_repatch(app, logged_in): + platform = await setup(app) + url = "/api/v1/alphas/alpha1/submission" + state = (await logged_in.get(url)).json() + job_ids = [] + for _ in range(2): + response = await logged_in.post("/api/v1/alphas/alpha1/submission-check", json={ + "snapshot": state["snapshot"], "descriptions": {"regular": Description(**FIELDS).text()}, + }) + assert response.status_code == 202 + job_ids.append(response.json()["id"]) + await app.state.runner.execute(job_ids[-1]) + state = (await logged_in.get(url)).json() + assert state["job"]["status"] == "completed" + assert state["job"]["checkpoint"]["review_snapshot"] == state["snapshot"] + assert job_ids[0] != job_ids[1] + assert len(platform.patches) == 1 + assert platform.calls.count(("GET", "/alphas/alpha1/check")) == 2 diff --git a/frontend/src/alphaMetrics.ts b/frontend/src/alphaMetrics.ts new file mode 100644 index 0000000..58f72df --- /dev/null +++ b/frontend/src/alphaMetrics.ts @@ -0,0 +1,12 @@ +/** Format the five headline Alpha metrics for display; stored/filter values stay raw. + * Unsupported fields return undefined so callers can retain their existing format. + */ +export function formatAlphaMetric( + key: string, + value: unknown, +): string | undefined { + const percent = key === "turnover" || key === "returns" || key === "drawdown"; + if (!percent && key !== "sharpe" && key !== "fitness") return undefined; + if (typeof value !== "number" || !Number.isFinite(value)) return "—"; + return percent ? `${(value * 100).toFixed(2)}%` : value.toFixed(2); +} diff --git a/frontend/src/components/AlphaDetail.tsx b/frontend/src/components/AlphaDetail.tsx index b06b4bb..fd0b3b9 100644 --- a/frontend/src/components/AlphaDetail.tsx +++ b/frontend/src/components/AlphaDetail.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState } from "react"; +import { formatAlphaMetric } from "../alphaMetrics"; import { Banner, Button, @@ -464,7 +465,10 @@ function MetricTable({
未提供
diff --git a/frontend/src/components/SubmissionPanel.tsx b/frontend/src/components/SubmissionPanel.tsx index 4629f0f..9d6e506 100644 --- a/frontend/src/components/SubmissionPanel.tsx +++ b/frontend/src/components/SubmissionPanel.tsx @@ -34,6 +34,7 @@ export function SubmissionPanel({ const [busy, setBusy] = useState(""); const [error, setError] = useState(""); const dirty = useRef(false); + const submittedJob = useRef- AI 一次生成完整 Description,可在下方统一修改。写回并检查会更新 BRAIN 的 + AI 一次生成完整 Description,可在下方统一修改。平台检查会更新 BRAIN 的 Description,随后获取平台提交检查结果,不会正式提交 Alpha。
{error &&