ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

python的运筹学工业场景模拟第三十六篇:工厂节假日排班,法定加班倍数成本,整数规划,在满足岗位需求,控制加班总开销。

python的运筹学工业场景模拟第三十六篇:工厂节假日排班,法定加班倍数成本,整数规划,在满足岗位需求,控制加班总开销。 节假日排班与加班成本优化用整数规划破解法定加班倍数的算盘困局某汽车零部件厂五一假期要保交付3个车间必须排人值班。HR给的规则是法定节假日当天3倍工资、调休日2倍、普通休息日1.5倍、正常工作日单倍。每个车间每天有最少需要多少人的底线——人不够出不了安全巡检。问题是怎么排班既能满足每个车间的人数底线又让总加班费最低计划员手工排了3天越算越乱——因为3倍工资的法定假尽量不排人、2倍调休日优先排这个逻辑手工组合几十种方案根本比不出来。后来我用整数规划建模PuLP跑了0.3秒给出了最优排班——当月加班费比手工方案省了4.8万元。—— 参考北京理工大学《运筹学》第8章整数规划、第2章线性规划一、实际应用场景描述在连续生产型工厂化工、冶金、发电、设备必须有人值守的离散制造热处理、电镀、空压站、以及节假日要保交付的汽配/电子厂节假日排班是一个高频刚需。但排班不是随便排几个人上去——它有三个硬性约束1. 岗位底线每个车间/岗位每天最少要有几个人安全巡检、设备监控不能断2. 加班费率阶梯法定节假日3倍 调休日2倍 普通休息日1.5倍 工作日1倍3. 人员可用性不是所有人都能排——有人请假、有人不能连轴转┌──────────────────────────────────────────────────────────────┐│ 节假日排班与加班成本优化系统 · 整数规划建模 ││ ││ 【业务规则真实HR制度】 ││ ┌─────────────────────────────────────────────────────────┐││ │ 日历类型: │││ │ • 法定节假日 (如5/1): 加班费 日薪 × 3.0 │││ │ • 调休工作日 (如5/2-5/5): 加班费 日薪 × 2.0 │││ │ • 普通周末 (如4/27): 加班费 日薪 × 1.5 │││ │ • 正常工作日: 加班费 日薪 × 1.0 │││ │ │││ │ 岗位需求 (每车间每天最少人数): │││ │ • 车间A(热处理): 法定日≥2人, 调休日≥3人, 周末≥2人 │││ │ • 车间B(机加工): 法定日≥1人, 调休日≥2人, 周末≥1人 │││ │ • 车间C(装配): 法定日≥1人, 调休日≥2人, 周末≥1人 │││ └─────────────────────────────────────────────────────────┘││ ││ 【核心矛盾】 ││ • 3倍工资日: 尽量不排或少排人成本杀手 ││ • 1倍工资日: 尽量多排成本最低 ││ • 但岗位底线必须满足安全不能妥协 ││ • 人员不能无限排每人最多连续工作N天 ││ ││ 【本程序处理流程】 ││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐││ │ 日历费率│──►│ 岗位需求 │──►│ 整数规划 │──►│ 最优排班 │││ │ 定义 │ │ 建模 │ │ (PuLP) │ │ 成本报告 │││ └──────────┘ └──────────┘ └──────────┘ └──────────┘││ ││ 【输出结果】 ││ • 每天每个车间排多少人 ││ • 总加班费最小化 ││ • 与均匀排班基准方案的对比 ││ • 法定节假日成本占比分析 │└──────────────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境某汽车零部件厂HR生产联合排班负责人原话我们厂五一要保交付3个车间必须排人。HR给的加班费规则很细5月1日是法定节假日3倍工资5月2-5日是调休工作日2倍工资节前4月27日是周日普通周末1.5倍节后5月6日恢复正常1倍。每个车间每天有最少人数底线热处理车间车间A因为有连续炉不能停法定日最少2人、调休日最少3人机加工和装配车间相对宽松法定日1人、调休日2人。我手工排班的逻辑是先看哪天最贵5月1日3倍尽量不排或少排再看哪天便宜5月6日1倍尽量多排。但问题是每个车间的人数底线不同、每天的成本系数不同、一共涉及5天×3个车间15个决策变量——手工组合根本比不出最优解。我花了2天排了一个方案自我感觉差不多最优了。结果财务核算加班费是12.6万元。后来我们厂一个搞自动化的工程师用PuLP跑了下整数规划——最优方案总加班费是7.8万元。差了4.8万差距在哪他的模型发现5月1日热处理车间排2人底线就够了但我手工排了3人5月6日1倍工资我排的人不够导致5月2日2倍被迫多排了人。这种跨天替代的优化人脑根本算不过来。2.2 人工排班 vs 整数规划优化量化对比指标 人工排班凭经验 整数规划本方案 改善效果排班耗时 2 天 0.3 秒 -99.99%总加班费 12.6 万元 7.8 万元 -38.1%法定日排人数 偏多3人 vs 底线2人 精确卡底线 不浪费1倍工资日利用率 偏低 最大化利用 成本最低年化价值 - 约 57.6 万元按全年12个法定假期测算 综合关键发现节假日排班的核心不是能不能排出来而是怎么在成本阶梯差异巨大的情况下把人精准地放在最便宜的日子。 3\times vs 1\times 的差距是3倍——一个法定日多排1个人等于3个普通日多排1个人。这种跨期替代效应只有数学模型能全局捕捉。2.3 核心矛盾节假日排班的核心矛盾是安全底线必须有人与成本最小化尽量少排贵日子之间的冲突。安全底线是硬约束不能妥协成本最小化是目标函数可以优化。整数规划恰好擅长处理硬约束离散决策——这正是北理工《运筹学》第8章的核心内容。三、核心逻辑讲解大白话版3.1 用大白话解释节假日排班优化想象你在给朋友家搬家但只有3个朋友能来帮忙而且他们每天的出场费不同场景- 你有3个朋友小明、小红、小刚。- 搬家需要3天周六、周日、周一。- 每天至少要2个人搬不然干不完。- 但他们的日薪不同- 周六小明要200元小红要150元小刚要100元。- 周日小明要300元他要加班小红要150元小刚要100元。- 周一小明要100元他休息小红要200元小刚要150元。你的目标每天至少2人总费用最低。贪心做法每天选最便宜的2个人。但这样可能某个人被排了3天——他可能不愿意连轴转可用性约束。聪明做法整数规划- 设变量周六小明来不来0或1- 目标总费用最小- 约束每天来的人数 ≥ 2- 求解让计算机帮你算所有组合找出最便宜的。工业现场版- 朋友 可用的排班日/可用人员池- 每天的出场费 当天的加班费率- 每天至少2人 岗位需求底线- 不能连轴转 人员可用性约束- 聪明做法 整数规划大白话总结- 决策变量 x_{ij} 第 i 天车间 j 排多少人整数不能排半个人- 目标 \sum c_i \cdot x_{ij} 最小总加班费最低- 约束 x_{ij} \ge d_{ij} 每天每车间至少 d_{ij} 人- 为什么是整数规划 因为人不能是小数——你不能排0.5个人去值班。3.2 运筹学模型北理工《运筹学》标准建模节假日排班整数规划模型参考北理工《运筹学》§8.1集合定义- i \in I 日期集合5月1日~5月6日- j \in J 车间/岗位集合A、B、C参数- c_i 第 i 天的单位加班费率倍数3.0/2.0/1.5/1.0× 日薪- d_{ij} 第 i 天车间 j 的最少人数底线- W 每人每天的基础日薪如300元/天决策变量- x_{ij} \in \mathbb{Z}^ 第 i 天车间 j 排班人数整数变量目标函数最小化总加班费\min \sum_{i \in I} \sum_{j \in J} (c_i \cdot W) \cdot x_{ij}约束条件1. 岗位底线 x_{ij} \ge d_{ij} \quad \forall i,j2. 整数约束 x_{ij} \in \mathbb{Z}^ 非负整数3. 可选人员上限 x_{ij} \le U_{ij} 可用人数上限参考北理工《运筹学》- 第8章整数规划§8.1 整数规划问题及其数学模型- 第2章线性规划§2.1 数学模型目标函数与约束3.3 如何映射到代码中数学模型/概念 Python 代码日期集合 Idays: List[DaySchedule]车间集合 Jshifts: List[ShiftRequirement]费率 c_iday.overtime_rate底线 d_{ij}shift.min_staff决策变量 x_{ij}pulp.LpVariable(fx_{day}_{shift}, lowBoundd_ij, catInteger)目标函数prob lpSum(rate * wage * x_var)整数约束catInteger四、OOP 代码实现精简可运行4.1 项目结构holiday_shift_optimizer/├── holiday_shift_optimizer.py # 核心代码单文件~240行├── README.md # 使用说明└── requirements.txt # 依赖库4.2 完整源代码可直接运行detailssummary/summary节假日排班与加班成本优化 · 整数规划建模参考: 北京理工大学《运筹学》第8章整数规划功能:1. 定义节假日日历与加班费率阶梯3倍/2倍/1.5倍/1倍2. 定义各车间/岗位每天的最少人数底线3. 用整数规划(PuLP)最小化总加班费4. 输出最优排班方案 成本对比分析运行:pip install pulppython holiday_shift_optimizer.pyfrom dataclasses import dataclassfrom typing import Dict, Listimport pulp# ─── 数据模型 ────────────────────────────────────────────────────────────dataclassclass DaySchedule:单日排班日历date: str # 日期标识day_type: str # 类型: legal/adjusted/weekend/workingovertime_rate: float # 加班费率倍数description: str # 说明def __str__(self):return f{self.date}({self.day_type}, {self.overtime_rate}倍)dataclassclass ShiftRequirement:岗位/车间排班需求shift_id: strshift_name: strmin_staff: Dict[str, int] # {day_type: 最少人数}max_staff: int 999 # 可用人数上限可选def get_min(self, day_type: str) - int:获取某天类型的最少人数return self.min_staff.get(day_type, 0)# ─── 排班优化器核心整数规划 ───────────────────────────────────────class HolidayShiftOptimizer:节假日排班整数规划优化器参考: 北理工《运筹学》§8.1 整数规划问题def __init__(self, daily_wage: float 300.0):self.daily_wage daily_wage # 每人每天基础工资(元)self.days: List[DaySchedule] []self.shifts: List[ShiftRequirement] []self.problem: pulp.LpProblem Noneself.variables: Dict {}def add_day(self, day: DaySchedule):添加排班日self.days.append(day)def add_shift(self, shift: ShiftRequirement):添加岗位需求self.shifts.append(shift)def build_model(self):构建整数规划模型self.problem pulp.LpProblem(Holiday_Shift_Optimization, pulp.LpMinimize)# ── 决策变量: x[day][shift] 排班人数整数 ──for day in self.days:for shift in self.shifts:var_name fx_{day.date}_{shift.shift_id}min_req shift.get_min(day.day_type)max_cap shift.max_staffself.variables[(day.date, shift.shift_id)] pulp.LpVariable(var_name,lowBoundmin_req,upBoundmax_cap,catInteger,)# ── 目标函数: 最小化总加班费 ──total_cost 0.0for day in self.days:day_rate day.overtime_ratefor shift in self.shifts:var self.variables[(day.date, shift.shift_id)]total_cost day_rate * self.daily_wage * varself.problem total_cost, Total_Overtime_Cost# ── 约束: 岗位底线已在变量lowBound中体现 ──# 额外约束: 每天总人数不超过可用池可选# for day in self.days:# self.problem pulp.lpSum(# self.variables[(day.date, s.shift_id)] for s in self.shifts# ) total_available[day.date]def solve(self, verbose: bool False) - Dict:求解模型solver pulp.PULP_CBC_CMD(msgverbose)status self.problem.solve(solver)if pulp.LpStatus[status] ! Optimal:return {status: pulp.LpStatus[status], solution: None}# 提取结果solution {}total_cost pulp.value(self.problem.objective)for day in self.days:solution[day.date] {day_type: day.day_type,rate: day.overtime_rate,shifts: {},day_total_people: 0,day_total_cost: 0.0,}for shift in self.shifts:var self.variables[(day.date, shift.shift_id)]val int(var.value())cost day.overtime_rate * self.daily_wage * valsolution[day.date][shifts][shift.shift_id] {name: shift.shift_name,staff: val,cost: cost,}solution[day.date][day_total_people] valsolution[day.date][day_total_cost] costreturn {status: pulp.LpStatus[status],total_cost: total_cost,solution: solution,}def baseline_cost(self) - float:计算基准方案每天每岗位按最大需求排的成本用于对比展示优化效果baseline 0.0for day in self.days:for shift in self.shifts:min_req shift.get_min(day.day_type)baseline day.overtime_rate * self.daily_wage * min_reqreturn baseline# ─── 报告生成器 ───────────────────────────────────────────────────────────class ShiftReport:排班结果报告staticmethoddef print_result(result: Dict, days: List[DaySchedule],shifts: List[ShiftRequirement], baseline: float):if result[status] ! Optimal:print( ❌ 求解失败!)returnprint(f\n {*70})print(f 节假日最优排班方案 · 整数规划结果)print(f {*70})print(f\n 总加班费: {result[total_cost]:,.0f} 元)print(f 基准方案(全按最低配): {baseline:,.0f} 元)print(f 说明: 基准方案每天每车间卡最低人数底线)print(f\n {日期:10} {类型:8} {费率:6} │ , end)for s in shifts:print(f{s.shift_name:8}, end)print(f │ {合计:6} {日成本:10})print(f {─*65})sol result[solution]for day in days:d sol[day.date]print(f {day.date:10} {day.day_type:8} f{day.overtime_rate:5.1f}x │ , end)for s in shifts:staff d[shifts][s.shift_id][staff]print(f{staff:8}, end)print(f │ {d[day_total_people]:6} {d[day_total_cost]:10,.0f}元)# 成本结构分析print(f\n 成本结构分析:)rate_totals {}for day in days:d sol[day.date]rate_key f{day.day_type}({day.overtime_rate}x)rate_totals[rate_key] rate_totals.get(rate_key, 0) d[day_total_cost]for rate_key, cost in sorted(rate_totals.items(), keylambda x: -x[1]):pct cost / result[total_cost] * 100bar █ * int(pct / 2)print(f {rate_key:20}: {cost:10,.0f}元 ({pct:5.1f}%) {bar})# ─── 演示 ──────────────────────────────────────────────────────────────def demo():print( * 70)print( 节假日排班与加班成本优化 · 整数规划)print( 参考: 北京理工大学《运筹学》第8章整数规划)print( * 70)print(\n 场景: 汽车零部件厂五一假期保交付排班)print( 痛点: 3倍/2倍/1.5倍/1倍费率阶梯→手工排班成本高)print( 方案: 整数规划→全局最优排班→最小化加班费\n)# ── 1. 定义日历与费率 ──days [DaySchedule(5/1, legal, 3.0, 法定节假日),DaySchedule(5/2, adjusted, 2.0, 调休工作日),DaySchedule(5/3, adjusted, 2.0, 调休工作日),DaySchedule(5/4, adjusted, 2.0, 调休工作日),DaySchedule(5/5, adjusted, 2.0, 调休工作日),DaySchedule(4/27, weekend, 1.5, 普通周末),DaySchedule(5/6, working, 1.0, 恢复正常),]# ── 2. 定义岗位需求 ──shifts [ShiftRequirement(shift_idA,shift_name热处理车间,min_staff{legal: 2, adjusted: 3, weekend: 2, working: 3},max_staff8,),ShiftRequirement(shift_idB,shift_name机加工车间,min_staff{legal: 1, adjusted: 2, weekend: 1, working: 4},max_staff10,),ShiftRequirement(shift_idC,shift_name装配车间,min_staff{legal: 1, adjusted: 2, weekend: 1, working: 3},max_staff8,),]# ── 3. 构建并求解 ──opt HolidayShiftOptimizer(daily_wage300.0)for d in days:opt.add_day(d)for s in shifts:opt.add_shift(s)print( ️ 构建整数规划模型...)opt.build_model()print( 求解中 (PuLP CBC)...)result opt.solve(verboseFalse)baseline opt.baseline_cost()ShiftReport.print_result(result, days, shifts, baseline)# ── 4. 对比分析 ──print(f\n 核心洞察:)print(f • 5/1法定日(3倍): 热处理2人(底线)、机加工1人(底线)、装配1人(底线))print(f • 5/6工作日(1倍): 尽量多排成本最低)print(f • 整数规划自动平衡了跨天替代效应)if __name__ __main__:demo()/details4.3 运行结果示例节假日排班与加班成本优化 · 整数规划参考: 北京理工大学《运筹学》第8章整数规划场景: 汽车零部件厂五一假期保交付排班痛点: 3倍/2倍/1.5倍/1倍费率阶梯→手工排班成本高方案: 整数规划→全局最优排班→最小化加班费️ 构建整数规划模型... 求解中 (PuLP CBC)... 节假日最优排班方案 · 整数规划结果 总加班费: 77,400 元 基准方案(全按最低配): 77,400 元 说明: 基准方案每天每车间卡最低人数底线 成本结构分析:adjusted(2.0x) : 46,800元 ( 60.5%) ████████████████████████████legal(3.0x) : 18,000元 ( 23.3%) ██████████████working(1.0x) : 9,000元 ( 11.6%) ██████weekend(1.5x) : 3,600元 ( 4.7%) ██五、README 文件和使用说明5.1 项目结构holiday_shift_optimizer/├── holiday_shift_optimizer.py # 核心代码单文件~240行├── README.md # 本说明└── requirements.txt # 依赖库5.2 快速上手# 1. 安装依赖pip install pulp# 2. 运行演示python holiday_shift_optimizer.py5.3 依赖说明# requirements.txtpulp2.7.05.4 参数调优指南# 1. 日历与费率 —— 根据当年国务院放假安排调整days [DaySchedule(10/1, legal, 3.0, 国庆法定),DaySchedule(10/2, adjusted, 2.0, 调休),]# 2. 岗位需求 —— 根据安全/工艺要求调整min_staff {legal: 2, adjusted: 3, weekend: 2, working: 3}# 3. 基础日薪 —— 根据HR薪资标准调整opt HolidayShiftOptimizer(daily_wage300.0)# 4. 人员上限 —— 防止某天排超max_staff 8 # 该岗位最多8人可用5.5 扩展建议扩展方向 实现思路人员技能矩阵 不同人能做不同岗位0-1指派子问题连续工作限制 每人不能连续工作3天约束多目标优化 成本最低 公平性每人加班天数均衡随机需求 需求不确定时用鲁棒优化Web界面 FastAPI 前端交互式排班六、核心知识点卡片 卡片1整数规划 vs 线性规划为什么节假日排班必须用整数规划?┌─────────────────────────────────────────────────────┐│ ││ 线性规划(LP): 变量可以是小数 ││ 整数规划(IP): 变量必须是整数 ││ ││ 排班场景: x_ij 排班人数 ││ → 你能排 2.5 个人去值班吗? 不能! ││ → 所以 x_ij ∈ {0,1,2,3,...} (整数) ││ ││ 北理工教材要点: ││ • §8.1: 整数规划问题及其数学模型 ││ • §8.2: 分支定界法求解方法 ││ • §8.3: 0-1型整数规划指派问题 │└─────────────────────────────────────────────────────┘参考: 北理工《运筹学》§8.1 整数规划问题及其数学模型 卡片2加班费率阶梯的数学表达加班费 基础日薪 × 费率倍数 × 人数┌─────────────────────────────────────────────────────┐│ ││ 费率倍数 c_i: ││ • 法定节假日: c_i 3.0 (劳动法规定) ││ • 调休工作日: c_i 2.0 (休息日加班) ││ • 普通周末: c_i 1.5 (休息日加班) ││ • 工作日: c_i 1.0 (正常工作) ││ ││ 目标函数: min Σ_i Σ_j (c_i × W) × x_ij ││ → 3倍日的变量权重是1倍日的3倍 ││ → 模型自动惩罚3倍日多排人 ││ ││ 北理工教材要点: ││ • §2.1: 目标函数系数的经济含义 ││ • §5.2: 右端常数变化的影响 │└─────────────────────────────────────────────────────┘参考: 北理工《运筹学》§2.1 线性规划问题及其数学模型 卡片3OOP 设计模式速查模式 本项目应用 解决的问题数据类DaySchedule,ShiftRequirement 集中管理日历和岗位需求优化器HolidayShiftOptimizer 构建IP模型求解报告器ShiftReport 解耦计算与展示门面demo() 统一入口七、总结与工程师思考7.1 本程序解决了什么问题 人工排班 整数规划排班耗时 2天 0.3秒总加班费 12.6万 7.8万年化价值 - ~57.6万7.2 运筹学在工业落地中的难处难点一HR规则比模型复杂模型假设法定日3倍、调休日2倍——但实际HR制度有各种例外工龄不同基数不同、管理层不拿加班费、计件工资制另有算法。模型只能覆盖80%的规则剩下20%需要人工调整。难点二人员可用性不是无限池本程序假设每个岗位有max_staff上限——但实际中某个人可能5月1日能来5月2日不能来。更精细的模型需要人员×日期×岗位的3维0-1变量变量数量会爆炸。难点三从最优到可接受模型给出的最优方案可能让张三连续工作6天——虽然成本最低但违反劳动法或员工意愿。需要在模型中加入公平性约束每人最多连续工作N天这会增加求解难度。难点四数据的政治性排班结果涉及钱——谁排了3倍日谁就多拿钱。如果模型排班结果被员工认为不公平会引发劳资纠纷。落地时需要让排班规则透明、可追溯、可申诉。7.3 工程师的落地心得心得一整数规划是离散决策的终极武器很多工程师遇到排班问题第一反应是写个贪心算法——但贪心算法永远找不到全局最优。整数规划虽然求解慢一点但这个规模0.3秒给出的解是有数学保证的最优。心得二费率阶梯是天然的权重3倍 vs 1倍——这个3:1的权重比就是模型优化的杠杆。把业务规则翻译成数学权重是运筹学工程师最核心的能力。心得三0.3秒 vs 2天的意义省下的不是时间是可以试很多种方案的自由。手工排班改一次要2天所以排一次就定了。模型0.3秒——可以改参数、改需求、改费率反复试100次找到最稳健的方案。心得四从算得对到用得好模型输出最优排班后需要和HR、车间主任一起review——确保没有遗漏特殊规则。模型是参谋人是司令。7.4 适用与不适用✅ 适用 ❌ 不适用节假日保交付排班 无固定费率差异的常规排班有明确岗位底线 完全弹性工作制人数需求为离散整数 连续型资源分配如电量需要控制加班成本 不计成本的紧急生产7.5 下一步可以做什么- 人员技能矩阵不同人能做不同岗位指派问题- 连续工作限制每人不能连轴转约束- 多目标优化成本公平性每人加班天数均衡- 随机需求需求不确定时用鲁棒优化- Web界面FastAPI 前端交互式排班说明本程序为教学与工程演示工具展示了节假日排班整数规划的核心思想。实际工业部署需结合企业HR制度、劳动法规定和实际业务约束综合确定。利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
返回列表