This commit is contained in:
Your Name
2026-08-28 18:24:37 +08:00
parent 43ad07208f
commit ed48f8be31
383 changed files with 8673 additions and 2222 deletions
+136 -1
View File
@@ -12,9 +12,11 @@ from PySide6.QtCore import Qt
from PySide6.QtTest import QTest
from PySide6.QtWidgets import (
QApplication,
QDialog,
QLabel,
QPushButton,
QTextBrowser,
QVBoxLayout,
QWidget,
)
@@ -1506,9 +1508,17 @@ def test_sectioned_reply_becomes_a_structured_report(application: QApplication)
titles = [
label.text()
for label in panel.findChildren(QLabel)
if label.objectName() == "AiConsultClinicalSectionTitle"
if label.objectName() == "AiConsultReportSectionTitle"
]
assert titles == ["症状演变与疗效评估", "血糖控制与监测细节", "用药依从性与生活方式"]
# 要点不再每句一个方框,而是「小标题 + 正文」两级文字。
assert panel.findChildren(ai_consult_module.QFrame, "AiConsultGapRow") == []
leads = [
label.text()
for label in panel.findChildren(QLabel)
if label.objectName() == "AiConsultReportLead"
]
assert leads == ["麻木症状", "皮肤瘙痒", "空腹血糖波动", "西药服用情况"]
bubble.deleteLater()
@@ -1522,3 +1532,128 @@ def test_sectioned_reply_becomes_a_structured_report(application: QApplication)
)
def test_unsectioned_replies_stay_plain_text(reply: str, application: QApplication) -> None:
assert ai_consult_module.parse_structured_report(reply) is None
def test_report_points_split_into_a_scannable_label_and_body() -> None:
split = ai_consult_module.split_report_lead
assert split("糖尿病管理缺失: 患者确诊糖尿病3年,空腹血糖为 8.5 mmol/L。") == (
"糖尿病管理缺失",
"患者确诊糖尿病3年,空腹血糖为 8.5 mmol/L。",
)
assert split("结论:由于患者当前未使用任何药物,无需复核。")[0] == "结论"
# 冒号前是一整句话,或正文太短,都按普通要点整段显示。
assert split("尽管无需复核用药,但基于患者病史,以下临床风险点需重点关注。以下为要点:细节")[0] == ""
assert split("空腹血糖: 8.5") == ("", "空腹血糖: 8.5")
assert split("没有冒号的一条要点") == ("", "没有冒号的一条要点")
def test_report_section_titles_drop_the_number_the_chip_already_shows() -> None:
strip = ai_consult_module._strip_leading_ordinal
assert strip("1. 当前用药状态评估") == "当前用药状态评估"
assert strip("二、临床风险与干预提示") == "临床风险与干预提示"
assert strip("建议下一步行动") == "建议下一步行动"
def test_report_body_escapes_markup_and_carries_reading_rhythm() -> None:
html = ai_consult_module._reading_html('血糖 <7.0 mmol/L 且 "达标" & 稳定')
assert "line-height" in html
assert "&lt;7.0" in html
assert "&amp;" in html
assert "<7.0" not in html
def test_structured_report_uses_a_readable_column_width(application: QApplication) -> None:
reply = (
"针对该患者的用药复核评估如下:\n\n"
"### 1. 当前用药状态评估\n"
"- 无当前处方药物: 病例数据中明确记录患者目前没有服药,系统内也没有有效处方记录。\n"
"- 结论: 患者当前未使用任何药物,不存在药物相互作用或配伍禁忌风险。\n"
"### 2. 临床风险与干预提示\n"
"- 糖尿病管理缺失: 患者确诊糖尿病3年,空腹血糖高于一般控制目标且未接受药物治疗。\n"
"重要提示: 本分析不能替代执业医师的面诊与完整病历评估。\n"
)
bubble = ai_consult_module._ChatBubble(role="ai", text=reply, time_text="千问 · 16:32")
assert bubble.finalize_clinical_analysis() is True
panel = bubble.findChild(ai_consult_module._StructuredReportPanel)
assert panel is not None
# 报告收窄到易读行宽,而不是继续用多栏面板的 1080。
assert panel.PREFERRED_MAX_WIDTH == 880
assert bubble._bubble_frame.maximumWidth() == 880
sections = panel.findChildren(ai_consult_module.QFrame, "AiConsultReportSection")
assert len(sections) == 2
assert panel.findChildren(ai_consult_module.QFrame, "AiConsultGapRow") == []
bubble.deleteLater()
def _fitted_bubble(reply: str, width: int = 900) -> Any:
host = QDialog()
host.setObjectName("AiConsultDialog")
host.setStyleSheet(ai_consult_module.AI_CONSULT_QSS)
layout = QVBoxLayout(host)
layout.setContentsMargins(12, 12, 12, 12)
bubble = ai_consult_module._ChatBubble(role="ai", text=reply, time_text="千问 · 16:32")
assert bubble.finalize_clinical_analysis() is True
layout.addWidget(bubble)
layout.addStretch(1)
host.setFixedWidth(width)
host.show()
for _ in range(12):
QApplication.processEvents()
host.adjustSize()
for _ in range(6):
QApplication.processEvents()
return host, bubble
@pytest.mark.parametrize(
"reply",
[
LONG_CLINICAL_REPLY,
(
"针对该患者的用药复核评估如下:\n\n"
"### 1. 当前用药状态评估\n"
"- 无当前处方药物: 病例数据中明确记录患者目前没有服药,系统内也没有有效处方记录,"
"因此不存在药物相互作用或配伍禁忌风险,无需再做安全性复核。\n"
"### 2. 临床风险与干预提示\n"
"- 糖尿病管理缺失: 患者确诊糖尿病3年,空腹血糖高于一般控制目标且未接受任何药物治疗,"
"存在长期高血糖导致微血管及大血管并发症的风险,需要尽快评估。\n"
),
],
ids=["clinical", "structured"],
)
def test_report_bubbles_report_the_height_they_actually_paint(
reply: str,
application: QApplication,
) -> None:
"""否则聊天区会按高估的高度撑出滚动空白,打开就是一片空白要往上滑。"""
host, bubble = _fitted_bubble(reply)
assert bubble.height() > 0
assert abs(bubble.sizeHint().height() - bubble.height()) <= 2
host.close()
host.deleteLater()
def test_risk_block_uses_the_red_alert_palette() -> None:
qss = ai_consult_module.AI_CONSULT_QSS
risk_card = qss.split("QFrame#AiConsultRiskCard {", 1)[1].split("}", 1)[0]
assert "#FEF3F2" in risk_card
assert "#F1B35C" not in risk_card # 旧的橙色描边
marker = qss.split("QLabel#AiConsultRiskMarker {", 1)[1].split("}", 1)[0]
assert "#C0392B" in marker
def test_clinical_bodies_are_no_longer_rendered_at_eleven_pixels() -> None:
qss = ai_consult_module.AI_CONSULT_QSS
body = qss.split("QLabel#AiConsultRiskBody {\n color: #46557A;", 1)
assert len(body) == 2 or "font-size: 13px" in qss
block = qss.split("QLabel#AiConsultClinicalBody,", 1)[1].split("}", 1)[0]
assert "font-size: 13px" in block
assert "font-size: 11px" not in block