fix: allow repeated platform checks and unify Alpha metric formatting
Deploy production / deploy (push) Successful in 54s

This commit is contained in:
yuxuanhui
2026-09-12 22:42:37 +08:00
parent c18960946b
commit f3eb239e1a
8 changed files with 196 additions and 17 deletions
+4 -1
View File
@@ -359,4 +359,7 @@ async def run_check(runner, job_id, payload):
setattr(alpha, key, value) setattr(alpha, key, value)
db.add(JobItem(job_id=job_id, alpha_id=alpha_id)) db.add(JobItem(job_id=job_id, alpha_id=alpha_id))
job.processed = 1 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)),
}
+23
View File
@@ -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
+12
View File
@@ -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);
}
+5 -1
View File
@@ -1,4 +1,5 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { formatAlphaMetric } from "../alphaMetrics";
import { import {
Banner, Banner,
Button, Button,
@@ -464,7 +465,10 @@ function MetricTable({
<h3>{title}</h3> <h3>{title}</h3>
{entries.length ? ( {entries.length ? (
<DetailFieldGrid <DetailFieldGrid
data={entries.map(([key, value]) => ({ key, value }))} data={entries.map(([key, value]) => ({
key,
value: formatAlphaMetric(key, value) ?? value,
}))}
/> />
) : ( ) : (
<p className="muted">未提供</p> <p className="muted">未提供</p>
+26 -14
View File
@@ -34,6 +34,7 @@ export function SubmissionPanel({
const [busy, setBusy] = useState(""); const [busy, setBusy] = useState("");
const [error, setError] = useState(""); const [error, setError] = useState("");
const dirty = useRef(false); const dirty = useRef(false);
const submittedJob = useRef<string | null>(null);
const mounted = useRef(true); const mounted = useRef(true);
useEffect(() => { useEffect(() => {
mounted.current = true; mounted.current = true;
@@ -47,6 +48,16 @@ export function SubmissionPanel({
.then((value) => { .then((value) => {
if (!active) return; if (!active) return;
setData(value); setData(value);
// Rebase only our completed writeback and its exact resulting snapshot.
// A different expression/settings/description must still require review.
if (
value.job?.id === submittedJob.current &&
value.job?.status === "completed" &&
value.job.checkpoint.review_snapshot === value.snapshot
) {
setSnapshot(value.snapshot);
submittedJob.current = null;
}
if (!dirty.current) { if (!dirty.current) {
setDraft(value.descriptions); setDraft(value.descriptions);
setSnapshot(value.snapshot); setSnapshot(value.snapshot);
@@ -89,6 +100,7 @@ export function SubmissionPanel({
if (!mounted.current) return; if (!mounted.current) return;
// Keep the reviewed draft visible while the durable task runs. // Keep the reviewed draft visible while the durable task runs.
dirty.current = true; dirty.current = true;
submittedJob.current = job.id;
setData((current) => (current ? { ...current, job } : current)); setData((current) => (current ? { ...current, job } : current));
onTask(); onTask();
} catch (e) { } catch (e) {
@@ -105,14 +117,14 @@ export function SubmissionPanel({
<div className="detail-section"> <div className="detail-section">
<h3>Description 与提交检查</h3> <h3>Description 与提交检查</h3>
<p className="muted"> <p className="muted">
AI 一次生成完整 Description,可在下方统一修改。写回并检查会更新 BRAIN 的 AI 一次生成完整 Description,可在下方统一修改。平台检查会更新 BRAIN 的
Description,随后获取平台提交检查结果,不会正式提交 Alpha。 Description,随后获取平台提交检查结果,不会正式提交 Alpha。
</p> </p>
{error && <Banner type="danger" description={error} />} {error && <Banner type="danger" description={error} />}
{!data.can_check && ( {!data.can_check && (
<Banner <Banner
type="info" type="info"
description="写回并检查需要待提交 Alpha。有本地比较基准时,需取得有效、完整且低于阈值的自相关结果;没有本地基准时可直接继续平台检查。" description="平台检查需要待提交 Alpha。有本地比较基准时,需取得有效、完整且低于阈值的自相关结果;没有本地基准时可直接继续平台检查。"
/> />
)} )}
{!data.can_generate && ( {!data.can_generate && (
@@ -121,6 +133,17 @@ export function SubmissionPanel({
</p> </p>
)} )}
<div className="inline-actions"> <div className="inline-actions">
<Button
disabled={!!busy || running}
onClick={() => {
dirty.current = false;
setDraft(data.descriptions);
setSnapshot(data.snapshot);
setError("");
}}
>
载入最新描述
</Button>
<Button <Button
onClick={() => void generate()} onClick={() => void generate()}
loading={busy === "generate"} loading={busy === "generate"}
@@ -181,18 +204,7 @@ export function SubmissionPanel({
disabled={!!busy || running || conflict || !data.can_check} disabled={!!busy || running || conflict || !data.can_check}
onClick={() => void check()} onClick={() => void check()}
> >
写回 Description 并检查 平台检查
</Button>
<Button
disabled={!!busy || running}
onClick={() => {
dirty.current = false;
setDraft(data.descriptions);
setSnapshot(data.snapshot);
setError("");
}}
>
载入最新描述
</Button> </Button>
</div> </div>
{data.job && ( {data.job && (
+4 -1
View File
@@ -27,6 +27,7 @@ import {
IconRefresh, IconRefresh,
} from "@douyinfe/semi-icons"; } from "@douyinfe/semi-icons";
import "./AlphaPage.css"; import "./AlphaPage.css";
import { formatAlphaMetric } from "../alphaMetrics";
import type { ColumnProps } from "@douyinfe/semi-ui-19/lib/es/table/interface"; import type { ColumnProps } from "@douyinfe/semi-ui-19/lib/es/table/interface";
import { import {
api, api,
@@ -84,8 +85,10 @@ function metricColor(key: string, value: unknown): string | undefined {
return "var(--semi-color-danger)"; return "var(--semi-color-danger)";
} }
/** Convert Margin only for display; filtering and sorting still use raw values. */ /** Format metrics only for display; filtering and sorting still use raw values. */
function formatMetric(key: string, value: unknown): string { function formatMetric(key: string, value: unknown): string {
const headline = formatAlphaMetric(key, value);
if (headline !== undefined) return headline;
if (key !== "margin") return formatNumber(value, 3); if (key !== "margin") return formatNumber(value, 3);
return typeof value === "number" && Number.isFinite(value) return typeof value === "number" && Number.isFinite(value)
? `${formatNumber(value * 10000, 2)}bps` ? `${formatNumber(value * 10000, 2)}bps`
+1
View File
@@ -138,6 +138,7 @@ export type Job = {
alpha_ids?: string[]; alpha_ids?: string[];
}; };
checkpoint: { checkpoint: {
review_snapshot?: string;
date?: string; date?: string;
dataset_id?: string; dataset_id?: string;
datasets_completed?: number; datasets_completed?: number;
+121
View File
@@ -0,0 +1,121 @@
import { expect, test } from "@playwright/test";
import type { Job } from "../src/types";
test("platform check can repeat after its own description writeback", async ({
page,
}) => {
let snapshot = "a".repeat(64);
let descriptions = { regular: "" };
let job: Job | null = null;
const submitted: string[] = [];
await page.route(/\/api\/v1\/sync-jobs$/, (route) =>
route.fulfill({ json: job ? [job] : [] }),
);
await page.route(/\/api\/v1\/alphas\/[^/]+\/submission$/, (route) =>
route.fulfill({
json: {
snapshot,
descriptions,
sections: {
regular: { code: "rank(close)", description: descriptions.regular },
},
model: "test-model",
can_generate: true,
can_check: true,
job,
},
}),
);
await page.route(
/\/api\/v1\/alphas\/[^/]+\/submission-check$/,
async (route) => {
const body = route.request().postDataJSON();
expect(body.snapshot).toBe(snapshot);
submitted.push(body.snapshot);
descriptions = body.descriptions;
snapshot = String(submitted.length).repeat(64);
job = {
id: `repeat-${submitted.length}`,
kind: "submission_check",
status: "completed",
processed: 1,
failed: 0,
total: 1,
error: null,
next_retry_at: null,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
payload: {},
checkpoint: { phase: "checked", review_snapshot: snapshot },
};
await route.fulfill({ status: 202, json: { ...job, status: "queued" } });
},
);
await page.goto("/#alphas");
await page.getByLabel("密码", { exact: true }).fill("browser-test-password");
await page.getByRole("button", { name: "进入工作空间" }).click();
await expect(
page.getByRole("tab", { name: "待提交", exact: true }),
).toBeVisible();
const headers = { "X-WQ-Request": "1" };
await page.request.put("/api/v1/account/credentials", {
headers,
data: { email: "test@example.com", password: "synthetic-password" },
});
await page.request.post("/api/v1/account/connect", { headers });
await expect
.poll(
async () =>
(await (await page.request.get("/api/v1/account")).json())
.connection_status,
)
.toBe("connected");
const imported = await (
await page.request.post("/api/v1/sync-jobs", {
headers,
data: { kind: "alpha_refresh", alpha_ids: ["TEST0004"] },
})
).json();
await expect
.poll(
async () =>
(
await (
await page.request.get(`/api/v1/sync-jobs/${imported.id}`)
).json()
).status,
)
.toBe("completed");
await page.reload();
await page.locator(".alpha-link").first().click();
const sheet = page.locator(".alpha-detail");
await sheet.getByRole("tab", { name: "相关性检查", exact: true }).click();
const load = sheet.getByRole("button", { name: "载入最新描述", exact: true });
const generate = sheet.getByRole("button", {
name: "AI 生成 Description",
exact: true,
});
await expect(load).toBeVisible();
expect((await load.boundingBox())!.x).toBeLessThan(
(await generate.boundingBox())!.x,
);
const draft = sheet.getByRole("textbox", {
name: "regular Description",
exact: true,
});
await draft.fill("A reviewed description for repeat checking.");
const check = sheet.getByRole("button", { name: "平台检查", exact: true });
for (let i = 1; i <= 2; i++) {
await expect(check).toBeEnabled();
await check.click();
await expect.poll(() => submitted.length).toBe(i);
await expect(page.locator(".job-panel")).toBeVisible();
await page.keyboard.press("Escape");
await expect(page.locator(".job-panel")).not.toBeVisible();
await expect(check).toBeEnabled();
await expect(draft).toHaveValue(
"A reviewed description for repeat checking.",
);
}
expect(submitted).toEqual(["a".repeat(64), "1".repeat(64)]);
});