refactor: unify data preparations and research input snapshots
Deploy production / deploy (push) Successful in 53s

This commit is contained in:
yuxuanhui
2026-09-12 01:24:02 +08:00
parent 849f86fef7
commit 394438e753
82 changed files with 4146 additions and 2076 deletions
+7 -1
View File
@@ -6,6 +6,7 @@ from typing import Literal
from pydantic import Field, field_validator, model_validator
from ..preparations.contracts import PreparationReference
from ..schemas import Contract
@@ -48,13 +49,18 @@ class Source(Contract):
kind: str = Field(default="manual", min_length=1, max_length=100)
reference: str | None = Field(default=None, max_length=200)
batch_id: str | None = Field(default=None, max_length=200)
template_input_id: str | None = Field(default=None, max_length=200)
input_snapshot_ids: list[str] = Field(default_factory=list, max_length=100)
input_snapshot_id: str | None = Field(default=None, max_length=200)
research_id: str | None = Field(default=None, max_length=200)
parent_run_id: str | None = Field(default=None, max_length=36)
hypothesis: str | None = Field(default=None, max_length=2000)
class DraftInput(Contract):
preparation_refs: list[PreparationReference] = Field(default_factory=list, max_length=20)
input_ids: list[str] = Field(default_factory=list, max_length=20)
name: str = Field(min_length=1, max_length=200)
source: Source = Field(default_factory=Source)
candidates: list[Candidate] = Field(min_length=1, max_length=10000)
+27 -1
View File
@@ -106,8 +106,33 @@ class Backtests:
"limits": "并发和批大小为本地配置,并非平台可用额度;账户外提交不在预算内",
}
async def bind_preparations(self, body):
from ..preparations.service import Preparations
from ..research.expressions import analyze
if not body.preparation_refs and not body.input_ids:
return
await Preparations(self.db).bind(body)
snapshots = [await Preparations(self.db).snapshot(i) for i in body.input_ids]
for candidate in body.candidates:
scope = dict(instrument_type=candidate.settings.instrumentType, region=candidate.settings.region,
universe=candidate.settings.universe, delay=candidate.settings.delay)
if any(s["scope"] != scope for s in snapshots):
raise HTTPException(422, "数据准备集合与回测范围不一致")
fields = {}
for snapshot in snapshots:
for field, kind in snapshot["field_types"].items():
if field in fields and fields[field] != kind:
raise HTTPException(422, "输入字段类型冲突")
fields[field] = kind
validation = analyze(candidate.expression, fields)
if validation["syntax"] or validation["types"]:
raise HTTPException(422, ";".join(validation["syntax"] + validation["types"]))
body.source.input_snapshot_ids = body.input_ids
body.source.input_snapshot_id = body.input_ids[0] if len(body.input_ids) == 1 else None
async def save_draft(self, body, draft_id=None):
data = body.model_dump(mode="json", exclude={"version"})
await self.bind_preparations(body)
data = body.model_dump(mode="json", exclude={"version", "preparation_refs", "input_ids"})
if draft_id:
changed = await self.db.execute(
update(BacktestDraft)
@@ -168,6 +193,7 @@ class Backtests:
producer. ai_context separately identifies whoever starts the execution.
"""
if body.inline:
await self.bind_preparations(body.inline)
data = body.inline.model_dump(mode="json")
if self.ai_context and not preserve_source:
data["source"] = {