引言:手术排程优化的核心挑战与重要性

在现代医疗体系中,手术排程是医院运营效率的关键环节。手术室作为医院的核心资源,其利用率直接影响医院的收入和患者的就医体验。然而,传统的手术排程方式往往依赖人工经验,存在诸多痛点:患者等待时间过长、手术室空闲率高、突发情况难以应对等。根据相关研究,优化手术排程可以将手术室利用率提升15%-25%,同时减少患者平均等待时间30%以上。

手术排程优化的核心在于平衡多个相互冲突的目标:最大化手术室利用率、最小化患者等待时间、确保医疗安全、满足紧急手术需求等。这需要综合考虑手术类型、时长、医生 availability、设备需求、患者状况等多维度因素。随着信息技术的发展,特别是人工智能和运筹学算法的应用,为手术排程优化提供了新的解决方案。

本文将从现状分析、优化策略、技术实现、实施步骤等多个维度,详细阐述如何优化医院手术排程流程,帮助医院管理者构建高效、智能的手术排程体系。

现状分析:传统手术排程的痛点

1. 人工排程的局限性

传统手术排程主要依赖护士长或排程员的经验,这种方式存在明显的主观性和不稳定性:

  • 经验依赖性强:不同排程员的决策标准不一,导致排程质量参差不齐
  • 信息更新滞后:纸质或Excel表格难以实时反映患者状况变化、医生临时变动等信息
  • 优化能力有限:人工难以同时考虑数十个约束条件和优化目标,容易出现资源冲突或浪费

2. 数据孤岛问题

手术排程涉及多个部门的信息系统:

  • 住院部系统:患者入院时间、病情紧急程度
  • 门诊系统:术前检查结果、麻醉评估
  • 医生排班系统:医生、护士的出勤情况
  • 设备管理系统:手术器械、监护设备的可用性

这些系统往往相互独立,信息无法实时共享,导致排程员需要手动整合信息,效率低下且容易出错。

3. 不确定性因素处理困难

手术过程中存在大量不确定性:

  • 手术时长波动:实际手术时间可能与预估相差甚远
  • 紧急手术插入:急诊手术需要打断原有排程
  • 患者状况变化:术前检查异常导致手术延期或取消
  • 设备故障:关键设备突然故障影响排程

传统排程方式难以动态调整,往往导致连锁反应,影响后续所有手术。

4. 缺乏数据驱动的决策支持

传统排程缺乏对历史数据的分析,无法回答以下问题:

  • 不同类型手术的平均时长和变异系数
  • 各医生的手术效率和质量
  • 手术室的峰谷时段分布
  • 患者等待时间与手术室利用率的关系

这些问题导致排程决策缺乏科学依据,难以持续改进。

优化策略:构建智能排程体系

1. 建立标准化手术时长预估模型

准确的手术时长预估是优化排程的基础。传统方式依赖医生主观估计,误差较大。应建立基于历史数据的统计模型:

数据收集

  • 收集过去2-3年所有手术记录,包括:手术名称、主刀医生、实际时长、患者BMI、年龄、并发症等
  • 标准化手术名称(使用ICD-10或CPT编码)
  • 记录手术过程中的关键节点(麻醉开始、切皮、缝合、苏醒)

模型构建: 使用多元线性回归或机器学习算法(如随机森林)建立预估模型:

# 示例:使用Python构建手术时长预估模型
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split

# 加载历史手术数据
# 假设数据包含:手术类型、医生、患者BMI、年龄、并发症数量、实际时长
data = pd.read_csv('surgery_history.csv')

# 特征工程
features = pd.get_dummies(data[['surgery_type', 'surgeon']])
features['patient_bmi'] = data['patient_bmi']
features['patient_age'] = data['patient_age']
features['complications'] = data['complications']

target = data['actual_duration']

# 训练模型
X_train, X_test, y_train, y_test = train_test_split(features, target, test_size=0.2)
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# 预估新手术时长
def estimate_duration(surgery_type, surgeon, patient_bmi, patient_age, complications):
    input_data = pd.DataFrame({
        'surgery_type': [surgery_type],
        'surgeon': [surgeon],
        'patient_bmi': [patient_bmi],
        'patient_age': [patient_age],
        'complications': [complications]
    })
    input_encoded = pd.get_dummies(input_data)
    # 确保所有特征列存在
    for col in features.columns:
        if col not in input_encoded.columns:
            input_encoded[col] = 0
    input_encoded = input_encoded[features.columns]
    return model.predict(input_encoded)[0]

# 示例:预估一台腹腔镜胆囊切除术
estimated = estimate_duration(
    surgery_type='腹腔镜胆囊切除术',
    surgeon='张医生',
    patient_bmi=28,
    patient_age=45,
    complications=1
)
print(f"预估手术时长:{estimated:.1f}分钟")

模型应用

  • 为每台手术提供95%置信区间的时长预估
  • 根据预估时长自动分配手术室和时间段
  • 实际手术时长与预估偏差超过阈值时触发预警

2. 引入优先级动态调整机制

手术优先级不能仅依据申请时间,应综合考虑多维度因素:

优先级评分公式

优先级分数 = 基础分 × 紧急系数 × 资源匹配度 × 等待时间系数

其中:
- 基础分:手术类型(急诊100分,限期80分,择期60分)
- 紧急系数:病情严重程度(1.0-2.0)
- 资源匹配度:所需资源与当前可用资源的匹配程度(0.8-1.2)
- 等待时间系数:患者已等待天数/30(上限2.0)

动态调整规则

  • 每天凌晨0点重新计算所有待排程患者的优先级分数
  • 急诊手术自动获得最高优先级,立即插入排程
  • 等待时间超过30天的患者自动提升优先级
  • 资源不匹配(如需要特殊设备但设备不可用)则降低优先级

示例场景: 患者A(择期手术,基础分60)已等待25天,病情中等(紧急系数1.2),所需资源匹配(1.0): 优先级分数 = 60 × 1.2 × 1.0 × (2530) = 60 × 1.2 × 0.833 = 60

患者B(限期手术,基础分80)刚申请,病情紧急(紧急系数1.5),所需资源匹配(1.0): 优先级分数 = 80 × 1.5 × 1.0 × (130) = 4

虽然患者B病情更紧急,但患者A等待时间更长,系统会优先安排患者A,但会在排程中预留急诊窗口。

3. 采用块状排程与缓冲时间结合

传统排程按分钟精确安排,缺乏弹性。推荐采用”块状排程+缓冲”模式:

块状排程原则

  • 将手术室时间划分为固定长度的块(如30分钟或1小时)
  • 同类手术尽量安排在同一块内,减少设备切换时间
  • 每个块内预留10%-15%的缓冲时间

排程模板示例

