feat: 合并 Docker Compose、标注表格优化与部署文档
将 platform + CVAT 合并为单文件 docker-compose.yml,完善 .env 与 init/dev_up 脚本; 新增 docs/DEPLOY.md 与更新 README 以支持新机器部署;含数据湖示例、车队地图、 紧凑表格 UI、ADAS det_7cls 路径与批次台账等近期改动。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -45,6 +45,45 @@ class DeliveryPatchBody(BaseModel):
|
||||
owner_name: str | None = None
|
||||
|
||||
|
||||
@router.get("/scan")
|
||||
def api_scan_deliveries(
|
||||
_user: Annotated[User, Depends(require_any_permission("read:deliveries", "read:pending", "*"))],
|
||||
projects: str | None = Query(None, description="逗号分隔: dms,adas,lane"),
|
||||
) -> dict[str, Any]:
|
||||
from as_platform.deliveries.scan import scan_delivery_sources
|
||||
|
||||
projs = [p.strip() for p in projects.split(",") if p.strip()] if projects else None
|
||||
return scan_delivery_sources(projects=projs)
|
||||
|
||||
|
||||
class ScanRegisterBody(BaseModel):
|
||||
items: list[dict[str, Any]] = Field(default_factory=list)
|
||||
sync_workbench: bool = True
|
||||
|
||||
|
||||
@router.post("/scan/register")
|
||||
def api_register_scanned_deliveries(
|
||||
body: ScanRegisterBody,
|
||||
user: Annotated[User, Depends(require_any_permission("write:delivery_submit", "*"))],
|
||||
) -> dict[str, Any]:
|
||||
from as_platform.deliveries.scan import register_scanned_to_ledger
|
||||
|
||||
return register_scanned_to_ledger(body.items, user, sync_workbench=body.sync_workbench)
|
||||
|
||||
|
||||
@router.post("/{delivery_id}/sync-workbench")
|
||||
def api_sync_delivery_workbench(
|
||||
delivery_id: str,
|
||||
_user: Annotated[User, Depends(require_any_permission("write:delivery_submit", "*"))],
|
||||
) -> dict[str, Any]:
|
||||
from as_platform.deliveries.scan import bridge_delivery_to_workbench
|
||||
|
||||
try:
|
||||
return bridge_delivery_to_workbench(delivery_id)
|
||||
except ValueError as e:
|
||||
raise HTTPException(400, str(e)) from e
|
||||
|
||||
|
||||
@router.get("")
|
||||
def api_list_deliveries(
|
||||
_user: Annotated[User, Depends(require_any_permission("read:deliveries", "read:pending", "*"))],
|
||||
|
||||
@@ -128,10 +128,40 @@ def api_assign_campaign(
|
||||
def api_labeling_batches(
|
||||
_user: Annotated[User, Depends(require_permission("read:pending"))],
|
||||
stage: str | None = Query(None),
|
||||
stages: str | None = Query(None, description="逗号分隔多阶段,一次扫描返回"),
|
||||
offset: int = Query(0, ge=0),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
refresh: bool = Query(False, description="true 时先重建索引再返回"),
|
||||
q: str | None = Query(None, description="搜索批次名/任务/项目"),
|
||||
) -> dict[str, Any]:
|
||||
return list_labeling_batches(stage=stage, offset=offset, limit=limit)
|
||||
stage_list = [s.strip() for s in stages.split(",")] if stages else None
|
||||
return list_labeling_batches(
|
||||
stage=stage, stages=stage_list, offset=offset, limit=limit, refresh=refresh, q=q,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/api/v1/labeling/batches/rebuild-index")
|
||||
def api_rebuild_batch_index(
|
||||
_user: Annotated[User, Depends(require_permission("write:labeling_assign"))],
|
||||
) -> dict[str, Any]:
|
||||
from as_platform.labeling.batch_index import rebuild_batch_index
|
||||
|
||||
return rebuild_batch_index()
|
||||
|
||||
|
||||
@router.post("/api/v1/labeling/batches/{campaign_id}/archive")
|
||||
def api_archive_batch(
|
||||
campaign_id: str,
|
||||
_user: Annotated[User, Depends(require_permission("write:labeling_assign"))],
|
||||
) -> dict[str, Any]:
|
||||
from as_platform.labeling.batch_index import archive_batch
|
||||
|
||||
try:
|
||||
return archive_batch(campaign_id)
|
||||
except FileNotFoundError:
|
||||
raise HTTPException(404, "batch not found") from None
|
||||
except ValueError as e:
|
||||
raise HTTPException(400, str(e)) from e
|
||||
|
||||
|
||||
@router.post("/api/v1/labeling/campaigns/open")
|
||||
@@ -488,23 +518,7 @@ def api_review_image(
|
||||
from as_platform.audit.review import get_review_image
|
||||
import tempfile
|
||||
try:
|
||||
# Get class names from registry
|
||||
import yaml
|
||||
from as_platform.data.core import load_wf, proj_root
|
||||
wf = load_wf()
|
||||
root = proj_root(wf, "dms")
|
||||
reg = yaml.safe_load((root / wf["projects"]["dms"]["registry"]).read_text())
|
||||
# Build class_names dict from campaign scope
|
||||
class_names: dict[int, str] = {}
|
||||
# Try to get from the specific task
|
||||
tasks = reg.get("tasks", {})
|
||||
for task_cfg in tasks.values():
|
||||
names = task_cfg.get("names")
|
||||
if isinstance(names, list):
|
||||
for i, n in enumerate(names):
|
||||
class_names[i] = n
|
||||
|
||||
data = get_review_image(campaign_id, path, class_names)
|
||||
data = get_review_image(campaign_id, path)
|
||||
tmp = tempfile.NamedTemporaryFile(suffix=".jpg", delete=False)
|
||||
tmp.write(data)
|
||||
tmp.close()
|
||||
@@ -528,6 +542,16 @@ def api_review_submit(
|
||||
def api_review_progress(
|
||||
campaign_id: str,
|
||||
_user: Annotated[User, Depends(require_permission("read:pending"))],
|
||||
) -> dict[str, int]:
|
||||
) -> dict[str, Any]:
|
||||
from as_platform.audit.review import review_progress
|
||||
return review_progress(campaign_id)
|
||||
|
||||
|
||||
@router.get("/api/v1/labeling/review-progress")
|
||||
def api_review_progress_batch(
|
||||
_user: Annotated[User, Depends(require_permission("read:pending"))],
|
||||
campaign_ids: str = Query(..., description="逗号分隔 campaign id,最多 50 个"),
|
||||
) -> dict[str, Any]:
|
||||
from as_platform.audit.review import review_progress_batch
|
||||
ids = [x.strip() for x in campaign_ids.split(",") if x.strip()]
|
||||
return review_progress_batch(ids)
|
||||
|
||||
@@ -693,7 +693,8 @@ def api_scan_inbox(
|
||||
project: str = Query("dms"),
|
||||
) -> dict[str, Any]:
|
||||
"""扫描 inbox 目录,返回未登记的新批次。"""
|
||||
from as_platform.data.core import get_pending_report, load_wf, proj_root
|
||||
from as_platform.data.core import load_wf, proj_root
|
||||
from as_platform.labeling.batch_index import index_is_empty, rebuild_batch_index
|
||||
|
||||
wf = load_wf()
|
||||
root = proj_root(wf, project)
|
||||
@@ -701,8 +702,20 @@ def api_scan_inbox(
|
||||
if not inbox.is_dir():
|
||||
return {"project": project, "items": [], "inbox_path": str(inbox)}
|
||||
|
||||
report = get_pending_report()
|
||||
registered = {b.get("batch", "") for b in report.get("batches", [])}
|
||||
if index_is_empty():
|
||||
rebuild_batch_index(wf)
|
||||
|
||||
from as_platform.db.engine import session_scope
|
||||
from as_platform.db.models import BatchIndex
|
||||
|
||||
with session_scope() as db:
|
||||
registered = {
|
||||
(r.task or "", r.batch)
|
||||
for r in db.query(BatchIndex).filter(
|
||||
BatchIndex.project == project,
|
||||
BatchIndex.archived.is_(False),
|
||||
).all()
|
||||
}
|
||||
|
||||
items: list[dict[str, Any]] = []
|
||||
for task_dir in sorted(inbox.iterdir()):
|
||||
@@ -713,7 +726,7 @@ def api_scan_inbox(
|
||||
continue
|
||||
batch_name = batch_dir.name
|
||||
task_name = task_dir.name
|
||||
if batch_name in registered:
|
||||
if (task_name, batch_name) in registered:
|
||||
continue # 已登记
|
||||
|
||||
# Count images (含 images/ 子目录)
|
||||
|
||||
Reference in New Issue
Block a user