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:
@@ -8,13 +8,27 @@ from typing import Any
|
||||
from as_platform.db.engine import session_scope
|
||||
from as_platform.db.models import Approval, User
|
||||
from as_platform.config import LANE_DATA_VIZ_ENABLED
|
||||
from as_platform.integrations.feishu_notify import send_chat_async
|
||||
|
||||
ACTIONS_REQUIRING_APPROVAL = {
|
||||
"build_dms", "build_lane", "enable_pack", "disable_pack",
|
||||
"train_dms", "train_lane", "eval_dms", "promote_dms",
|
||||
"pipeline_dms", "register_batch", "eval_lane", "visualize_dms", "visualize_lane",
|
||||
"delivery_ingest",
|
||||
}
|
||||
|
||||
REJECTION_CATEGORIES = {
|
||||
"data_quality": "数据质量问题",
|
||||
"wrong_params": "参数配置有误",
|
||||
"duplicate": "重复提交",
|
||||
"not_needed": "无需此操作",
|
||||
"permission": "权限不足",
|
||||
"other": "其他原因",
|
||||
}
|
||||
|
||||
REJECTION_CATEGORY_LABEL = {k: v for k, v in REJECTION_CATEGORIES.items()}
|
||||
|
||||
|
||||
ACTION_LABELS = {
|
||||
"build_dms": "DMS 入库 (build)",
|
||||
"build_lane": "车道线合并列表 (build lane)",
|
||||
@@ -29,6 +43,7 @@ ACTION_LABELS = {
|
||||
"promote_dms": "DMS 模型晋级",
|
||||
"pipeline_dms": "DMS 半自动流水线",
|
||||
"register_batch": "登记批次元数据",
|
||||
"delivery_ingest": "数据送标入湖",
|
||||
}
|
||||
|
||||
|
||||
@@ -77,15 +92,36 @@ def submit_approval(
|
||||
|
||||
if auto_execute:
|
||||
return approve_and_execute(out["id"], reviewed_by="system", comment="auto_execute")
|
||||
|
||||
# 飞书通知 + 审计日志
|
||||
label = ACTION_LABELS.get(action, action)
|
||||
send_chat_async(f"📋 新审核提交\n{label}\n提单人: {submitted_by or '—'}\n备注: {note or '—'}")
|
||||
from as_platform.audit.log_utils import log_op
|
||||
log_op(user_id=submitted_by_user_id, user_name=submitted_by or "", category="audit", action="submit_approval",
|
||||
target_type="approval", target_id=out["id"], summary=f"提交审核: {label}",
|
||||
detail={"action": action, "params": params, "note": note})
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def list_approvals(status: str | None = None, limit: int = 100) -> list[dict[str, Any]]:
|
||||
def list_approvals(
|
||||
status: str | None = None,
|
||||
*,
|
||||
offset: int = 0,
|
||||
limit: int = 20,
|
||||
) -> dict[str, Any]:
|
||||
with session_scope() as db:
|
||||
q = db.query(Approval).order_by(Approval.submitted_at.desc())
|
||||
if status:
|
||||
q = q.filter(Approval.status == status)
|
||||
return [a.to_dict() for a in q.limit(limit).all()]
|
||||
total = q.count()
|
||||
rows = q.offset(max(0, offset)).limit(max(1, limit)).all()
|
||||
return {
|
||||
"items": [a.to_dict() for a in rows],
|
||||
"total": total,
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
}
|
||||
|
||||
|
||||
def get_approval(record_id: str) -> dict[str, Any] | None:
|
||||
@@ -137,6 +173,16 @@ def approve_and_execute(
|
||||
job = enqueue_job(rec["action"], rec.get("params") or {}, approval_id=record_id, async_run=True)
|
||||
|
||||
_update(record_id, job_id=job.get("id"), status="running")
|
||||
|
||||
# 飞书通知 + 审计日志
|
||||
label = ACTION_LABELS.get(rec["action"], rec["action"])
|
||||
submitter = rec.get("submitted_by") or rec.get("submitted_by_name") or ""
|
||||
send_chat_async(f"✅ 审核通过\n{label}\n审核人: {reviewed_by or '—'}\n提单人: {submitter}")
|
||||
from as_platform.audit.log_utils import log_op
|
||||
log_op(user_id=reviewed_by_user_id, user_name=reviewed_by or "", category="audit", action="approve",
|
||||
target_type="approval", target_id=record_id, summary=f"审核通过: {label}",
|
||||
detail={"comment": comment})
|
||||
|
||||
return get_approval(record_id) or {}
|
||||
|
||||
|
||||
@@ -146,17 +192,75 @@ def reject_approval(
|
||||
reviewed_by: str | None = None,
|
||||
reviewed_by_user_id: int | None = None,
|
||||
comment: str | None = None,
|
||||
rejection_category: str = "",
|
||||
) -> dict[str, Any]:
|
||||
rec = get_approval(record_id)
|
||||
if not rec:
|
||||
raise ValueError(f"审核单不存在: {record_id}")
|
||||
if rec.get("status") != "pending":
|
||||
raise ValueError(f"当前状态不可驳回: {rec.get('status')}")
|
||||
return _update(
|
||||
out = _update(
|
||||
record_id,
|
||||
status="rejected",
|
||||
reviewed_by_name=reviewed_by,
|
||||
reviewed_by_user_id=reviewed_by_user_id,
|
||||
reviewed_at=_now(),
|
||||
review_comment=comment,
|
||||
rejection_category=rejection_category or "",
|
||||
) or {}
|
||||
# 飞书通知:审核驳回
|
||||
label = ACTION_LABELS.get(rec["action"], rec["action"])
|
||||
cat = REJECTION_CATEGORIES.get(rejection_category, rejection_category) if rejection_category else ""
|
||||
submitter = rec.get("submitted_by") or rec.get("submitted_by_name") or ""
|
||||
reason_text = f"\n原因: {cat}" if cat else ""
|
||||
send_chat_async(f"❌ 审核驳回\n{label}\n审核人: {reviewed_by or '—'}\n提单人: {submitter}{reason_text}\n意见: {comment or '—'}")
|
||||
from as_platform.audit.log_utils import log_op
|
||||
log_op(user_id=reviewed_by_user_id, user_name=reviewed_by or "", category="audit", action="reject",
|
||||
target_type="approval", target_id=record_id, summary=f"审核驳回: {label}",
|
||||
detail={"comment": comment, "rejection_category": rejection_category})
|
||||
|
||||
try:
|
||||
from as_platform.deliveries.service import mark_delivery_rejected_by_approval
|
||||
mark_delivery_rejected_by_approval(record_id)
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
def batch_approve(
|
||||
record_ids: list[str],
|
||||
*,
|
||||
reviewed_by: str | None = None,
|
||||
reviewed_by_user_id: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""批量通过审核。返回 {approved, failed, errors}。"""
|
||||
approved: list[str] = []
|
||||
errors: list[dict[str, str]] = []
|
||||
for rid in record_ids:
|
||||
try:
|
||||
approve_and_execute(rid, reviewed_by=reviewed_by, reviewed_by_user_id=reviewed_by_user_id)
|
||||
approved.append(rid)
|
||||
except Exception as e:
|
||||
errors.append({"id": rid, "error": str(e)})
|
||||
return {"approved": len(approved), "failed": len(errors), "errors": errors}
|
||||
|
||||
|
||||
def batch_reject(
|
||||
record_ids: list[str],
|
||||
*,
|
||||
reviewed_by: str | None = None,
|
||||
reviewed_by_user_id: int | None = None,
|
||||
comment: str | None = None,
|
||||
rejection_category: str = "",
|
||||
) -> dict[str, Any]:
|
||||
"""批量驳回审核。"""
|
||||
rejected: list[str] = []
|
||||
errors: list[dict[str, str]] = []
|
||||
for rid in record_ids:
|
||||
try:
|
||||
reject_approval(rid, reviewed_by=reviewed_by, reviewed_by_user_id=reviewed_by_user_id,
|
||||
comment=comment, rejection_category=rejection_category)
|
||||
rejected.append(rid)
|
||||
except Exception as e:
|
||||
errors.append({"id": rid, "error": str(e)})
|
||||
return {"rejected": len(rejected), "failed": len(errors), "errors": errors}
|
||||
|
||||
Reference in New Issue
Block a user