finalize medical consultation agent backend

This commit is contained in:
刘金宝
2026-06-03 15:51:46 +08:00
parent 93d9e1c6a5
commit eb43573a44
33 changed files with 1063 additions and 281 deletions
+25
View File
@@ -9,6 +9,7 @@ class ReportAgent:
"score_type": scoring_result.get("score_type", "percentage"),
"total_score": total_score,
"dimension_scores": dimension_scores,
"score_details": self._normalize_score_details(scoring_result.get("score_details", []), dimension_scores),
"errors": self._ensure_list(scoring_result.get("errors")),
"improvement_plan": self._ensure_list(scoring_result.get("improvement_plan")),
"evidence_summary": self._ensure_list(scoring_result.get("evidence_summary")),
@@ -32,6 +33,30 @@ class ReportAgent:
"score": self._safe_float(item.get("score"), 0),
"max_score": self._safe_float(item.get("max_score"), 0),
"comment": str(item.get("comment", "")),
"evidence": self._ensure_list(item.get("evidence")),
"deductions": self._ensure_list(item.get("deductions")),
"improvement": str(item.get("improvement", "")),
}
)
return normalized
def _normalize_score_details(self, raw_details: object, dimension_scores: list[dict]) -> list[dict]:
"""评分明细校验:保留可写入 training_score_detail 的细粒度字段。"""
source = raw_details if isinstance(raw_details, list) and raw_details else dimension_scores
normalized: list[dict] = []
for item in source:
if not isinstance(item, dict):
continue
deductions = self._ensure_list(item.get("deductions"))
normalized.append(
{
"rule_id": item.get("rule_id"),
"dimension": str(item.get("dimension", "综合表现")),
"score": self._safe_float(item.get("score"), 0),
"deducted_reason": str(item.get("deducted_reason") or "".join(str(value) for value in deductions)),
"evidence_message_ids": self._ensure_list(item.get("evidence_message_ids") or item.get("evidence")),
"ai_confidence": self._safe_float(item.get("ai_confidence"), 0.85),
"comment": str(item.get("comment") or item.get("improvement") or ""),
}
)
return normalized
+64 -1
View File
@@ -114,8 +114,9 @@ class ScoringAgent:
"你是医学教学问诊评分专家,只输出合法 JSON。"
"请结合病例、问诊过程、检查申请、诊断和治疗提交进行教学评价。"
"输出字段固定为 score_type,total_score,dimension_scores,errors,improvement_plan,"
"evidence_summary,guideline_refs,overall_comment。"
"evidence_summary,guideline_refs,overall_comment,score_details"
"dimension_scores 为 5-6 项,每项包含 dimension,score,max_score,comment,evidence,deductions,improvement。"
"score_details 对应 scoring_rules,每项包含 rule_id,dimension,score,deducted_reason,evidence_message_ids,ai_confidence,comment。"
"evidence、deductions、improvement_plan、evidence_summary 必须是数组,每个元素一句话。"
"errors 每项包含 title,description,severity,related_dimension。"
"评价必须具体指出用户问了什么、申请了什么检查、诊断治疗哪里充分或不足。"
@@ -129,6 +130,7 @@ class ScoringAgent:
for item in scoring_rules[:12]:
compact.append(
{
"rule_id": getattr(item, "id", None),
"dimension": getattr(item, "dimension", ""),
"competency_dimension": getattr(item, "competency_dimension", ""),
"score_weight": float(getattr(item, "score_weight", 0) or 0),
@@ -161,6 +163,7 @@ class ScoringAgent:
data.setdefault("score_type", "percentage")
data.setdefault("total_score", 0)
data.setdefault("dimension_scores", [])
data.setdefault("score_details", [])
data.setdefault("errors", [])
data.setdefault("improvement_plan", [])
data.setdefault("evidence_summary", [])
@@ -183,6 +186,7 @@ class ScoringAgent:
}
)
data["dimension_scores"] = normalized_dimensions or self._fallback_score("percentage", guideline_refs)["dimension_scores"]
data["score_details"] = self._normalize_score_details(data.get("score_details"), data["dimension_scores"])
data["errors"] = self._normalize_errors(data.get("errors"))
data["improvement_plan"] = self._ensure_list(data.get("improvement_plan"))
data["evidence_summary"] = self._ensure_list(data.get("evidence_summary"))
@@ -195,6 +199,29 @@ class ScoringAgent:
data["score_type"] = "percentage"
return data
def _normalize_score_details(self, raw_details: object, dimension_scores: list[dict]) -> list[dict]:
"""评分明细归一化:生成可落库的 training_score_detail 数据。"""
source = raw_details if isinstance(raw_details, list) and raw_details else dimension_scores
details = []
for item in source:
if not isinstance(item, dict):
continue
deducted_reason = item.get("deducted_reason")
if not deducted_reason:
deducted_reason = "".join(str(value) for value in item.get("deductions", []) if value)
details.append(
{
"rule_id": item.get("rule_id"),
"dimension": str(item.get("dimension") or "综合表现"),
"score": float(item.get("score") or 0),
"deducted_reason": self._truncate(deducted_reason or "", 260),
"evidence_message_ids": self._ensure_list(item.get("evidence_message_ids") or item.get("evidence")),
"ai_confidence": float(item.get("ai_confidence") or 0.85),
"comment": self._truncate(item.get("comment") or item.get("improvement") or "", 220),
}
)
return details
def _normalize_errors(self, errors: object) -> list[dict]:
"""错误项归一化:转为报告可渲染的扣分项。"""
normalized = []
@@ -320,6 +347,35 @@ class ScoringAgent:
"improvement": "用 SOAP 结构归纳病情,把证据、判断和计划串联起来。",
},
],
"score_details": [
{
"rule_id": None,
"dimension": "信息获取",
"score": 20,
"deducted_reason": "既往喘息史、过敏史、疫苗接种史、家属照护能力等信息不够完整。",
"evidence_message_ids": ["围绕发热、咳嗽、喘息等核心症状展开问诊。"],
"ai_confidence": 0.85,
"comment": "完成主要症状追问,但儿科专科病史仍需补充。",
},
{
"rule_id": None,
"dimension": "分析推理",
"score": 16,
"deducted_reason": "鉴别诊断和严重程度判断未充分引用血氧、胸片和炎症指标。",
"evidence_message_ids": ["主要诊断指向支气管肺炎。"],
"ai_confidence": 0.84,
"comment": "诊断方向基本正确,但严重程度分层需要更清晰。",
},
{
"rule_id": None,
"dimension": "检查利用",
"score": 12,
"deducted_reason": "对 SpO2、胸片异常和炎症指标的临床意义解释不够具体。",
"evidence_message_ids": ["胸片、血氧或炎症指标可支持肺炎诊断和严重程度判断。"],
"ai_confidence": 0.84,
"comment": "关键检查申请较完整,但检查结果解释仍可细化。",
},
],
"errors": [
{
"title": "信息采集不够系统",
@@ -359,6 +415,13 @@ class ScoringAgent:
}
for item in data.get("dimension_scores", [])
]
converted["score_details"] = [
{
**item,
"score": round(float(item.get("score", 0)) / 20, 1),
}
for item in data.get("score_details", [])
]
return converted
def _truncate(self, value: Any, limit: int) -> str: