- 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
91 lines
2.5 KiB
Python
91 lines
2.5 KiB
Python
"""Upload voice confirmation WAV objects to MinIO (S3-compatible)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import io
|
|
from dataclasses import dataclass
|
|
|
|
from loguru import logger
|
|
from minio import Minio
|
|
from minio.error import S3Error
|
|
|
|
from app.config import Settings
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class StoredAudio:
|
|
object_key: str
|
|
sha256_hex: str
|
|
size_bytes: int
|
|
|
|
|
|
class MinioAudioStorageService:
|
|
"""Stores raw doctor voice WAV for audit trail."""
|
|
|
|
def __init__(self, settings: Settings) -> None:
|
|
self._s = settings
|
|
self._client: Minio | None = None
|
|
if settings.minio_configured:
|
|
endpoint = settings.minio_endpoint.strip()
|
|
self._client = Minio(
|
|
endpoint,
|
|
access_key=settings.minio_access_key.strip(),
|
|
secret_key=settings.minio_secret_key.strip(),
|
|
secure=bool(settings.minio_secure),
|
|
region=(settings.minio_region or "").strip() or None,
|
|
)
|
|
|
|
@property
|
|
def configured(self) -> bool:
|
|
return self._client is not None
|
|
|
|
def ensure_bucket(self) -> None:
|
|
if self._client is None:
|
|
return
|
|
name = self._s.minio_bucket.strip()
|
|
try:
|
|
if not self._client.bucket_exists(name):
|
|
self._client.make_bucket(name)
|
|
logger.info("MinIO bucket created: {}", name)
|
|
except S3Error as exc:
|
|
logger.warning("MinIO ensure_bucket failed: {}", exc)
|
|
raise
|
|
|
|
def upload_voice_wav(
|
|
self,
|
|
*,
|
|
surgery_id: str,
|
|
confirmation_id: str,
|
|
data: bytes,
|
|
content_type: str | None,
|
|
) -> StoredAudio:
|
|
if self._client is None:
|
|
raise RuntimeError("MinIO is not configured")
|
|
|
|
digest = hashlib.sha256(data).hexdigest()
|
|
suffix = digest[:12]
|
|
object_key = (
|
|
f"surgeries/{surgery_id}/confirmations/{confirmation_id}/{suffix}.wav"
|
|
)
|
|
bucket = self._s.minio_bucket.strip()
|
|
ct = content_type or "audio/wav"
|
|
stream = io.BytesIO(data)
|
|
try:
|
|
self._client.put_object(
|
|
bucket,
|
|
object_key,
|
|
stream,
|
|
length=len(data),
|
|
content_type=ct,
|
|
)
|
|
except S3Error as exc:
|
|
logger.warning("MinIO put_object failed: {}", exc)
|
|
raise
|
|
|
|
return StoredAudio(
|
|
object_key=object_key,
|
|
sha256_hex=digest,
|
|
size_bytes=len(data),
|
|
)
|