Files
zyt/app/scripts/render_patient_appointment_density.py
2026-08-20 17:47:14 +08:00

191 lines
7.3 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,
"source_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,
"video_call_hint": (
{"state": "live", "label": "视频通话进行中"}
if index == 0
else {"state": "idle", "label": "等待医生发起"}
),
}
)
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 appointments.content_layout.count() != 1:
raise RuntimeError("appointment list still reserves a secondary side panel")
if appointments.table_card.width() != appointments.content_host.width():
raise RuntimeError("appointment table does not fill the content viewport")
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}, table_full_width=True"
)
shell.close()
application.processEvents()
return paths
if __name__ == "__main__":
for rendered in render():
print(rendered)