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
This commit is contained in:
Kevin
2026-04-21 18:33:54 +08:00
parent d1a3d029ec
commit 04866559db
56 changed files with 7196 additions and 43 deletions

View File

@@ -1,4 +1,10 @@
# Local development: PostgreSQL only. Run the API on the host with ./start.sh or `uv run`.
# Local development stack. You can either:
# 1. run `docker compose -f docker-compose.dev.yml up --build` for API + DB in containers, or
# 2. run `./start.sh` to start only DB and keep the API on the host with hot reload.
#
# Default host ports avoid common 5432/8000 clashes when many services run in parallel:
# Postgres published: POSTGRES_PORT -> 35432 (container still listens on 5432)
# API published: API_PORT -> 38080 (uvicorn inside container still listens on 8000)
services:
db:
image: postgres:16-alpine
@@ -7,7 +13,7 @@ services:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
POSTGRES_DB: ${POSTGRES_DB:-operation_room}
ports:
- "${POSTGRES_PORT:-5432}:5432"
- "${POSTGRES_PORT:-35432}:5432"
volumes:
- pgdata_dev:/var/lib/postgresql/data
healthcheck:
@@ -17,5 +23,54 @@ services:
retries: 20
start_period: 5s
api:
build:
context: .
dockerfile: Dockerfile
environment:
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
POSTGRES_DB: ${POSTGRES_DB:-operation_room}
POSTGRES_HOST: db
POSTGRES_PORT: 5432
CONSUMABLE_CLASSIFIER_IMGSZ: ${CONSUMABLE_CLASSIFIER_IMGSZ:-224}
CONSUMABLE_CLASSIFIER_DEVICE: ${CONSUMABLE_CLASSIFIER_DEVICE:-}
CONSUMABLE_CLASSIFIER_TOPK: ${CONSUMABLE_CLASSIFIER_TOPK:-5}
TEAR_ACTION_IMGSZ: ${TEAR_ACTION_IMGSZ:-224}
TEAR_ACTION_DEVICE: ${TEAR_ACTION_DEVICE:-}
TEAR_ACTION_TOPK: ${TEAR_ACTION_TOPK:-5}
VIDEO_DEFAULT_BACKEND: ${VIDEO_DEFAULT_BACKEND:-rtsp}
VIDEO_RTSP_URL_TEMPLATE: ${VIDEO_RTSP_URL_TEMPLATE:-}
VIDEO_RTSP_URLS_JSON_FILE: ${VIDEO_RTSP_URLS_JSON_FILE:-}
VIDEO_RTSP_URLS_JSON: ${VIDEO_RTSP_URLS_JSON:-}
VIDEO_CAMERA_BACKEND_OVERRIDES_JSON: ${VIDEO_CAMERA_BACKEND_OVERRIDES_JSON:-}
HIKVISION_SDK_ENABLED: ${HIKVISION_SDK_ENABLED:-false}
HIKVISION_LIB_DIR: ${HIKVISION_LIB_DIR:-/opt/hikvision/lib}
HIKVISION_DEVICE_IP: ${HIKVISION_DEVICE_IP:-}
HIKVISION_USER: ${HIKVISION_USER:-}
HIKVISION_PASSWORD: ${HIKVISION_PASSWORD:-}
HIKVISION_PREVIEW_RTSP_TEMPLATE: ${HIKVISION_PREVIEW_RTSP_TEMPLATE:-}
ports:
- "${API_PORT:-38080}:8000"
command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
volumes:
- ./app:/app/app
- ./main.py:/app/main.py
depends_on:
db:
condition: service_healthy
healthcheck:
test:
[
"CMD",
"python",
"-c",
"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2)",
]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
volumes:
pgdata_dev: