完善训练链路接口与PDF下载
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.exceptions import AppError
|
||||
from app.models.source_case import CaseBase
|
||||
from app.repositories.case_repository import CaseRepository
|
||||
from app.schemas.training_config import (
|
||||
ConfigOption,
|
||||
@@ -18,9 +21,9 @@ class TrainingConfigService:
|
||||
self.case_repo = CaseRepository(db)
|
||||
|
||||
def get_recommended(self, case_id: int) -> TrainingConfigRecommendedResponse:
|
||||
"""推荐配置:根据病例返回训练页默认病人初始化配置。"""
|
||||
self._ensure_case(case_id)
|
||||
recommended = self.default_patient_config()
|
||||
"""推荐配置:根据病例内容返回训练页默认病人初始化配置。"""
|
||||
case = self._get_case(case_id)
|
||||
recommended = self.default_patient_config(case)
|
||||
return TrainingConfigRecommendedResponse(
|
||||
case_id=case_id,
|
||||
recommended=recommended,
|
||||
@@ -29,9 +32,9 @@ class TrainingConfigService:
|
||||
)
|
||||
|
||||
def get_options(self, case_id: int) -> TrainingConfigOptionsResponse:
|
||||
"""配置选项:返回训练页自定义配置的全部可选项。"""
|
||||
self._ensure_case(case_id)
|
||||
recommended = self.default_patient_config()
|
||||
"""配置选项:返回训练页自定义病人初始化配置的全部可选项和病例推荐值。"""
|
||||
case = self._get_case(case_id)
|
||||
recommended = self.default_patient_config(case)
|
||||
return TrainingConfigOptionsResponse(
|
||||
case_id=case_id,
|
||||
recommended=recommended,
|
||||
@@ -39,18 +42,51 @@ class TrainingConfigService:
|
||||
options=self.config_options(),
|
||||
)
|
||||
|
||||
def default_patient_config(self) -> PatientConfig:
|
||||
"""默认配置:按当前产品原型初始化病人信息。"""
|
||||
def default_patient_config(self, case: CaseBase | None = None) -> PatientConfig:
|
||||
"""默认配置:按病例年龄、科室语义和病情关键词初始化 AI 病人沟通风格。"""
|
||||
if case is None:
|
||||
return PatientConfig(
|
||||
visit_environment="outpatient",
|
||||
age_group="youth",
|
||||
education_level="higher",
|
||||
personality="calm",
|
||||
)
|
||||
|
||||
text = self._case_text(case)
|
||||
patient_age = case.patient_age
|
||||
|
||||
visit_environment = "outpatient"
|
||||
personality = "calm"
|
||||
if self._contains_any(
|
||||
text,
|
||||
["急诊", "危重", "休克", "昏迷", "意识障碍", "呼吸困难", "低氧", "抢救", "SpO2 90", "血氧明显下降"],
|
||||
):
|
||||
visit_environment = "emergency"
|
||||
personality = "anxious"
|
||||
elif self._contains_any(text, ["住院", "病房", "入院", "住院治疗"]):
|
||||
visit_environment = "ward"
|
||||
|
||||
age_group = "youth"
|
||||
if (patient_age is not None and patient_age < 14) or self._contains_any(
|
||||
text,
|
||||
["儿童", "患儿", "儿科", "小儿", "幼儿", "婴儿"],
|
||||
):
|
||||
age_group = "child"
|
||||
elif patient_age is not None and patient_age >= 65:
|
||||
age_group = "elderly"
|
||||
elif patient_age is not None and patient_age >= 45:
|
||||
age_group = "middle_aged"
|
||||
|
||||
return PatientConfig(
|
||||
visit_environment="outpatient",
|
||||
age_group="youth",
|
||||
visit_environment=visit_environment,
|
||||
age_group=age_group,
|
||||
education_level="higher",
|
||||
personality="calm",
|
||||
personality=personality,
|
||||
)
|
||||
|
||||
def normalize_patient_config(self, config: PatientConfig | None) -> dict:
|
||||
def normalize_patient_config(self, config: PatientConfig | None, case: CaseBase | None = None) -> dict:
|
||||
"""配置归一:校验并补齐前端传入的病人初始化配置。"""
|
||||
selected = config or self.default_patient_config()
|
||||
selected = config or self.default_patient_config(case)
|
||||
values = selected.model_dump()
|
||||
allowed = {key: {item.value for item in items} for key, items in self.config_options().items()}
|
||||
for key, value in values.items():
|
||||
@@ -75,7 +111,7 @@ class TrainingConfigService:
|
||||
return {
|
||||
"visit_environment": [
|
||||
ConfigOption(value="outpatient", label="门诊", description="适合常规问诊训练"),
|
||||
ConfigOption(value="emergency", label="急诊", description="病情紧急、沟通节奏更快"),
|
||||
ConfigOption(value="emergency", label="急诊", description="病情紧急,沟通节奏更快"),
|
||||
ConfigOption(value="ward", label="病房", description="适合住院病情追踪和处置沟通"),
|
||||
],
|
||||
"age_group": [
|
||||
@@ -98,7 +134,49 @@ class TrainingConfigService:
|
||||
],
|
||||
}
|
||||
|
||||
def _ensure_case(self, case_id: int) -> None:
|
||||
"""病例校验:确认配置请求对应已发布病例。"""
|
||||
if not self.case_repo.get_active_case(case_id):
|
||||
def _get_case(self, case_id: int) -> CaseBase:
|
||||
"""病例校验:读取配置请求对应的已发布病例。"""
|
||||
case = self.case_repo.get_active_case(case_id)
|
||||
if not case:
|
||||
raise AppError("CASE_NOT_FOUND", "case not found or inactive", 404)
|
||||
return case
|
||||
|
||||
def _case_text(self, case: CaseBase) -> str:
|
||||
"""病例文本:拼接用于推荐配置推断的病例字段。"""
|
||||
parts: list[str] = [
|
||||
case.title or "",
|
||||
case.chief_complaint or "",
|
||||
case.description or "",
|
||||
case.tags or "",
|
||||
case.patient_gender or "",
|
||||
]
|
||||
for field_name in (
|
||||
"symptom_tags",
|
||||
"disease_tags",
|
||||
"competency_tags",
|
||||
"guideline_tags",
|
||||
"knowledge_points",
|
||||
"icd_codes",
|
||||
):
|
||||
parts.extend(self._stringify(getattr(case, field_name, None)))
|
||||
if case.traditional_case:
|
||||
parts.extend(self._stringify(case.traditional_case.guideline_reference))
|
||||
if case.teaching_case:
|
||||
parts.extend(self._stringify(case.teaching_case.teaching_goal))
|
||||
return " ".join(item for item in parts if item)
|
||||
|
||||
def _stringify(self, value: Any) -> list[str]:
|
||||
"""字段展开:把 JSON、列表和普通文本统一转换为可检索字符串。"""
|
||||
if value is None:
|
||||
return []
|
||||
if isinstance(value, str):
|
||||
return [value]
|
||||
if isinstance(value, dict):
|
||||
return [str(item) for pair in value.items() for item in pair if item is not None]
|
||||
if isinstance(value, list | tuple | set):
|
||||
return [str(item) for item in value if item is not None]
|
||||
return [str(value)]
|
||||
|
||||
def _contains_any(self, text: str, keywords: list[str]) -> bool:
|
||||
"""关键词判断:判断病例文本是否命中任一推荐配置关键词。"""
|
||||
return any(keyword in text for keyword in keywords)
|
||||
|
||||
Reference in New Issue
Block a user