免疫治疗门诊动线优化:Go离散事件仿真(DES)从“常规排队”到“ResusBay挤兑”的技术全解(上)

免疫治疗门诊动线优化:Go离散事件仿真(DES)从“常规排队”到“ResusBay挤兑”的技术全解(上)
在这里插入图片描述
面向读者:算法/后端/数据工程/运筹优化/医疗信息化
关键词:离散事件仿真(DES)、队列系统、资源竞争、预约模板、药房预配、irAE、抢救床位(ResusBay)、尾部风险(P90/超时率)、Go

免疫检查点抑制剂(ICI)是临床上的重大进展,但在医院运营视角下,它把输注中心变成了一个典型的复杂系统:
随机到达 + 随机服务时长 + 多站点串联 + 多资源并发 + 低概率高冲击风险事件(irAE)
如果你只靠经验改流程,常常会陷入“改了这里堵了那里”;而工程上更稳的方法,是用 **离散事件仿真(DES)**在虚拟世界里“跑一天、一周、一年”,对比策略组合,找出瓶颈与最优干预点。

这篇文章从零到一搭建一个“可跑”的 Go DES 仿真框架,并逐步扩展到免疫治疗的关键真实因素:

  • 患者分型(短/长/联合输注)
  • 预约模板(均匀 vs 长短错峰)
  • 药房策略(按需 vs 预配)
  • irAE 突发事件(分级)
  • **ResusBay(抢救/留观床位)**造成的“系统挤兑”:Severe 患者在输注椅上占位等待转运 → 椅位周转下降 → 全局排队雪崩 → P90/超时率恶化

1. 业务抽象:把输注中心变成“可计算的系统”

1.1 站点与路径(流程动线的图模型)

我们先从最常见的 ICI 门诊输注日路径抽象:

SignIn → Lab → Doctor → Pharmacy → Infusion → Observation → Done

这是一条典型串行路径,但注意两点:

  1. 串行 ≠ 简单:每个站点都有队列与资源容量(Cap),并发服务(多台“服务器”)
  2. 真正复杂性来自 波动:到达波动(预约聚集)、服务时间波动(医生评估 irAE)、以及 突发事件(irAE → Resus)

1.2 “动线优化”在流程场景 A 中到底优化什么?

我们聚焦几个最常用 KPI:

  • Avg / P90 等待时间(按站点拆分:医生/药房/输注/观察/Resus)
  • Avg / P90 总逗留时间(从到达到离开)
  • 超时率:例如总逗留 > 240 分钟(>4h)
  • 资源利用率:药房、医生、输注椅、观察椅、Resus 床位
技术上:只优化平均值通常会误导。医疗系统最敏感的是“尾部风险”(P90/P95/超时率),因为它对应投诉、加班、延迟、与临床安全风险。

2. 为什么选离散事件仿真(DES):而不是简单排队公式

2.1 DES 的核心思想:时间只在“事件发生”时跳动

离散事件仿真把系统演化看成一串事件:

  • 到达(Arrival)
  • 开始服务/结束服务(ServiceDone)
  • 突发事件(irAE)
  • 入队事件(QueueEnter)
  • 特殊释放事件(ReleaseInfusion:Severe 转入 Resus 时释放椅位)

系统时间从 t 跳到 next_event_time,因此复杂系统也能高效模拟。

2.2 为什么不直接用“每站点 M/M/c”?

因为现实里存在:

  • 不同患者类别(服务时长分布不同)
  • 串联系统(上一站结束才到下一站)
  • 调度策略(预约模板改变到达分布)
  • 优先级队列(紧急插队)
  • 资源耦合(Severe 把输注椅“卡死”直到 Resus 有空)

这些让解析解非常难用,但 DES 只需在事件逻辑里表达即可。


3. 模型工程化设计:数据结构与事件机制

3.1 仿真状态(State)要包含什么?

最小可用的仿真状态包括:

  • Patients[]:每个患者的到达时间、各站点开始/结束/等待、服务时长、类别、irAE 状态
  • Queues[Station]:每站点一个 FIFO 队列
  • Resources[Station]:容量 Cap、当前 InUse、BusyMin(用于利用率)
  • Events:事件优先队列(最小堆)

3.2 为什么事件队列用最小堆?

事件队列的操作是:

  • push:安排未来事件
  • pop:取最早事件执行

最小堆让每次 push/pop 都是 O(log N),非常适合“事件驱动”的仿真。

3.3 BusyMin(利用率)怎么计算才不骗自己?

严格方法:在每次资源占用/释放的瞬间更新占用区间。
为了保持代码“最小可跑”,我们采用常见的简化方式:

  • 每当开始服务,BusyMin += dur
  • Severe 导致“额外占椅”,用 ReleaseInfusion 事件把额外占用时间补回 Infusion.BusyMin

这让利用率在策略对比上非常稳定且可解释;若要做精确曲线(按分钟利用率热图),可以再加时间片统计(后面会给扩展建议)。


4. 随机性建模:分布选择与参数化

4.1 为什么常用对数正态(LogNormal)?

