feat: surgery pipeline API, video inference, voice confirm, and tests
- Add FastAPI routes for surgery start/end, results, pending confirmation (WAV upload), and health checks.
- Implement RTSP/Hikvision capture, consumable classification, session manager, MinIO/Baidu voice resolution, and DB persistence.
- Add documentation (client API, video backends, staging checklist) and sample camera/RTSP config.
- Add pytest suite (API contract, session manager, voice, repositories, pipeline persistence) and httpx dev dependency.
- Replace deprecated HTTP_422_UNPROCESSABLE_ENTITY with HTTP_422_UNPROCESSABLE_CONTENT.
- Fix SurgeryPipeline DB reads to use an explicit transaction with autobegin disabled.
Made-with: Cursor
2026-04-21 18:33:54 +08:00
|
|
|
import json
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from urllib.parse import quote_plus
|
|
|
|
|
from typing import Any, Literal
|
|
|
|
|
|
2026-04-24 15:33:22 +08:00
|
|
|
from pydantic import Field
|
2026-04-20 17:58:03 +08:00
|
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
2026-04-24 15:33:22 +08:00
|
|
|
from app.baked import algorithm as baked_algorithm
|
2026-04-23 20:42:21 +08:00
|
|
|
|
|
|
|
|
|
2026-04-24 15:33:22 +08:00
|
|
|
class _SettingsGroup:
|
|
|
|
|
"""按主题分组的 Settings 视图;属性访问代理回主 Settings 实例。"""
|
2026-04-23 20:42:21 +08:00
|
|
|
|
|
|
|
|
_FIELDS: tuple[str, ...] = ()
|
|
|
|
|
|
|
|
|
|
def __init__(self, root: "Settings") -> None:
|
|
|
|
|
object.__setattr__(self, "_root", root)
|
|
|
|
|
|
|
|
|
|
def __getattr__(self, name: str) -> Any:
|
|
|
|
|
if name not in self._FIELDS:
|
|
|
|
|
raise AttributeError(
|
|
|
|
|
f"{type(self).__name__} has no field '{name}'; "
|
|
|
|
|
f"available: {self._FIELDS}"
|
|
|
|
|
)
|
|
|
|
|
return getattr(self._root, name)
|
|
|
|
|
|
|
|
|
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
|
|
|
if name in self._FIELDS:
|
|
|
|
|
setattr(self._root, name, value)
|
|
|
|
|
else:
|
|
|
|
|
object.__setattr__(self, name, value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _VideoGroup(_SettingsGroup):
|
2026-04-24 15:33:22 +08:00
|
|
|
"""仅含 RTSP 解析与按路后端;抽帧/推理/耗材等见 app.baked.pipeline / algorithm。"""
|
|
|
|
|
|
2026-04-23 20:42:21 +08:00
|
|
|
_FIELDS = (
|
|
|
|
|
"video_default_backend",
|
|
|
|
|
"video_camera_backend_overrides_json",
|
|
|
|
|
"video_rtsp_url_template",
|
|
|
|
|
"video_rtsp_urls_json",
|
|
|
|
|
"video_rtsp_urls_json_file",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _VoiceGroup(_SettingsGroup):
|
2026-04-24 15:33:22 +08:00
|
|
|
_FIELDS = ()
|
2026-04-23 20:42:21 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class _HikvisionGroup(_SettingsGroup):
|
|
|
|
|
_FIELDS = (
|
|
|
|
|
"hikvision_lib_dir",
|
|
|
|
|
"hikvision_sdk_enabled",
|
|
|
|
|
"hikvision_device_ip",
|
|
|
|
|
"hikvision_device_port",
|
|
|
|
|
"hikvision_user",
|
|
|
|
|
"hikvision_password",
|
|
|
|
|
"hikvision_channel",
|
|
|
|
|
"hikvision_preview_rtsp_template",
|
|
|
|
|
"hikvision_camera_rtsp_urls_json",
|
|
|
|
|
"hikvision_sdk_fallback_to_rtsp",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _MinioGroup(_SettingsGroup):
|
|
|
|
|
_FIELDS = (
|
|
|
|
|
"minio_endpoint",
|
|
|
|
|
"minio_access_key",
|
|
|
|
|
"minio_secret_key",
|
|
|
|
|
"minio_bucket",
|
|
|
|
|
"minio_secure",
|
|
|
|
|
"minio_region",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _BaiduGroup(_SettingsGroup):
|
|
|
|
|
_FIELDS = (
|
|
|
|
|
"baidu_speech_app_id",
|
|
|
|
|
"baidu_speech_api_key",
|
|
|
|
|
"baidu_speech_secret_key",
|
|
|
|
|
"baidu_speech_connection_timeout_ms",
|
|
|
|
|
"baidu_speech_socket_timeout_ms",
|
|
|
|
|
"baidu_speech_asr_dev_pid",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _DemoGroup(_SettingsGroup):
|
|
|
|
|
_FIELDS = (
|
|
|
|
|
"demo_cors_enabled",
|
|
|
|
|
"demo_cors_origins",
|
|
|
|
|
"demo_orchestrator_enabled",
|
|
|
|
|
"demo_orchestrator_rtsp_port",
|
|
|
|
|
"demo_orchestrator_rtsp_json_host",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _DatabaseGroup(_SettingsGroup):
|
|
|
|
|
_FIELDS = (
|
|
|
|
|
"database_url",
|
|
|
|
|
"postgres_user",
|
|
|
|
|
"postgres_password",
|
|
|
|
|
"postgres_db",
|
|
|
|
|
"postgres_host",
|
|
|
|
|
"postgres_port",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _ServerGroup(_SettingsGroup):
|
|
|
|
|
_FIELDS = (
|
|
|
|
|
"server_host",
|
|
|
|
|
"server_port",
|
|
|
|
|
)
|
|
|
|
|
|
feat: surgery pipeline API, video inference, voice confirm, and tests
- Add FastAPI routes for surgery start/end, results, pending confirmation (WAV upload), and health checks.
- Implement RTSP/Hikvision capture, consumable classification, session manager, MinIO/Baidu voice resolution, and DB persistence.
- Add documentation (client API, video backends, staging checklist) and sample camera/RTSP config.
- Add pytest suite (API contract, session manager, voice, repositories, pipeline persistence) and httpx dev dependency.
- Replace deprecated HTTP_422_UNPROCESSABLE_ENTITY with HTTP_422_UNPROCESSABLE_CONTENT.
- Fix SurgeryPipeline DB reads to use an explicit transaction with autobegin disabled.
Made-with: Cursor
2026-04-21 18:33:54 +08:00
|
|
|
_PACKAGE_DIR = Path(__file__).resolve().parent
|
2026-04-23 14:24:20 +08:00
|
|
|
_REPO_ROOT = _PACKAGE_DIR.parent
|
|
|
|
|
_DEFAULT_ENV_FILE = _REPO_ROOT / ".env"
|
feat: surgery pipeline API, video inference, voice confirm, and tests
- Add FastAPI routes for surgery start/end, results, pending confirmation (WAV upload), and health checks.
- Implement RTSP/Hikvision capture, consumable classification, session manager, MinIO/Baidu voice resolution, and DB persistence.
- Add documentation (client API, video backends, staging checklist) and sample camera/RTSP config.
- Add pytest suite (API contract, session manager, voice, repositories, pipeline persistence) and httpx dev dependency.
- Replace deprecated HTTP_422_UNPROCESSABLE_ENTITY with HTTP_422_UNPROCESSABLE_CONTENT.
- Fix SurgeryPipeline DB reads to use an explicit transaction with autobegin disabled.
Made-with: Cursor
2026-04-21 18:33:54 +08:00
|
|
|
|
|
|
|
|
|
2026-04-20 17:58:03 +08:00
|
|
|
class Settings(BaseSettings):
|
2026-04-24 15:33:22 +08:00
|
|
|
"""Application configuration loaded from environment / .env.
|
|
|
|
|
算法与管线默认可调项见 ``app.baked.algorithm`` / ``app.baked.pipeline``。
|
|
|
|
|
"""
|
2026-04-20 17:58:03 +08:00
|
|
|
|
feat: surgery pipeline API, video inference, voice confirm, and tests
- Add FastAPI routes for surgery start/end, results, pending confirmation (WAV upload), and health checks.
- Implement RTSP/Hikvision capture, consumable classification, session manager, MinIO/Baidu voice resolution, and DB persistence.
- Add documentation (client API, video backends, staging checklist) and sample camera/RTSP config.
- Add pytest suite (API contract, session manager, voice, repositories, pipeline persistence) and httpx dev dependency.
- Replace deprecated HTTP_422_UNPROCESSABLE_ENTITY with HTTP_422_UNPROCESSABLE_CONTENT.
- Fix SurgeryPipeline DB reads to use an explicit transaction with autobegin disabled.
Made-with: Cursor
2026-04-21 18:33:54 +08:00
|
|
|
database_url: str | None = None
|
|
|
|
|
postgres_user: str = "postgres"
|
|
|
|
|
postgres_password: str = "postgres"
|
|
|
|
|
postgres_db: str = "operation_room"
|
|
|
|
|
postgres_host: str = "localhost"
|
|
|
|
|
postgres_port: int = 35432
|
2026-04-23 20:42:21 +08:00
|
|
|
|
|
|
|
|
server_host: str = "0.0.0.0"
|
|
|
|
|
server_port: int = Field(default=38080, ge=1, le=65535)
|
2026-04-24 15:33:22 +08:00
|
|
|
|
feat: surgery pipeline API, video inference, voice confirm, and tests
- Add FastAPI routes for surgery start/end, results, pending confirmation (WAV upload), and health checks.
- Implement RTSP/Hikvision capture, consumable classification, session manager, MinIO/Baidu voice resolution, and DB persistence.
- Add documentation (client API, video backends, staging checklist) and sample camera/RTSP config.
- Add pytest suite (API contract, session manager, voice, repositories, pipeline persistence) and httpx dev dependency.
- Replace deprecated HTTP_422_UNPROCESSABLE_ENTITY with HTTP_422_UNPROCESSABLE_CONTENT.
- Fix SurgeryPipeline DB reads to use an explicit transaction with autobegin disabled.
Made-with: Cursor
2026-04-21 18:33:54 +08:00
|
|
|
video_default_backend: Literal["rtsp", "hikvision_sdk", "auto"] = "rtsp"
|
|
|
|
|
video_camera_backend_overrides_json: str = ""
|
|
|
|
|
video_rtsp_url_template: str = ""
|
|
|
|
|
video_rtsp_urls_json: str = ""
|
|
|
|
|
video_rtsp_urls_json_file: str = ""
|
2026-04-24 15:33:22 +08:00
|
|
|
|
feat: surgery pipeline API, video inference, voice confirm, and tests
- Add FastAPI routes for surgery start/end, results, pending confirmation (WAV upload), and health checks.
- Implement RTSP/Hikvision capture, consumable classification, session manager, MinIO/Baidu voice resolution, and DB persistence.
- Add documentation (client API, video backends, staging checklist) and sample camera/RTSP config.
- Add pytest suite (API contract, session manager, voice, repositories, pipeline persistence) and httpx dev dependency.
- Replace deprecated HTTP_422_UNPROCESSABLE_ENTITY with HTTP_422_UNPROCESSABLE_CONTENT.
- Fix SurgeryPipeline DB reads to use an explicit transaction with autobegin disabled.
Made-with: Cursor
2026-04-21 18:33:54 +08:00
|
|
|
hikvision_lib_dir: str = "/opt/hikvision/lib"
|
|
|
|
|
hikvision_sdk_enabled: bool = False
|
|
|
|
|
hikvision_device_ip: str = ""
|
|
|
|
|
hikvision_device_port: int = Field(default=8000, ge=1, le=65535)
|
|
|
|
|
hikvision_user: str = ""
|
|
|
|
|
hikvision_password: str = ""
|
|
|
|
|
hikvision_channel: int = Field(default=1, ge=1, le=512)
|
|
|
|
|
hikvision_preview_rtsp_template: str = ""
|
|
|
|
|
hikvision_camera_rtsp_urls_json: str = ""
|
|
|
|
|
hikvision_sdk_fallback_to_rtsp: bool = True
|
2026-04-24 15:33:22 +08:00
|
|
|
|
|
|
|
|
baidu_speech_app_id: str = Field(default="", validation_alias="BAIDU_APP_ID")
|
|
|
|
|
baidu_speech_api_key: str = Field(default="", validation_alias="BAIDU_API_KEY")
|
|
|
|
|
baidu_speech_secret_key: str = Field(default="", validation_alias="BAIDU_SECRET_KEY")
|
|
|
|
|
baidu_speech_connection_timeout_ms: int | None = Field(
|
|
|
|
|
default=None, validation_alias="BAIDU_CONNECTION_TIMEOUT_MS"
|
|
|
|
|
)
|
|
|
|
|
baidu_speech_socket_timeout_ms: int | None = Field(
|
|
|
|
|
default=None, validation_alias="BAIDU_SOCKET_TIMEOUT_MS"
|
|
|
|
|
)
|
|
|
|
|
baidu_speech_asr_dev_pid: int = Field(
|
|
|
|
|
default=1537, ge=1000, le=99999, validation_alias="BAIDU_ASR_DEV_PID"
|
|
|
|
|
)
|
|
|
|
|
|
feat: surgery pipeline API, video inference, voice confirm, and tests
- Add FastAPI routes for surgery start/end, results, pending confirmation (WAV upload), and health checks.
- Implement RTSP/Hikvision capture, consumable classification, session manager, MinIO/Baidu voice resolution, and DB persistence.
- Add documentation (client API, video backends, staging checklist) and sample camera/RTSP config.
- Add pytest suite (API contract, session manager, voice, repositories, pipeline persistence) and httpx dev dependency.
- Replace deprecated HTTP_422_UNPROCESSABLE_ENTITY with HTTP_422_UNPROCESSABLE_CONTENT.
- Fix SurgeryPipeline DB reads to use an explicit transaction with autobegin disabled.
Made-with: Cursor
2026-04-21 18:33:54 +08:00
|
|
|
minio_endpoint: str = ""
|
|
|
|
|
minio_access_key: str = ""
|
|
|
|
|
minio_secret_key: str = ""
|
|
|
|
|
minio_bucket: str = "operation-room-voice"
|
|
|
|
|
minio_secure: bool = False
|
|
|
|
|
minio_region: str = ""
|
|
|
|
|
|
2026-04-22 17:00:56 +08:00
|
|
|
demo_cors_enabled: bool = True
|
|
|
|
|
demo_cors_origins: str = "*"
|
2026-04-23 14:24:20 +08:00
|
|
|
demo_orchestrator_enabled: bool = False
|
|
|
|
|
demo_orchestrator_rtsp_port: int = Field(default=18554, ge=1, le=65535)
|
|
|
|
|
demo_orchestrator_rtsp_json_host: str = "host.docker.internal"
|
|
|
|
|
|
2026-04-22 17:00:56 +08:00
|
|
|
def parsed_demo_cors_origins(self) -> list[str]:
|
|
|
|
|
raw = (self.demo_cors_origins or "").strip()
|
|
|
|
|
if not raw:
|
|
|
|
|
return []
|
|
|
|
|
if raw == "*":
|
|
|
|
|
return ["*"]
|
|
|
|
|
return [item.strip() for item in raw.split(",") if item.strip()]
|
|
|
|
|
|
2026-04-20 17:58:03 +08:00
|
|
|
model_config = SettingsConfigDict(
|
2026-04-23 14:24:20 +08:00
|
|
|
env_file=(str(_DEFAULT_ENV_FILE),),
|
2026-04-20 17:58:03 +08:00
|
|
|
env_file_encoding="utf-8",
|
|
|
|
|
extra="ignore",
|
2026-04-24 15:33:22 +08:00
|
|
|
env_ignore_empty=True,
|
|
|
|
|
populate_by_name=True,
|
2026-04-20 17:58:03 +08:00
|
|
|
)
|
|
|
|
|
|
feat: surgery pipeline API, video inference, voice confirm, and tests
- Add FastAPI routes for surgery start/end, results, pending confirmation (WAV upload), and health checks.
- Implement RTSP/Hikvision capture, consumable classification, session manager, MinIO/Baidu voice resolution, and DB persistence.
- Add documentation (client API, video backends, staging checklist) and sample camera/RTSP config.
- Add pytest suite (API contract, session manager, voice, repositories, pipeline persistence) and httpx dev dependency.
- Replace deprecated HTTP_422_UNPROCESSABLE_ENTITY with HTTP_422_UNPROCESSABLE_CONTENT.
- Fix SurgeryPipeline DB reads to use an explicit transaction with autobegin disabled.
Made-with: Cursor
2026-04-21 18:33:54 +08:00
|
|
|
@property
|
|
|
|
|
def sqlalchemy_database_url(self) -> str:
|
|
|
|
|
component_values = (
|
|
|
|
|
self.postgres_user,
|
|
|
|
|
self.postgres_password,
|
|
|
|
|
self.postgres_db,
|
|
|
|
|
self.postgres_host,
|
|
|
|
|
self.postgres_port,
|
|
|
|
|
)
|
|
|
|
|
default_component_values = (
|
|
|
|
|
"postgres",
|
|
|
|
|
"postgres",
|
|
|
|
|
"operation_room",
|
|
|
|
|
"localhost",
|
|
|
|
|
35432,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if component_values != default_component_values or not self.database_url:
|
|
|
|
|
user = quote_plus(self.postgres_user)
|
|
|
|
|
password = quote_plus(self.postgres_password)
|
|
|
|
|
database = quote_plus(self.postgres_db)
|
|
|
|
|
return (
|
|
|
|
|
"postgresql+asyncpg://"
|
|
|
|
|
f"{user}:{password}@{self.postgres_host}:{self.postgres_port}/{database}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return self.database_url
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def baidu_speech_configured(self) -> bool:
|
|
|
|
|
return bool(
|
|
|
|
|
self.baidu_speech_app_id.strip()
|
|
|
|
|
and self.baidu_speech_api_key.strip()
|
|
|
|
|
and self.baidu_speech_secret_key.strip()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def minio_configured(self) -> bool:
|
|
|
|
|
return bool(
|
|
|
|
|
self.minio_endpoint.strip()
|
|
|
|
|
and self.minio_access_key.strip()
|
|
|
|
|
and self.minio_secret_key.strip()
|
|
|
|
|
and self.minio_bucket.strip()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _parse_rtsp_urls_object(raw: str) -> dict[str, str]:
|
|
|
|
|
raw = (raw or "").strip()
|
|
|
|
|
if not raw:
|
|
|
|
|
return {}
|
|
|
|
|
try:
|
|
|
|
|
data: Any = json.loads(raw)
|
|
|
|
|
except json.JSONDecodeError as exc:
|
|
|
|
|
raise ValueError(f"Invalid VIDEO_RTSP_URLS_JSON: {exc}") from exc
|
|
|
|
|
if not isinstance(data, dict):
|
|
|
|
|
raise ValueError("VIDEO_RTSP_URLS_JSON must be a JSON object")
|
|
|
|
|
return {str(k): str(v) for k, v in data.items()}
|
|
|
|
|
|
|
|
|
|
def video_rtsp_url_map(self) -> dict[str, str]:
|
|
|
|
|
merged: dict[str, str] = {}
|
|
|
|
|
path_raw = (self.video_rtsp_urls_json_file or "").strip()
|
|
|
|
|
if path_raw:
|
|
|
|
|
path = Path(path_raw).expanduser()
|
|
|
|
|
if not path.is_file():
|
|
|
|
|
raise ValueError(
|
|
|
|
|
f"VIDEO_RTSP_URLS_JSON_FILE is set but file not found: {path}"
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
file_obj: Any = json.loads(path.read_text(encoding="utf-8"))
|
|
|
|
|
except json.JSONDecodeError as exc:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
f"Invalid JSON in VIDEO_RTSP_URLS_JSON_FILE {path}: {exc}"
|
|
|
|
|
) from exc
|
|
|
|
|
if not isinstance(file_obj, dict):
|
|
|
|
|
raise ValueError(
|
|
|
|
|
f"VIDEO_RTSP_URLS_JSON_FILE must contain a JSON object: {path}"
|
|
|
|
|
)
|
|
|
|
|
merged = {str(k): str(v) for k, v in file_obj.items()}
|
|
|
|
|
merged.update(self._parse_rtsp_urls_object(self.video_rtsp_urls_json))
|
|
|
|
|
return merged
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def camera_rtsp_urls_sample_path(self) -> str:
|
2026-04-24 15:33:22 +08:00
|
|
|
return baked_algorithm.default_camera_rtsp_urls_sample_path()
|
feat: surgery pipeline API, video inference, voice confirm, and tests
- Add FastAPI routes for surgery start/end, results, pending confirmation (WAV upload), and health checks.
- Implement RTSP/Hikvision capture, consumable classification, session manager, MinIO/Baidu voice resolution, and DB persistence.
- Add documentation (client API, video backends, staging checklist) and sample camera/RTSP config.
- Add pytest suite (API contract, session manager, voice, repositories, pipeline persistence) and httpx dev dependency.
- Replace deprecated HTTP_422_UNPROCESSABLE_ENTITY with HTTP_422_UNPROCESSABLE_CONTENT.
- Fix SurgeryPipeline DB reads to use an explicit transaction with autobegin disabled.
Made-with: Cursor
2026-04-21 18:33:54 +08:00
|
|
|
|
2026-04-23 20:42:21 +08:00
|
|
|
@property
|
|
|
|
|
def video(self) -> _VideoGroup:
|
|
|
|
|
return _VideoGroup(self)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def voice(self) -> _VoiceGroup:
|
|
|
|
|
return _VoiceGroup(self)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def hikvision(self) -> _HikvisionGroup:
|
|
|
|
|
return _HikvisionGroup(self)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def minio(self) -> _MinioGroup:
|
|
|
|
|
return _MinioGroup(self)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def baidu(self) -> _BaiduGroup:
|
|
|
|
|
return _BaiduGroup(self)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def demo(self) -> _DemoGroup:
|
|
|
|
|
return _DemoGroup(self)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def database(self) -> _DatabaseGroup:
|
|
|
|
|
return _DatabaseGroup(self)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def server(self) -> _ServerGroup:
|
|
|
|
|
return _ServerGroup(self)
|
|
|
|
|
|
2026-04-20 17:58:03 +08:00
|
|
|
|
|
|
|
|
settings = Settings()
|