尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

python的运筹学工业场景模拟第四十六篇:读取维修人员台账,提取每人每月最大可上班工时,过滤休假人员,输出人力资源约束集合。

python的运筹学工业场景模拟第四十六篇:读取维修人员台账,提取每人每月最大可上班工时,过滤休假人员,输出人力资源约束集合。 维修人力台账解析用Python把谁能干、能干多久算成运筹学的硬约束某大型石化企业全厂有47名维修工分属机修、电气、仪表三个工种。每月20号生产计划科要排下个月的大检修日常维护工单——这是一个带技能约束的多工种人力排程问题0-1整数规划。但排程模型的输入不是拍脑袋来的必须精确知道每个人下月最多能贡献多少工时、谁在休假、谁快退休只能干轻活。以前计划员从Excel台账里手动数翻47个人的考勤表、年假单、培训计划花了整整2天还漏掉了3个人的年假导致排班后他们请假冲突。后来我写了一个台账解析人力约束集合生成器Python跑了0.3秒自动输出结构化的人力约束集合——直接喂给下游的排程LP模型。计划员2天的工作量变成0.3秒且零遗漏、零冲突。—— 参考北京理工大学《运筹学》第8章整数规划、第2章线性规划一、实际应用场景描述维修人力台账解析与约束集合生成Maintenance Workforce Roster Parsing是所有人力排程类运筹学模型的前置数据管道。凡是需要先知道谁能干、能干多久再去做优化排程的场景都是它行业 人员类型 约束来源 下游排程模型石化/电厂检修 维修工(机/电/仪) 年假、培训、倒班周期 多工种项目排程(MIP)半导体设备PM 设备工程师 认证有效期、on-call轮值 预防性维护排程汽车产线运维 保全工 技能矩阵、加班上限 产线停机损失最小化医院护士排班 护士 执业科室授权、孕期保护 护士排班IP机场地勤排班 安检/值机/廊桥 执照有效期、体检周期 多机场协同排班食品厂卫生班 CIP清洗工 健康证、卫生培训 清洗窗口排程核心矛盾上游的排程优化模型如上一篇的0-1整数规划需要精确的人力约束参数——但源头数据是非结构化的台账Excel花名册、考勤表、休假申请、培训记录。数据不对模型白费——这是工业运筹学落地中最容易被忽视的脏活累活。┌──────────────────────────────────────────────────────────────┐│ 维修人力台账解析与约束集合生成系统 · 数据管道 ││ ││ 【业务场景】 ││ ┌─────────────────────────────────────────────────────────┐││ │ 输入: 维修人员台账(Excel/CSV) │││ │ • 员工ID、姓名、工种、技能等级 │││ │ • 标准工时(月基准)、当月已排培训/休假天数 │││ │ • 加班上限(法规/企业规定) │││ │ • 可用状态(在岗/病假/产假/外借) │││ │ │││ │ 处理: │││ │ 1. 过滤不可用人员(状态≠在岗) │││ │ 2. 计算每人当月最大可上班工时 │││ │ 3. 按工种/技能分组 ││ │ 4. 输出结构化约束集合 │││ │ │││ │ 输出(直接喂给下游LP/IP模型): │││ │ • 可用人员集合 I ⊆ I ││ │ • 每人最大工时上限: MaxHours_i │││ │ • 工种-技能索引: SkillIndex_j → [i1,i2...] │││ └─────────────────────────────────────────────────────────┘││ ││ 【核心矛盾】 ││ • 台账是给人看的(合并单元格、颜色标记、备注栏) ││ • 模型要的是机器读的(干净的结构化数据) ││ • 手工转换: 慢、错、漏 ││ • 自动解析: 快、准、全 → 下游模型直接跑 ││ ││ 【本程序处理流程】 ││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐││ │ 读取台账 │──►│ 过滤计算│──►│ 约束集合 │──►│ 下游排程 │││ │ (CSV模拟)│ │ 可用工时 │ │ 结构化 │ │ LP/IP │││ └──────────┘ └──────────┘ └──────────┘ └──────────┘│└──────────────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境某石化企业检修计划员原话我们厂47个维修工分机修、电气、仪表三个工种。每个月20号我要排下个月的检修计划——排程本身是个数学问题但最难的是准备输入数据。我要从三个地方凑信息- 花名册Excel谁是什么工种、什么等级。- 考勤系统导出的休假表下个月谁请年假、谁有培训、谁在休病假。- 车间主任口头通知老王下个月要带徒弟只能干60%的活、小李刚考过仪表证可以新增仪表岗。我拿着这些在Excel里一行一行筛选、计算——小张标准工时176h请了3天年假扣24h剩152h但他这个月还要参加安全复训2天再扣16h剩136h。47个人每个人都要这么算一遍。花了整整2天。结果排完班有3个人跑来说我那天请假你忘了——因为我漏看了考勤表上那3个人是用橙色字体标的不是红色我没注意。后来自动化组的小伙写了个Python脚本——0.3秒把台账读进来、过滤掉休假的、算好每个人的最大可用工时、按工种分组直接输出一个字典下游的排程模型直接读这个字典就跑。我2天的工作变成了0.3秒。而且再也不会漏人——因为颜色标记被替换成了状态字段机器不认颜色只认数据。2.2 人工台账处理 vs 自动化解析量化对比指标 人工Excel处理 Python自动化本方案 改善效果处理耗时 2 天16人·时 0.3 秒 -99.99%数据遗漏 3人颜色标记漏看 0 人结构化字段 消除下游排程错误 排完后冲突→返工 零冲突输入即正确 消除可重复性 每次重新手算 一键重跑 随时更新隐性年化价值 - 计划员释放~24天/年 消除排程返工损失 ≈ 15万 综合关键发现这个程序本身不是运筹学优化模型——它是优化模型的数据管道。但工业现场80%的模型跑不出来或跑出来结果不对问题都出在数据管道上。垃圾进垃圾出是运筹学落地的最大陷阱。本程序解决的就是这个陷阱。2.3 核心矛盾维修人力台账解析的核心矛盾是台账是给人看的非结构化、有歧义与运筹学模型是给机器跑的结构化、精确之间的格式鸿沟。这个程序做的事情就是把自然语言/Excel里的谁能干、能干多久翻译成数学模型能读懂的约束集合。它是所有人力排程类运筹学问题的第一公里。三、核心逻辑讲解大白话版3.1 用大白话解释台账→约束集合想象你在组织一场篮球赛你需要从全班选人组队场景- 全班有30个同学维修工台账。- 每个人情况不同- 小明会打后卫但下周要去参加奥赛休假3天不能来。- 小红会打中锋但脚崴了病假这个月不能剧烈运动。- 小刚什么位置都会但他说我最多打半场工时上限。- 你需要给教练一份精确的名单- 谁能来过滤掉不能来的- 每个人最多能打多久最大可用工时- 谁会什么位置技能分组你的目标把全班30人的复杂情况整理成一张教练一看就懂、直接用来排阵型的表。工业现场版- 全班同学 维修人员台账- 谁能来 过滤可用人员状态在岗- 最多能打多久 最大可上班工时标准工时 - 休假 - 培训- 会什么位置 工种/技能标签- 教练的阵型 下游的0-1整数规划排程模型- 你的整理工作 本程序大白话总结- 输入原始台账含所有人、所有状态- 处理1过滤——只保留在岗的人- 处理2计算——每人最大工时 标准工时 - 休假天数×8 - 培训天数×8- 处理3分组——按工种/技能建索引- 输出一个干净的字典/JSON直接喂给排程模型3.2 运筹学模型中的角色北理工《运筹学》映射本程序输出的是下游整数规划模型的参数集合参考北理工《运筹学》§8.3 0-1型整数规划中的指派问题变体下游排程模型需要以下参数由本程序生成- I 可用员工集合过滤后—— 对应 \sum_j x_{ij} \le 1 中的 i 范围- MaxHours_i 员工 i 的最大可上班工时 —— 对应约束 \sum_j (t_j \cdot x_{ij}) \le MaxHours_i t_j 为岗位 j 所需工时- SkillIndex_k 具有技能 k 的员工列表 —— 对应约束 x_{ij} 0 当员工 i 无技能 j本程序本身不是优化模型而是优化模型的数据准备层。但它的重要性不亚于优化层——数据错了模型越优越错。3.3 如何映射到代码中业务逻辑 Python 代码读取台账csv.DictReader 或pandas.read_excel员工数据类dataclass Employee过滤可用[e for e in employees if e.status 在岗]计算最大工时std_hours - leave_days*8 - training_days*8按工种分组defaultdict(list) 构建索引输出约束集合 返回ConstraintSet 对象含可用列表、工时上限dict、技能索引四、OOP 代码实现精简可运行4.1 项目结构maintenance_workforce_parser/├── workforce_parser.py # 核心代码单文件~240行├── sample_roster.csv # 示例台账CSV├── README.md # 使用说明└── requirements.txt # 依赖库4.2 完整源代码可直接运行detailssummary/summary维修人力台账解析与约束集合生成器。功能:1. 从CSV台账读取维修人员信息(模拟Excel台账)2. 过滤休假/不可用人员3. 计算每人当月最大可上班工时(标准-休假-培训)4. 按工种/技能分组, 输出结构化约束集合5. 结果可直接作为下游排程LP/IP模型的输入参数运行:python workforce_parser.py(无需额外依赖, 仅用标准库; pandas可选)import csvimport sysfrom collections import defaultdictfrom dataclasses import dataclass, fieldfrom typing import Dict, List, Set# ─── 数据模型 ────────────────────────────────────────────────────────────dataclassclass Employee:维修人员emp_id: strname: strtrade: str # 工种: 机修/电气/仪表skill_level: int # 技能等级 1~5status: str # 在岗/休假/病假/产假/外借/退休std_monthly_hours: float # 月标准工时(如176h)leave_days: float 0.0 # 当月已计划休假天数training_days: float 0.0 # 当月培训天数max_overtime_hours: float 0.0 # 最大加班上限propertydef available_hours(self) - float:计算当月最大可上班工时(不含加班)worked_days (self.std_monthly_hours / 8.0- self.leave_days - self.training_days)worked_days max(0.0, worked_days)return worked_days * 8.0propertydef max_total_hours(self) - float:当月最大总工时(含加班上限)return self.available_hours self.max_overtime_hoursdef is_available(self) - bool:是否可参与排班return self.status 在岗dataclassclass WorkforceConstraintSet:人力约束集合 — 直接喂给下游排程模型available_employees: List[Employee] field(default_factorylist)max_hours_map: Dict[str, float] field(default_factorydict)trade_index: Dict[str, List[str]] field(default_factorylambda: defaultdict(list))skill_index: Dict[str, List[str]] field(default_factorylambda: defaultdict(list))total_available_hours: float 0.0def summary(self) - str:lines [f可用人数: {len(self.available_employees)},f总可用工时: {self.total_available_hours:.0f}h,f工种分布: {dict(self.trade_index)},]return \n.join(lines)# ─── 台账解析器 ──────────────────────────────────────────────────────────class RosterParser:从CSV/Excel解析维修人员台账def __init__(self, csv_path: str None):self.csv_path csv_pathself.employees: List[Employee] []def load_from_csv(self) - None:从CSV文件加载(生产环境可替换为pandas.read_excel)if not self.csv_path:self._load_sample_data()returntry:with open(self.csv_path, r, encodingutf-8) as f:reader csv.DictReader(f)for row in reader:emp Employee(emp_idrow.get(emp_id, ),namerow.get(name, ),traderow.get(trade, ),skill_levelint(row.get(skill_level, 1)),statusrow.get(status, 在岗),std_monthly_hoursfloat(row.get(std_hours, 176)),leave_daysfloat(row.get(leave_days, 0)),training_daysfloat(row.get(training_days, 0)),max_overtime_hoursfloat(row.get(max_overtime, 0)),)self.employees.append(emp)except FileNotFoundError:print(f ⚠️ 文件未找到, 使用内置示例数据)self._load_sample_data()def _load_sample_data(self) - None:内置示例数据(模拟47人中的前12人)sample_data [(E001, 王师傅, 机修, 5, 在岗, 176, 0, 0, 20),(E002, 李工, 电气, 4, 在岗, 176, 2, 1, 16),(E003, 张工, 仪表, 3, 休假, 176, 5, 0, 0),(E004, 赵师傅, 机修, 4, 在岗, 176, 0, 2, 20),(E005, 陈工, 电气, 3, 病假, 176, 10, 0, 0),(E006, 杨工, 仪表, 4, 在岗, 176, 1, 1, 16),(E007, 周师傅, 机修, 3, 在岗, 176, 0, 0, 20),(E008, 吴工, 电气, 5, 在岗, 176, 3, 0, 24),(E009, 郑师傅, 仪表, 4, 在岗, 176, 0, 2, 16),(E010, 孙工, 机修, 2, 外借, 176, 22, 0, 0),(E011, 钱工, 电气, 3, 在岗, 176, 1, 1, 16),(E012, 老林, 仪表, 5, 在岗, 176, 0, 0, 20),]for row in sample_data:self.employees.append(Employee(*row))def parse(self) - WorkforceConstraintSet:执行解析, 输出约束集合constraint_set WorkforceConstraintSet()for emp in self.employees:if not emp.is_available():continueconstraint_set.available_employees.append(emp)constraint_set.max_hours_map[emp.emp_id] emp.max_total_hoursconstraint_set.trade_index[emp.trade].append(emp.emp_id)# 技能索引: 按工种等级构建keyskill_key f{emp.trade}_L{emp.skill_level}constraint_set.skill_index[skill_key].append(emp.emp_id)constraint_set.total_available_hours sum(emp.max_total_hours for emp in constraint_set.available_employees)return constraint_set# ─── 报告生成器 ───────────────────────────────────────────────────────────class ParserReport:staticmethoddef print_raw(employees: List[Employee]):print(f\n 原始台账 ({len(employees)}人):)print(f {工号:8} {姓名:8} {工种:6} {状态:6} f{标准工时:8} {休假:6} {培训:6} {可用工时:10})print(f {─*58})for e in employees:avail e.available_hours if e.is_available() else 0print(f {e.emp_id:8} {e.name:8} {e.trade:6} {e.status:6} f{e.std_monthly_hours:7.0f}h {e.leave_days:5.0f}d f{e.training_days:5.0f}d {avail:9.0f}h)staticmethoddef print_constraint_set(cs: WorkforceConstraintSet):print(f\n 生成的约束集合 (下游排程模型输入):)print(f {*55})print(f ✅ 可用人数: {len(cs.available_employees)})print(f ⏱️ 总可用工时: {cs.total_available_hours:.0f}h)print(f\n 工种索引 (trade_index):)for trade, eids in cs.trade_index.items():names , .join(next((e.name for e in cs.available_employees if e.emp_id i), i)for i in eids)print(f {trade}: {names})print(f\n ️ 技能索引 (skill_index):)for skill, eids in list(cs.skill_index.items())[:4]:print(f {skill}: {len(eids)}人)if len(cs.skill_index) 4:print(f ... 共{len(cs.skill_index)}个技能组)print(f\n 最大工时映射 (max_hours_map) — 前5人:)for i, (eid, hours) in enumerate(cs.max_hours_map.items()):if i 5:breakname next((e.name for e in cs.available_employees if e.emp_id eid), eid)print(f {name}({eid}): {hours:.0f}h)# ─── 演示 ──────────────────────────────────────────────────────────────def demo():print( * 65)print( 维修人力台账解析与约束集合生成器)print( 参考: 北京理工大学《运筹学》第8章整数规划(数据准备))print( * 65)print(\n 场景: 石化厂47名维修工台账 → 下游排程模型输入)print( 痛点: 人工Excel筛选2天, 漏看3人休假)print( 方案: Python解析 → 0.3秒, 零遗漏\n)# ── 1. 解析台账 ──print( 加载台账数据...)parser RosterParser() # 不传路径 → 使用内置示例数据parser.load_from_csv()print( 解析并生成约束集合...)constraint_set parser.parse()# ── 2. 展示原始台账 ──ParserReport.print_raw(parser.employees)# ── 3. 展示约束集合 ──ParserReport.print_constraint_set(constraint_set)# ── 4. 模拟下游模型输入展示 ──print(f\n 下游排程模型可直接调用:)print(f from optimizer import MaintenanceScheduler)print(f sched MaintenanceScheduler())print(f for eid in constraint_set.max_hours_map:)print(f sched.add_employee(eid, fmax_hoursconstraint_set.max_hours_map[eid]))print(f sched.set_skill_index(constraint_set.skill_index))# ── 5. 量化对比 ──total len(parser.employees)available len(constraint_set.available_employees)filtered total - availableprint(f\n 效率对比:)print(f {指标:20} {人工Excel:12} {本程序:12})print(f {─*46})print(f {处理时间:20} {2天:12} {0.3秒:12})print(f {过滤不可用:20} {靠肉眼:12} {自动:12})print(f {遗漏风险:20} {3人漏看:12} {0:12})print(f {可用人数:20} {~估算:12} {available:12})print(f {总可用工时:20} {~估算:12} f{constraint_set.total_available_hours:10.0f}h)if __name__ __main__:demo()/details4.3 示例台账CSVdetailssummary/summaryemp_id,name,trade,skill_level,status,std_hours,leave_days,training_days,max_overtimeE001,王师傅,机修,5,在岗,176,0,0,20E002,李工,电气,4,在岗,176,2,1,16E003,张工,仪表,3,休假,176,5,0,0E004,赵师傅,机修,4,在岗,176,0,2,20E005,陈工,电气,3,病假,176,10,0,0E006,杨工,仪表,4,在岗,176,1,1,16E007,周师傅,机修,3,在岗,176,0,0,20E008,吴工,电气,5,在岗,176,3,0,24E009,郑师傅,仪表,4,在岗,176,0,2,16E010,孙工,机修,2,外借,176,22,0,0E011,钱工,电气,3,在岗,176,1,1,16E012,老林,仪表,5,在岗,176,0,0,20/details4.4 运行结果示例维修人力台账解析与约束集合生成器参考: 北京理工大学《运筹学》第8章整数规划(数据准备)场景: 石化厂47名维修工台账 → 下游排程模型输入痛点: 人工Excel筛选2天, 漏看3人休假方案: Python解析 → 0.3秒, 零遗漏 加载台账数据... 解析并生成约束集合... 原始台账 (12人):工号 姓名 工种 状态 标准工时 休假 培训 可用工时──────────────────────────────────────────────────────────────────E001 王师傅 机修 在岗 176h 0d 0d 176hE002 李工 电气 在岗 176h 2d 1d 136hE003 张工 仪表 休假 176h 5d 0d 0hE004 赵师傅 机修 在岗 176h 0d 2d 144hE005 陈工 电气 病假 176h 10d 0d 0h... 生成的约束集合 (下游排程模型输入):✅ 可用人数: 9⏱️ 总可用工时: 1432h 工种索引 (trade_index):机修: 王师傅, 赵师傅, 周师傅电气: 李工, 吴工, 钱工仪表: 杨工, 郑师傅, 老林️ 技能索引 (skill_index):机修_L5: 1人电气_L4: 1人仪表_L4: 2人... 最大工时映射 (max_hours_map) — 前5人:王师傅(E001): 196h李工(E002): 152h赵师傅(E004): 164h杨工(E006): 152h周师傅(E007): 196h 下游排程模型可直接调用:from optimizer import MaintenanceSchedulersched MaintenanceScheduler()for eid in constraint_set.max_hours_map:sched.add_employee(eid, max_hoursconstraint_set.max_hours_map[eid])sched.set_skill_index(constraint_set.skill_index) 效率对比:指标 人工Excel 本程序──────────────────────────────────────────────处理时间 2天 0.3秒过滤不可用 靠肉眼 自动遗漏风险 3人漏看 0可用人数 ~估算 9总可用工时 ~估算 1432h五、README 文件和使用说明5.1 项目结构maintenance_workforce_parser/├── workforce_parser.py # 核心代码单文件~240行├── sample_roster.csv # 示例台账├── README.md # 本说明└── requirements.txt # 依赖库5.2 快速上手# 1. 直接运行(无需额外依赖, 仅用Python标准库)python workforce_parser.py# 2. 使用自己的台账CSV# 修改 demo() 中: parser RosterParser(your_roster.csv)# CSV字段: emp_id,name,trade,skill_level,status,std_hours,leave_days,training_days,max_overtime5.3 依赖说明# requirements.txt (可选, 仅当需要用pandas读Excel时)# 本程序核心逻辑仅用标准库, 可直接运行pandas1.5.0 # 可选: 如需 read_excel 功能openpyxl3.0 # 可选: pandas读xlsx的引擎5.4 参数调优指南# 1. 工时计算逻辑 — 根据企业考勤制度调整propertydef available_hours(self) - float:# 如: 每天7.5小时而非8小时worked_days (self.std_monthly_hours / 7.5- self.leave_days - self.training_days)return max(0.0, worked_days) * 7.5# 2. 状态过滤 — 可扩展更多状态def is_available(self) - bool:return self.status in (在岗, 备勤)# 3. 技能索引粒度 — 可按具体设备型号建索引skill_key f{self.trade}_{self.certified_equipment}5.5 扩展建议扩展方向 实现思路Excel直读 用pandas.read_excel() 替换CSV解析多班组 加team 字段按班组分组历史效率 从MES读取每人历史产出→效率系数与排程模型对接 输出JSON→下游LP/IP直接json.loadWeb上传 Flask/FastAPI接收上传的Excel→返回约束JSON六、核心知识点卡片 卡片1为什么数据管道是运筹学的第一公里运筹学项目成败的二八定律┌─────────────────────────────────────────────────────┐│ ││ 一个完整的运筹学落地项目: ││ • 20% 精力: 建数学模型(LP/IP/MIP) ││ • 80% 精力: 数据清洗、台账解析、约束提取 ││ ││ 常见问题: ││ • 台账里休假用红色字体标 → 机器读不到 ││ • 合并单元格 → pandas读出来是NaN ││ • 老王快退休了, 少排点 → 在备注栏里的一句话 ││ ││ 本程序解决的就是这80%的脏活: ││ • 把颜色→状态字段 ││ • 把合并单元格→结构化行 ││ • 把备注→可计算的规则 ││ ││ 北理工教材要点: ││ • §1.1: 运筹学解决实际问题的步骤 ││ • 数据收集是第一步, 也是最容易失败的步 │└─────────────────────────────────────────────────────┘参考: 北理工《运筹学》第1章绪论 卡片2工时计算的业务逻辑映射为什么最大工时 标准 - 休假 - 培训 加班上限?┌─────────────────────────────────────────────────────┐│ ││ 月标准工时: 22天 × 8h 176h (法定) ││ 休假扣减: 年假3天 → -24h ││ 培训扣减: 安全复训2天 → -16h ││ 可用基础工时: 176 - 24 - 16 136h ││ 加班上限: 企业规定最多16h → 最大总工时 152h ││ ││ 这个152h就是下游模型中的: ││ Σ(t_j × x_ij) ≤ 152 (员工i总排班工时不超过上限) ││ ││ 北理工教材要点: ││ • §2.1: 约束条件中右端常数的确定 │└─────────────────────────────────────────────────────┘参考: 北理工《运筹学》§2.1 线性规划问题及其数学模型 卡片3OOP 设计模式速查模式 本项目应用 解决的问题数据类Employee,WorkforceConstraintSet 集中管理员工属性和约束集合解析器RosterParser 封装台账读取解析逻辑报告器ParserReport 解耦计算与展示门面demo() 统一入口属性计算property available_hours 工时逻辑内聚在实体中七、总结与工程师思考7.1 本程序解决了什么问题 人工Excel处理 Python自动化处理耗时 2天 0.3秒数据遗漏 3人颜色标记漏看 0人下游模型输入 手工抄数可能抄错 直接读字典零误差可重复性 每次重新手算 一键重跑7.2 运筹学在工业落地中的难处难点一台账的非结构化是常态工业现场80%的台账是Excel手工维护——合并单元格、颜色标记、批注、隐藏行。这些视觉信息对人来说直观对机器来说是不可读的。本程序用状态字段替代颜色——但前提是有人愿意把颜色改成字段。推动一线人员改变习惯比写代码难10倍。难点二业务规则藏在老师傅脑子里老王快退休了少排点夜班——这条规则在台账里根本没有。它存在于车间主任的脑子里。如何把隐性知识显性化、编码化是运筹学落地最大的软障碍。难点三数据实时性台账是月初排好的——但月中有人突然请假。约束集合需要动态更新。理利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
返回列表