医疗服务时长经常呈现:

  • 下界 > 0(不可能负时间)
  • 右偏长尾(少数患者非常久)

对数正态正好符合这个形态。
因此我们用:

  • SignIn、Lab、Doctor、Pharmacy、Observation、Resus:LogNormal + clamp 边界
  • Infusion:按患者类型给不同均值与方差(可视为截断正态/常数+噪声)

4.2 参数从哪来?

实际项目中你会做三层:

  1. 经验初值(像本文)用于搭框架
  2. 用历史数据拟合(LogNormal/Gamma/Weibull),并做 KS 检验
  3. 用“反推校准”:使仿真输出的均值、分位数、资源利用率与真实对齐
技术提示:校准(calibration)比“分布选型”更重要。选错分布但校准对了,模型仍有决策价值;反之则可能误导。

5. 策略建模:预约模板与药房预配怎么“落到代码里”

5.1 预约模板 = 到达时间分布的控制

我们实现两个典型策略:

  • Uniform:把到达均匀铺在 6 小时窗口(0~360min),加小扰动
  • Staggered:短输注(TypeShort)偏上午,长/联合偏下午(180~360min),加扰动

这本质上是改变系统的“输入过程”,改变拥堵峰值。

5.2 药房预配 = 缩短 Pharmacy 服务时间(同时引入成本问题)

在最小模型中,我们把预配简化为:

  • PharmacyPre=true 时,Pharmacy 服务时间视作 0

现实里你会引入:

  • 预配提前量(比如提前 30min 开始配)
  • 取消/改期概率 → 产生浪费成本
  • 稳定性窗口 → 超过窗口失效(需要重配)

这些都可以在 DES 中继续扩展(后面给扩展蓝图)。


6. 免疫治疗独有冲击:irAE 分级与 ResusBay 挤兑

6.1 为什么 Severe irAE 必须单独建模?

轻中度 irAE 通常导致:

  • 观察时间延长
  • 可能额外医生评估

但 Severe irAE 引入关键耦合:

  • 需要 Resus(抢救床位)
  • Resus 可能无空位
  • 患者在输注区“占位等待转运”
  • 输注椅位不能释放 → 后续患者输注排队、等待上升
  • 最后表现为尾部变厚(P90、超时率上升)

这个“占位等待”就是医疗系统里非常真实的挤兑机制

6.2 关键实现点:什么时候释放输注椅?

如果你在 irAE 发生那一刻就释放椅位,会低估真实拥堵。
我们采用更真实逻辑:

  • Severe irAE 发生后:仍占椅做初步处置/转运准备(prep 15~30min)
  • 准备完入 Resus 队列
  • 当 Resus 真正开始服务(即拿到床位)时,再触发 EvReleaseInfusion 释放椅位

这一步是模型“像现实”的关键。


7. 完整可运行 Go 代码(含 ResusBay + irAE)

✅ 复制到 main.go
go run main.go 即可
✅ 输出包含 Avg/P90/超时/各站等待/Resus 等待与利用率/Severe 比例
你可以把参数(到达量、资源容量、irAE 概率)改成你院数据做仿真对比。
package main import("container/heap""fmt""math""math/rand""sort""strings""time")// ---------- 类型定义 ----------type PatientType intconst( TypeShort PatientType =iota TypeLong TypeCombo )type Station intconst( SignIn Station =iota Lab Doctor Pharmacy Infusion Observation Resus // 新增:抢救/留观床位 Done )func(s Station)String()string{ switch s { case SignIn:return"SignIn"case Lab:return"Lab"case Doctor:return"Doctor"case Pharmacy:return"Pharmacy"case Infusion:return"Infusion"case Observation:return"Observation"case Resus:return"Resus"default:return"Done"}}type IrAEGrade intconst( IrAENone IrAEGrade =iota IrAEMild IrAEModerate IrAESevere )func(g IrAEGrade)String()string{ switch g { case IrAEMild:return"Mild"case IrAEModerate:return"Moderate"case IrAESevere:return"Severe"default:return"None"}}type Patient struct{  ID int PType PatientType Arrive float64 Start map[Station]float64 End map[Station]float64 Wait map[Station]float64 ServiceDur map[Station]float64// irAE IrAE IrAEGrade IrAEAt float64 IrAEFired bool// Severe 专用:椅位“占位等待转运” InfusionInterrupted bool InfusionReleased bool InfusionPlannedEnd float64// 原计划输注结束 InfusionHoldStartTime float64// 原计划结束时刻(开始“额外占位”计时点)}type EventType intconst( EvArrival EventType =iota EvServiceDone EvIrAE EvQueueEnter // 在某时刻进入某站点队列 EvReleaseInfusion // 在某时刻释放输注椅)type Event struct{  T float64 Type EventType PID int At Station index int}type EventPQ []*Event func(pq EventPQ)Len()int{ returnlen(pq)}func(pq EventPQ)Less(i, j int)bool{ return pq[i].T < pq[j].T }func(pq EventPQ)Swap(i, j int){  pq[i], pq[j]= pq[j], pq[i] pq[i].index, pq[j].index = i, j }func(pq *EventPQ)Push(x any){  e := x.(*Event) e.index =len(*pq)*pq =append(*pq, e)}func(pq *EventPQ)Pop() any {  old :=*pq n :=len(old) e := old[n-1]*pq = old[:n-1]return e }// ---------- 队列/资源 ----------type Queue []intfunc(q *Queue)Enq(pid int){ *q =append(*q, pid)}func(q *Queue)Deq()(int,bool){ iflen(*q)==0{ return0,false} pid :=(*q)[0]*q =(*q)[1:]return pid,true}type Resource struct{  Cap int InUse int BusyMin float64}// ---------- 仿真 ----------type Sim struct{  DayMinutes float64 Rand *rand.Rand Patients []*Patient Events EventPQ Queues map[Station]*Queue UrgentQueues map[Station]*Queue Resources map[Station]*Reso

