药房月度排班 (pharmacy-schedule)
用途
按药房固定轮转规律 + 十余项业务约束,求解一个月(17 人:15 人轮班 + 2 人固定审方)的排班表,并生成 3 个工作表的 Excel 交付文件。
何时使用
- 用户要求生成某月药房排班表 / 班表 / 轮班表
- 对已有班表提出修改意见(换年假日期、调整约束、重排)后需重新求解
输入准备(排班前先向用户收集,缺省时按上期合理推断并在交付中注明)
- 人员顺序(15 人轮班名单,顺序=轮转初始顺序,位置敏感)
- 两名固定审方及各自休息规则(通常:审方1=周末双休;审方2=周日+周一休)
- 有审方资格的备选名单(通常 9 人)
- 月份与日历:首日星期、国家节假日(节假日每天仅 1 个审方岗、节假日首日排 K5 班)
- 可补休时长(单位:小时,决定休息天数分配方向)
- 上月最后一天每人已连续上班天数(接续约束,9 月排班需接 8 月 31 日状态)
- 年假/补休(人→日期列表)
- 特殊限制:某人不能上某岗(如柯若男不上 A/核/审方)、调休上班日处理方式
关键规则(必须逐条满足,详见 references/constraints.md)
| # | 约束 |
|---|---|
| R1 | 轮转规律:岗位固定 K1→K2→K3→K4→备→休→K5→P1→P2→休→P3→P4→核→A→休(15 位),人员整体向后平移 → pos = (p_idx - OFFSET - d) % 15。验证基准:9/1 K1=第8人、K2=第9人,9/2 K1=第9人、K2=第10人 |
| R2 | 连续上班(含上月延续首段)≤5 天;备班不算上班(休息断点) |
| R3 | 备班=二线班:备班次日必须休息 |
| R4 | K1/K2 位置按轮转固定不动,仅年假/补休日允许他人顶替 |
| R5 | 审方1 周末双休、审方2 周日周一休(基本);第一周可灵活调整休息位,之后尽量连休 2 天 |
| R6 | 审方覆盖:工作日 2 个、周六日/节假日 1 个;缺口由备选审方顶上,每人次数均一 |
| R7 | 柯若男不上 A/核/审方 |
| R8 | A/核尽量连在一起(A 的次日为核班,轮转自然顺序);每人 A:核次数 ∈ {2:2, 2:3, 3:2} |
| R9 | 休息天数(休+备)按可补休小时单调分配:时长多者休更多 |
| R10 | K5 班仅排在:周六、每月第 2 个周三、节假日首日 |
| R11 | 月初与月底的 K1/K2 尽量不是相同 2 人 |
| R12 | 统计口径:休息天数=休+备班;上班天数=K1-K4/P1-P4/核/A/K5/审方(不含备) |
工作流
- 确认月份与关键输入:首日星期、节假日、K5 日期(周六+第2周三+节假首日)、人员/审方/年假/可补休/上月连续天数。需要时用 WebSearch 查国务院放假安排(如"2026年中秋节放假安排")。
- 复制并修改模板:将
scripts/solver_template.py复制到工作目录(如scheduler/solver.py),仅修改文件头配置区(PEOPLE/REVIEWER/BACKUP_9/DAYS/WEEKDAY/K5_DAYS/HOLIDAY/LEAVE/HOURS/AUG_STREAK/REST_TARGET/reviewer_schedule 微调/seed/路径),不要改动算法部分。 - 求解:
python solver.py→ 产出grid.pkl。检查输出中的A核配对率、休息单调违反、全部约束通过等报告行。 - 生成 Excel:复制
scripts/make_excel_template.py,更新标题/人名/备注/校验清单/输出文件名后运行 → 产出output/<月份>药房排班表.xlsx。 - 独立验证(不依赖求解器自检):用一次性脚本从 grid.pkl 核验 R1-R12(K1/K2 关键日、备后必休零违反、连续段、审方数、A核比例、休息单调、9/30 有数据、统计列不重叠)。
- 交付:
present_files打开 Excel,按"修改落实情况 + 校验表 + 说明"结构汇报;说明中必须标注跨月事项(如 9/30 备班 → 10/1 需休)与个别无法满足项("尽量"类约束的例外)。
求解器架构(solver_template.py)
- 轮转:
build_base()按 R1 生成初始网格,K5 非排班日替换为'休' - 审方:
reviewer_schedule()生成两名审方全月班次(含微调),review_gap_days()计算缺口日,assign_backups回溯分配备选审方(缺口日数 = 备选人数时每人 1 次) - 链式换班:
place_shift(shift, d, excluded, ...)把某岗位交给休息者,若休息者当天接了班会影响连续段,则用make_workable递归"释放"其相邻上班日(交换给其他休息者)——深度 ≤4,全部成功路径必须通过coverage_ok(11 常规岗各 1 人)+streak_ok+k12_ok+bei_ok,否则回滚 - 不变式:
coverage_ok(岗位覆盖)、bei_ok(备后必休)、k12_ok(K1/K2 不动)——所有换班成功路径都要校验 - 候选过滤:顶替者必须来自
resters_available(当天'休'且前一天非备 → 备后日休者不可拉走,保证 bei_ok) - solve 步骤顺序(顺序敏感):① 柯若男 A/核配对顶替(A+次日核给同一人,链式)→ ② 备后必休初始化修复(轮转自然状态,多轮 place_shift)→ ③ 首段连续超标修复(8 月延续段>5 的人,9 月初安排休息)→ ④ 年假/补休(place_leave,先标假再顶班)→ ⑤ 备选审方回溯分配 → ⑥ 全量校验
- 后处理:
pair_ah_opt(A 核配对+比例优化,纯 swap 休息不变,30 秒时间限制)→balance(休息按 HOURS 单调)→balance_ah(A/核配对互换 + 阶段2单边兜底)→ 全量校验失败则回退平衡前网格 - 多种子:主流程 120 次尝试不同 seed,取第一个全量通过的网格
调试陷阱(本项目踩过的坑,务必避免)
- Python heredoc 转义坑:单引号
<< 'PYEOF'内\\不转义,含\\的字符串替换断言会静默失败且整个文件不写入。→ 用 Edit 工具逐处修改,或避免转义。 - 连续段统计 bug:
max_streak必须用"峰值统计"(cur = v而非cur += 1),否则换班后校验失真。前置段(8 月延续)放在序列首位。 - 链式释放被 visited 拦截:
place_shift调make_workable(c, d)前已把(c, d)加入 visited,而make_workable开头if (name, d) in visited: return False会让链式永远失败——调make_workable时不要传{(c, d)}。 - 孤立休息日接班 = 长连班:顶替者当天的"休"若前后都上班,接班后会把两段合并(吴嘉炜 9/3 休→K4 后 9/1-7=8 连)。链式释放可解决,但必须在轮转最自然状态下尽早处理(备后修复放柯若男/年假之前)。
- 柯若男排除过度:她"不上 A/核"不等于"不能上其他班"。把她完全排除在顶替候选外会占用大量休息预算,导致高可补休者(梁秋莹 84h)休息不足。应只禁止她接 A/核。
- 休息结构刚性:某些人的休息日 = "5 天连班断点 + 备后日",是连续≤5 天的极限解(余奔婷休6+备2=8、蔡国栋休7+备2=9 不可再压缩)。平衡交换若对不上对方可换班次(排除 A/核/K1/K2/审方/备后后只剩 K3/K4/P1-P4)会收敛失败 → 调整 REST_TARGET 或通过"审方班转移"(审方日换人,休息分布随之变化)破局。
- 首段断点休日保护:9 月初"若上班会导致首段>5"的休日必须保护(PROTECT_REST,用 can_work 精确判断),否则 balance 简单交换会破坏首段。但 place_shift 的链式场景不要用 PROTECT(会锁死顶替)。
- pair_ah_opt 性能:含 place_shift 递归的优化器迭代极慢(4 万次 >100s)——必须加 30 秒时间限制;move 操作会改变休息分布(余奔婷休+1),balance 修不回来 → 优先纯 swap(休息不变)。
- Excel 统计列覆盖:日期列到 32 列(9/30),统计列必须从 33 列(AG)起,否则覆盖 9/30 数据。
- 审方标签冻结:审方班一旦分配即冻结,
make_workable/balance不得移动(曾导致郭婉 9/13 变双审方、余奔婷连续超标)。
交付物格式
Excel 3 个工作表:
1. <月份>班表:17 人×30 天(颜色:灰=休、黄=备、蓝=年假、浅蓝=补休、绿=审方、金黄=K5、周末/节假日列底色)+ 每人统计列(休息含备/备班/年假补休/上班/审方/A/核/K1K2/最大连续,最大连续>5 标红)
2. 统计与校验:逐人统计(含可补休小时)+ 约束校验清单(✔/✘)+ 说明
3. 每日岗位覆盖:每天 K1-K4/备/P1-P4/核/A/K5/审方岗/休息人员明细
汇报结构:修改落实情况表 → 全月校验表 → 说明(跨月事项、无法满足项的原因)。