183 lines
6.9 KiB
Python
183 lines
6.9 KiB
Python
"""Render patient and appointment density gates in the real desktop shell."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from datetime import date
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|
|
|
from PySide6.QtCore import QThreadPool
|
|
from PySide6.QtGui import QFont, QFontDatabase
|
|
from PySide6.QtWidgets import QApplication
|
|
|
|
from doctor_workstation.services import DemoDoctorRepository
|
|
from doctor_workstation.ui import ShellWindow, apply_theme
|
|
|
|
|
|
def _drain(application: QApplication) -> None:
|
|
QThreadPool.globalInstance().waitForDone(5_000)
|
|
for _index in range(8):
|
|
application.processEvents()
|
|
|
|
|
|
def _patient_rows() -> list[dict[str, Any]]:
|
|
names = ("林晓岚", "赵明远", "吴诗雨", "周安然", "程静", "许清和")
|
|
rows: list[dict[str, Any]] = []
|
|
for index in range(15):
|
|
rows.append(
|
|
{
|
|
"id": 101 + index,
|
|
"diagnosis_id": 501 + index,
|
|
"patient_id": 301 + index,
|
|
"patient_name": names[index % len(names)],
|
|
"gender": 2 if index % 2 == 0 else 1,
|
|
"age": 34 + index,
|
|
"phone_masked": f"138****{1200 + index:04d}",
|
|
"assistant_id": 8,
|
|
"assistant_name": "周医助",
|
|
"appointment_id": 701 + index,
|
|
"appointment_status": 1,
|
|
"appointment_doctor_name": "陈医生",
|
|
"appointment_time_text": f"2026-08-{17 + index % 3:02d} {9 + index % 7:02d}:00",
|
|
"revisit_count": index % 4,
|
|
"confirmation_text": "已确认" if index % 2 == 0 else "待确认",
|
|
"diagnosis_date_text": "第 2 次复诊" if index % 3 else "初诊",
|
|
"has_id_card": index % 4 != 0,
|
|
}
|
|
)
|
|
return rows
|
|
|
|
|
|
def _appointment_rows() -> list[dict[str, Any]]:
|
|
names = ("林晓岚", "赵明远", "吴诗雨", "周安然", "程静", "许清和")
|
|
today = date.today().isoformat()
|
|
rows: list[dict[str, Any]] = []
|
|
for index in range(8):
|
|
rows.append(
|
|
{
|
|
"id": 801 + index,
|
|
"diagnosis_id": 901 + index,
|
|
"patient_id": 401 + index,
|
|
"patient_name": names[index % len(names)],
|
|
"patient_phone": f"1380013{8000 + index}",
|
|
"gender": 2 if index % 2 == 0 else 1,
|
|
"age": 38 + index,
|
|
"doctor_name": "陈医生",
|
|
"assistant_id": 8,
|
|
"assistant_name": "周医助",
|
|
"appointment_date": today,
|
|
"appointment_time": f"{9 + index:02d}:00",
|
|
"channel_name": "线上复诊",
|
|
"diagnosis_confirmed": index % 2 == 0,
|
|
"has_prescription": index % 3 == 0,
|
|
"status": 1,
|
|
"status_desc": "已挂号",
|
|
"revisit_time": "复诊" if index % 2 else "初诊",
|
|
"unserved_days": index,
|
|
}
|
|
)
|
|
return rows
|
|
|
|
|
|
def render() -> list[Path]:
|
|
application = QApplication.instance() or QApplication([])
|
|
apply_theme(application)
|
|
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:
|
|
application.setFont(QFont(families[0], 9))
|
|
|
|
output = Path(__file__).resolve().parents[1] / "artifacts" / "patient_appointment_density"
|
|
output.mkdir(parents=True, exist_ok=True)
|
|
paths: list[Path] = []
|
|
|
|
for width, height in ((1366, 768), (1024, 640)):
|
|
repository = DemoDoctorRepository()
|
|
session = repository.login(repository.DEMO_ACCOUNT, repository.DEMO_PASSWORD)
|
|
shell = ShellWindow(
|
|
repository,
|
|
{"session": session, "demo_mode": True},
|
|
permissions=session.permissions,
|
|
)
|
|
shell.resize(width, height)
|
|
shell.show()
|
|
_drain(application)
|
|
|
|
if not shell.navigate("patients"):
|
|
raise RuntimeError("patients navigation is unavailable")
|
|
_drain(application)
|
|
patients = shell.pages["patients"]
|
|
patient_rows = _patient_rows()
|
|
patients.patient_workspace._apply_result(
|
|
{
|
|
"lists": patient_rows,
|
|
"count": len(patient_rows),
|
|
"extend": {
|
|
"scope": {"label": "当前医生与部门"},
|
|
"summary": {"today": 6, "tomorrow": 5, "day_after": 4},
|
|
},
|
|
},
|
|
patients.patient_workspace._generation,
|
|
)
|
|
application.processEvents()
|
|
patient_slots = patients.patient_workspace.table.viewport().height() // 40
|
|
if width == 1366 and patient_slots < 6:
|
|
raise RuntimeError(f"patient table only exposes {patient_slots} ordinary rows")
|
|
patient_path = output / f"patients_{width}x{height}.png"
|
|
if not shell.grab().save(str(patient_path), "PNG"):
|
|
raise RuntimeError(f"failed to save {patient_path}")
|
|
paths.append(patient_path)
|
|
|
|
if not shell.navigate("appointments"):
|
|
raise RuntimeError("appointments navigation is unavailable")
|
|
_drain(application)
|
|
appointments = shell.pages["appointments"]
|
|
appointments.poll_timer.stop()
|
|
appointment_rows = _appointment_rows()
|
|
appointments._loaded(
|
|
{
|
|
"lists": appointment_rows,
|
|
"count": len(appointment_rows),
|
|
"extend": {
|
|
"status_count": {"1": len(appointment_rows), "3": 0},
|
|
"unassigned_count": 0,
|
|
},
|
|
},
|
|
appointments._generation,
|
|
False,
|
|
)
|
|
application.processEvents()
|
|
row_heights = [
|
|
appointments.table.rowHeight(index)
|
|
for index in range(appointments.table.rowCount())
|
|
]
|
|
appointment_slots = appointments.table.viewport().height() // max(row_heights)
|
|
if width == 1366 and appointment_slots < 4:
|
|
raise RuntimeError(
|
|
f"appointment table only exposes {appointment_slots} ordinary rows"
|
|
)
|
|
if width == 1024 and not appointments.video_panel.isHidden():
|
|
raise RuntimeError("narrow appointment viewport did not collapse the video panel")
|
|
appointment_path = output / f"appointments_{width}x{height}.png"
|
|
if not shell.grab().save(str(appointment_path), "PNG"):
|
|
raise RuntimeError(f"failed to save {appointment_path}")
|
|
paths.append(appointment_path)
|
|
print(
|
|
f"{width}x{height}: patient_slots={patient_slots}, "
|
|
f"appointment_slots={appointment_slots}, video_hidden={appointments.video_panel.isHidden()}"
|
|
)
|
|
shell.close()
|
|
application.processEvents()
|
|
|
|
return paths
|
|
|
|
|
|
if __name__ == "__main__":
|
|
for rendered in render():
|
|
print(rendered)
|