手术室1(周一):
08:00-09:00:腹腔镜手术块(胆囊切除术×2)
09:00-09:10:缓冲时间
09:10-10:40:骨科手术块(关节置换×1)
10:40-10:55:缓冲时间
10:55-12:00:普外手术块(甲状腺切除×1)
12:00-13:00:午餐休息(强制空闲)
13:00-14:30:急诊预留窗口
14:30-14:45:缓冲时间
14:45-16:30:妇科手术块(子宫切除×2)
16:30-17:00:清理消毒时间

缓冲时间管理

  • 如果前序手术提前完成,缓冲时间可用于下一台手术,减少患者等待
  • 如果前序手术超时,占用缓冲时间,不影响后续手术准时开始
  • 缓冲时间用完后,系统自动预警,提示排程员调整后续安排

4. 实施多手术室协同调度

单个手术室的优化是局部最优,需要考虑全院手术室的协同:

协同策略

  • 资源池化:将麻醉医生、巡回护士、特殊设备视为全院资源,而非固定于某手术室
  • 动态分配:根据手术类型和医生偏好,动态分配手术室
  • 负载均衡:自动平衡各手术室的工作量,避免部分手术室过载而部分空闲

协同算法示例

# 多手术室协同调度算法框架
class OperatingRoomScheduler:
    def __init__(self, rooms, surgeries):
        self.rooms = rooms  # 手术室列表
        self.surgeries = surgeries  # 待排程手术列表
        self.schedule = {room: [] for room in rooms}
    
    def calculate_room_score(self, room, surgery, start_time):
        """计算手术在该手术室的得分"""
        score = 0
        
        # 1. 设备匹配度(权重30%)
        if set(surgery.required_equipment).issubset(set(room.available_equipment)):
            score += 30
        else:
            return -100  # 不满足设备要求直接排除
        
        # 2. 医生便利性(权重25%)
        # 医生在该手术室的历史效率
        efficiency = get_historical_efficiency(room.id, surgery.surgeon)
        score += 25 * efficiency
        
        # 3. 时间连续性(权重20%)
        # 如果该医生在该手术室已有排程,且间隔合理
        if has_adjacent_surgery(room.id, surgery.surgeon, start_time):
            score += 20
        
        # 4. 患者转运距离(权重15%)
        # 患者所在病区与手术室的距离
        distance = get_distance(surgery.patient_ward, room.location)
        score += 15 * (1 - distance/1000)  # 距离越近得分越高
        
        # 5. 特殊偏好(权重10%)
        if surgery.surgeon in room.preferred_surgeons:
            score += 10
        
        return score
    
    def schedule_surgeries(self):
        """主调度函数"""
        # 按优先级排序待排程手术
        sorted_surgeries = sorted(self.surgeries, key=lambda x: x.priority, reverse=True)
        
        for surgery in sorted_surgeries:
            best_room = None
            best_score = -1
            best_start_time = None
            
            # 遍历所有手术室寻找最优位置
            for room in self.rooms:
                # 寻找该手术室的可用时间段
                available_slots = find_available_slots(room, surgery.estimated_duration)
                
                for slot in available_slots:
                    score = self.calculate_room_score(room, surgery, slot.start)
                    if score > best_score:
                        best_score = score
                        best_room = room
                        best_start_time = slot.start
            
            if best_room:
                # 将手术安排到最优位置
                self.schedule[best_room].append({
                    'surgery': surgery,
                    'start_time': best_start_time,
                    'end_time': best_start_time + surgery.estimated_duration
                })
                # 更新手术室占用情况
                update_room_availability(best_room, best_start_time, surgery.estimated_duration)
        
        return self.schedule

# 使用示例
rooms = [Room('OR1', ['腹腔镜', '骨科设备'], ['张医生', '李医生']),
         Room('OR2', ['腹腔镜', '妇科设备'], ['王医生', '赵医生'])]

surgeries = [Surgery('胆囊切除', '张医生', 90, ['腹腔镜'], 'wardA', priority=85),
             Surgery('关节置换', '李医生', 120, ['骨科设备'], 'wardB', priority=70)]

scheduler = OperatingRoomScheduler(rooms, surgeries)
final_schedule = scheduler.schedule_surgeries()

5. 建立实时动态调整机制

手术排程不是一成不变的,需要建立基于实时数据的动态调整机制:

触发调整的事件

  • 手术超时超过15分钟
  • 急诊手术插入
  • 患者术前检查异常
  • 医生临时缺席
  • 设备故障

调整策略

  1. 局部调整:仅调整受影响的后续手术
  2. 全局重排:当影响超过3台手术时,触发全局重新排程
  3. 人工干预:系统提供调整建议,由排程员确认

动态调整算法

def dynamic_adjustment(trigger_event, current_schedule):
    """动态调整函数"""
    if trigger_event.type == 'SURGERY_OVERTIME':
        overtime_minutes = trigger_event.overtime_minutes
        affected_surgery = trigger_event.affected_surgery
        
        if overtime_minutes < 15:
            # 轻度超时,仅延长当前手术时间,后续手术顺延
            return adjust_single_surgery(affected_surgery, overtime_minutes)
        
        elif 15 <= overtime_minutes < 30:
            # 中度超时,尝试压缩后续手术准备时间或寻找空闲手术室
            alternative = find_alternative_room(current_schedule, affected_surgery)
            if alternative:
                return move_surgery_to_room(affected_surgery, alternative)
            else:
                # 延迟后续手术,通知患者
                return delay_subsequent_surgeries(affected_surgery, overtime_minutes)
        
        else:
            # 严重超时,触发全局重排
            return reschedule_all_surgeries(current_schedule, affected_surgery)
    
    elif trigger_event.type == 'EMERGENCY_INSERT':
        # 急诊插入策略
        emergency_surgery = trigger_event.surgery
        
        # 1. 优先使用预留的急诊窗口
        slot = find_emergency_slot(current_schedule)
        if slot:
            return insert_into_slot(emergency_surgery, slot)
        
        # 2. 如果没有预留窗口,延迟优先级最低的择期手术
        else:
            lowest_priority_surgery = find_lowest_priority_surgery(current_schedule)
            return replace_with_emergency(lowest_priority_surgery, emergency_surgery)
    
    elif trigger_event.type == 'PATIENT_CANCELLATION':
        # 患者取消手术,释放时间段
        cancelled_surgery = trigger_event.surgery
        return release_and_fill_slot(cancelled_surgery, current_schedule)

# 示例:处理手术超时
trigger = SurgeryOvertimeEvent(surgery_id='S001', overtime_minutes=20)
new_schedule = dynamic_adjustment(trigger, current_schedule)

技术实现:构建智能排程系统

1. 系统架构设计

一个完整的智能手术排程系统应包含以下模块:

┌─────────────────────────────────────────────────────────────┐
│                     用户界面层(UI)                         │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │
│  │ 排程员界面  │  │ 医生查询界面 │  │ 患者查询界面 │        │
│  └─────────────┘  └─────────────┘  └─────────────┘        │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                     应用服务层(Service)                    │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │
│  │ 排程引擎    │  │ 实时监控    │  │ 通知服务    │        │
│  └─────────────┘  └─────────────┘  └─────────────┘        │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                     数据访问层(DAO)                        │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │
│  │ 手术数据    │  │ 资源数据    │  │ 实时数据    │        │
│  └─────────────┘  └─────────────┘  └─────────────┘        │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                     数据存储层(Storage)                    │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │
│  │ 关系型数据库│  │ 时序数据库  │  │ 缓存数据库  │        │
│  └─────────────┘  └─────────────┘  └─────────────┘        │
└─────────────────────────────────────────────────────────────┘

2. 核心算法实现

2.1 混合整数规划(MIP)算法

对于精确优化,可以使用MIP算法:

from ortools.linear_solver import pywraplp

def optimize_surgery_schedule_mip(surgeries, rooms, time_slots):
    """
    使用混合整数规划优化手术排程
    
    参数:
        surgeries: 待排程手术列表
        rooms: 手术室列表
        time_slots: 时间槽列表
    """
    solver = pywraplp.Solver.CreateSolver('SCIP')
    
    # 决策变量:x[i,j,k] = 1 表示手术i在手术室j的k时段执行
    x = {}
    for i in range(len(surgeries)):
        for j in range(len(rooms)):
            for k in range(len(time_slots)):
                x[i,j,k] = solver.IntVar(0, 1, f'x_{i}_{j}_{k}')
    
    # 约束1:每台手术只能安排一次
    for i in range(len(surgeries)):
        solver.Add(sum(x[i,j,k] for j in range(len(rooms)) for k in range(len(time_slots))) == 1)
    
    // 约束2:每个手术室在同一时段只能执行一台手术
    for j in range(len(rooms)):
        for k in range(len(time_slots)):
            solver.Add(sum(x[i,j,k] for i in range(len(surgeries))) <= 1)
    
    // 约束3:手术时长约束(需要连续的时间槽)
    for i in range(len(surgeries)):
        duration_slots = surgeries[i].estimated_duration // 30  // 假设每个槽30分钟
        for j in range(len(rooms)):
            for k in range(len(time_slots) - duration_slots + 1):
                // 检查从k开始的duration_slots个槽是否连续
                solver.Add(sum(x[i,j,k+t] for t in range(duration_slots)) <= duration_slots * (1 - x[i,j,k]))
    
    // 约束4:设备需求约束
    for i in range(len(surgeries)):
        for j in range(len(rooms)):
            if not set(surgeries[i].required_equipment).issubset(set(rooms[j].available_equipment)):
                for k in range(len(time_slots)):
                    solver.Add(x[i,j,k] == 0)
    
    // 目标函数:最小化患者等待时间 + 最大化手术室利用率
    // 等待时间惩罚:手术申请时间越早,权重越高
    // 利用率奖励:占用非高峰时段给予奖励
    objective = solver.Objective()
    for i in range(len(surgeries)):
        for j in range(len(rooms)):
            for k in range(len(time_slots)):
                wait_penalty = (len(time_slots) - k) * 0.1  // 时段越靠后惩罚越大
                utilization_bonus = 0.05 if is_off_peak(time_slots[k]) else 0
                objective.SetCoefficient(x[i,j,k], wait_penalty - utilization_bonus)
    
    objective.SetMinimization()
    
    // 求解
    status = solver.Solve()
    
    if status == pywraplp.Solver.OPTIMAL:
        schedule = []
        for i in range(len(surgeries)):
            for j in range(len(rooms)):
                for k in range(len(time_slots)):
                    if x[i,j,k].solution_value() > 0.5:
                        schedule.append({
                            'surgery': surgeries[i].name,
                            'room': rooms[j].name,
                            'start_slot': k,
                            'start_time': time_slots[k]
                        })
        return schedule
    else:
        return None

// 使用示例
surgeries = [
    {'name': '胆囊切除', 'duration': 90, 'equipment': ['腹腔镜'], 'priority': 85},
    {'name': '关节置换', 'duration': 120, 'equipment': ['骨科设备'], 'priority': 70}
]

rooms = [
    {'name': 'OR1', 'equipment': ['腹腔镜', '骨科设备']},
    {'name': 'OR2', 'equipment': ['腹腔镜', '妇科设备']}
]

time_slots = ['08:00', '08:30', '09:00', '09:30', '10:00', '10:30', '11:00', '11:30', '13:00', '13:30', '14:00', '14:30', '15:00', '15:30', '16:00', '16:30']

schedule = optimize_surgery_schedule_mip(surgeries, rooms, time_slots)
print("优化后的排程:", schedule)

2.2 启发式算法(遗传算法)

当手术数量较大时,精确算法求解时间过长,可以使用遗传算法:

import random
from typing import List, Tuple
import numpy as np

class GeneticScheduler:
    def __init__(self, surgeries, rooms, population_size=100, generations=200):
        self.surgeries = surgeries
        self.rooms = rooms
        self.population_size = population_size
        self.generations = generations
        self.population = []
    
    def create_individual(self):
        """创建个体:随机分配手术到手术室和时间段"""
        individual = []
        for surgery in self.surgeries:
            room = random.choice(self.rooms)
            start_time = random.randint(8, 16) * 60 + random.randint(0, 59)  // 分钟
            individual.append((surgery, room, start_time))
        return individual
    
    def calculate_fitness(self, individual):
        """计算适应度(越小越好)"""
        total_score = 0
        
        // 1. 惩罚时间冲突
        room_schedule = {}
        for surgery, room, start_time in individual:
            if room not in room_schedule:
                room_schedule[room] = []
            room_schedule[room].append((start_time, start_time + surgery.duration))
        
        for room, slots in room_schedule.items():
            slots.sort()
            for i in range(len(slots) - 1):
                if slots[i][1] > slots[i+1][0]:  // 时间重叠
                    total_score += 1000
        
        // 2. 惩罚设备不匹配
        for surgery, room, _ in individual:
            if not set(surgery.required_equipment).issubset(set(room.available_equipment)):
                total_score += 500
        
        // 3. 惩罚医生时间冲突(同一医生不能同时在两台手术)
        doctor_schedule = {}
        for surgery, _, start_time in individual:
            if surgery.surgeon not in doctor_schedule:
                doctor_schedule[surgery.surgeon] = []
            doctor_schedule[surgery.surgeon].append((start_time, start_time + surgery.duration))
        
        for doctor, slots in doctor_schedule.items():
            slots.sort()
            for i in range(len(slots) - 1):
                if slots[i][1] > slots[i+1][0]:
                    total_score += 800
        
        // 4. 惩罚等待时间(手术安排越晚惩罚越大)
        for surgery, _, start_time in individual:
            wait_days = (start_time - surgery.application_time) / (24*60)
            total_score += wait_days * 2
        
        // 5. 奖励手术室连续使用(减少切换时间)
        for room, slots in room_schedule.items():
            slots.sort()
            for i in range(len(slots) - 1):
                gap = slots[i+1][0] - slots[i][1]
                if gap < 30:  // 间隔小于30分钟
                    total_score -= 5  // 奖励
        
        return total_score
    
    def selection(self, population, fitnesses):
        """锦标赛选择"""
        tournament_size = 5
        selected = []
        for _ in range(len(population)):
            tournament = random.sample(list(zip(population, fitnesses)), tournament_size)
            winner = min(tournament, key=lambda x: x[1])[0]
            selected.append(winner)
        return selected
    
    def crossover(self, parent1, parent2):
        """单点交叉"""
        if len(parent1) < 2:
            return parent1, parent2
        
        point = random.randint(1, len(parent1) - 1)
        child1 = parent1[:point] + parent2[point:]
        child2 = parent2[:point] + parent1[point:]
        return child1, child2
    
    def mutate(self, individual, mutation_rate=0.1):
        """变异:随机改变手术的手术室或时间"""
        mutated = individual.copy()
        for i in range(len(mutated)):
            if random.random() < mutation_rate:
                surgery, old_room, old_time = mutated[i]
                // 随机改变手术室或时间
                if random.random() < 0.5:
                    new_room = random.choice([r for r in self.rooms if r != old_room])
                    mutated[i] = (surgery, new_room, old_time)
                else:
                    new_time = old_time + random.randint(-60, 60)
                    mutated[i] = (surgery, old_room, new_time)
        return mutated
    
    def run(self):
        """运行遗传算法"""
        // 初始化种群
        self.population = [self.create_individual() for _ in range(self.population_size)]
        
        best_individual = None
        best_fitness = float('inf')
        
        for generation in range(self.generations):
            // 计算适应度
            fitnesses = [self.calculate_fitness(ind) for ind in self.population]
            
            // 更新最佳个体
            min_fitness = min(fitnesses)
            if min_fitness < best_fitness:
                best_fitness = min_fitness
                best_individual = self.population[fitnesses.index(min_fitness)]
            
            // 选择
            selected = self.selection(self.population, fitnesses)
            
            // 交叉和变异
            new_population = []
            for i in range(0, len(selected), 2):
                parent1 = selected[i]
                parent2 = selected[i+1] if i+1 < len(selected) else selected[0]
                child1, child2 = self.crossover(parent1, parent2)
                new_population.append(self.mutate(child1))
                new_population.append(self.mutate(child2))
            
            self.population = new_population
            
            // 打印进度
            if generation % 50 == 0:
                print(f"Generation {generation}: Best Fitness = {best_fitness}")
        
        return best_individual, best_fitness

// 使用示例
scheduler = GeneticScheduler(surgeries, rooms, population_size=50, generations=100)
best_schedule, fitness = scheduler.run()
print(f"最佳方案适应度:{fitness}")
print("排程结果:", best_schedule)

3. 数据库设计

合理的数据库结构是系统稳定运行的基础:

-- 手术表
CREATE TABLE surgeries (
    id VARCHAR(36) PRIMARY KEY,
    patient_id VARCHAR(36) NOT NULL,
    surgery_name VARCHAR(200) NOT NULL,
    surgery_code VARCHAR(50),  -- ICD-10或CPT编码
    estimated_duration INT,  -- 预估时长(分钟)
    actual_duration INT,  -- 实际时长(分钟)
    surgeon_id VARCHAR(36) NOT NULL,
    priority INT,  -- 优先级分数
    status ENUM('pending', 'scheduled', 'in_progress', 'completed', 'cancelled'),
    required_equipment JSON,  -- 所需设备列表
    required_staff JSON,  -- 所需人员列表
    patient_ward VARCHAR(50),
    application_time DATETIME,
    scheduled_start DATETIME,
    scheduled_end DATETIME,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_status (status),
    INDEX idx_priority (priority),
    INDEX idx_application_time (application_time)
);

