This commit is contained in:
Your Name
2026-08-12 17:18:56 +08:00
parent 28cd110dae
commit f48a66b611
24 changed files with 3095 additions and 233 deletions
+175 -20
View File
@@ -5,7 +5,7 @@ from __future__ import annotations
import mimetypes
import re
import time
from collections.abc import Mapping
from collections.abc import Mapping, Sequence
from contextlib import suppress
from datetime import date
from io import BytesIO
@@ -619,8 +619,38 @@ class DoctorRepository(Protocol):
def end_call(self, diagnosis_id: int) -> Any:
"""End the active diagnosis call."""
def bind_call_room(self, diagnosis_id: int, room_id: str) -> Any:
"""Bind a TRTC room to the active call."""
def bind_call_room(self, diagnosis_id: int, room_id: str) -> Any:
"""Bind a TRTC room to the active call."""
def start_call_transcription(
self,
diagnosis_id: int,
call_record_id: int | str,
transcription_session_id: str,
*,
language: str = "zh-CN",
) -> Any:
"""Start a transcript stored on one exact call record."""
def upsert_call_transcript_segments(
self,
diagnosis_id: int,
call_record_id: int | str,
transcription_session_id: str,
segments: Sequence[Mapping[str, Any]],
) -> Any:
"""Idempotently persist completed transcript segments."""
def finish_call_transcription(
self,
diagnosis_id: int,
call_record_id: int | str,
transcription_session_id: str,
expected_segment_count: int,
*,
status: str = "completed",
) -> Any:
"""Finalize and materialize the transcript text on a call record."""
def my_self(self) -> Session:
"""Compatibility alias for :meth:`get_session`."""
@@ -2171,32 +2201,157 @@ class RemoteDoctorRepository:
ticket.diagnosis_id = diagnosis_id
return ticket
def start_call(self, diagnosis_id: int, patient_id: int, *, call_type: int = 2) -> Any:
"""Create the server-side call record before ringing participants."""
return self.client.post(
"tcm.diagnosis/startCall",
{
"diagnosis_id": diagnosis_id,
"patient_id": patient_id,
"call_type": call_type,
},
)
def start_call(self, diagnosis_id: int, patient_id: int, *, call_type: int = 2) -> Any:
"""Create the server-side call record before ringing participants."""
payload = self.client.post(
"tcm.diagnosis/startCall",
{
"diagnosis_id": diagnosis_id,
"patient_id": patient_id,
"call_type": call_type,
},
)
if not isinstance(payload, Mapping):
raise ApiProtocolError(
"tcm.diagnosis/startCall returned no valid call_record_id object", data=payload
)
raw_id = next(
(
payload[key]
for key in ("call_record_id", "callRecordId", "id")
if key in payload
),
None,
)
try:
if isinstance(raw_id, bool):
raise ValueError
call_record_id = int(raw_id)
except (TypeError, ValueError) as exc:
raise ApiProtocolError(
"tcm.diagnosis/startCall returned no valid call_record_id",
data=dict(payload),
) from exc
if call_record_id <= 0:
raise ApiProtocolError(
"tcm.diagnosis/startCall returned no valid call_record_id",
data=dict(payload),
)
return {"call_record_id": call_record_id}
def end_call(self, diagnosis_id: int) -> Any:
"""End the active call/recording associated with a diagnosis."""
return self.client.post("tcm.diagnosis/endCall", {"diagnosis_id": diagnosis_id})
def bind_call_room(self, diagnosis_id: int, room_id: str) -> Any:
"""Bind the actual TRTC room to the active call record."""
def bind_call_room(self, diagnosis_id: int, room_id: str) -> Any:
"""Bind the actual TRTC room to the active call record."""
if not room_id.strip():
raise ValueError("room_id is required")
return self.client.post(
"tcm.diagnosis/bindCallRoom",
{"diagnosis_id": diagnosis_id, "room_id": room_id.strip()},
)
return self.client.post(
"tcm.diagnosis/bindCallRoom",
{"diagnosis_id": diagnosis_id, "room_id": room_id.strip()},
)
@staticmethod
def _transcription_identity(
diagnosis_id: int,
call_record_id: int | str,
transcription_session_id: str,
) -> dict[str, Any]:
if diagnosis_id <= 0:
raise ValueError("diagnosis_id must be positive")
if isinstance(call_record_id, bool) or not str(call_record_id).strip():
raise ValueError("call_record_id is required")
clean_session = transcription_session_id.strip()
if not clean_session or len(clean_session) > 128:
raise ValueError("transcription_session_id must contain 1 to 128 characters")
return {
"diagnosis_id": diagnosis_id,
"call_record_id": call_record_id,
"transcription_session_id": clean_session,
}
def start_call_transcription(
self,
diagnosis_id: int,
call_record_id: int | str,
transcription_session_id: str,
*,
language: str = "zh-CN",
) -> Any:
"""Create the server transcript session for one call record."""
body = self._transcription_identity(
diagnosis_id, call_record_id, transcription_session_id
)
body["language"] = language.strip()[:32] or "zh-CN"
return self.client.post("tcm.diagnosis/startCallTranscription", body)
def upsert_call_transcript_segments(
self,
diagnosis_id: int,
call_record_id: int | str,
transcription_session_id: str,
segments: Sequence[Mapping[str, Any]],
) -> Any:
"""Upsert a bounded batch using each segment_id as the idempotency key."""
body = self._transcription_identity(
diagnosis_id, call_record_id, transcription_session_id
)
normalized: list[dict[str, Any]] = []
if len(segments) > 50:
raise ValueError("at most 50 transcript segments may be submitted at once")
for segment in segments:
segment_id = str(segment.get("segment_id") or "").strip()
text = str(segment.get("text") or "").strip()
if not segment_id or len(segment_id) > 160:
raise ValueError("transcript segment_id is invalid")
if not text or len(text) > 4_000:
raise ValueError("transcript text is invalid")
normalized.append(
{
"segment_id": segment_id,
"speaker_user_id": str(segment.get("speaker_user_id") or "")[:160],
"speaker_role": str(segment.get("speaker_role") or "unknown")[:20],
"timestamp": max(int(segment.get("timestamp") or 0), 0),
"text": text,
}
)
if not normalized:
raise ValueError("at least one transcript segment is required")
body["segments"] = normalized
return self.client.post("tcm.diagnosis/upsertCallTranscriptSegments", body)
def finish_call_transcription(
self,
diagnosis_id: int,
call_record_id: int | str,
transcription_session_id: str,
expected_segment_count: int,
*,
status: str = "completed",
) -> Any:
"""Finalize a transcript; repeated requests use the same session identity."""
body = self._transcription_identity(
diagnosis_id, call_record_id, transcription_session_id
)
clean_status = status.strip().lower()
if clean_status not in {"completed", "partial", "failed"}:
raise ValueError("transcription status is invalid")
if expected_segment_count < 0:
raise ValueError("expected_segment_count must not be negative")
body.update(
{
"expected_segment_count": expected_segment_count,
"status": clean_status,
}
)
return self.client.post("tcm.diagnosis/finishCallTranscription", body)
# Compatibility aliases keep UI naming independent from endpoint history.
def my_self(self) -> Session: