from __future__ import annotations import asyncio import threading from dataclasses import dataclass, field from datetime import datetime from typing import List, Optional @dataclass class MeasureSnapshot: result: List[dict] video_left: str video_right: str updated_at: Optional[datetime] = None error: Optional[str] = None raw_prediction_path: Optional[str] = None pred: Optional[float] = None # 最终预测的体重值 star: bool = False # DB 是唯一真相源:segment/batch 恒 False;final 行按参与聚合段的 dgcnn_summary 规则判定 #: 与 FishMeasure ``test_dgcnn_weight_estimator.py`` 终端输出一致的体重推算过程文本 calculation_log: Optional[str] = None @dataclass class HealthSnapshot: behavior_result: str health_result: str updated_at: Optional[datetime] = None error: Optional[str] = None raw_class_en: str = "" video_path: str = "" @dataclass class AppState: measure_lock: asyncio.Lock = field(default_factory=asyncio.Lock) #: 与 ``run_full_measure`` 子进程串行化(跨 ``measure_watch`` / ingest 线程) measure_thread_lock: threading.Lock = field(default_factory=threading.Lock) action_lock: asyncio.Lock = field(default_factory=asyncio.Lock) # job status for optional polling(业务结果见 SQLite) measure_status: str = "idle" action_status: str = "idle" #: ZED 分段录制线程与协作停止事件(由 ``zed_recording_control`` 管理) zed_recording_lock: threading.Lock = field(default_factory=threading.Lock) zed_recording_stop_event: Optional[threading.Event] = None zed_recording_thread: Optional[threading.Thread] = None #: 当前会话(与 ``zed_recording_sessions`` 表对应) zed_recording_session_row_id: Optional[int] = None zed_recording_fish_id: Optional[int] = None app_state = AppState()