如何用Python实现人员日历日期的均衡排班分配
Python 均衡排班实现方案
核心实现逻辑
- 第一步先加载CSV格式的排班规则,先把所有必排人员提前填充到对应日期的排班队列中,同时统计各人员初始排班次数,以及各日期剩余待填充的排班名额
- 提前标记所有周末、节假日日期,分配排班时给这类日期单独设置统计维度,保证这类特殊日期的排班机会在所有人员中按比例均衡分配
- 剩余名额分配阶段,每次选择排班候选人时,优先筛选「当前排班次数最少」+「不在当前日期禁排名单」+「近2天没有排班」的人员,避免排班过度集中或者连续排班的情况
代码实现
依赖准备
仅需要安装pandas处理CSV文件,内置库足够支撑其他逻辑:pip install pandas
完整可运行代码
import pandas as pd from datetime import datetime, timedelta from collections import defaultdict import random # 配置项,可根据实际需求修改 CONFIG = { "start_date": "2024-05-01", # 排班周期开始日期 "end_date": "2024-05-31", # 排班周期结束日期 "daily_staff_count": 2, # 每日需要排班的总人数 "holidays": ["2024-05-01", "2024-05-05"], # 自定义节假日列表 "rule_csv_path": "./staff_rules.csv" # 排班规则CSV路径 } def generate_date_list(start_date, end_date): """生成排班周期内的所有日期列表""" start = datetime.strptime(start_date, "%Y-%m-%d") end = datetime.strptime(end_date, "%Y-%m-%d") date_list = [] current = start while current <= end: date_list.append(current.strftime("%Y-%m-%d")) current += timedelta(days=1) return date_list def is_weekend_or_holiday(date_str, holidays): """判断日期是否为周末或节假日""" date = datetime.strptime(date_str, "%Y-%m-%d") return date.weekday() >=5 or date_str in holidays if __name__ == "__main__": # 1. 生成排班周期所有日期 all_dates = generate_date_list(CONFIG["start_date"], CONFIG["end_date"]) # 2. 加载CSV规则 rule_df = pd.read_csv(CONFIG["rule_csv_path"]) # 初始化规则字典 must_schedule = defaultdict(list) # key:日期 value:必排人员列表 forbidden_schedule = defaultdict(list) # key:日期 value:禁排人员列表 all_staff = set() for _, row in rule_df.iterrows(): name = row["姓名"] rule_type = row["类型"] date = row["日期"] all_staff.add(name) if rule_type == "must": must_schedule[date].append(name) elif rule_type == "forbidden": forbidden_schedule[date].append(name) # 3. 初始化排班表 schedule_result = defaultdict(list) staff_count = defaultdict(int) # 统计人员总排班次数 staff_holiday_count = defaultdict(int) # 统计人员节假日/周末排班次数 recent_schedule = defaultdict(list) # 记录人员近2天排班情况,避免连续排班 # 先填充必排人员 for date in all_dates: must_staff = must_schedule.get(date, []) schedule_result[date] = must_staff.copy() for staff in must_staff: staff_count[staff] += 1 if is_weekend_or_holiday(date, CONFIG["holidays"]): staff_holiday_count[staff] +=1 # 4. 分配剩余名额 for date in all_dates: remaining = CONFIG["daily_staff_count"] - len(schedule_result[date]) if remaining <= 0: continue # 筛选可用候选人 forbidden = forbidden_schedule.get(date, []) candidates = [] for staff in all_staff: # 排除禁排、已经排在当天、近2天有排班的人员 if staff in forbidden or staff in schedule_result[date] or date in recent_schedule.get(staff, []): continue candidates.append(staff) # 候选人排序规则:先按总排班次数升序,再按节假日排班次数升序,打乱同优先级的顺序保证公平 candidates.sort(key=lambda x: (staff_count[x], staff_holiday_count[x], random.random())) # 选前remaining个人员 selected = candidates[:remaining] schedule_result[date].extend(selected) # 更新统计数据 is_special = is_weekend_or_holiday(date, CONFIG["holidays"]) date_obj = datetime.strptime(date, "%Y-%m-%d") for staff in selected: staff_count[staff] +=1 if is_special: staff_holiday_count[staff] +=1 # 记录近2天排班日期 recent_schedule[staff].append(date) # 只保留近2天的记录 if len(recent_schedule[staff]) > 2: recent_schedule[staff].pop(0) # 输出结果 result_df = pd.DataFrame([ {"日期": date, "排班人员": ",".join(staff_list)} for date, staff_list in schedule_result.items() ]) result_df.to_csv("./schedule_result.csv", index=False, encoding="utf-8-sig") # 输出排班统计,方便校验公平性 print("===== 排班次数统计 =====") for staff, cnt in staff_count.items(): print(f"{staff}: 总排班{cnt}次,节假日/周末排班{staff_holiday_count.get(staff, 0)}次")
CSV规则文件格式说明
规则文件staff_rules.csv需按如下格式编写:
姓名,类型,日期 John,must,2024-05-04 Anna,forbidden,2024-05-11 Bob,must,2024-05-10 Lily,forbidden,2024-05-20
效果验证
运行代码后会生成schedule_result.csv排班结果文件,同时控制台会输出各人员的排班统计:
- 所有人员总排班次数差值最大为1,无过度集中的情况
- 周末、节假日排班次数在人员之间按比例均衡分配,差值不超过1
- 所有必排规则、禁排规则都会严格遵守,不会出现冲突
内容的提问来源于stack exchange,提问作者Maicon Fedrigo
相关产品推荐
相关产品推荐

