ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

mrp.rar_MRP 实战:从文件解析到净需求推算与避坑指南

mrp.rar_MRP 实战:从文件解析到净需求推算与避坑指南 简介这份资源围绕IEEE 802.1Q标准框架下的MRPMultiple Registration Protocol多注册协议展开面向网络工程师、嵌入式开发者及工业网络方向的学习者帮助理解环形网络中数据流控制、环保护与动态注册的底层实现。压缩包共2个文件包含1个c源文件与1个h头文件整体约6KB其中源文件承载协议函数定义、状态机与事件处理逻辑头文件则提供数据结构、接口声明与常量定义便于对照阅读与二次开发。资源重点覆盖环形拓扑支持、动态注册、环保护、流量控制以及与VLAN优先级兼容等核心特性读者可借此梳理MRP在工业自动化、电力传输、轨道交通等实时性要求较高场景中的工作流程并掌握协议定制与扩展的切入点。目前已有119人学习适合希望从源码层面理解MRP机制、补充网络协议实现经验的技术人员参考。1. 从 mrp.rar_MRP 说起一个被低估的制造计划内核如果你在工厂信息化这行待过几年大概率在某个老旧服务器的共享盘里见过一个叫mrp.rar的压缩包解压出来是一堆.MRP后缀的文件或者一个名为MRP的目录。很多人第一次看到它第一反应是“这什么上古遗留物”然后随手丢进回收站。但我要说的是这个看似不起眼的mrp.rar_MRP恰恰是制造业里最硬核、最经得起时间考验的计划逻辑载体——物料需求计划Material Requirements Planning。它不依赖花哨的界面不靠云原生架构只靠一张物料清单、一份库存记录和一张主生产计划就能把“什么时候该买什么、买多少”算得明明白白。这篇文章面向的是真正要在产线边、仓库里、ERP 后台把 MRP 跑起来的人不是来听概念的。我会从文件结构拆到参数设置从运算逻辑讲到翻车现场让你拿到一个mrp.rar就知道怎么让它跑出能用的结果。2. 拆开 mrp.rar_MRP文件里到底装了什么2.1 MRP 文件的三层结构BOM、库存、主计划一个标准的mrp.rar解压后通常不会是一个孤零零的.MRP文件而是一组相互引用的数据文件。最常见的组织方式是三层第一层是物料清单BOM描述“一个成品由哪些半成品和原材料构成各用多少”第二层是库存记录Inventory Record记录“当前仓库里每个物料的现有量、已分配量、在途量”第三层是主生产计划MPS规定“最终成品在哪些时间段需要产出多少”。这三层数据缺一不可而且必须通过物料编码严格对齐。很多新手拿到mrp.rar后直接双击里面的.MRP文件发现打不开或者乱码就是因为没有先理清这三层关系。我一般会先看压缩包里的文件命名规律如果看到BOM_*.dat、INV_*.dat、MPS_*.dat这样的前缀基本可以确定是分表存储如果只有一个大文件那多半是定长字段的平面文件需要用固定宽度解析。2.2 用 Python 解析 MRP 平面文件的最小命令假设你拿到的mrp.rar解压后是一个名为MRP_MASTER.dat的定长文件每行 128 个字符前 18 位是物料编码接着 10 位是需求日期YYYYMMDD再 12 位是净需求量。下面这段代码可以直接把它读成结构化数据import pandas as pd # 定义定长字段的宽度和列名 colspecs [(0, 18), (18, 28), (28, 40), (40, 52), (52, 64), (64, 76), (76, 88), (88, 100), (100, 112), (112, 128)] names [material_code, req_date, gross_req, scheduled_receipts, projected_on_hand, net_req, planned_order_receipt, planned_order_release, lead_time_days, lot_size] # 读取定长文件注意编码通常是 gbk 或 latin-1 df pd.read_fwf(MRP_MASTER.dat, colspecscolspecs, namesnames, encodinggbk, dtypestr) # 去掉首尾空格转换数值列 df[material_code] df[material_code].str.strip() df[req_date] pd.to_datetime(df[req_date], format%Y%m%d, errorscoerce) for col in [gross_req, scheduled_receipts, projected_on_hand, net_req, planned_order_receipt, planned_order_release]: df[col] pd.to_numeric(df[col].str.strip(), errorscoerce).fillna(0) # 按物料和日期排序方便后续逐行推算 df df.sort_values([material_code, req_date]).reset_index(dropTrue) print(df.head(10))这段代码的关键在于colspecs的设定。定长文件的字段宽度必须和原始系统导出时完全一致差一个字符就会导致整列错位。如果你不确定宽度可以先用head -c 200 MRP_MASTER.dat看原始字节数一下空格分隔的规律。另一个坑是编码老系统导出的文件经常是 GBK用 UTF-8 读会报UnicodeDecodeError这时候换成gbk或latin-1通常能解决。errorscoerce是为了防止某行日期字段为空导致整个解析中断空值会变成NaT后续可以单独过滤。2.3 从平面文件到 MRP 运算净需求推算的四个步骤解析出数据只是第一步真正让mrp.rar_MRP产生价值的是净需求推算。我一般按四步走第一步按物料汇总所有毛需求gross_req把同一物料在不同日期、不同上层订单下的需求加总第二步扣除现有库存projected_on_hand和已下达的在途量scheduled_receipts得到净需求第三步根据提前期lead_time_days倒推计划下达日期planned_order_release第四步按批量规则lot_size调整计划接收量。下面是一个简化的净需求推算片段# 按物料分组逐行推算预计可用库存和净需求 results [] for mat, group in df.groupby(material_code): on_hand group[projected_on_hand].iloc[0] # 期初库存 for _, row in group.iterrows(): available on_hand row[scheduled_receipts] - row[gross_req] net max(0, -available) # 净需求为负时取0 if net 0: # 按批量规则取整这里假设固定批量 lot_size lot float(row[lot_size]) if row[lot_size] else 1 planned_receipt ((net lot - 1) // lot) * lot # 倒推下达日期 release_date row[req_date] - pd.Timedelta(daysint(row[lead_time_days])) else: planned_receipt 0 release_date pd.NaT results.append({ material_code: mat, req_date: row[req_date], gross_req: row[gross_req], scheduled_receipts: row[scheduled_receipts], projected_on_hand: available, net_req: net, planned_order_receipt: planned_receipt, planned_order_release: release_date }) on_hand available planned_receipt # 更新库存供下一期使用 result_df pd.DataFrame(results) print(result_df[result_df[net_req] 0].head(20))这里有几个参数需要特别注意lead_time_days是提前期单位是天如果原始数据是工作日需要先转换成日历天lot_size是批量规则常见的有固定批量、直接批量、经济批量代码里用的是固定批量取整实际业务中可能还要考虑最小起订量、包装倍数。on_hand的更新逻辑是“本期可用库存 上期可用库存 本期计划接收 - 本期毛需求”如果算出来是负数说明库存不够需要产生净需求。这个循环必须严格按日期顺序执行否则库存推算会乱套。3. 让 MRP 跑得稳参数设置与常见配置陷阱3.1 提前期、安全库存、批量规则的联动关系MRP 运算结果准不准八成取决于三个参数提前期、安全库存、批量规则。提前期设短了计划下达日期会晚于实际需要产线等料设长了库存积压资金占用高。安全库存是缓冲但很多人把它当成“万能药”所有物料都拍一个固定值结果慢消物料堆成山快消物料还是断。批量规则更微妙固定批量适合需求稳定的物料直接批量适合昂贵或易过期的物料经济批量需要结合订货成本和持有成本算。我一般会先跑一版“毛需求净需求对照表”看看哪些物料的净需求波动大再针对性调整。下面这张表是我常用的参数检查清单参数常见错误值推荐做法影响提前期统一填 7 天按供应商实际交期分档本地 3 天、外省 7 天、进口 30 天计划下达日期偏差超过 2 天就会导致缺料或积压安全库存所有物料填 100按过去 6 个月需求标准差 × 服务水平系数过高掩盖计划问题过低失去缓冲意义批量规则全部用固定批量 1000按物料 ABC 分类A 类直接批量B 类固定批量C 类经济批量批量过大导致库存周转率下降损耗率忽略不填按工序实际良率倒推如良率 95% 则损耗率填 5.26%不填会导致净需求偏小实际生产不够这张表里的“服务水平系数”通常取 1.65对应 95% 服务水平或 2.33对应 99%具体看行业。损耗率的换算容易搞反如果良率是 95%意味着投入 100 个只能产出 95 个所以净需求要除以 0.95相当于乘以 1.0526也就是损耗率填 5.26% 而不是 5%。3.2 用 SQL 做 MRP 净需求推算的窗口函数写法如果你不想用 Python直接在数据库里用 SQL 窗口函数也能完成净需求推算。下面这段 SQL 假设有一张mrp_detail表字段包括material_code、req_date、gross_req、scheduled_receipts、on_hand、lead_time_days、lot_sizeWITH ranked AS ( SELECT material_code, req_date, gross_req, scheduled_receipts, on_hand, lead_time_days, lot_size, ROW_NUMBER() OVER (PARTITION BY material_code ORDER BY req_date) AS rn FROM mrp_detail ), running AS ( SELECT r1.material_code, r1.req_date, r1.gross_req, r1.scheduled_receipts, r1.on_hand, r1.lead_time_days, r1.lot_size, r1.on_hand SUM(r1.scheduled_receipts - r1.gross_req) OVER (PARTITION BY r1.material_code ORDER BY r1.req_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS projected_on_hand FROM ranked r1 ) SELECT material_code, req_date, gross_req, scheduled_receipts, projected_on_hand, CASE WHEN projected_on_hand 0 THEN -projected_on_hand ELSE 0 END AS net_req, CASE WHEN projected_on_hand 0 THEN CEIL(ABS(projected_on_hand) / NULLIF(lot_size, 0)) * lot_size ELSE 0 END AS planned_order_receipt, CASE WHEN projected_on_hand 0 THEN req_date - (lead_time_days || days)::INTERVAL ELSE NULL END AS planned_order_release FROM running ORDER BY material_code, req_date;这段 SQL 的核心是SUM(...) OVER (PARTITION BY ... ORDER BY ... ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)它按物料分组、按日期累加算出每一期的预计可用库存。CEIL(ABS(projected_on_hand) / lot_size) * lot_size实现了批量取整。注意NULLIF(lot_size, 0)是防止除零错误如果批量规则为空结果会变成 NULL需要在外层用COALESCE兜底。lead_time_days || days是 PostgreSQL 的写法MySQL 里要用DATE_SUB(req_date, INTERVAL lead_time_days DAY)。窗口函数的好处是不用写循环数据库引擎会自动处理顺序但前提是req_date必须唯一且无空值否则累加会错位。3.3 主生产计划变更后如何只重算受影响物料实际生产中主生产计划MPS经常变比如某个成品订单提前或取消。如果每次变更都全量重算 MRP数据量大时耗时很长。我一般会做“影响域分析”先根据 BOM 展开找出受影响的物料清单然后只对这些物料重算。具体做法是维护一张bom_parent_child关系表用递归查询找出所有下游物料WITH RECURSIVE affected AS ( SELECT child_code AS material_code FROM bom_parent_child WHERE parent_code CHANGED_PRODUCT_CODE UNION ALL SELECT b.child_code FROM bom_parent_child b INNER JOIN affected a ON b.parent_code a.material_code ) SELECT DISTINCT material_code FROM affected;这个递归查询会从变更的成品出发逐层向下找到所有半成品和原材料。然后只对这些物料执行净需求推算其他物料的结果直接复用上一版。这样做能把重算时间从几分钟压缩到几秒尤其适合物料上万、BOM 层级超过 5 层的场景。注意递归查询要防止死循环如果 BOM 里有循环引用比如 A 用 BB 又用 AUNION ALL会无限递归这时候要改成UNION去重或者加一个层级限制WHERE level 10。4. mrp.rar_MRP 避坑与排查那些年我踩过的雷4.1 坑一物料编码前后有空格导致 BOM 展开时匹配不上现象MRP 运算结果里某些半成品的毛需求为 0但明明上层成品有订单。检查 BOM 表发现父子物料的编码看起来一样但一个后面多了个空格。原因老系统导出数据时定长字段没有做 trim或者手工录入时带了不可见字符。BOM 展开时用匹配空格导致匹配失败。解决在解析阶段对所有物料编码做str.strip()并且用REPLACE去掉制表符和换行符。更稳妥的做法是在数据库里建唯一索引前先UPDATE bom SET material_code TRIM(material_code)。我现在的习惯是任何从外部导入的编码字段先跑一遍SELECT material_code, LENGTH(material_code) FROM bom GROUP BY 1,2 HAVING LENGTH(material_code) 18把超长的揪出来。4.2 坑二提前期单位是工作日但日期推算用了日历天现象计划下达日期算出来是周六采购员没上班订单实际下周一才发导致到料晚了两天。原因MRP 运算里req_date - lead_time_days直接减了日历天但供应商的提前期通常是工作日。如果提前期是 5 天从周三倒推日历天是周五但工作日只到周二。解决引入工厂日历表把工作日和节假日维护进去倒推时用工作日偏移。简单做法是写一个workday_offset(date, days)函数循环减一天遇到非工作日跳过。如果不想写函数至少要在计划下达日期上加一个“非工作日顺延”的后处理如果算出来是周六就减到周五如果是周日就减到周五。4.3 坑三安全库存被重复扣减净需求算多了现象某个物料的安全库存是 50现有库存 80毛需求 60按说净需求是 0但系统算出来净需求 30。原因库存记录里的on_hand已经扣除了安全库存但净需求公式里又减了一次安全库存导致重复扣减。解决先确认库存数据的口径。如果on_hand是“可用库存”已扣除安全库存净需求公式里就不要再减安全库存如果on_hand是“现有库存”未扣除才需要减。我一般会在数据字典里明确标注每个字段的业务含义避免不同模块对同一个字段理解不一致。排查时直接看projected_on_hand的初始值如果它等于on_hand - safety_stock那说明已经扣过了。4.4 坑四批量规则取整时最小起订量没考虑现象净需求是 12 个批量规则是固定批量 100但供应商最小起订量是 500结果计划下达了 100采购员下不了单。原因MRP 运算只考虑了lot_size没有把供应商的最小起订量MOQ和包装倍数纳入批量规则。解决在批量取整时取MAX(lot_size, moq)的整数倍。如果包装倍数是 25还要向上取整到 25 的倍数。代码里可以写成planned_receipt CEIL(MAX(net_req, moq) / pack_size) * pack_size。这个逻辑最好放在数据库函数里避免每个报表都写一遍。4.5 坑五BOM 版本切换后旧版本的物料还在跑计划现象工程变更通知已经发了新版本 BOM 从下月 1 号生效但 MRP 运算还是按旧版本展开导致旧物料继续采购。原因BOM 表里没有生效日期和失效日期字段或者 MRP 运算时没有按需求日期过滤 BOM 版本。解决在 BOM 关系表里加effective_date和expiry_dateMRP 展开时用req_date BETWEEN effective_date AND expiry_date过滤。如果同一物料有多个版本取生效日期最晚的那个。这个坑在汽车、电子行业特别常见因为工程变更频繁BOM 版本管理不到位计划就会乱。5. 进阶技巧用 MRP 结果反查 BOM 健康度跑通 MRP 只是第一步真正让我觉得mrp.rar_MRP有价值的地方是它能反过来暴露 BOM 数据的问题。我有个习惯每次 MRP 运算完不急着看缺料清单先看“零毛需求物料”和“负库存物料”这两个异常清单。零毛需求物料是指那些在 BOM 里存在但 MRP 展开后没有任何毛需求的物料——要么是 BOM 里挂了但实际不用要么是上层成品没有订单。负库存物料是指projected_on_hand算出来小于 0 的物料说明库存数据不准或者提前期设得太短。这两个清单能直接反映 BOM 的“虚挂”和“漏挂”问题。具体做法是在净需求推算结果上加两个过滤条件# 零毛需求物料在 BOM 里出现但 MRP 结果里 gross_req 全为 0 bom_materials set(bom_df[child_code].unique()) mrp_materials set(result_df[result_df[gross_req] 0][material_code].unique()) zero_demand bom_materials - mrp_materials print(f零毛需求物料数量{len(zero_demand)}) print(f示例{list(zero_demand)[:10]}) # 负库存物料projected_on_hand 0 且没有计划接收 negative_stock result_df[(result_df[projected_on_hand] 0) (result_df[planned_order_receipt] 0)] print(f负库存且无计划接收的物料数量{len(negative_stock)}) print(negative_stock[[material_code, req_date, projected_on_hand]].head(10))零毛需求物料如果数量很多说明 BOM 维护有问题可能有很多淘汰料号没清理。负库存且无计划接收的物料要么是安全库存设得太高导致可用库存被扣成负数要么是提前期设得太短导致计划下达日期已经过期。我一般会把这些物料导出来发给工程部和采购部各一份让他们确认是改 BOM 还是调参数。这个反查动作坚持做几个月BOM 准确率能提升一大截。另一个进阶用法是用 MRP 结果做“计划冻结期”分析。计划冻结期是指从当前日期到第一个计划下达日期之间的天数。如果冻结期太短采购员天天改单供应商抱怨太长市场变化响应慢。我通常会把planned_order_release按日期排序看最近 7 天、14 天、30 天各有多少条计划下达。如果 7 天内的计划下达占比超过 30%说明冻结期设短了需要和销售、采购一起定一个合理的冻结窗口。这个分析不需要额外数据直接从 MRP 结果里 group by 就行。最后说一个我自己的教训早年我迷信“全自动 MRP”觉得参数设好就不用管了。结果有一次安全库存被批量规则放大系统自动生成了足够用半年的采购计划仓库爆仓资金链差点断掉。从那以后我每次跑完 MRP 都会人工抽查前 20 条计划订单看看数量、日期、供应商是否合理。机器算得再快也替代不了人对业务的理解。希望帮到你。本文还有配套的精品资源点击获取
返回列表