feat: HSAP platform v2 — modular navigation, quality review, audit log, world model simulation

Major changes:
- New frontend (platform/web/): Vite + React 18 + TypeScript + Tailwind
- 4-module navigation: 数据送标 / 模型管理 / 车队管理 / 系统管理
- Data catalog with charts (DMS/ADAS/Lane 3-tab view)
- Quality review workflow (标注质检): Good/Fine/Bad scoring with auto-advance
- Audit enhancements: batch operations, rejection categories, Feishu notifications
- Operation audit log (操作日志)
- World model simulation studio (仿真工坊)
- Dataset version management with snapshots and diff
- ADAS 7-class dataset integration (138K images organized + compressed)
- User management with Feishu integration and pagination
- CRUD/search/filter on all pages, card layout redesign
- PIL-optimized image overlay rendering
- Auto-snapshot on build, in_review workflow stage
- Removed embedded algorithm code (now in workspace)
This commit is contained in:
2026-06-03 11:40:21 +08:00
parent 7c43b44c57
commit e72bc061c5
5487 changed files with 979207 additions and 6197 deletions

View File

@@ -19,6 +19,18 @@ AS_PY = ML_PY
LONG_ACTIONS = {"train_dms", "train_lane", "pipeline_dms", "eval_dms", "eval_lane", "visualize_dms", "visualize_lane"}
def _auto_snapshot(project: str, task: str = "") -> None:
"""build 成功后自动创建数据集版本快照。"""
try:
from as_platform.data.versions import create_snapshot
desc = f"自动快照 · build {project}"
if task:
desc += f"/{task}"
create_snapshot(project, description=desc, author="system")
except Exception:
pass # 快照失败不影响 build
def _run_ml(argv: list[str], timeout: int = 7200) -> dict[str, Any]:
cmd = [sys.executable, str(ML_PY), *argv]
proc = subprocess.run(cmd, cwd=str(WORKSPACE), capture_output=True, text=True, timeout=timeout)
@@ -34,9 +46,14 @@ def execute_action(action: str, params: dict[str, Any]) -> dict[str, Any]:
track = p.get("track", "platform")
if track == "local":
from algorithms.dms_yolo.adapter import train_local
return train_local(p["task"], p.get("mode", "full"), p.get("config_overrides"))
return train_local(
p["task"],
p.get("mode", "full"),
p.get("config_overrides"),
submode=p.get("submode"),
)
from algorithms.dms_yolo.adapter import train_platform
return train_platform(p["task"], p.get("mode", "full"))
return train_platform(p["task"], p.get("mode", "full"), submode=p.get("submode"))
if action == "train_lane":
track = p.get("track", "platform")
@@ -69,10 +86,15 @@ def execute_action(action: str, params: dict[str, Any]) -> dict[str, Any]:
argv.append("--skip-validate")
if p.get("no_refresh"):
argv.append("--no-refresh")
return _run_ml(argv)
result = _run_ml(argv)
# 自动创建数据集快照
_auto_snapshot("dms", task=p.get("task", ""))
return result
if action == "build_lane":
return _run_ml(["build", "lane"])
result = _run_ml(["build", "lane"])
_auto_snapshot("lane")
return result
if action == "enable_pack":
return _run_ml(["enable", p["project"], p["pack"]])
@@ -144,6 +166,20 @@ def execute_action(action: str, params: dict[str, Any]) -> dict[str, Any]:
)
return {"ok": True, "stdout": "register_batch ok", "stderr": ""}
if action == "delivery_ingest":
from as_platform.integrations.delivery_ingest import run_delivery_ingest
delivery_id = p.get("delivery_id") or ""
if not delivery_id:
raise ValueError("缺少 delivery_id")
result = run_delivery_ingest(delivery_id)
return {
"ok": True,
"stdout": json.dumps(result, ensure_ascii=False),
"stderr": "",
"result": result,
}
if action == "analyze_uploaded_dataset":
from as_platform.data.lake import analyze_uploaded_candidate
@@ -156,4 +192,76 @@ def execute_action(action: str, params: dict[str, Any]) -> dict[str, Any]:
"result": result,
}
if action == "labeling_export":
from as_platform.db.engine import session_scope
from as_platform.db.models import LabelingCampaign
from as_platform.labeling.annotate import resolve_campaign_batch_dir
from as_platform.labeling.service import get_campaign
campaign_id = p.get("campaign_id", "")
row = get_campaign(campaign_id)
if not row:
raise ValueError("campaign not found")
task = row.get("task") or "dam"
batch = row.get("batch") or ""
pack = row.get("pack") or "dms_v2"
export = row.get("export_default") or "yolo"
if row.get("project") == "dms" and export in ("yolo", "yolo_pose") and batch:
scripts_dir = WORKSPACE / "datasets" / "dms" / "scripts"
if str(scripts_dir) not in sys.path:
sys.path.insert(0, str(scripts_dir))
from export_ls_to_yolo import export_batch
with session_scope() as db:
camp = db.get(LabelingCampaign, campaign_id)
if not camp:
raise ValueError("campaign not found")
batch_dir = resolve_campaign_batch_dir(camp)
export_mode = "pose" if export == "yolo_pose" else "detect"
conv = export_batch(
batch_dir,
task,
mode=export_mode,
task_mode=row.get("mode"),
)
if conv.get("written", 0) == 0:
raise ValueError(
"export_ls_to_yolo: 无有效标注可导出 (written=0); "
f"skipped_empty={conv.get('skipped_empty')} missing_ann={conv.get('missing_ann')}"
)
argv = ["build", "dms", task, "--pack", pack, "--batch", batch]
result = _run_ml(argv)
result["export_convert"] = conv
return result
if row.get("project") == "lane" and export == "lane_gt_txt":
scripts_dir = WORKSPACE / "datasets" / "lane" / "scripts"
if str(scripts_dir) not in sys.path:
sys.path.insert(0, str(scripts_dir))
from export_ls_to_lane_gt import export_batch
with session_scope() as db:
camp = db.get(LabelingCampaign, campaign_id)
if not camp:
raise ValueError("campaign not found")
batch_dir = resolve_campaign_batch_dir(camp)
conv = export_batch(batch_dir)
if conv.get("written", 0) == 0:
raise ValueError(
"export_ls_to_lane_gt: 无有效标注可导出 (written=0); "
f"skipped_empty={conv.get('skipped_empty')} missing_ann={conv.get('missing_ann')}"
)
argv = ["build", "lane"]
result = _run_ml(argv)
result["export_convert"] = conv
return result
return {
"ok": True,
"stdout": json.dumps({"export": export, "campaign": row}, ensure_ascii=False),
"stderr": "",
"message": f"export 类型 {export} 暂无 CLI已记录",
}
if action == "labeling_ml_predict":
raise ValueError("labeling_ml_predict 已停用")
raise ValueError(f"未实现执行: {action}")