-- 手术室表
CREATE TABLE operating_rooms (
    id VARCHAR(36) PRIMARY KEY,
    room_name VARCHAR(50) NOT NULL,
    location VARCHAR(100),
    available_equipment JSON,  -- 可用设备列表
    capacity INT,  -- 容量(同时可进行手术数)
    is_active BOOLEAN DEFAULT TRUE,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- 手术排程表
CREATE TABLE surgery_schedule (
    id VARCHAR(36) PRIMARY KEY,
    surgery_id VARCHAR(36) NOT NULL,
    room_id VARCHAR(36) NOT NULL,
    scheduled_start DATETIME NOT NULL,
    scheduled_end DATETIME NOT NULL,
    status ENUM('scheduled', 'in_progress', 'completed', 'delayed', 'cancelled'),
    actual_start DATETIME,
    actual_end DATETIME,
    delay_reason TEXT,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (surgery_id) REFERENCES surgeries(id),
    FOREIGN KEY (room_id) REFERENCES operating_rooms(id),
    INDEX idx_room_time (room_id, scheduled_start)
);

-- 医生排班表
CREATE TABLE doctor_schedules (
    id VARCHAR(36) PRIMARY KEY,
    doctor_id VARCHAR(36) NOT NULL,
    date DATE NOT NULL,
    start_time TIME NOT NULL,
    end_time TIME NOT NULL,
    room_id VARCHAR(36),
    is_available BOOLEAN DEFAULT TRUE,
    notes TEXT,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_doctor_date (doctor_id, date)
);

-- 设备表
CREATE TABLE equipment (
    id VARCHAR(36) PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    type VARCHAR(50),
    location VARCHAR(100),
    status ENUM('available', 'in_use', 'maintenance', 'broken'),
    last_maintenance DATE,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- 实时监控表(记录手术过程中的关键事件)
CREATE TABLE surgery_events (
    id VARCHAR(36) PRIMARY KEY,
    surgery_id VARCHAR(36) NOT NULL,
    event_type ENUM('anesthesia_start', 'incision', 'main_procedure', 'closure', 'wake_up', 'completion'),
    event_time DATETIME NOT NULL,
    notes TEXT,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (surgery_id) REFERENCES surgeries(id),
    INDEX idx_surgery_time (surgery_id, event_time)
);

-- 等待队列表
CREATE TABLE waiting_list (
    id VARCHAR(36) PRIMARY KEY,
    patient_id VARCHAR(36) NOT NULL,
    surgery_id VARCHAR(36) NOT NULL,
    priority_score INT,
    wait_days INT DEFAULT 0,
    status ENUM('active', 'scheduled', 'removed'),
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_priority (priority_score),
    INDEX idx_status (status)
);

-- 系统配置表
CREATE TABLE system_config (
    key VARCHAR(100) PRIMARY KEY,
    value TEXT,
    description TEXT,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 插入初始配置
INSERT INTO system_config (key, value, description) VALUES
('emergency_buffer_minutes', '60', '每天预留的急诊缓冲时间(分钟)'),
('overtime_threshold', '15', '触发预警的超时阈值(分钟)'),
('max_wait_days', '30', '患者最大等待天数,超过自动提升优先级'),
('block_duration', '60', '手术块时长(分钟)'),
('buffer_ratio', '0.15', '每个手术块的缓冲时间比例');

4. 实时监控与预警系统

4.1 实时数据采集

import time
from datetime import datetime, timedelta
import threading

class SurgeryMonitor:
    def __init__(self, db_connection):
        self.db = db_connection
        self.running = False
    
    def start_monitoring(self):
        """启动监控线程"""
        self.running = True
        monitor_thread = threading.Thread(target=self._monitor_loop)
        monitor_thread.daemon = True
        monitor_thread.start()
        print("实时监控已启动")
    
    def _monitor_loop(self):
        """监控循环"""
        while self.running:
            try:
                # 检查即将开始的手术
                self._check_upcoming_surgeries()
                
                # 检查进行中的手术
                self._check_in_progress_surgeries()
                
                # 检查等待队列
                self._check_waiting_list()
                
                # 检查设备状态
                self._check_equipment_status()
                
                time.sleep(60)  # 每分钟检查一次
                
            except Exception as e:
                print(f"监控错误: {e}")
                time.sleep(300)  # 出错后等待5分钟
    
    def _check_upcoming_surgeries(self):
        """检查即将开始的手术(30分钟内)"""
        now = datetime.now()
        threshold = now + timedelta(minutes=30)
        
        query = """
        SELECT s.*, r.room_name, p.name as patient_name, p.bed_number
        FROM surgery_schedule ss
        JOIN surgeries s ON ss.surgery_id = s.id
        JOIN operating_rooms r ON ss.room_id = r.id
        JOIN patients p ON s.patient_id = p.id
        WHERE ss.scheduled_start BETWEEN %s AND %s
        AND ss.status = 'scheduled'
        """
        
        self.db.execute(query, (now, threshold))
        upcoming = self.db.fetchall()
        
        for surgery in upcoming:
            # 检查患者是否已到达术前准备区
            if not self._is_patient_ready(surgery['patient_id']):
                self._send_alert(
                    f"患者 {surgery['patient_name']} 未到达术前准备区",
                    "warning",
                    surgery['id']
                )
            
            # 检查医生是否已签到
            if not self._is_doctor_checked_in(surgery['surgeon_id']):
                self._send_alert(
                    f"主刀医生 {surgery['surgeon_id']} 未签到",
                    "error",
                    surgery['id']
                )
    
    def _check_in_progress_surgeries(self):
        """检查进行中的手术是否超时"""
        query = """
        SELECT ss.*, s.estimated_duration, s.surgery_name
        FROM surgery_schedule ss
        JOIN surgeries s ON ss.surgery_id = s.id
        WHERE ss.status = 'in_progress'
        AND ss.scheduled_start < %s
        """
        
        self.db.execute(query, (datetime.now() - timedelta(minutes=5),))
        in_progress = self.db.fetchall()
        
        for surgery in in_progress:
            elapsed = (datetime.now() - surgery['scheduled_start']).total_seconds() / 60
            if elapsed > surgery['estimated_duration'] + 15:  # 超时15分钟
                self._send_alert(
                    f"手术 {surgery['surgery_name']} 已超时 {int(elapsed - surgery['estimated_duration'])} 分钟",
                    "error",
                    surgery['id']
                )
    
    def _check_waiting_list(self):
        """检查等待队列"""
        query = """
        SELECT w.*, s.surgery_name, p.name as patient_name, DATEDIFF(NOW(), w.created_at) as wait_days
        FROM waiting_list w
        JOIN surgeries s ON w.surgery_id = s.id
        JOIN patients p ON s.patient_id = p.id
        WHERE w.status = 'active'
        AND DATEDIFF(NOW(), w.created_at) >= %s
        ORDER BY w.priority_score DESC
        """
        
        max_wait = self._get_config('max_wait_days', 30)
        self.db.execute(query, (max_wait,))
        long_waiters = self.db.fetchall()
        
        for patient in long_waiters:
            self._send_alert(
                f"患者 {patient['patient_name']} 等待超过{max_wait}天,需优先安排",
                "warning",
                patient['surgery_id']
            )
    
    def _check_equipment_status(self):
        """检查设备状态"""
        query = """
        SELECT * FROM equipment 
        WHERE status IN ('maintenance', 'broken')
        OR last_maintenance < DATE_SUB(NOW(), INTERVAL 30 DAY)
        """
        
        self.db.execute(query)
        equipment_issues = self.db.fetchall()
        
        for equipment in equipment_issues:
            self._send_alert(
                f"设备 {equipment['name']} 状态异常: {equipment['status']}",
                "warning",
                equipment['id']
            )
    
    def _send_alert(self, message, level, reference_id):
        """发送预警"""
        # 写入数据库
        query = """
        INSERT INTO alerts (message, level, reference_id, created_at)
        VALUES (%s, %s, %s, NOW())
        """
        self.db.execute(query, (message, level, reference_id))
        
        # 推送通知(邮件、短信、APP推送)
        if level == 'error':
            self._push_notification(message, ['排程员', '科室主任'])
        elif level == 'warning':
            self._push_notification(message, ['排程员'])
    
    def _push_notification(self, message, recipients):
        """推送通知(示例)"""
        # 这里可以集成企业微信、钉钉、短信网关等
        print(f"[{datetime.now()}] 推送通知: {message} -> {recipients}")

# 使用示例
monitor = SurgeryMonitor(db_connection)
monitor.start_monitoring()

4.2 预警规则配置

# 预警规则配置表
ALERT_RULES = {
    'surgery_overtime': {
        'condition': lambda surgery: surgery['elapsed'] > surgery['estimated'] + 15,
        'level': 'error',
        'message': lambda surgery: f"手术 {surgery['name']} 超时 {surgery['elapsed'] - surgery['estimated']} 分钟",
        'recipients': ['排程员', '科室主任', '麻醉科主任']
    },
    'patient_long_wait': {
        'condition': lambda patient: patient['wait_days'] > 30,
        'level': 'warning',
        'message': lambda patient: f"患者 {patient['name']} 等待超过30天",
        'recipients': ['排程员', '科室主任']
    },
    'doctor_not_arrived': {
        'condition': lambda surgery: surgery['minutes_to_start'] <= 15 and not surgery['doctor_checked_in'],
        'level': 'error',
        'message': lambda surgery: f"手术 {surgery['name']} 15分钟后开始,医生未签到",
        'recipients': ['排程员', '医生']
    },
    'equipment_unavailable': {
        'condition': lambda equipment: equipment['status'] != 'available',
        'level': 'warning',
        'message': lambda equipment: f"设备 {equipment['name']} 不可用: {equipment['status']}",
        'recipients': ['设备管理员', '排程员']
    },
    'room_conflict': {
        'condition': lambda schedule: schedule['overlap_count'] > 0,
        'level': 'error',
        'message': lambda schedule: f"手术室 {schedule['room']} 存在时间冲突",
        'recipients': ['排程员']
    }
}

5. 用户界面设计

5.1 排程员界面(Web端)

<!DOCTYPE html>
<html>
<head>
    <title>智能手术排程系统</title>
    <style>
        .schedule-grid {
            display: grid;
            grid-template-columns: 100px repeat(5, 1fr);
            gap: 2px;
            background: #f0f0f0;
            padding: 2px;
        }
        .room-header {
            background: #2c3e50;
            color: white;
            padding: 10px;
            text-align: center;
            font-weight: bold;
        }
        .time-slot {
            background: white;
            padding: 8px;
            min-height: 60px;
            border: 1px solid #ddd;
            position: relative;
        }
        .surgery-block {
            background: #3498db;
            color: white;
            padding: 5px;
            border-radius: 3px;
            font-size: 12px;
            margin: 2px 0;
            cursor: pointer;
            position: relative;
        }
        .surgery-block.emergency {
            background: #e74c3c;
            animation: pulse 2s infinite;
        }
        .surgery-block.completed {
            background: #27ae60;
        }
        .surgery-block.delayed {
            background: #f39c12;
        }
        @keyframes pulse {
            0% { opacity: 1; }
            50% { opacity: 0.7; }
            100% { opacity: 1; }
        }
        .alert-panel {
            position: fixed;
            top: 20px;
            right: 20px;
            width: 300px;
            max-height: 400px;
            overflow-y: auto;
            background: white;
            border: 1px solid #ddd;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            z-index: 1000;
        }
        .alert-item {
            padding: 10px;
            border-bottom: 1px solid #eee;
            cursor: pointer;
        }
        .alert-item.error { border-left: 4px solid #e74c3c; }
        .alert-item.warning { border-left: 4px solid #f39c12; }
        .alert-item:hover { background: #f5f5f5; }
        .stats-panel {
            display: flex;
            justify-content: space-around;
            margin: 20px 0;
            padding: 15px;
            background: #ecf0f1;
            border-radius: 5px;
        }
        .stat-item {
            text-align: center;
        }
        .stat-value {
            font-size: 24px;
            font-weight: bold;
            color: #2c3e50;
        }
        .stat-label {
            font-size: 12px;
            color: #7f8c8d;
        }
        .control-panel {
            margin: 20px 0;
            padding: 15px;
            background: #fff;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        button {
            background: #3498db;
            color: white;
            border: none;
            padding: 8px 16px;
            border-radius: 3px;
            cursor: pointer;
            margin: 2px;
        }
        button:hover {
            background: #2980b9;
        }
        button.danger {
            background: #e74c3c;
        }
        button.danger:hover {
            background: #c0392b;
        }
        .modal {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.5);
            z-index: 2000;
            justify-content: center;
            align-items: center;
        }
        .modal-content {
            background: white;
            padding: 20px;
            border-radius: 5px;
            width: 500px;
            max-height: 80vh;
            overflow-y: auto;
        }
        .form-group {
            margin: 10px 0;
        }
        .form-group label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        .form-group input, .form-group select, .form-group textarea {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 3px;
        }
        .waiting-list {
            max-height: 300px;
            overflow-y: auto;
            border: 1px solid #ddd;
            margin: 10px 0;
        }
        .waiting-item {
            padding: 10px;
            border-bottom: 1px solid #eee;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .priority-badge {
            background: #e74c3c;
            color: white;
            padding: 2px 8px;
            border-radius: 10px;
            font-size: 12px;
        }
    </style>
</head>
<body>
    <h1>智能手术排程系统</h1>
    
    <!-- 统计面板 -->
    <div class="stats-panel">
        <div class="stat-item">
            <div class="stat-value" id="today-surgeries">0</div>
            <div class="stat-label">今日手术</div>
        </div>
        <div class="stat-item">
            <div class="stat-value" id="utilization-rate">0%</div>
            <div class="stat-label">手术室利用率</div>
        </div>
        <div class="stat-item">
            <div class="stat-value" id="avg-wait">0</div>
            <div class="stat-label">平均等待(天)</div>
        </div>
        <div class="stat-item">
            <div class="stat-value" id="pending-count">0</div>
            <div class="stat-label">待排程</div>
        </div>
    </div>

    <!-- 控制面板 -->
    <div class="control-panel">
        <button onclick="autoSchedule()">智能排程</button>
        <button onclick="showEmergencyModal()">插入急诊</button>
        <button onclick="showAdjustModal()">调整排程</button>
        <button onclick="exportSchedule()">导出报表</button>
        <button class="danger" onclick="emergencyReset()">紧急重置</button>
    </div>

    <!-- 手术排程表格 -->
    <div id="schedule-grid" class="schedule-grid">
        <!-- 动态生成 -->
    </div>

    <!-- 等待队列 -->
    <h3>等待队列</h3>
    <div id="waiting-list" class="waiting-list">
        <!-- 动态生成 -->
    </div>

    <!-- 预警面板 -->
    <div id="alert-panel" class="alert-panel">
        <div style="padding: 10px; background: #2c3e50; color: white; font-weight: bold;">
            实时预警
        </div>
        <div id="alert-list">
            <!-- 动态生成 -->
        </div>
    </div>

    <!-- 模态框:急诊插入 -->
    <div id="emergency-modal" class="modal">
        <div class="modal-content">
            <h2>插入急诊手术</h2>
            <div class="form-group">
                <label>患者姓名</label>
                <input type="text" id="emergency-patient">
            </div>
            <div class="form-group">
                <label>手术名称</label>
                <input type="text" id="emergency-surgery">
            </div>
            <div class="form-group">
                <label>紧急程度</label>
                <select id="emergency-level">
                    <option value="1">危急(立即)</option>
                    <option value="2">紧急(2小时内)</option>
                    <option value="3">急(6小时内)</option>
                </select>
            </div>
            <div class="form-group">
                <label>预估时长(分钟)</label>
                <input type="number" id="emergency-duration">
            </div>
            <button onclick="insertEmergency()">确认插入</button>
            <button onclick="closeModal('emergency-modal')">取消</button>
        </div>
    </div>

    <!-- 模态框:调整排程 -->
    <div id="adjust-modal" class="modal">
        <div class="modal-content">
            <h2>调整排程</h2>
            <div class="form-group">
                <label>选择手术</label>
                <select id="adjust-surgery"></select>
            </div>
            <div class="form-group">
                <label>新手术室</label>
                <select id="adjust-room"></select>
            </div>
            <div class="form-group">
                <label>新时间</label>
                <input type="datetime-local" id="adjust-time">
            </div>
            <div class="form-group">
                <label>调整原因</label>
                <textarea id="adjust-reason"></textarea>
            </div>
            <button onclick="confirmAdjust()">确认调整</button>
            <button onclick="closeModal('adjust-modal')">取消</button>
        </div>
    </div>

    <script>
        // 模拟数据
        let scheduleData = [];
        let waitingList = [];
        let alerts = [];

        // 初始化
        function init() {
            loadSchedule();
            loadWaitingList();
            loadAlerts();
            updateStats();
            renderSchedule();
            renderWaitingList();
            renderAlerts();
            
            // 每30秒刷新一次
            setInterval(() => {
                loadSchedule();
                loadAlerts();
                updateStats();
                renderSchedule();
                renderAlerts();
            }, 30000);
        }

        // 加载排程数据
        function loadSchedule() {
            // 这里应该调用API获取真实数据
            // 模拟数据
            scheduleData = [
                {
                    id: 'S001',
                    patient: '张三',
                    surgery: '胆囊切除',
                    room: 'OR1',
                    start: '08:00',
                    end: '10:00',
                    status: 'scheduled',
                    doctor: '张医生',
                    isEmergency: false
                },
                {
                    id: 'S002',
                    patient: '李四',
                    surgery: '关节置换',
                    room: 'OR1',
                    start: '10:30',
                    end: '12:30',
                    status: 'in_progress',
                    doctor: '李医生',
                    isEmergency: false
                }
            ];
        }

        // 加载等待队列
        function loadWaitingList() {
            waitingList = [
                { id: 'W001', patient: '王五', surgery: '阑尾切除', waitDays: 25, priority: 85 },
                { id: 'W002', patient: '赵六', surgery: '疝气修补', waitDays: 18, priority: 70 },
                { id: 'W003', patient: '钱七', surgery: '甲状腺切除', waitDays: 12, priority: 60 }
            ];
        }

        // 加载预警
        function loadAlerts() {
            alerts = [
                { id: 'A001', message: 'OR1手术超时20分钟', level: 'error', time: '09:45' },
                { id: 'A002', message: '患者王五等待超过25天', level: 'warning', time: '08:00' }
            ];
        }

        // 更新统计
        function updateStats() {
            document.getElementById('today-surgeries').textContent = scheduleData.length;
            document.getElementById('utilization-rate').textContent = '85%';
            document.getElementById('avg-wait').textContent = '18';
            document.getElementById('pending-count').textContent = waitingList.length;
        }

        // 渲染排程表格
        function renderSchedule() {
            const grid = document.getElementById('schedule-grid');
            grid.innerHTML = '';
            
            // 表头
            const timeHeader = document.createElement('div');
            timeHeader.className = 'room-header';
            timeHeader.textContent = '时间';
            grid.appendChild(timeHeader);
            
            const rooms = ['OR1', 'OR2', 'OR3', 'OR4', 'OR5'];
            rooms.forEach(room => {
                const roomHeader = document.createElement('div');
                roomHeader.className = 'room-header';
                roomHeader.textContent = room;
                grid.appendChild(roomHeader);
            });
            
            // 时间行(每30分钟一行)
            for (let hour = 8; hour < 17; hour++) {
                for (let min = 0; min < 60; min += 30) {
                    const timeCell = document.createElement('div');
                    timeCell.className = 'time-slot';
                    timeCell.textContent = `${hour.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}`;
                    grid.appendChild(timeCell);
                    
                    // 每个手术室的单元格
                    rooms.forEach(room => {
                        const cell = document.createElement('div');
                        cell.className = 'time-slot';
                        
                        // 查找该时间段该手术室的手术
                        const surgery = scheduleData.find(s => 
                            s.room === room && 
                            s.start === `${hour.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}`
                        );
                        
                        if (surgery) {
                            const block = document.createElement('div');
                            block.className = `surgery-block ${surgery.status} ${surgery.isEmergency ? 'emergency' : ''}`;
                            block.innerHTML = `
                                <strong>${surgery.patient}</strong><br>
                                ${surgery.surgery}<br>
                                ${surgery.doctor}
                            `;
                            block.onclick = () => showSurgeryDetail(surgery);
                            cell.appendChild(block);
                        }
                        
                        grid.appendChild(cell);
                    });
                }
            }
        }

        // 渲染等待队列
        function renderWaitingList() {
            const list = document.getElementById('waiting-list');
            list.innerHTML = '';
            
            waitingList.forEach(item => {
                const div = document.createElement('div');
                div.className = 'waiting-item';
                div.innerHTML = `
                    <div>
                        <strong>${item.patient}</strong> - ${item.surgery}<br>
                        <small>等待 ${item.waitDays} 天</small>
                    </div>
                    <span class="priority-badge">优先级 ${item.priority}</span>
                `;
                list.appendChild(div);
            });
        }

        // 渲染预警
        function renderAlerts() {
            const list = document.getElementById('alert-list');
            list.innerHTML = '';
            
            alerts.forEach(alert => {
                const div = document.createElement('div');
                div.className = `alert-item ${alert.level}`;
                div.innerHTML = `
                    <strong>${alert.time}</strong> - ${alert.message}
                `;
                div.onclick = () => handleAlert(alert);
                list.appendChild(div);
            });
        }

        // 模态框控制
        function showEmergencyModal() {
            document.getElementById('emergency-modal').style.display = 'flex';
        }
        
        function showAdjustModal() {
            // 填充下拉框
            const surgerySelect = document.getElementById('adjust-surgery');
            surgerySelect.innerHTML = '';
            scheduleData.forEach(s => {
                const option = document.createElement('option');
                option.value = s.id;
                option.textContent = `${s.patient} - ${s.surgery}`;
                surgerySelect.appendChild(option);
            });
            
            const roomSelect = document.getElementById('adjust-room');
            roomSelect.innerHTML = '';
            ['OR1', 'OR2', 'OR3', 'OR4', 'OR5'].forEach(room => {
                const option = document.createElement('option');
                option.value = room;
                option.textContent = room;
                roomSelect.appendChild(option);
            });
            
            document.getElementById('adjust-modal').style.display = 'flex';
        }
        
        function closeModal(modalId) {
            document.getElementById(modalId).style.display = 'none';
        }

        // 操作函数
        function autoSchedule() {
            alert('正在运行智能排程算法,请稍候...');
            // 这里调用后端API
            setTimeout(() => {
                alert('排程完成!已优化手术室利用率,减少患者等待时间。');
                loadSchedule();
                renderSchedule();
            }, 2000);
        }

        function insertEmergency() {
            const patient = document.getElementById('emergency-patient').value;
            const surgery = document.getElementById('emergency-surgery').value;
            const level = document.getElementById('emergency-level').value;
            const duration = document.getElementById('emergency-duration').value;
            
            if (!patient || !surgery || !duration) {
                alert('请填写完整信息');
                return;
            }
            
            alert(`急诊手术已插入:${patient} - ${surgery}(紧急程度${level})`);
            closeModal('emergency-modal');
            // 调用后端API
        }

        function confirmAdjust() {
            const surgeryId = document.getElementById('adjust-surgery').value;
            const newRoom = document.getElementById('adjust-room').value;
            const newTime = document.getElementById('adjust-time').value;
            const reason = document.getElementById('adjust-reason').value;
            
            if (!surgeryId || !newRoom || !newTime || !reason) {
                alert('请填写完整信息');
                return;
            }
            
            alert(`排程已调整:手术 ${surgeryId} 移至 ${newRoom} ${newTime}\n原因:${reason}`);
            closeModal('adjust-modal');
            // 调用后端API
        }

        function exportSchedule() {
            alert('正在生成排程报表...');
            // 这里生成PDF或Excel
        }

        function emergencyReset() {
            if (confirm('确定要紧急重置所有排程吗?此操作不可恢复!')) {
                alert('排程已重置,请重新进行智能排程。');
                // 调用后端API
            }
        }

        function showSurgeryDetail(surgery) {
            alert(`手术详情:
患者:${surgery.patient}
手术:${surgery.surgery}
医生:${surgery.doctor}
手术室:${surgery.room}
时间:${surgery.start} - ${surgery.end}
状态:${surgery.status}
${surgery.isEmergency ? '【急诊】' : ''}`);
        }

        function handleAlert(alert) {
            if (confirm(`${alert.message}\n\n是否标记为已处理?`)) {
                // 移除预警
                alerts = alerts.filter(a => a.id !== alert.id);
                renderAlerts();
                // 调用后端API标记已处理
            }
        }

        // 页面加载完成后初始化
        window.onload = init;
    </script>
</body>
</html>

实施步骤:从规划到落地

第一阶段:需求分析与准备(1-2周)

1. 成立项目小组

  • 项目负责人:医院副院长或医务科主任
  • 核心成员:手术室护士长、信息科技术负责人、麻醉科主任、外科主任
  • 用户代表:各科室排程员、手术室护士

2. 现状调研

  • 访谈所有相关人员,了解当前排程流程和痛点
  • 收集过去6个月的手术排程数据(Excel表格、纸质记录)
  • 统计关键指标:
    • 平均手术室利用率(目标:>85%)
    • 患者平均等待时间(目标:天)
    • 急诊手术响应时间(目标:<30分钟)
    • 手术取消率(目标:%)

3. 需求确认

  • 明确系统功能边界
  • 确定优先级:先解决最痛的1-2个问题
  • 制定成功标准(KPI)

第二阶段:系统选型与设计(2-3周)

1. 技术方案选择

  • 自研 vs 采购:根据预算和IT能力决定
    • 采购:选择成熟的HIS厂商或专业排程软件(如:易惠科技、京颐科技)
    • 自研:需要3-5名开发人员,3-6个月开发周期

2. 数据接口开发

  • 与HIS系统对接,获取患者信息、医嘱
  • 与电子病历系统对接,获取术前检查结果
  • 与医生排班系统对接,获取医生可用性

3. 原型设计

  • 设计排程界面(参考上文HTML示例)
  • 设计移动端界面(医生查询、护士执行)
  • 设计管理报表(利用率分析、等待时间分析)

第三阶段:试点运行(4-6周)

1. 选择试点科室

  • 建议选择:普外科或骨科(手术量适中,流程相对规范)
  • 试点手术室:1-2间

2. 并行运行

  • 新旧系统同时运行1-2周
  • 每天对比排程结果,记录差异
  • 收集用户反馈,快速迭代

3. 培训

  • 排程员培训:系统操作、异常处理
  • 医生培训:如何查询排程、如何申请调整
  • 护士培训:如何执行排程、如何记录事件

第四阶段:全面推广(4-8周)

1. 分科室推广

  • 按科室逐步推广,每2-3天增加一个科室
  • 每个科室推广前进行针对性培训

2. 数据监控

  • 每日监控关键指标
  • 每周召开项目例会,解决问题

3. 持续优化

  • 根据运行数据调整算法参数
  • 增加新的优化规则

第五阶段:持续改进(长期)

1. 数据分析

  • 每月生成运营分析报告
  • 识别新的优化机会

2. 算法升级

  • 引入机器学习,提高时长预估准确性
  • 考虑更多约束条件(如:患者特殊需求、医生疲劳度)

3. 功能扩展

  • 移动端APP
  • 患者自助查询
  • 与医保系统对接

关键成功因素与风险控制

成功因素

  1. 高层支持:必须获得院长/分管院长的明确支持
  2. 用户参与:排程员和医生的深度参与是关键
  3. 数据质量:历史数据的准确性和完整性直接影响算法效果
  4. 分步实施:避免一次性大范围上线,降低风险
  5. 持续培训:系统上线后持续培训和答疑

风险控制

风险1:医生抵触

  • 表现:医生不信任算法,坚持人工调整
  • 对策:让医生参与算法设计,展示数据证明算法效果

风险2:数据质量问题

  • 表现:历史数据不准确,导致预估偏差大
  • 对策:先清洗数据,建立数据质量监控机制

风险3:系统故障

  • 表现:系统宕机导致排程混乱
  • 对策:保留纸质备份流程,建立应急预案

风险4:急诊处理不当

  • 表现:急诊插入导致排程大面积延误
  • 对策:预留充足的急诊缓冲时间,建立快速响应机制

效果评估与持续优化

评估指标

效率指标

  • 手术室利用率:目标>85%
  • 台日手术量:目标提升20%
  • 连台手术间隔时间:目标<30分钟

质量指标

  • 患者平均等待时间:目标天
  • 手术取消率:目标%
  • 急诊手术响应时间:目标<30分钟

满意度指标

  • 医生满意度:>85%
  • 护士满意度:>85%
  • 患者满意度:>90%

持续优化方法

1. PDCA循环

  • Plan:每月设定优化目标
  • Do:实施优化措施
  • Check:评估效果
  • Act:标准化成功经验

2. A/B测试

  • 对比不同排程策略的效果
  • 例如:块状排程 vs 精确排程

3. 用户反馈机制

  • 建立反馈渠道(系统内反馈按钮)
  • 每月收集改进建议

结论

优化医院手术排程流程是一个系统工程,需要技术、管理、文化三方面的配合。通过建立数据驱动的智能排程系统,可以显著减少患者等待时间,提高手术室利用率,最终提升医院整体运营效率。

关键成功要点:

  1. 先标准化再自动化:先理顺流程,再用系统固化
  2. 算法+人工:算法提供最优建议,人工保留最终决策权
  3. 持续改进:系统上线只是开始,需要持续优化
  4. 以人为本:所有优化必须以患者安全和医疗质量为前提

通过本文提供的详细策略和实施指南,医院可以循序渐进地推进手术排程优化,在3-6个月内看到明显成效,1-2年内建立成熟的智能排程体系。