免疫治疗门诊动线优化: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

Clawdbot部署Qwen3:32B实操:解决‘gateway token missing’的三种Token注入方式对比

Clawdbot部署Qwen3:32B实操:解决‘gateway token missing’的三种Token注入方式对比 Clawdbot 是一个统一的 AI 代理网关与管理平台,旨在为开发者提供一个直观的界面来构建、部署和监控自主 AI 代理。通过集成的聊天界面、多模型支持和强大的扩展系统,Clawdbot 让 AI 代理的管理变得简单高效。 当你在 ZEEKLOG 星图镜像广场一键部署 Clawdbot 并集成本地运行的 qwen3:32b 模型后,大概率会遇到这样一个提示: disconnected (1008): unauthorized: gateway token missing (open a tokenized dashboard URL or paste token in Control UI settings) 这不是报错,也不是服务没起来—

By Ne0inhk
PostgreSQL 模式(SCHEMA)详解:数据库对象的命名空间管理

PostgreSQL 模式(SCHEMA)详解:数据库对象的命名空间管理

@[TOC](PostgreSQL 模式(SCHEMA)详解:数据库对象的命名空间管理) 🌺The Begin🌺点点关注,收藏不迷路🌺 一、模式(SCHEMA)概念解析 PostgreSQL中的模式(Schema)是数据库内部的一个命名空间,它包含表、视图、索引、序列、数据类型、函数、操作符等数据库对象。模式可以看作是数据库中的"文件夹",为数据库对象提供逻辑分组。 模式的核心特性: * 逻辑隔离:不同模式中的对象可以同名而不会冲突 * 权限控制:可以针对模式设置独立的访问权限 * 组织管理:将相关对象分组管理,提高可维护性 DatabaseSchema1Schema2Table1View1Function1 二、模式的应用场景 1. 多用户环境隔离 当多个用户共享一个数据库时,为每个用户创建独立的模式,避免命名冲突。 2. 应用程序隔离 第三方应用可以使用独立模式,避免与现有对象名称冲突。 3.

By Ne0inhk
Flutter 组件 okay 的适配 鸿蒙Harmony 深度进阶 - 驾驭异步结果链式融合、实现鸿蒙端分布式业务逻辑解耦与精密审计方案

Flutter 组件 okay 的适配 鸿蒙Harmony 深度进阶 - 驾驭异步结果链式融合、实现鸿蒙端分布式业务逻辑解耦与精密审计方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 okay 的适配 鸿蒙Harmony 深度进阶 - 驾驭异步结果链式融合、实现鸿蒙端分布式业务逻辑解耦与精密审计方案 前言 在前文中,我们探讨了 okay 在鸿蒙(OpenHarmony)端实现基础 Result 模式包装的实战。但在真正的“分布式微服务聚合”、“高并发资产对账”以及“具备自愈能力的 IoT 指令链”场景中。简单的 ok() 与 err() 判定往往不足以支撑起复杂的业务全景。面对需要同时并行发起 3 个 API 请求,并要求在“所有请求均成功时执行合并、任一请求失败时执行局部逻辑路由”的高阶需求。如果缺乏一套完善的异步结果映射与多级逻辑聚合机制。不仅会导致异步回调地狱(Callback Hell)在

By Ne0inhk
国产替代不掉链子:KingbaseES如何做到MySQL零感迁移

国产替代不掉链子:KingbaseES如何做到MySQL零感迁移

前言 在信创国产化的大趋势下,数据库作为数字基础设施的核心,其替代迁移工作成为企业数字化转型的关键环节。MySQL 作为国内企业应用最广泛的开源关系型数据库之一,凭借轻量、易用、生态完善的特点,在互联网、金融、政务、制造等多个行业落地生根。但不少企业在将 MySQL 向国产数据库迁移的过程中,却陷入了 “看似简单,实则踩坑” 的困境 —— 表面上的语法兼容背后,是 JSON 数据类型行为差异、事务隔离级别在高并发下的隐性适配问题、Group By 严格模式等细节带来的兼容性故障,甚至出现 “改一行代码,崩整个系统” 的极端情况。 业务方对迁移的核心顾虑,从来都不是 “能不能迁”,而是 “能不能稳迁、低成本迁、不影响业务迁”。本文将从 MySQL 迁移的核心痛点出发,深度解析电科金仓 KingbaseES 的 MySQL 兼容性技术实现,以及全流程迁移工程的落地能力,为企业 MySQL

By Ne0inhk