Read more

【Linux】mmap文件内存映射

【Linux】mmap文件内存映射

📝前言: 这篇文章我们来讲讲Linux——mmap 1. mmap介绍 2. mmap接口介绍 3. mmap使用示例 🎬个人简介:努力学习ing 📋个人专栏:Linux 🎀ZEEKLOG主页 愚润求学 🌄其他专栏:C++学习笔记,C语言入门基础,python入门基础,C++刷题专栏 这里写目录标题 * 一,mmap介绍 * 1. 基本介绍 * 2. mmap的优势 * 二,接口介绍 * 1. mmap建立映射 * 2. munmap取消映射 * 三,使用示例 * 1. 写入映射 * 2. 读取映射 * 3. 简单模拟实现malloc 一,mmap介绍 1. 基本介绍 允许用户空间程序将文件或设备的内容直接映射到进程的虚拟地址空间中。(直接建立虚拟内存到文件页缓存的映射关系) 2.

By Ne0inhk
Docker Compose Ul:不用记命令也能管容器!cpolar新手友好的远程管理技巧 get√

Docker Compose Ul:不用记命令也能管容器!cpolar新手友好的远程管理技巧 get√

Docker Compose UI 的主要作用是把 Docker Compose 的命令行操作变成可视化界面,通过点击、拖拽就能管理容器的启动、停止,查看实时日志,调整配置。它特别适合刚接触 Docker 的新手 —— 不用死记硬背docker-compose up这类命令,也适合团队协作场景,大家能直观看到服务依赖关系。优点很明显:操作门槛低,状态一目了然,改配置比直接改 YAML 文件快得多,能少踩很多命令行的坑。 实际用下来有几个小经验:部署时要确保 Docker 服务处于运行状态,不然面板可能识别不到容器;挂载目录的时候注意权限设置,不然可能出现容器无法访问文件的问题;如果是团队用,最好给不同成员设置操作权限,避免误删重要容器。 但它默认只能在局域网内使用,这会带来不少不便。比如程序员在家想远程查看公司服务器上的容器状态,根本登不进去;团队成员异地协作时,没法实时调整容器配置,只能靠电话沟通命令行操作,效率很低;万一服务器出问题,不在现场就没法及时重启服务,可能影响业务。 而 Docker Compose

By Ne0inhk
实战指南:将 OpenClaw 集成至飞书,构建自动化办公智能体

实战指南:将 OpenClaw 集成至飞书,构建自动化办公智能体

一、 前言 在企业自动化办公场景中,将强大的爬虫与自动化工具集成到即时通讯软件(IM)中是提高效率的关键 。OpenClaw(原 Moltbot)作为一款开源的抓取与自动化工具,通过与飞书(Feishu)机器人的集成,可以实现高效的信息推送、智能问答及任务自动化 。本文将详细介绍如何在 AWS 环境下完成这一集成过程. 如果还没在 AWS EC2 上部署 OpenClaw,可参考文章:OpenClaw(Moltbot) + Amazon Bedrock 构建企业级全能 Agent 实战指南 二、 基础环境配置 在开始集成前,请确保您已具备以下基础环境 : * 云基础设施:AWS 俄勒冈区域(Oregon),已创建 VPC、子网及安全组 。 * 计算资源:启动 AWS EC2 实例,推荐机型t4g.medium,运行

By Ne0inhk
全网最全Win10/11系统下WSL2+Ubuntu20.04的全流程安装指南(两种支持安装至 D 盘方式)

全网最全Win10/11系统下WSL2+Ubuntu20.04的全流程安装指南(两种支持安装至 D 盘方式)

前言 WSL2(Windows Subsystem for Linux 2)是 Windows 提供的一种轻量级 Linux 运行环境,具备完整的 Linux 内核,并支持更好的文件系统性能和兼容性。它允许用户在 Windows 系统中运行 Linux 命令行工具和应用程序,而无需安装虚拟机或双系统。 本教程将介绍 如何安装 WSL2 并将 Ubuntu-20.04 安装到 D 盘,涵盖 WSL2 的启用、Ubuntu 的下载与解压、WSL2 发行版的导入,以及普通用户的设置与安装验证。这是全网最全的 WSL2 安装与配置指南,参考了大量博客教程,并结合实践经验,整理出最实用、最详细的方法,适用于所有 Windows 10/11

By Ne0inhk