feat: 分离 Alpha 检查与提交限制,优化检查统计与前端展示
This commit is contained in:
+15
-4
@@ -7,6 +7,7 @@ from datetime import datetime
|
||||
from sqlalchemy import or_, select, update
|
||||
|
||||
from .models import Alpha, Research, ResearchTag, SelfCorrelation, now
|
||||
from .platform_checks import split_checks, submission_limits
|
||||
from .research.provenance import source_alpha_ids
|
||||
|
||||
METRIC_FIELDS = (
|
||||
@@ -16,23 +17,24 @@ METRIC_FIELDS = (
|
||||
|
||||
|
||||
def failed_checks(checks):
|
||||
"""Return failed platform check names; local correlation never changes this list."""
|
||||
"""Return failed Alpha check names, excluding submission limits and local correlation."""
|
||||
return [
|
||||
check.get("name") if isinstance(check.get("name"), str) else "未命名检查"
|
||||
for check in checks if isinstance(check, dict) and check.get("result") == "FAIL"
|
||||
for check in split_checks(checks)[0] if isinstance(check, dict) and check.get("result") == "FAIL"
|
||||
] if isinstance(checks, list) else []
|
||||
|
||||
|
||||
def snapshot_columns(settings, metrics, checks):
|
||||
"""Derive list fields from a platform snapshot, preserving missing metrics as null.
|
||||
|
||||
Only explicit FAIL results count. Empty, malformed and unfinished checks are
|
||||
Submission limits are excluded. Only explicit Alpha FAIL results count.
|
||||
Empty, malformed and unfinished Alpha checks are
|
||||
pending; all known checks passing without PROD_CORRELATION is only a pre-check.
|
||||
No submission eligibility or activity eligibility is inferred here.
|
||||
"""
|
||||
settings = settings if isinstance(settings, dict) else {}
|
||||
metrics = metrics if isinstance(metrics, dict) else {}
|
||||
checks = checks if isinstance(checks, list) else []
|
||||
checks, _ = split_checks(checks)
|
||||
valid = [check for check in checks if isinstance(check, dict)]
|
||||
failures = len(failed_checks(checks))
|
||||
by_name = {check["name"]: check for check in valid if isinstance(check.get("name"), str)}
|
||||
@@ -64,6 +66,15 @@ def snapshot_columns(settings, metrics, checks):
|
||||
}
|
||||
|
||||
|
||||
def check_summary(checks):
|
||||
"""Separate cached Alpha findings from submission limits; infer no live eligibility."""
|
||||
return {
|
||||
"check_type": snapshot_columns({}, {}, checks)["check_type"],
|
||||
"failed_checks": failed_checks(checks),
|
||||
"submission_limits": submission_limits(checks),
|
||||
}
|
||||
|
||||
|
||||
def submission_condition(submission):
|
||||
"""Match the platform list contract; a missing status is never assumed submitted."""
|
||||
return Alpha.status == "UNSUBMITTED" if submission == "UNSUBMITTED" else Alpha.status != "UNSUBMITTED"
|
||||
|
||||
@@ -24,7 +24,7 @@ TOOLS = {
|
||||
"search_data_preparations": (c.PreparationSearch, "preparations", "research:read", "分页查询数据准备集合,返回固定范围、字段数及版本。研究可使用多个集合,各集合范围独立。"),
|
||||
"get_data_preparation": (c.PreparationRead, "preparation", "research:read", "按集合 ID 与版本分页预览字段、类型、描述和数据集归属。提交回测时携带 preparation_refs,由服务端核对版本并固定独立输入快照;空集合不能用于研究。"),
|
||||
"create_research_template": (c.CreateTemplate, "create_template", "research:write", "将调用方大模型研究后自行总结的参数化模板保存到模板工坊,供用户后续批量回测。先用 get_backtest_results 阅读实际指标和检查,选择 1–20 个已完成采集的 source_item_ids,并说明 hypothesis;不要把 completed 当作检查通过。template 使用 {name} 占位符及逐一对应的 variables,字段变量须声明 MATRIX/VECTOR/GROUP,VECTOR 聚合须明确写入表达式。提供唯一名称和 idempotency_key,可附 reference。返回模板 ID、版本和理论组合数;仅核验结构及来源,不验证所有参数组合,不再次调用模型、不执行回测、不覆盖已有模板。"),
|
||||
"get_submission_check": (c.SelfCorrelationReference, "submission_check_context", "research:read", "读取已导入 Alpha 的表达式、Description、snapshot 和缓存检查结果;不发起检查。先核对或生成三段 Description,再调用 check_submission。"),
|
||||
"get_submission_check": (c.SelfCorrelationReference, "submission_check_context", "research:read", "读取已导入 Alpha 的表达式、Description、snapshot 和缓存检查结果;check_summary 分离 Alpha 检查和 REGULAR_SUBMISSION 提交限制,原始 checks 保留;限制不代表当前额度。不发起检查。先核对或生成三段 Description,再调用 check_submission。"),
|
||||
"check_submission": (c.SubmissionCheck, "check_submission", "research:refresh", "对单个待提交 Alpha 写回已获用户授权的 Description 并调用平台 GET /check,返回 job_id。须先用 get_submission_check 获取 snapshot;保留本地自相关门槛和冲突保护。通过 get_refresh_job 查进度、get_submission_check 读结果。无论检查结果如何,都不会调用 /submit 或正式提交 Alpha。"),
|
||||
"get_worldquant_connection": (c.ConnectionReference, "connection", "research:read", "读取 WorldQuant 连接状态及可选认证 job_id 的进度,不发起认证;人工验证在网页完成。"),
|
||||
"authenticate_worldquant": (c.Authentication, "authenticate", "research:refresh", "使用服务端已保存凭据连接或重新认证 WorldQuant,返回 job_id;action=connect(默认)或人工验证后 verify。用 get_worldquant_connection 查询,不接收密码,不修改账户配置。"),
|
||||
@@ -38,7 +38,7 @@ TOOLS = {
|
||||
"search_backtests": (c.History, "history", "research:read", "分页查历史候选与固定设置;candidates 按完整输入精确匹配,不推断数学等价。"),
|
||||
"submit_backtests": (c.Submit, "submit", "backtests:execute", "执行用户已授权的固定批次,自动留痕并立即返回运行 ID。可携带 preparation_refs 选择集合,版本变化须重新读取;每项必须完整设置;重复默认拒绝,rerun 明确重跑。不需要研究资产。"),
|
||||
"get_backtest": (c.RunReference, "run", "research:read", "读取真实运行进度、提交数量和可选增量事件;受理不等于成功。"),
|
||||
"get_backtest_results": (c.Results, "results", "research:read", "分页读取固定快照指标、全部非通过检查及三层状态;缺失指标不补零。"),
|
||||
"get_backtest_results": (c.Results, "results", "research:read", "分页读取固定快照指标、Alpha 非通过检查及三层状态;REGULAR_SUBMISSION 单列 submission_limits,不计入 Alpha 失败统计。缺失指标不补零。"),
|
||||
"get_backtest_artifact": (c.Artifact, "artifact", "research:read", "分页读取候选脱敏快照的顶层键值或独立采集的 PnL;缺缓存不自动刷新。"),
|
||||
"control_backtest": (c.Control, "control", "backtests:control", "对已授权运行暂停、继续、停止或恢复采集;不远程取消、不重提未知模拟。需要版本和幂等键。"),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Classify platform evidence without discarding unknown checks or inferring eligibility."""
|
||||
|
||||
|
||||
def is_submission_limit(check):
|
||||
"""Recognize only the confirmed account-limit check; unknown names remain Alpha checks."""
|
||||
return isinstance(check, dict) and check.get("name") == "REGULAR_SUBMISSION"
|
||||
|
||||
|
||||
def split_checks(checks):
|
||||
"""Return Alpha checks and submission limits, retaining malformed Alpha evidence."""
|
||||
items = checks if isinstance(checks, list) else []
|
||||
return ([c for c in items if not is_submission_limit(c)],
|
||||
[c for c in items if is_submission_limit(c)])
|
||||
|
||||
|
||||
def submission_limits(checks):
|
||||
"""Summarize the observed limit, never the account's current allowance or reset time."""
|
||||
_, limits = split_checks(checks)
|
||||
status = "blocked" if any(c.get("result") == "FAIL" for c in limits) else (
|
||||
"not_blocked" if limits and all(c.get("result") == "PASS" for c in limits) else "unknown"
|
||||
)
|
||||
return {"status": status, "checks": limits,
|
||||
"meaning": "仅反映缓存观测时的提交限制,不代表当前额度或正式提交资格"}
|
||||
@@ -7,6 +7,7 @@ from sqlalchemy import select
|
||||
|
||||
from ..backtests.service import Backtests, uid
|
||||
from ..models import Alpha, ResearchEvaluation, ResearchExperiment, SelfCorrelation
|
||||
from ..platform_checks import split_checks, submission_limits
|
||||
from .experiments import Experiments
|
||||
from .serialization import encode_snapshot as jsonable_encoder
|
||||
|
||||
@@ -30,13 +31,14 @@ def assess(snapshot, rules):
|
||||
evidence.append(
|
||||
{"metric": key, "value": value, "bound": bound, "direction": direction, "status": status}
|
||||
)
|
||||
checks = metrics.get("checks") or []
|
||||
raw_checks = metrics.get("checks") or []
|
||||
checks, _ = split_checks(raw_checks)
|
||||
if not checks:
|
||||
missing.append("platform_checks")
|
||||
for check in checks:
|
||||
if check.get("result") == "FAIL":
|
||||
if isinstance(check, dict) and check.get("result") == "FAIL":
|
||||
failed.append(f"platform:{check.get('name', 'unknown')}")
|
||||
unknown_checks = [check for check in checks if check.get("result") not in ("PASS", "FAIL")]
|
||||
unknown_checks = [check for check in checks if not isinstance(check, dict) or check.get("result") not in ("PASS", "FAIL")]
|
||||
if unknown_checks:
|
||||
missing.append("unresolved_platform_checks")
|
||||
return {
|
||||
@@ -44,7 +46,8 @@ def assess(snapshot, rules):
|
||||
"evidence": evidence,
|
||||
"failed": failed,
|
||||
"missing": missing,
|
||||
"existing_platform_checks": checks,
|
||||
"existing_platform_checks": raw_checks,
|
||||
"submission_limits": submission_limits(raw_checks),
|
||||
"meaning": "本地研究筛选结果,不是官方提交资格",
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from sqlalchemy import func, select
|
||||
from ..alphas import number, sanitize
|
||||
from ..backtests.contracts import fingerprint
|
||||
from ..models import BacktestItem, BacktestResult, BacktestRun, Pnl
|
||||
from ..platform_checks import is_submission_limit
|
||||
from ..research.serialization import encode_snapshot
|
||||
|
||||
|
||||
@@ -26,6 +27,8 @@ def checks_summary(snapshot):
|
||||
if "checks" in snapshot:
|
||||
raw = snapshot["checks"]
|
||||
checks.extend({"section": "root", "raw": c} for c in (raw if isinstance(raw, list) else [raw]))
|
||||
submission_checks = [c for c in checks if is_submission_limit(c["raw"])]
|
||||
checks = [c for c in checks if not is_submission_limit(c["raw"])]
|
||||
counts = Counter({key: 0 for key in ("PASS", "FAIL", "PENDING", "WARNING", "UNKNOWN")})
|
||||
non_pass = []
|
||||
for check in checks:
|
||||
@@ -36,7 +39,9 @@ def checks_summary(snapshot):
|
||||
if state != "PASS":
|
||||
non_pass.append({**check, "status": state})
|
||||
return {"status": "unknown" if not checks else "reported", "counts": dict(counts),
|
||||
"total": len(checks), "non_pass": non_pass}
|
||||
"total": len(checks), "non_pass": non_pass,
|
||||
"submission_limits": submission_checks,
|
||||
"meaning": "Alpha 检查统计不含提交限制;限制为快照观测,不代表实时提交资格"}
|
||||
|
||||
|
||||
def item_summary(item, result):
|
||||
|
||||
@@ -9,6 +9,7 @@ from uuid import uuid4
|
||||
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from ..alphas import check_summary
|
||||
from ..backtests.contracts import ControlInput, DraftInput, PreviewInput, Source, StartInput, fingerprint
|
||||
from ..backtests.service import Backtests
|
||||
from ..business import Business
|
||||
@@ -221,7 +222,7 @@ class ResearchAccess:
|
||||
return {"alpha_id": args.alpha_id, "snapshot": submission_fingerprint(context),
|
||||
**context, "descriptions": {key: item["description"] for key, item in context["sections"].items()},
|
||||
"can_check": alpha.status == "UNSUBMITTED" and await correlation_allows_check(self.db, args.alpha_id),
|
||||
"checks": alpha.checks, "source": "local_cache", "production_submission": False,
|
||||
"checks": alpha.checks, "check_summary": check_summary(alpha.checks), "source": "local_cache", "production_submission": False,
|
||||
"job_id": job.id if job else None, "job_status": job.status if job else None,
|
||||
"checked_at": job.checkpoint.get("checked_at") if job else None}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ from pydantic_ai.usage import UsageLimits
|
||||
from sqlalchemy import select
|
||||
|
||||
from .ai.provider import public_error
|
||||
from .alphas import code, sanitize, snapshot_columns, submission_condition
|
||||
from .alphas import check_summary, code, sanitize, snapshot_columns, submission_condition
|
||||
from .jobs import ACTIVE
|
||||
from .models import Account, AISettings, Alpha, Job, JobItem, SelfCorrelation, now
|
||||
from .schemas import Contract, JobOutput, valid_ids
|
||||
@@ -206,6 +206,8 @@ def router(runner, ai):
|
||||
)
|
||||
return {
|
||||
"snapshot": fingerprint(context),
|
||||
"checks": alpha.checks,
|
||||
"check_summary": check_summary(alpha.checks),
|
||||
"sections": context["sections"],
|
||||
"descriptions": {key: item["description"] for key, item in context["sections"].items()},
|
||||
"model": config.description_model,
|
||||
|
||||
Reference in New Issue
Block a user