feat: 语音确认、联调与运维增强

- 语音:序数解析(第一个/第二个等)、解析失败计数与 API detail.retry_remaining;
  百度 ASR 固定 dev_pid 为普通话;SurgeryPipelineError 支持 extra 并入 HTTP detail。
- Demo:demo 路由与假 RTSP、客户端 index 与 README;BackendResolver 与配置调整。
- 可观测:消耗 TSV 日志、语音文件日志、终端 Markdown 辅助;相关测试与依赖更新。
- 注意:.env 仍被 gitignore,本地密钥不会进入本提交。

Made-with: Cursor
This commit is contained in:
Kevin
2026-04-23 14:24:20 +08:00
parent 42720f81cf
commit 0c05463617
39 changed files with 3030 additions and 143 deletions

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
from datetime import datetime, timezone
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.models import VoiceConfirmationAudit
@@ -10,6 +11,29 @@ from app.db.models import VoiceConfirmationAudit
class VoiceAuditRepository:
"""Persist voice confirmation audit rows."""
async def list_by_surgery(
self,
session: AsyncSession,
surgery_id: str,
*,
limit: int = 50,
offset: int = 0,
) -> tuple[list[VoiceConfirmationAudit], int]:
"""按手术号分页列出审计行,按 `created_at` 降序(新在前)。"""
c = select(func.count()).select_from(VoiceConfirmationAudit).where(
VoiceConfirmationAudit.surgery_id == surgery_id
)
total = int((await session.execute(c)).scalar_one())
q = (
select(VoiceConfirmationAudit)
.where(VoiceConfirmationAudit.surgery_id == surgery_id)
.order_by(VoiceConfirmationAudit.created_at.desc())
.offset(offset)
.limit(limit)
)
rows = list((await session.execute(q)).scalars().all())
return rows, total
async def save_audit(
self,
session: AsyncSession,