- 新增后端默认模板服务,集中维护腹腔镜胆囊切除术和头颈部CT分割的权威分类树、颜色、maskid 和层级定义。 - 演示恢复出厂设置时强制恢复系统默认模板,缺失模板会重建,已修改或删减的默认语义分类树会覆盖回默认状态。 - 清理 main.py 中重复的默认模板定义,让启动 seed 复用同一套服务逻辑,避免后续默认模板定义漂移。 - 扩展管理员恢复出厂设置测试,覆盖头颈部CT模板被改坏和腹腔镜模板缺失后的恢复结果。 - 更新 AGENTS、README 和需求/API/测试/前端审计文档,明确恢复出厂设置会权威恢复系统默认模板。
152 lines
5.6 KiB
Python
152 lines
5.6 KiB
Python
"""Bundled system ontology templates and restore helpers."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from copy import deepcopy
|
||
|
||
from sqlalchemy.orm import Session
|
||
|
||
from models import Template
|
||
|
||
RESERVED_UNCLASSIFIED_CLASS = {
|
||
"id": "reserved-unclassified",
|
||
"name": "待分类",
|
||
"color": "#000000",
|
||
"zIndex": 0,
|
||
"maskId": 0,
|
||
"category": "系统保留",
|
||
}
|
||
|
||
|
||
def _with_reserved_unclassified_class(classes: list[dict]) -> list[dict]:
|
||
filtered = [
|
||
item for item in classes
|
||
if item.get("id") != RESERVED_UNCLASSIFIED_CLASS["id"]
|
||
and item.get("name") != RESERVED_UNCLASSIFIED_CLASS["name"]
|
||
and item.get("maskId") != 0
|
||
]
|
||
return [*filtered, dict(RESERVED_UNCLASSIFIED_CLASS)]
|
||
|
||
|
||
def _template_classes(
|
||
template_name: str,
|
||
names: list[str],
|
||
colors: list[tuple[int, int, int]],
|
||
*,
|
||
id_prefix: str,
|
||
) -> list[dict]:
|
||
classes = []
|
||
for idx, (rgb, name) in enumerate(zip(colors, names)):
|
||
color_hex = f"#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}"
|
||
classes.append({
|
||
"id": f"{id_prefix}-{idx}",
|
||
"name": name,
|
||
"color": color_hex,
|
||
"zIndex": (len(names) - idx) * 10,
|
||
"maskId": idx + 1,
|
||
"category": template_name,
|
||
})
|
||
return classes
|
||
|
||
|
||
def bundled_default_template_definitions() -> list[dict]:
|
||
"""Return fresh definitions for all bundled system templates."""
|
||
return [
|
||
{
|
||
"name": "腹腔镜胆囊切除术",
|
||
"description": "腹腔镜胆囊切除术(LC)手术器械与解剖结构语义分割模板,共35个分类",
|
||
"color": "#06b6d4",
|
||
"z_index": 0,
|
||
"classes": _with_reserved_unclassified_class(_template_classes(
|
||
"腹腔镜胆囊切除术",
|
||
[
|
||
"针", "线", "肿瘤", "血管阻断夹", "棉球", "双极电凝",
|
||
"肝脏", "胆囊", "分离钳", "脂肪", "止血海绵", "肝总管",
|
||
"吸引器", "剪刀", "超声刀", "止血纱布", "胆总管", "生物夹",
|
||
"无损伤钳", "钳夹", "喷洒", "胆囊管", "动脉", "电凝",
|
||
"静脉", "标本袋", "引流管", "纱布", "金属钛夹", "韧带",
|
||
"肝蒂", "推结器", "乳胶管-血管阻断", "吻合器", "术中超声",
|
||
],
|
||
[
|
||
(134, 124, 118), (0, 157, 142), (245, 161, 0), (255, 172, 159), (146, 175, 236), (155, 62, 0),
|
||
(255, 91, 0), (255, 234, 0), (85, 111, 181), (155, 132, 0), (181, 227, 14), (72, 0, 255),
|
||
(255, 0, 255), (29, 32, 136), (240, 16, 116), (160, 15, 95), (0, 155, 33), (0, 160, 233),
|
||
(52, 184, 178), (66, 115, 82), (90, 120, 41), (255, 0, 0), (117, 0, 0), (167, 24, 233),
|
||
(42, 8, 66), (112, 113, 150), (0, 255, 0), (255, 255, 255), (0, 255, 255), (181, 85, 105),
|
||
(113, 102, 140), (202, 202, 200), (197, 83, 181), (136, 162, 196), (138, 251, 213),
|
||
],
|
||
id_prefix="cls-lap",
|
||
)),
|
||
},
|
||
{
|
||
"name": "头颈部CT分割",
|
||
"description": "头颈部CT分割",
|
||
"color": "#ef4444",
|
||
"z_index": 10,
|
||
"classes": _with_reserved_unclassified_class(_template_classes(
|
||
"头颈部CT分割",
|
||
[
|
||
"肿瘤/结节 (Tumor/Nodule)",
|
||
"下颌骨 (Mandible)",
|
||
"甲状腺 (Thyroid)",
|
||
"气管 (Trachea)",
|
||
"颈椎 (Cervical Spine)",
|
||
"颈动脉 (Carotid Artery)",
|
||
"颈静脉 (Jugular Vein)",
|
||
"腮腺 (Parotid Gland)",
|
||
"下颌下腺 (Submandibular Gland)",
|
||
"舌骨 (Hyoid Bone)",
|
||
],
|
||
[
|
||
(255, 0, 0),
|
||
(0, 255, 0),
|
||
(0, 0, 255),
|
||
(255, 255, 0),
|
||
(255, 0, 255),
|
||
(0, 255, 255),
|
||
(255, 128, 0),
|
||
(128, 0, 128),
|
||
(0, 128, 128),
|
||
(128, 128, 0),
|
||
],
|
||
id_prefix="cls-head-neck-ct",
|
||
)),
|
||
},
|
||
]
|
||
|
||
|
||
def ensure_default_templates(db: Session, *, restore_existing: bool = False) -> list[Template]:
|
||
"""Create bundled system templates, optionally restoring existing ones exactly."""
|
||
templates: list[Template] = []
|
||
for definition in bundled_default_template_definitions():
|
||
existing = db.query(Template).filter(
|
||
Template.name == definition["name"],
|
||
Template.owner_user_id.is_(None),
|
||
).first()
|
||
if existing is None:
|
||
existing = Template(owner_user_id=None)
|
||
db.add(existing)
|
||
elif not restore_existing:
|
||
templates.append(existing)
|
||
continue
|
||
|
||
existing.name = definition["name"]
|
||
existing.description = definition["description"]
|
||
existing.color = definition["color"]
|
||
existing.z_index = definition["z_index"]
|
||
existing.owner_user_id = None
|
||
existing.mapping_rules = {
|
||
"classes": deepcopy(definition["classes"]),
|
||
"rules": [],
|
||
}
|
||
templates.append(existing)
|
||
db.commit()
|
||
for template in templates:
|
||
db.refresh(template)
|
||
return templates
|
||
|
||
|
||
def restore_default_templates(db: Session) -> list[Template]:
|
||
"""Restore bundled system templates after demo factory reset."""
|
||
return ensure_default_templates(db, restore_existing=True)
|