ARTICLE DETAIL

资讯详情

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

python的运筹学工业场景模拟第三十一篇:解析车间成本报表,拆分原材料,工时,能耗分项单位成本,输出每种产品完整成本向量,作为线性规划目标函数输入。

python的运筹学工业场景模拟第三十一篇:解析车间成本报表,拆分原材料,工时,能耗分项单位成本,输出每种产品完整成本向量,作为线性规划目标函数输入。 车间成本报表解析与产品成本向量构建用 Python 打通运筹学目标函数的数据源头某精密机械加工厂5条产线、30多种产品混线生产。厂长每次看月度成本报表只看到一个总数本月生产成本187万。但他真正想知道的是每种产品的真实单位成本是多少 原材料、工时、能耗、折旧——这些成本混在一起根本拆不出来。排产时用经验单价做线性规划目标函数结果模型总是推荐多产看起来便宜的产品实际上那些产品的能耗成本被严重低估。后来我写了个Python解析器自动从车间成本报表中拆分原材料/工时/能耗分项输出每种产品的完整成本向量——直接喂给PuLP做目标函数。当月按模型排产总成本比经验排产低了11.7%约21.3万元。—— 参考北京理工大学《运筹学》第2章线性规划、第3章对偶理论与灵敏度分析一、实际应用场景描述在离散制造、机加工、电子组装等行业车间成本数据普遍存在一个结构性顽疾财务给的是总成本但排产需要的是每种产品的分项单位成本向量。财务ERP系统月底结账输出的是这样的报表- 本月原材料采购82万元- 本月直接人工45万元- 本月电费/气费28万元- 本月设备折旧32万元- 合计187万元但线性规划排产模型需要的是- 产品P01原材料8.2元/件 工时成本3.5元/件 能耗1.8元/件 折旧分摊2.1元/件 15.6元/件- 产品P02原材料22.0元/件 工时成本5.2元/件 能耗3.5元/件 折旧分摊4.8元/件 35.5元/件- ……这两个视角之间的翻译工作目前绝大多数工厂靠计划员手工完成——用Excel拉比例、拍脑袋分摊。┌──────────────────────────────────────────────────────────────┐│ 车间成本解析 · 产品成本向量构建系统 ││ ││ 【输入数据源车间典型报表】 ││ ┌─────────────────────────────────────────────────────────┐││ │ 成本月报_2025_08.xlsx │││ │ ├── 原材料汇总! (物料编码, 领用量, 单价, 总金额) │││ │ ├── 工时记录! (产品ID, 工序, 标准工时, 实际工时, 人数) │││ │ ├── 能耗账单! (设备/区域, 电度, 气费, 分摊规则) │││ │ ├── 设备折旧! (设备ID, 原值, 月折旧额, 可用工时) │││ │ └── 生产台账! (产品ID, 生产数量, 用料清单, 工序路径) │││ └─────────────────────────────────────────────────────────┘││ ││ 【核心解析逻辑】 ││ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐│││ │ 原材料成本 │ │ 工时成本 │ │ 能耗折旧成本 ││││ │ 领料÷产量 │ │ 工时×费率 │ │ 按工时比例分摊 ││││ │ → 元/件 │ │ → 元/件 │ │ → 元/件 ││││ └──────┬───────┘ └──────┬───────┘ └──────┬───────────┘│││ └─────────────────┼─────────────────┘ │││ ▼ │││ ┌─────────────────────────┐ │││ │ 产品成本向量 c_p │ │││ │ [原材料, 工时, 能耗, │ │││ │ 折旧, 合计] │ │││ └───────────┬─────────────┘ │││ ▼ │││ ┌─────────────────────────┐ │││ │ min Σ c_p × x_p │ │││ │ (PuLP目标函数) │ │││ └─────────────────────────┘ │││ ││ 【输出结果】 ││ • 每种产品的完整成本向量5维 │││ • 成本结构饼图数据原材料/工时/能耗/折旧占比 │││ • 可直接作为LP目标函数系数的c向量 │││ • 与历史成本对比的偏差报告 ││└──────────────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境某精密机械加工厂生产主管的原话我们厂做精密齿轮和轴套30多种产品混线生产。5台CNC、2台磨床、1条装配线。每月排产我都要做线性规划——模型本身不复杂就是在满足设备工时约束下决定每种产品各产多少使总成本最小。但目标函数的系数每种产品的单位成本一直是个大问题。财务给我的成本报表只有总数原材料82万、人工45万、电费气费28万、折旧32万。我需要知道每种产品的单位成本才能作为LP的目标函数系数。但财务说你要的明细我们系统出不了你自己拆吧。于是我每个月手工拆1. 打开生产台账看每种产品这个月产了多少件。2. 打开领料记录按BOM大致估算每种产品用了多少材料。3. 打开工时记录按标准工时×人数×时薪算人工。4. 能耗和折旧我直接按产量比例拍脑袋分摊——产得多的多摊产得少的分摊少。这套手工拆分我每个月要花3天时间。而且问题很大- 能耗按产量比例分摊是严重错误的。高精密齿轮要用CNC精加工单位能耗是普通轴套的3倍但我的方法让轴套也摊了同样的能耗——齿轮的真实能耗成本被低估了。- 折旧同理。CNC设备折旧贵主要用来加工高价值产品按产量分摊就把CNC折旧摊到了简单产品上。结果就是LP模型算出来多产轴套、少产齿轮——因为轴套的经验成本看起来很低。但实际上轴套的能耗和折旧被低估了真实利润反而齿轮更高。上个月我按经验排产结果齿轮库存不够紧急外协了一批外协价比自产高了40%。厂长问我为什么不多排齿轮我说模型说齿轮成本高——他把我骂了一顿。后来我写了个Python脚本从原始报表中按实际工艺路径和工时比例来分摊能耗和折旧而不是按产量拍脑袋。跑出来齿轮的真实单位成本是38.2元我之前估的是31.5元轴套是12.8元我之前估的是10.2元。用新成本向量重新跑LP模型自动增加了齿轮的产量——当月总成本比经验排产低了21.3万。2.2 经验分摊 vs 作业成本法ABC精确拆分量化对比指标 经验分摊按产量拍脑袋 ABC精确拆分本方案 差异齿轮单位成本 31.5 元/件 38.2 元/件 21.3%轴套单位成本 10.2 元/件 12.8 元/件 25.5%齿轮能耗成本 被低估 35% 精确反映实际 修正轴套折旧分摊 被高估 28% 精确反映实际 修正LP推荐产量结构 齿轮40% / 轴套60% 齿轮55% / 轴套45% 逆转月均总成本 182.6 万元 161.3 万元 -11.7%紧急外协次数 2~3次/月 0次 消除数据准备时间 3天/月手工Excel 5分钟/月自动 -99.9%年化收益 - 约 255 万元 成本节约外协消除关键发现成本分摊方法直接影响LP模型的决策方向。按产量分摊能耗和折旧本质上是把高能耗设备的成本补贴给了低能耗产品——模型会错误地推荐多产低能耗产品导致高价值高能耗产品的产能被浪费。2.3 核心矛盾车间成本管理的核心矛盾是财务视角的汇总成本与排产视角的分项单位成本之间的错位。财务需要汇总算总账、做报表排产需要分项每种产品各花了多少钱。这个翻译层如果靠人拍脑袋就会系统性地扭曲成本结构导致LP模型给出错误的排产建议。三、核心逻辑讲解大白话版3.1 用大白话解释成本向量构建想象你在经营一家奶茶店场景- 你卖3种奶茶珍珠奶茶、水果茶、纯茶。- 月底你只看到一张总账单牛奶花了500元、茶叶300元、珍珠200元、电费150元、店员工资800元。- 但你真正想知道的是每杯珍珠奶茶赚多少钱每杯水果茶赚多少钱贪心做法经验分摊- 我这个月卖了500杯珍珠奶茶、300杯水果茶、200杯纯茶总共1000杯。- 电费150元平均每杯摊0.15元——所以珍珠奶茶电费0.15元水果茶0.15元纯茶0.15元。- 问题来了珍珠奶茶要煮珍珠用电磁炉15分钟水果茶只是切水果倒茶几乎不用电纯茶直接泡。三种饮料的实际耗电量差3倍但你按产量平均分摊——珍珠奶茶的电费被严重低估聪明做法作业成本法 ABC- 记录每杯珍珠奶茶煮珍珠用了多少电0.05度水果茶几乎不用电0.005度纯茶泡茶0.01度。- 按实际耗电比例分摊电费珍珠奶茶多摊水果茶少摊。- 这样算出来的每杯成本才是真实的。工业现场版- 奶茶 产品- 牛奶/茶叶/珍珠 原材料- 电费 能耗成本- 店员工资 工时成本- 店铺租金折旧 设备折旧- 贪心做法 按产量分摊错误- 聪明做法 按实际资源消耗比例分摊ABC法大白话总结- 输入车间总成本报表一堆总数- 处理用BOM和工艺路线把总数按实际消耗比例拆到每种产品上- 输出每种产品的成本向量[原材料, 工时, 能耗, 折旧] → 这就是LP目标函数的 c向量3.2 运筹学模型北理工《运筹学》标准建模产品成本向量构建模型基于作业成本法 ABC集合定义- p \in P 产品集合- r \in R \{Raw, Labor, Energy, Depreciation\} 成本分项集合已知数据- Q_p 产品 p 的月产量件- C_r^{total} 成本分项 r 的月度总成本- t_{pr} 产品 p 在成本分项 r 上的消耗强度如每件的原材料用量、工时、能耗度数成本向量计算c_{pr} \frac{t_{pr} \cdot Q_p}{\sum_{p} t_{pr} \cdot Q_{p}} \cdot C_r^{total} \div Q_p \frac{t_{pr}}{\sum_{p} t_{pr} \cdot Q_{p}} \cdot C_r^{total}简化后c_{pr} \frac{t_{pr}}{T_r^{total}} \cdot C_r^{total}其中 T_r^{total} \sum_{p} t_{pr} \cdot Q_{p} 是分项 r 的总消耗基准。产品总成本向量\mathbf{c}_p [c_{p,raw}, c_{p,labor}, c_{p,energy}, c_{p,depr}]线性规划目标函数\min \sum_{p \in P} \mathbf{c}_p^{total} \cdot x_p参考北理工《运筹学》- 第2章线性规划§2.1 数学模型目标函数系数构建- 第3章对偶理论与灵敏度分析§3.4 影子价格成本系数的经济含义3.3 如何映射到代码中数学模型/概念 Python 代码产品集合 PProductCost 对象列表成本分项 RCostCategory Enum消耗强度 t_{pr}product.bom_usage,product.standard_time 等总成本 C_r^{total}cost_totals[category] 字典分项成本 c_{pr}product.cost_vector[category]成本向量 \mathbf{c}_pproduct.unit_total_costLP目标函数系数prob lpSum(c_p * x[p] for p in products)四、OOP 代码实现精简可运行4.1 项目结构product_cost_vector_builder/├── product_cost_vector_builder.py # 核心代码单文件~280行├── sample_cost_data.xlsx # 示例数据由代码自动生成├── README.md # 使用说明└── requirements.txt # 依赖库4.2 完整源代码可直接运行detailssummary/summary车间成本报表解析 · 产品分项成本向量构建器参考: 北京理工大学《运筹学》第2章线性规划功能:1. 解析车间月度成本报表原材料/工时/能耗/折旧2. 基于作业成本法(ABC)按实际消耗比例拆分各分项3. 输出每种产品的完整成本向量 [原材料, 工时, 能耗, 折旧]4. 构建PuLP线性规划模型成本向量直接作为目标函数系数5. 输出成本结构分析与对比报告运行:pip install pulp pandas openpyxlpython product_cost_vector_builder.pyimport warningsfrom dataclasses import dataclass, fieldfrom enum import Enumfrom pathlib import Pathfrom typing import Dict, List, Optional, Tupleimport numpy as npimport pandas as pdimport pulpwarnings.filterwarnings(ignore)# ─── 枚举与数据模型 ────────────────────────────────────────────────────────class CostCategory(Enum):成本分项枚举RAW_MATERIAL (原材料, rm)LABOR (工时, lb)ENERGY (能耗, en)DEPRECIATION (折旧, dp)def __init__(self, desc: str, code: str):self.desc descself.code codedef __str__(self):return self.descdataclassclass ProductCost:产品成本定义解析后product_id: strproduct_name: strquantity: float # 月产量件bom_usage: float 0.0 # 原材料单耗kg/件standard_time: float 0.0 # 标准工时h/件energy_rate: float 0.0 # 能耗强度kWh/件depreciation_base: float 0.0 # 占用设备原值万元cost_vector: Dict[str, float] field(default_factorydict)unit_total_cost: float 0.0def compute_total(self) - float:计算单位总成本self.unit_total_cost sum(self.cost_vector.values())return self.unit_total_cost# ─── Excel数据读取器 ──────────────────────────────────────────────────────class CostReportReader:读取车间成本月报Excel模拟真实工厂的成本报表结构def __init__(self, file_path: str sample_cost_data.xlsx):self.file_path Path(file_path)def read_all(self) - Dict:读取所有工作表文件不存在则生成模拟数据if not self.file_path.exists():print( ⚠️ 未找到数据文件使用内置模拟数据)return self._generate_mock_data()xl pd.ExcelFile(self.file_path)return {name: pd.read_excel(xl, sheet_namename)for name in xl.sheet_names}def _generate_mock_data(self) - Dict:生成含典型结构问题的模拟成本数据# 原材料汇总raw_material pd.DataFrame({物料编码: [M001, M002, M003, M004],物料名称: [合金钢棒料, 铝合金锭, 铜套, 润滑油],单价: [12.5, 28.0, 45.0, 18.0],领用总量: [3200.0, 1500.0, 600.0, 200.0],总金额: [40000.0, 42000.0, 27000.0, 3600.0],})# 工时记录labor pd.DataFrame({产品ID: [P01, P02, P03, P04, P05],产品名称: [精密齿轮A, 轴套B, 法兰C, 轴承D, 支架E],月产量: [1200.0, 3500.0, 800.0, 2000.0, 5000.0],单件标准工时: [1.8, 0.6, 1.2, 0.9, 0.3],月总工时: [2160.0, 2100.0, 960.0, 1800.0, 1500.0],})# 能耗账单energy pd.DataFrame({设备/区域: [CNC-1, CNC-2, CNC-3, 磨床-1, 装配线],耗电量_kWh: [3200.0, 2800.0, 3000.0, 1800.0, 800.0],气费: [500.0, 400.0, 450.0, 300.0, 100.0],})# 设备折旧depreciation pd.DataFrame({设备ID: [CNC-1, CNC-2, CNC-3, 磨床-1, 装配线],设备原值: [85.0, 78.0, 82.0, 45.0, 30.0],月折旧额: [1.42, 1.30, 1.37, 0.75, 0.50],月可用工时: [176.0, 176.0, 176.0, 168.0, 200.0],})# 生产台账BOM工艺路线production pd.DataFrame({产品ID: [P01, P02, P03, P04, P05],产品名称: [精密齿轮A, 轴套B, 法兰C, 轴承D, 支架E],月产量: [1200.0, 3500.0, 800.0, 2000.0, 5000.0],单件材料用量: [2.5, 1.2, 3.0, 1.8, 0.8],单件标准工时: [1.8, 0.6, 1.2, 0.9, 0.3],主要设备: [CNC-1/CNC-2, CNC-3, 磨床-1, CNC-1, 装配线],})return {原材料汇总: raw_material,工时记录: labor,能耗账单: energy,设备折旧: depreciation,生产台账: production,}# ─── 成本向量构建器核心ABC逻辑 ───────────────────────────────────────class CostVectorBuilder:基于作业成本法(ABC)构建产品成本向量参考: 北理工《运筹学》§2.1 目标函数系数构建def __init__(self):self.cost_totals: Dict[str, float] {}self.products: List[ProductCost] []def build_from_data(self, raw_data: Dict) - List[ProductCost]:从原始报表数据构建所有产品的成本向量# 1. 提取月度总成本self._extract_total_costs(raw_data)# 2. 解析产品基础信息self._parse_products(raw_data)# 3. 按ABC法分摊各分项self._allocate_raw_material(raw_data)self._allocate_labor(raw_data)self._allocate_energy(raw_data)self._allocate_depreciation(raw_data)# 4. 计算合计for p in self.products:p.compute_total()return self.productsdef _extract_total_costs(self, data: Dict):提取各分项月度总成本# 原材料总成本if 原材料汇总 in data:self.cost_totals[rm] data[原材料汇总][总金额].sum()else:self.cost_totals[rm] 112600.0# 工时总成本假设平均时薪35元/hself.hourly_rate 35.0if 工时记录 in data:total_hours data[工时记录][月总工时].sum()self.cost_totals[lb] total_hours * self.hourly_rateelse:self.cost_totals[lb] 85200.0# 能耗总成本if 能耗账单 in data:en data[能耗账单]self.cost_totals[en] en[耗电量_kWh].sum() * 0.85 en[气费].sum()else:self.cost_totals[en] 12400.0# 折旧总成本if 设备折旧 in data:self.cost_totals[dp] data[设备折旧][月折旧额].sum()else:self.cost_totals[dp] 53400.0def _parse_products(self, data: Dict):解析产品列表if 生产台账 in data:for _, row in data[生产台账].iterrows():self.products.append(ProductCost(product_idrow[产品ID],product_namerow[产品名称],quantityfloat(row[月产量]),bom_usagefloat(row[单件材料用量]),standard_timefloat(row[单件标准工时]),))elif 工时记录 in data:for _, row in data[工时记录].iterrows():self.products.append(ProductCost(product_idrow[产品ID],product_namerow[产品名称],quantityfloat(row[月产量]),standard_timefloat(row[单件标准工时]),))def _allocate_raw_material(self, data: Dict):分摊原材料成本 —— 按BOM用量比例total_usage sum(p.bom_usage * p.quantity for p in self.products)if total_usage 0:returnfor p in self.products:usage_ratio (p.bom_usage * p.quantity) / total_usagep.cost_vector[rm] usage_ratio * self.cost_totals[rm]def _allocate_labor(self, data: Dict):分摊工时成本 —— 按标准工时比例total_hours sum(p.standard_time * p.quantity for p in self.products)if total_hours 0:returnfor p in self.products:hour_ratio (p.standard_time * p.quantity) / total_hoursp.cost_vector[lb] hour_ratio * self.cost_totals[lb]def _allocate_energy(self, data: Dict):分摊能耗成本 —— 按设备工时比例ABC核心关键: 能耗与设备运行时间强相关应按占用设备工时分摊而不是按产量分摊# 从设备折旧表获取各设备可用工时作为能耗分摊基数device_hours {}if 设备折旧 in data:for _, row in data[设备折旧].iterrows():device_hours[row[设备ID]] float(row[月可用工时])# 简化: 按产品标准工时比例近似实际应按实际占用设备工时total_std_hours sum(p.standard_time * p.quantity for p in self.products)if total_std_hours 0:returnfor p in self.products:hour_ratio (p.standard_time * p.quantity) / total_std_hoursp.cost_vector[en] hour_ratio * self.cost_totals[en]def _allocate_depreciation(self, data: Dict):分摊折旧成本 —— 按设备工时比例total_std_hours sum(p.standard_time * p.quantity for p in self.products)if total_std_hours 0:returnfor p in self.products:hour_ratio (p.standard_time * p.quantity) / total_std_hoursp.cost_vector[dp] hour_ratio * self.cost_totals[dp]def get_cost_vector_summary(self) - pd.DataFrame:输出成本向量汇总表rows []for p in self.products:row {产品ID: p.product_id,产品名称: p.product_name,产量: p.quantity,原材料_元/件: p.cost_vector.get(rm, 0) / max(p.quantity, 1),工时_元/件: p.cost_vector.get(lb, 0) / max(p.quantity, 1),能耗_元/件: p.cost_vector.get(en, 0) / max(p.quantity, 1),折旧_元/件: p.cost_vector.get(dp, 0) / max(p.quantity, 1),合计_元/件: p.unit_total_cost / max(p.quantity, 1),}rows.append(row)return pd.DataFrame(rows)# ─── LP模型构建器 ─────────────────────────────────────────────────────────class ProductionLPBuilder:用构建好的成本向量建立线性规划排产模型参考: 北理工《运筹学》§2.2 线性规划标准形式def __init__(self, products: List[ProductCost],device_capacity: Dict[str, float]):self.products productsself.device_capacity device_capacitydef build(self) - pulp.LpProblem:构建最小化总成本的LP模型prob pulp.LpProblem(Production_Cost_Minimization, pulp.LpMinimize)# 决策变量: x_p 产品p的产量x {}for p in self.products:x[p.product_id] pulp.LpVariable(fx_{p.product_id},lowBound0,upBoundp.quantity * 1.2, # 最多增产20%catContinuous)# 目标函数: min Σ c_p × x_pprob pulp.lpSum(p.unit_total_cost / max(p.quantity, 1) * x[p.product_id]for p in self.products), Total_Production_Cost# 设备工时约束简化: 用标准工时汇总# 实际应从BOM中获取每台设备的工时total_std_hours sum(p.standard_time * p.quantity for p in self.products)if total_std_hours 0:for dev_id, cap in self.device_capacity.items():# 简化: 按设备可用工时占总工时的比例分配约束prob pulp.lpSum(p.standard_time * x[p.product_id]for p in self.products) cap * len(self.device_capacity), fCap_{dev_id}# 需求约束: 至少满足本月需求产量不低于计划量for p in self.products:prob x[p.product_id] p.quantity, fDemand_{p.product_id}return prob# ─── 报告生成器 ───────────────────────────────────────────────────────────class CostAnalysisReport:成本分析报告staticmethoddef print_report(products: List[ProductCost], cost_totals: Dict):print(f\n {*68})print(f 车间成本解析报告 · 产品分项成本向量)print(f {*68})print(f\n 月度总成本池:)for code, amount in cost_totals.items():desc {rm: 原材料, lb: 工时, en: 能耗, dp: 折旧}.get(code, code)print(f {desc:10}: {amount:12,.0f} 元)print(f\n 产品单位成本向量 (元/件):)print(f {产品:14} {原材料:8} {工时:8} {能耗:8} {折旧:8} {合计:8})print(f {─*60})for p in products:unit p.unit_total_cost / max(p.quantity, 1)rm_u p.cost_vector.get(rm, 0) / max(p.quantity, 1)lb_u p.cost_vector.get(lb, 0) / max(p.quantity, 1)en_u p.cost_vector.get(en, 0) / max(p.quantity, 1)dp_u p.cost_vector.get(dp, 0) / max(p.quantity, 1)print(f {p.product_name:14} f{rm_u:8.2f} {lb_u:8.2f} {en_u:8.2f} {dp_u:8.2f} {unit:8.2f})# 成本结构占比print(f\n 成本结构占比 (按总金额):)total_all sum(cost_totals.values())for code, amount in cost_totals.items():desc {rm: 原材料, lb: 工时, en: 能耗, dp: 折旧}.get(code, code)pct amount / total_all * 100bar █ * int(pct / 2)print(f {desc:8} {pct:5.1f}% {bar})staticmethoddef compare_with_naive(products: List[ProductCost]):对比ABC法与简单产量分摊法的差异print(f\n ⚖️ ABC法 vs 简单产量分摊法 对比:)print(f {产品:14} {ABC单位成本:12} {简单分摊:12} {偏差:8})print(f {─*52})total_q sum(p.quantity for p in products)for p in products:abc_cost p.unit_total_cost / max(p.quantity, 1)# 简单法: 总成本按产量比例分摊naive_cost sum(p.cost_vector.values()) / total_q if total_q 0 else 0# 重新算简单法: 各分项都按产量比例naive_total 0for cat_code in [rm, lb, en, dp]:cat_total sum(p.cost_vector.get(cat_code, 0) for p in products)naive_total cat_total * (p.quantity / total_q)naive_unit naive_total / max(p.quantity, 1)deviation (abc_cost - naive_unit) / naive_unit * 100 if naive_unit 0 else 0flag ⚠️ if abs(deviation) 10 else print(f {p.product_name:14} {abc_cost:12.2f} {naive_unit:12.2f} f{deviation:7.1f}% {flag}利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
返回列表