Files
HSAP/platform/as_platform/fleet/mock_seed.py
Chengfang Lu e72bc061c5 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)
2026-06-03 11:40:21 +08:00

213 lines
7.1 KiB
Python

"""Demo fleet seed data (Changsha area, curved paths)."""
from __future__ import annotations
import math
from datetime import datetime, timedelta, timezone
from sqlalchemy.orm import Session
from as_platform.db.models import (
FleetCollectionRun,
FleetRunMilestone,
FleetTrackPoint,
FleetVehicle,
)
from as_platform.fleet.geo import haversine_km
def _catmull_rom_point(
p0: tuple[float, float],
p1: tuple[float, float],
p2: tuple[float, float],
p3: tuple[float, float],
t: float,
) -> tuple[float, float]:
t2, t3 = t * t, t * t * t
lat = 0.5 * (
(2 * p1[0])
+ (-p0[0] + p2[0]) * t
+ (2 * p0[0] - 5 * p1[0] + 4 * p2[0] - p3[0]) * t2
+ (-p0[0] + 3 * p1[0] - 3 * p2[0] + p3[0]) * t3
)
lng = 0.5 * (
(2 * p1[1])
+ (-p0[1] + p2[1]) * t
+ (2 * p0[1] - 5 * p1[1] + 4 * p2[1] - p3[1]) * t2
+ (-p0[1] + 3 * p1[1] - 3 * p2[1] + p3[1]) * t3
)
return (lat, lng)
def build_curved_route(anchors: list[tuple[float, float]], steps_per_seg: int = 24) -> list[tuple[float, float]]:
"""Catmull-Rom 样条密点,地图折线呈弯道。"""
if len(anchors) < 2:
return list(anchors)
pts = [anchors[0], *anchors, anchors[-1]]
path: list[tuple[float, float]] = []
for i in range(1, len(pts) - 2):
p0, p1, p2, p3 = pts[i - 1], pts[i], pts[i + 1], pts[i + 2]
for j in range(steps_per_seg):
t = j / steps_per_seg
path.append(_catmull_rom_point(p0, p1, p2, p3, t))
path.append(anchors[-1])
return path
def _wiggle(path: list[tuple[float, float]], amp: float = 0.004) -> list[tuple[float, float]]:
"""叠加轻微蛇形偏移,避免视觉上像直线。"""
out: list[tuple[float, float]] = []
for i, (lat, lng) in enumerate(path):
w = math.sin(i * 0.35) * amp
out.append((lat + w, lng - w * 0.6))
return out
# 长沙:锚点刻意折线(之字形 / 弧形),再样条平滑
_ANCHORS: dict[str, list[tuple[float, float]]] = {
"TBOX-001": [
(28.172, 112.902),
(28.188, 112.918),
(28.205, 112.908),
(28.222, 112.928),
(28.238, 112.948),
(28.252, 112.978),
(28.248, 113.012),
(28.235, 113.042),
(28.218, 113.058),
(28.202, 113.048),
],
"TBOX-002": [
(28.278, 112.948),
(28.270, 112.972),
(28.255, 113.002),
(28.238, 113.028),
(28.218, 113.048),
(28.202, 113.038),
(28.198, 113.008),
(28.208, 112.978),
(28.225, 112.958),
(28.248, 112.952),
],
"TBOX-003": [
(28.212, 112.918),
(28.225, 112.938),
(28.242, 112.928),
(28.258, 112.948),
(28.272, 112.972),
(28.268, 113.002),
(28.252, 113.022),
(28.232, 113.028),
(28.215, 113.012),
(28.208, 112.982),
(28.212, 112.918),
],
}
ROUTES: dict[str, list[tuple[float, float]]] = {
k: _wiggle(build_curved_route(v, steps_per_seg=28), amp=0.003)
for k, v in _ANCHORS.items()
}
VEHICLES = [
("TBOX-001", "湘A·采集01", "岳麓采集车 A", "数据部"),
("TBOX-002", "湘A·采集02", "开福采集车 B", "数据部"),
("TBOX-003", "湘A·采集03", "天心采集车 C", "数据部"),
]
def _add_points(db: Session, run_id: int, coords: list[tuple[float, float]], start: datetime, interval_sec: int = 12) -> float:
mileage = 0.0
prev = None
for i, (lat, lng) in enumerate(coords):
ts = start + timedelta(seconds=i * interval_sec)
speed = 35.0 + (i % 5) * 3.0
pt = FleetTrackPoint(run_id=run_id, ts=ts, lat=lat, lng=lng, speed_kmh=speed, heading=float(i * 10 % 360))
db.add(pt)
if prev:
mileage += haversine_km(prev[0], prev[1], lat, lng)
prev = (lat, lng)
return mileage
def clear_demo_fleet(db: Session) -> None:
db.query(FleetRunMilestone).delete()
db.query(FleetTrackPoint).delete()
db.query(FleetCollectionRun).delete()
db.query(FleetVehicle).delete()
db.flush()
def seed_demo_fleet(db: Session) -> bool:
if db.query(FleetVehicle).count() > 0:
return False
now = datetime.now(timezone.utc)
for device_id, plate, name, team in VEHICLES:
coords = ROUTES[device_id]
v = FleetVehicle(
plate_no=plate,
tbox_device_id=device_id,
name=name,
team=team,
status="active",
online=True,
last_lat=coords[-1][0],
last_lng=coords[-1][1],
last_speed_kmh=42.0,
last_ts=now,
)
v.set_meta({"sim_index": 0, "sim_route": device_id, "sim_dir": 1})
db.add(v)
db.flush()
ended_start = now - timedelta(hours=6)
ended = FleetCollectionRun(
vehicle_id=v.id,
run_no=f"{device_id}-{(ended_start.strftime('%Y%m%d'))}-01",
engineer="陈工",
project="dms",
batch="demo_batch_01",
started_at=ended_start,
ended_at=ended_start + timedelta(hours=2),
status="ended",
source="mock",
note="演示历史趟次(长沙弯道)",
)
db.add(ended)
db.flush()
em = _add_points(db, ended.id, coords, ended_start)
ended.mileage_km = round(em, 3)
db.add(FleetRunMilestone(run_id=ended.id, type="start", name="出发", lat=coords[0][0], lng=coords[0][1], mileage_km=0.0, occurred_at=ended_start))
db.add(FleetRunMilestone(run_id=ended.id, type="end", name="结束", lat=coords[-1][0], lng=coords[-1][1], mileage_km=ended.mileage_km, occurred_at=ended.ended_at))
db.add(FleetRunMilestone(run_id=ended.id, type="data_site", name="采集点-1", lat=coords[len(coords)//2][0], lng=coords[len(coords)//2][1], mileage_km=ended.mileage_km / 2, occurred_at=ended_start + timedelta(hours=1)))
active_start = now - timedelta(minutes=45)
active = FleetCollectionRun(
vehicle_id=v.id,
run_no=f"{device_id}-{now.strftime('%Y%m%d')}-live",
engineer="李工",
project="lane",
batch="demo_batch_live",
started_at=active_start,
status="active",
source="mock",
note="演示进行中趟次(长沙弯道)",
)
db.add(active)
db.flush()
# 进行中趟次也写入完整弯道轨迹(不再只种前半段)
am = _add_points(db, active.id, coords, active_start)
active.mileage_km = round(am, 3)
v.last_lat = coords[-1][0]
v.last_lng = coords[-1][1]
v.last_ts = active_start + timedelta(seconds=(len(coords) - 1) * 12)
v.set_meta({"sim_index": len(coords) - 1, "sim_route": device_id, "sim_dir": -1})
db.add(FleetRunMilestone(run_id=active.id, type="start", name="出发", lat=coords[0][0], lng=coords[0][1], mileage_km=0.0, occurred_at=active_start))
db.flush()
return True
def reseed_demo_fleet(db: Session) -> bool:
"""清空并重新注入长沙演示数据。"""
clear_demo_fleet(db)
return seed_demo_fleet(db)