feat: 支持研究素材删除并统一 Semi 表格

This commit is contained in:
yuxuanhui
2026-09-11 23:21:08 +08:00
parent f29063c9a2
commit 8eec12f0e0
26 changed files with 1008 additions and 562 deletions
+2
View File
@@ -13,6 +13,7 @@ from sqlalchemy import (
String,
Text,
UniqueConstraint,
false,
)
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
@@ -443,6 +444,7 @@ class ResearchExperiment(Base):
id: Mapped[str] = mapped_column(String(36), primary_key=True)
name: Mapped[str] = mapped_column(String(200))
kind: Mapped[str] = mapped_column(String(30), index=True)
archived: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false())
hypothesis: Mapped[str] = mapped_column(Text)
inputs: Mapped[list] = mapped_column(JSON)
parents: Mapped[list] = mapped_column(JSON)
+8 -3
View File
@@ -76,11 +76,12 @@ class Assets:
ResearchAsset.id == asset_id,
ResearchAsset.version == body.version,
ResearchAsset.kind == body.kind,
ResearchAsset.archived.is_(False),
)
.values(version=body.version + 1, name=content["name"], updated_at=now())
)
if changed.rowcount != 1:
raise HTTPException(409, "素材已变化,保留草稿并读取最新版本")
raise HTTPException(409, "素材已变化或已删除,保留草稿并读取最新版本")
version = body.version + 1
else:
asset_id, version = uid(), 1
@@ -100,11 +101,15 @@ class Assets:
async def archive(self, asset_id, version):
result = await self.db.execute(
update(ResearchAsset)
.where(ResearchAsset.id == asset_id, ResearchAsset.version == version)
.where(
ResearchAsset.id == asset_id,
ResearchAsset.version == version,
ResearchAsset.archived.is_(False),
)
.values(archived=True, version=ResearchAsset.version + 1, updated_at=now())
)
if result.rowcount != 1:
raise HTTPException(409, "素材已变化或不存在")
raise HTTPException(409, "素材已变化、已删除或不存在,请刷新后重试")
# Archiving is itself a revision; old references remain resolvable.
previous = await self.db.get(ResearchRevision, (asset_id, version))
self.db.add(
+16 -2
View File
@@ -5,7 +5,7 @@ import json
from collections import defaultdict
from fastapi import HTTPException
from sqlalchemy import func, select
from sqlalchemy import func, select, update
from ..backtests.contracts import Candidate, DraftInput, PreviewInput, SimulationSettings, Source
from ..backtests.service import Backtests, uid
@@ -243,6 +243,7 @@ class Experiments:
"id",
"name",
"kind",
"archived",
"hypothesis",
"inputs",
"parents",
@@ -256,7 +257,7 @@ class Experiments:
)
async def list(self, kind=None, limit=25, offset=0):
query = select(ResearchExperiment)
query = select(ResearchExperiment).where(ResearchExperiment.archived.is_(False))
if kind:
query = query.where(ResearchExperiment.kind == kind)
total = await self.db.scalar(select(func.count()).select_from(query.subquery()))
@@ -281,6 +282,19 @@ class Experiments:
}
)
async def archive(self, experiment_id):
"""Hide an immutable experiment; backtests and lineage must still resolve it.
Returns an acknowledgement, or raises HTTP 404 for an unknown ID. Repeated
deletion is idempotent because candidate contents cannot change.
"""
result = await self.db.execute(
update(ResearchExperiment).where(ResearchExperiment.id == experiment_id).values(archived=True)
)
if result.rowcount != 1:
raise HTTPException(404, "研究实验不存在")
return {"ok": True}
async def preview(self, experiment_id, candidate_ids=None, source_kind=None, reference=None):
experiment = await self.get(experiment_id)
candidates = experiment["candidates"]
+6
View File
@@ -143,6 +143,12 @@ async def experiment(experiment_id: str, request: Request):
return await Experiments(db).get(experiment_id)
@router.delete("/experiments/{experiment_id}")
async def archive_experiment(experiment_id: str, request: Request):
async with request.app.state.sessions.begin() as db:
return await Experiments(db).archive(experiment_id)
@router.post("/experiments/{experiment_id}/preview", status_code=201)
async def preview(experiment_id: str, body: ExperimentPreview, request: Request):
async with request.app.state.sessions.begin() as db: