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

@@ -0,0 +1,94 @@
"""标注任务 Redis 互斥锁campaign + task 粒度)。"""
from __future__ import annotations
import json
import time
from typing import Any
from as_platform.redis.bus import get_redis
LOCK_TTL_SEC = 300
_LOCK_PREFIX = "labeling:lock:"
# API 进程内回退(无 Redis 时)
_memory: dict[str, dict[str, Any]] = {}
def _key(campaign_id: str, task_id: str) -> str:
return f"{_LOCK_PREFIX}{campaign_id}:{task_id}"
def _parse_holder(raw: str | None) -> dict[str, Any] | None:
if not raw:
return None
try:
return json.loads(raw)
except json.JSONDecodeError:
return {"name": raw}
def acquire_lock(campaign_id: str, task_id: str, *, user_id: int, user_name: str) -> dict[str, Any]:
payload = json.dumps({"user_id": user_id, "name": user_name}, ensure_ascii=False)
r = get_redis()
if not r:
now = time.time()
mem = _memory.get(_key(campaign_id, task_id))
if mem and mem.get("user_id") != user_id and now < mem.get("expires_at", 0):
return {"ok": False, "holder": mem.get("name"), "user_id": mem.get("user_id")}
_memory[_key(campaign_id, task_id)] = {
"user_id": user_id,
"name": user_name,
"expires_at": now + LOCK_TTL_SEC,
}
return {"ok": True, "holder": user_name, "ttl_sec": LOCK_TTL_SEC, "backend": "memory"}
key = _key(campaign_id, task_id)
if r.set(key, payload, nx=True, ex=LOCK_TTL_SEC):
return {"ok": True, "holder": user_name, "ttl_sec": LOCK_TTL_SEC, "backend": "redis"}
existing = _parse_holder(r.get(key))
if existing and existing.get("user_id") == user_id:
r.expire(key, LOCK_TTL_SEC)
return {"ok": True, "holder": user_name, "ttl_sec": LOCK_TTL_SEC, "renewed": True, "backend": "redis"}
return {
"ok": False,
"holder": (existing or {}).get("name"),
"user_id": (existing or {}).get("user_id"),
"backend": "redis",
}
def release_lock(campaign_id: str, task_id: str, *, user_id: int) -> dict[str, Any]:
r = get_redis()
if not r:
key = _key(campaign_id, task_id)
mem = _memory.get(key)
if mem and mem.get("user_id") == user_id:
_memory.pop(key, None)
return {"ok": True, "released": True, "backend": "memory"}
return {"ok": True, "released": False, "backend": "memory"}
key = _key(campaign_id, task_id)
existing = _parse_holder(r.get(key))
if not existing:
return {"ok": True, "released": False, "backend": "redis"}
if existing.get("user_id") != user_id:
return {"ok": False, "holder": existing.get("name"), "backend": "redis"}
r.delete(key)
return {"ok": True, "released": True, "backend": "redis"}
def renew_lock(campaign_id: str, task_id: str, *, user_id: int) -> dict[str, Any]:
r = get_redis()
if not r:
key = _key(campaign_id, task_id)
mem = _memory.get(key)
if mem and mem.get("user_id") == user_id:
mem["expires_at"] = time.time() + LOCK_TTL_SEC
return {"ok": True, "ttl_sec": LOCK_TTL_SEC, "backend": "memory"}
return {"ok": False, "backend": "memory"}
key = _key(campaign_id, task_id)
existing = _parse_holder(r.get(key))
if not existing or existing.get("user_id") != user_id:
return {"ok": False, "holder": (existing or {}).get("name"), "backend": "redis"}
r.expire(key, LOCK_TTL_SEC)
return {"ok": True, "ttl_sec": LOCK_TTL_SEC, "backend": "redis"}