更新
This commit is contained in:
@@ -12,20 +12,65 @@ os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
from PySide6.QtGui import QFont, QFontDatabase
|
||||
from PySide6.QtWidgets import QApplication, QWidget
|
||||
|
||||
from doctor_workstation.core.permissions import PermissionSet
|
||||
from doctor_workstation.ui import apply_theme
|
||||
from doctor_workstation.ui.dialogs import diagnosis as diagnosis_module
|
||||
from doctor_workstation.ui.dialogs.prescription import PrescriptionEditorDialog
|
||||
from doctor_workstation.ui.dialogs.prescription import (
|
||||
PrescriptionDetailDialog,
|
||||
PrescriptionEditorDialog,
|
||||
)
|
||||
from doctor_workstation.ui.dialogs.prescription_ai import PrescriptionAiReportDialog
|
||||
|
||||
|
||||
class VisualRepository:
|
||||
def list_medicines(self, **_filters: Any) -> dict[str, Any]:
|
||||
return {"lists": [], "count": 0}
|
||||
|
||||
def list_prescription_template_ai_reports(self, template_id: int) -> dict[str, Any]:
|
||||
report = {
|
||||
"summary": "益气健脾为主,兼顾养阴安神,适合脾气不足、气阴两虚方向的复核参考。",
|
||||
"possible_symptoms": ["乏力气短", "食少便溏", "睡眠不安"],
|
||||
"main_indications": "气阴两虚、脾气不足所见的倦怠与纳差。",
|
||||
"efficacy": ["益气健脾", "养阴生津", "宁心安神"],
|
||||
"suitable_people": ["辨证属气阴两虚者", "需由医师结合四诊确认"],
|
||||
"compatibility_analysis": "黄芪、党参、白术与茯苓协同补气健脾,麦冬、五味子兼顾养阴敛津,酸枣仁与远志用于宁心安神。",
|
||||
"cautions": ["仍需核对过敏史与现用药", "症状变化时及时复诊"],
|
||||
"disclaimer": "仅供专业人员辅助审方,不替代辨证、诊断和处方审核。",
|
||||
}
|
||||
return {
|
||||
"prescription_id": template_id,
|
||||
"can_refresh": True,
|
||||
"can_edit": True,
|
||||
"reports": [
|
||||
{
|
||||
"report_id": 21,
|
||||
"model_key": "qwen",
|
||||
"model_label": "千问",
|
||||
"model_name": "qwen3.6-35b",
|
||||
"generated_at": "2026-08-13 10:32:00",
|
||||
"report": report,
|
||||
"is_stale": False,
|
||||
"is_edited": True,
|
||||
},
|
||||
{
|
||||
"report_id": 22,
|
||||
"model_key": "openai",
|
||||
"model_label": "OpenAI",
|
||||
"model_name": "gpt-5.6-sol",
|
||||
"generated_at": "2026-08-13 10:32:00",
|
||||
"report": report,
|
||||
"is_stale": False,
|
||||
"is_edited": False,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _seed() -> dict[str, Any]:
|
||||
return {
|
||||
"diagnosis_id": 501,
|
||||
"id": 2501,
|
||||
"appointment_id": 2501,
|
||||
"prescription_no": "RX202608130021",
|
||||
"patient_name": "林晓岚",
|
||||
"phone": "13800138000",
|
||||
"gender": 0,
|
||||
@@ -57,6 +102,8 @@ def _seed() -> dict[str, Any]:
|
||||
"prescription_name": "宁心辅方",
|
||||
},
|
||||
"doctor_name": "陈医生",
|
||||
"audit_status": 1,
|
||||
"create_time": "2026-08-13 10:26:00",
|
||||
"herbs": [
|
||||
{"medicine_id": 11, "name": "黄芪", "dosage": 15, "formula_type": "主方"},
|
||||
{"medicine_id": 12, "name": "党参", "dosage": 12, "formula_type": "主方"},
|
||||
@@ -78,11 +125,6 @@ def _make_editor(host: QWidget) -> PrescriptionEditorDialog:
|
||||
current_user=SimpleNamespace(id=1, name="陈医生"),
|
||||
parent=host,
|
||||
)
|
||||
# Diagnosis-detail callers apply their scoped sheet after construction. The
|
||||
# prescription surface owns its own scoped rules, so this also verifies that
|
||||
# real entry path rather than a renderer-only appearance.
|
||||
editor.setObjectName("DiagnosisPrescriptionEditor")
|
||||
editor.setStyleSheet(diagnosis_module.DIAGNOSIS_QSS)
|
||||
return editor
|
||||
|
||||
|
||||
@@ -101,43 +143,59 @@ def render() -> list[Path]:
|
||||
if families:
|
||||
app.setFont(QFont(families[0], 9))
|
||||
|
||||
output = Path(__file__).resolve().parents[1] / "artifacts" / "diagnosis_visual"
|
||||
output = Path(__file__).resolve().parents[1] / "artifacts" / "subwindow_exact"
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
rendered: list[Path] = []
|
||||
|
||||
for host_width, height in ((1440, 900), (1024, 768), (920, 780)):
|
||||
host = QWidget()
|
||||
host.resize(host_width, height)
|
||||
host.show()
|
||||
editor = _make_editor(host)
|
||||
editor.show()
|
||||
_settle(app)
|
||||
editor.body_scroll.verticalScrollBar().setValue(0)
|
||||
_settle(app, 3)
|
||||
width = min(PrescriptionEditorDialog.DRAWER_WIDTH, host_width)
|
||||
path = output / f"diagnosis_state_prescription_editor_{width}x{height}.png"
|
||||
if not editor.grab().save(str(path), "PNG"):
|
||||
raise RuntimeError(f"failed to save {path}")
|
||||
rendered.append(path)
|
||||
editor.close()
|
||||
host.close()
|
||||
_settle(app, 2)
|
||||
|
||||
host = QWidget()
|
||||
host.resize(1440, 900)
|
||||
host.show()
|
||||
editor = _make_editor(host)
|
||||
editor.show()
|
||||
_settle(app)
|
||||
for index, state in ((1, "herbs"), (2, "usage"), (3, "signature")):
|
||||
editor.tabs.setCurrentIndex(index)
|
||||
_settle(app, 4)
|
||||
path = output / f"diagnosis_state_prescription_editor_{state}_1200x900.png"
|
||||
if not editor.grab().save(str(path), "PNG"):
|
||||
raise RuntimeError(f"failed to save {path}")
|
||||
rendered.append(path)
|
||||
editor.body_scroll.verticalScrollBar().setValue(0)
|
||||
_settle(app, 4)
|
||||
path = output / "prescription_editor_860x900.png"
|
||||
if not editor.grab().save(str(path), "PNG"):
|
||||
raise RuntimeError(f"failed to save {path}")
|
||||
rendered.append(path)
|
||||
editor.close()
|
||||
host.close()
|
||||
|
||||
detail = PrescriptionDetailDialog(
|
||||
_seed(),
|
||||
can_open_diagnosis=True,
|
||||
can_open_orders=True,
|
||||
)
|
||||
detail.show()
|
||||
_settle(app, 10)
|
||||
path = output / "prescription_detail_920x780.png"
|
||||
if not detail.grab().save(str(path), "PNG"):
|
||||
raise RuntimeError(f"failed to save {path}")
|
||||
rendered.append(path)
|
||||
detail.close()
|
||||
_settle(app, 2)
|
||||
|
||||
ai_dialog = PrescriptionAiReportDialog(
|
||||
VisualRepository(),
|
||||
PermissionSet(["*", "tcm.prescriptionLibrary/editAiReport"]),
|
||||
)
|
||||
ai_dialog.open_for(
|
||||
{
|
||||
"id": 2501,
|
||||
"prescription_name": "益气养阴安神方",
|
||||
"formula_type": "主方",
|
||||
"herbs": _seed()["herbs"],
|
||||
}
|
||||
)
|
||||
ai_dialog.show()
|
||||
_settle(app, 16)
|
||||
path = output / "prescription_ai_report_920x760.png"
|
||||
if not ai_dialog.grab().save(str(path), "PNG"):
|
||||
raise RuntimeError(f"failed to save {path}")
|
||||
rendered.append(path)
|
||||
ai_dialog.close()
|
||||
_settle(app, 2)
|
||||
return rendered
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
"""Render the 1710×920 reception AI acceptance artifact offscreen."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
|
||||
from PySide6.QtCore import QPoint, QThreadPool
|
||||
from PySide6.QtGui import QFontDatabase
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from doctor_workstation.services.mock_repository import DemoDoctorRepository
|
||||
from doctor_workstation.ui.shell import ShellWindow
|
||||
from doctor_workstation.ui.theme import apply_theme
|
||||
|
||||
|
||||
def main() -> int:
|
||||
app = QApplication.instance() or QApplication([])
|
||||
apply_theme(app)
|
||||
font_path = Path(r"C:\Windows\Fonts\msyh.ttc")
|
||||
if font_path.is_file():
|
||||
QFontDatabase.addApplicationFont(str(font_path))
|
||||
|
||||
repository = DemoDoctorRepository()
|
||||
session = repository.login("doctor", "doctor123")
|
||||
window = ShellWindow(repository, session)
|
||||
window.resize(1710, 920)
|
||||
window.show()
|
||||
if not window.navigate("reception"):
|
||||
raise RuntimeError("reception navigation is unavailable")
|
||||
|
||||
for _index in range(4):
|
||||
app.processEvents()
|
||||
QThreadPool.globalInstance().waitForDone(10_000)
|
||||
app.processEvents()
|
||||
|
||||
output = Path(__file__).resolve().parents[1] / "artifacts" / "reception_ai_exact" / "reception.png"
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
pixmap = window.grab()
|
||||
if pixmap.size().width() != 1710 or pixmap.size().height() != 920:
|
||||
raise RuntimeError(f"unexpected render size: {pixmap.size().width()}x{pixmap.size().height()}")
|
||||
if not pixmap.save(str(output), "PNG"):
|
||||
raise RuntimeError(f"failed to save {output}")
|
||||
|
||||
page = window.pages.get("reception")
|
||||
if page is not None:
|
||||
left = page.ai_analysis_card.geometry()
|
||||
right = page.ai_assistant_card.geometry()
|
||||
left_global = page.ai_analysis_card.mapTo(window, QPoint(0, 0))
|
||||
right_global = page.ai_assistant_card.mapTo(window, QPoint(0, 0))
|
||||
print(
|
||||
"AI_CARDS",
|
||||
left.x(),
|
||||
left.y(),
|
||||
left.width(),
|
||||
left.height(),
|
||||
right.x(),
|
||||
right.y(),
|
||||
right.width(),
|
||||
right.height(),
|
||||
page._ai_analysis_state,
|
||||
)
|
||||
print(
|
||||
"AI_CARDS_GLOBAL",
|
||||
left_global.x(),
|
||||
left_global.y(),
|
||||
right_global.x(),
|
||||
right_global.y(),
|
||||
)
|
||||
print(output)
|
||||
window.close()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,93 @@
|
||||
"""Render the structured reception medication/case card for visual review."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
|
||||
from PySide6.QtCore import QThreadPool
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from doctor_workstation.services.mock_repository import DemoDoctorRepository
|
||||
from doctor_workstation.ui.shell import ShellWindow
|
||||
from doctor_workstation.ui.theme import apply_theme
|
||||
|
||||
|
||||
def main() -> int:
|
||||
app = QApplication.instance() or QApplication([])
|
||||
apply_theme(app)
|
||||
repository = DemoDoctorRepository()
|
||||
session = repository.login("doctor", "doctor123")
|
||||
window = ShellWindow(repository, session)
|
||||
window.resize(1710, 920)
|
||||
window.show()
|
||||
if not window.navigate("reception"):
|
||||
raise RuntimeError("reception navigation is unavailable")
|
||||
for _index in range(4):
|
||||
app.processEvents()
|
||||
QThreadPool.globalInstance().waitForDone(10_000)
|
||||
|
||||
page = window.pages.get("reception")
|
||||
if page is None:
|
||||
raise RuntimeError("reception page was not created")
|
||||
page.detail_stack.setCurrentIndex(1)
|
||||
medication_index = next(
|
||||
index
|
||||
for index in range(page.detail_tabs.count())
|
||||
if page.detail_tabs.tabText(index) == "用药记录"
|
||||
)
|
||||
page.detail_tabs.setCurrentIndex(medication_index)
|
||||
page._render_case(
|
||||
{"has_prescription": True},
|
||||
{"age": 56, "gender": 1},
|
||||
{
|
||||
"diagnosis_date": "2026-08-14",
|
||||
"diagnosis_type_text": "复诊",
|
||||
"systolic": 148,
|
||||
"diastolic": 92,
|
||||
"blood_pressure_status": "偏高",
|
||||
"fasting_blood_sugar": "6.8",
|
||||
"fasting_blood_sugar_status": "偏高",
|
||||
"clinical_diagnosis": "2 型糖尿病(血糖控制未达标),合并高血压与轻度脂肪肝",
|
||||
"current_medications": [
|
||||
"二甲双胍缓释片 0.5 g,早晚餐后各一次",
|
||||
"格列吡嗪片 5 mg,早餐前一次",
|
||||
],
|
||||
"allergy_history_text": "青霉素过敏",
|
||||
"chief_complaint": "口干口苦、多饮多汗,近期睡眠欠佳",
|
||||
"present_illness": (
|
||||
"近两周空腹血糖波动,伴头昏、腰膝酸软及夜间多尿;"
|
||||
"未发生明确低血糖,服药依从性一般。"
|
||||
),
|
||||
"tongue": "舌红,苔黄腻",
|
||||
"pulse": "脉弦滑",
|
||||
"treatment_principle": "益气养阴、清热利湿,兼顾健脾化痰",
|
||||
"diabetes_history_text": "10 年",
|
||||
"past_history_text": "高血压 6 年、高脂血症 3 年",
|
||||
"family_history_text": "父亲患 2 型糖尿病",
|
||||
"sleep_condition_text": "多梦、入睡困难",
|
||||
"diet_condition_text": "主食量偏多,晚餐较晚",
|
||||
"remark": "重点核对降糖药剂量,复查肝肾功能,并关注夜间低血糖风险。",
|
||||
},
|
||||
)
|
||||
for _index in range(6):
|
||||
app.processEvents()
|
||||
|
||||
output = (
|
||||
Path(__file__).resolve().parents[1]
|
||||
/ "artifacts"
|
||||
/ "reception_medication_case"
|
||||
/ "reception_medication_case_1710x920.png"
|
||||
)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not window.grab().save(str(output), "PNG"):
|
||||
raise RuntimeError(f"failed to save {output}")
|
||||
print(output)
|
||||
window.close()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,121 @@
|
||||
"""Render focused blue-white diagnosis subwindow acceptance references."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SRC = ROOT / "src"
|
||||
if str(SRC) not in sys.path:
|
||||
sys.path.insert(0, str(SRC))
|
||||
|
||||
from PySide6.QtGui import QFont, QFontDatabase # noqa: E402
|
||||
from PySide6.QtWidgets import QApplication # noqa: E402
|
||||
from render_diagnosis_detail_visual import ( # noqa: E402
|
||||
ScreenshotRepository,
|
||||
_run_immediately,
|
||||
)
|
||||
from render_diagnosis_order_video_visual import order_detail # noqa: E402
|
||||
|
||||
from doctor_workstation.core import PermissionSet # noqa: E402
|
||||
from doctor_workstation.services import DemoDoctorRepository # noqa: E402
|
||||
from doctor_workstation.ui.diagnosis_editors import DailyRecordEditorDialog # noqa: E402
|
||||
from doctor_workstation.ui.dialogs import diagnosis as diagnosis_module # noqa: E402
|
||||
from doctor_workstation.ui.dialogs import prescription_ai as ai_module # noqa: E402
|
||||
from doctor_workstation.ui.dialogs.diagnosis import DiagnosisDialog # noqa: E402
|
||||
from doctor_workstation.ui.dialogs.prescription_ai import ( # noqa: E402
|
||||
DIAGNOSIS_AI_KIND,
|
||||
PrescriptionAiReportDialog,
|
||||
)
|
||||
|
||||
|
||||
def _save(widget: object, path: Path, app: QApplication) -> None:
|
||||
widget.show()
|
||||
for _ in range(8):
|
||||
app.processEvents()
|
||||
if not widget.grab().save(str(path), "PNG"):
|
||||
raise RuntimeError(f"failed to save {path}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
output = ROOT / "artifacts" / "subwindow_exact"
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
app = QApplication.instance() or QApplication([])
|
||||
font_path = Path("C:/Windows/Fonts/msyh.ttc")
|
||||
if font_path.is_file():
|
||||
font_id = QFontDatabase.addApplicationFont(str(font_path))
|
||||
families = QFontDatabase.applicationFontFamilies(font_id)
|
||||
if families:
|
||||
app.setFont(QFont(families[0], 9))
|
||||
diagnosis_module.run_async = _run_immediately
|
||||
ai_module.run_async = _run_immediately
|
||||
|
||||
host = DiagnosisDialog(ScreenshotRepository(locked=False), permissions=PermissionSet(["*"]))
|
||||
host.resize(1280, 800)
|
||||
host.open_for(501, editable=True)
|
||||
_save(host, output / "diagnosis_edit_drawer_1280x800.png", app)
|
||||
|
||||
order = host._build_order_detail_dialog(order_detail(), 801)
|
||||
_save(order, output / "diagnosis_order_detail_1280x800.png", app)
|
||||
order.close()
|
||||
host.close()
|
||||
app.processEvents()
|
||||
|
||||
daily = DailyRecordEditorDialog(
|
||||
"blood",
|
||||
{
|
||||
"id": 6101,
|
||||
"diagnosis_id": 501,
|
||||
"patient_id": 1501,
|
||||
"record_date": "2026-08-10",
|
||||
"record_time": "08:20",
|
||||
"fasting_blood_sugar": 8.2,
|
||||
"postprandial_blood_sugar": 12.4,
|
||||
"systolic_pressure": 146,
|
||||
"diastolic_pressure": 92,
|
||||
"western_medicine": "二甲双胍",
|
||||
"remark": "继续观察餐后波动。",
|
||||
},
|
||||
)
|
||||
daily.setStyleSheet(diagnosis_module.DIAGNOSIS_QSS)
|
||||
daily.resize(680, 660)
|
||||
_save(daily, output / "diagnosis_daily_editor_680x660.png", app)
|
||||
daily.close()
|
||||
app.processEvents()
|
||||
|
||||
ai_report = PrescriptionAiReportDialog(
|
||||
DemoDoctorRepository(),
|
||||
permissions=PermissionSet(["*"]),
|
||||
kind=DIAGNOSIS_AI_KIND,
|
||||
)
|
||||
ai_report.open_for(
|
||||
{
|
||||
"id": 501,
|
||||
"diagnosis_id": 501,
|
||||
"patient_name": "林晓岚",
|
||||
"diagnosis_date": "2026-08-10",
|
||||
"diagnosis_type": "复诊",
|
||||
"syndrome_type": "气阴两虚",
|
||||
"chief_complaint": "口干乏力,餐后血糖波动",
|
||||
"present_illness": "近两周睡眠改善,仍需记录空腹与餐后血糖。",
|
||||
"clinical_diagnosis": "2 型糖尿病",
|
||||
"treatment_principle": "益气养阴,兼顾饮食与运动管理",
|
||||
"doctor_advice": "连续记录七日血糖并按时复诊",
|
||||
}
|
||||
)
|
||||
ai_report.resize(920, 760)
|
||||
_save(ai_report, output / "diagnosis_ai_report_920x760.png", app)
|
||||
ai_report.close()
|
||||
app.processEvents()
|
||||
|
||||
for path in sorted(output.glob("*.png")):
|
||||
print(path)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user