FMS 质检缺陷工序追溯——从谁干的到为什么干坏了一、实际应用场景真实痛点时间周四下午 3:20。地点某汽车零部件厂 FMS柔性制造系统车间箱体类零件加工单元。质量工程师老马拿着一份检验报告站在三台立加和一台卧加中间眉头拧成疙瘩。这批变速箱壳体昨天夜班出的今天终检发现 23 件有孔位超差——定位销孔 Ø10H7实测 Ø10.08~10.12超差 0.08mm 以上。老马指着检验单公差带 IT7上限 Ø10.015现在干到 Ø10.12超了整整 8 倍。哪台机床干的生产班长问。问题就在这。老马翻出追溯系统这 23 件分散在三台立加上加工的。1 号机 8 件、2 号机 7 件、3 号机 8 件。三台机都超说明不是单台设备问题。那是不是程序问题刀补都一样刀补一样G代码一样装夹也一样——都用的是 4 号工位那个液压夹具Ø12mm 的弹簧夹套导向套。老马顿了顿但 4 号夹具上周换过导向套。换之前没这问题换完之后连续三批都出超差。那就是导向套的问题不一定。老马摇头导向套磨损会导致钻头偏摆增大但偏摆方向是随机的超差应该各方向都有。可这批超差全集中在 X 方向——孔心往 X 偏了 0.06~0.09mm。这说明有个固定的系统性偏移。夹具定位面磨损4 号夹具的定位键是铝的用了两年磨损量大概 0.03~0.05mm。但定位键磨损应该影响所有孔不只销孔。老马越说越绕还有这批料是新的材料批次硬度比之前高了 3-5 HRC。刀具还是原来的 Ø10 硬质合金钻没换。所以可能的原因导向套磨损 定位键磨损 材料变硬 刀具没换——四件事叠在一起谁也不知道哪个是主因。对。老马叹气现在只能一件一件拆开查。把 23 件超差件的生产数据全部拉出来——哪台机、哪个夹具、哪把刀、什么时间、什么材料批次——然后人工比对。光拉数据就花了半天分析还要两天。这期间产线还在跑每天又出新的超差件。你需要一个系统加载 FMS 质检数据自动关联每道工序的工装/刀具/设备/材料信息用统计方法追溯缺陷最可能的来源工序定位是工装漂移还是刀具磨损还是参数漂移对。老马点头输入质检结果合格/超差/超差方向/超差量程序自动回溯这道工序用了什么夹具、什么刀具、什么参数、哪个操作员、什么时间。然后算每道工序对最终缺陷的贡献概率——哪道工序最可能是罪魁祸首。这样我直接去查那道工序不用瞎猜。这活儿我干过。我说pandas 管工序追溯数据networkx 建零件→工序→工装/刀具/设备关系图scipy 做假设检验ANOVA 分析组间差异sklearn 跑随机森林算特征重要性定位主因matplotlib 画控制图和贡献度柱状图。代码 OOP能接 MES/质量系统导出的 CSV带单元测试。二、痛点分析映射到滨州职业学院课程模型《先进制造技术》课程模块 本篇痛点对应柔性制造系统 FMS 与先进生产管理 FMS 单元级质量追溯、工序间数据关联、异常定位数控加工与 CAD/CAM 技术 刀具补偿/磨损、切削参数对质量的影响先进制造技术基础 公差与配合、形位公差、加工误差来源分析工业机器人技术基础 自动上下料定位精度对后续工序的传递误差智能制造与数字孪生 质量数据驱动的根因分析、数字线程追溯先进制造新模式 数据驱动质量管控SPC/六西格玛一句话总结我们需要一个加载 FMS 质检数据零件ID/工序链/检测结果/工装刀具设备信息→ 构建工序追溯图networkx→ 统计假设检验定位异常工序scipy ANOVA→ 机器学习特征重要性排序sklearn→ 缺陷来源贡献度可视化 → 输出根因报告程序实现从人工逐件排查三天到数据自动追溯三分钟的升级。三、核心逻辑讲解大白话3.1 问题本质把缺陷追溯想成破案想象一个案子- 受害者 超差零件孔大了 0.08mm全往 X 偏。- 嫌疑人 每道工序涉及的工装、刀具、设备、材料、操作员。- 不在场证明 同一工序用不同工装加工的零件有没有超差没超差说明这个工装没问题。- 作案手法 超差方向一致X→ 说明是固定偏移不是随机磨损 → 指向定位基准问题。- 破案逻辑把每个嫌疑人分组看哪一组的犯罪率超差率明显不同。差异最大的那个就是主犯。本程序做的事把质检数据里每个超差件的生产履历拉出来按工序→工装/刀具/设备/材料分组统计用 ANOVA 检验看哪一组的组间差异最大p 值最小用随机森林看哪个特征对超差预测最重要。双管齐下锁定罪魁祸首。3.2 业务逻辑 → 代码映射输入: FMS 质检数据 CSV零件ID / 工序链 / 每道工序: 设备ID/夹具ID/刀具ID/参数/操作员/时间戳检测结果: 合格/超差/超差方向/超差量│▼ InspectionRecord (dataclass)质检数据模型:零件ID / 工序序列 / 设备 / 夹具 / 刀具 / 切削参数材料批次 / 操作员 / 检测结果 / 偏差向量(X,Y,Z)│▼ DataLoader数据加载器:pandas 读取 CSV → 清洗(去缺失) → 展平工序链│▼ TraceabilityGraph (networkx)追溯关系图:节点: 零件/工序/工装/刀具/设备/材料批次边: 加工关系/使用关系属性: 超差率/偏差均值│▼ StatsAnalyzer (scipy)统计假设检验:ANOVA: 不同夹具组间偏差均值差异t-test: 不同刀具超差率差异Chi-square: 操作员与超差的关联性输出: p值/效应量/显著性│▼ RootCauseClassifier (sklearn)根因分类器:RandomForest 分类: 输入工序条件 → 预测超差特征重要性 → 根因排序│▼ TraceViz (matplotlib networkx)可视化:1. 工序追溯网络图 (networkx)2. 各工序超差率柱状图3. ANOVA 箱线图 (偏差分布)4. 根因贡献度排序图5. 偏差方向散点图│▼ 输出根因报告: 最可能的缺陷来源工序 具体工装/刀具 建议措施3.3 为什么人工排查不行维度 人工排查 本程序定位时间 2-3 天 3 分钟覆盖维度 凭经验选 2-3 个查 全维度自动扫描客观性 我觉得是导向套 p 值 特征重要性双验证可追溯性 查完就忘 追溯图永久保存持续改进 同样问题下次还犯 根因报告积累成知识库四、OOP 代码实现4.1 项目结构fms_defect_tracer/├── fms_defect_tracer/│ ├── __init__.py│ ├── models.py # 质检数据模型│ ├── data_loader.py # CSV加载与清洗│ ├── traceability_graph.py # 追溯关系图 (networkx)│ ├── stats_analyzer.py # 统计假设检验 (scipy)│ ├── root_cause_classifier.py # 根因分类 (sklearn)│ ├── visualizer.py # 可视化│ └── report_generator.py # 报告生成├── tests/│ ├── __init__.py│ └── test_trace.py├── sample_data/│ └── fms_inspection.csv # 示例FMS质检数据├── results/│ ├── traceability_network.png│ ├── defect_rate_by_process.png│ ├── anova_boxplot.png│ ├── root_cause_contribution.png│ ├── deviation_scatter.png│ └── root_cause_report.txt├── run_trace.py└── README.md4.2 核心源码detailssummary/summaryFMS 质检数据模型 (dataclass)。from dataclasses import dataclass, fieldfrom typing import List, Optional, Dictimport numpy as npdataclassclass ProcessStep:单道工序记录。step_no: intprocess_name: str # 粗铣/精铣/钻孔/铰孔/攻丝machine_id: str # 设备编号fixture_id: str # 夹具编号tool_id: str # 刀具编号tool_life_remaining: float # 刀具剩余寿命 (%)operator_id: str # 操作员spindle_speed: float # 主轴转速 (rpm)feed_rate: float # 进给速度 (mm/min)cut_depth: float # 切削深度 (mm)cool_type: str flood # 冷却方式duration_sec: float 0.0 # 加工时长dataclassclass InspectionRecord:单个零件的质检记录。part_id: strbatch_id: strmaterial_batch: strmaterial_hardness_hrc: floatprocess_chain: List[ProcessStep] field(default_factorylist)# 检测结果is_defective: bool Falsedefect_type: Optional[str] None # 尺寸超差/形位超差/表面缺陷defect_feature: Optional[str] None # 哪个特征超差deviation_x: float 0.0 # 偏差向量 X (mm)deviation_y: float 0.0 # 偏差向量 Y (mm)deviation_z: float 0.0 # 偏差向量 Z (mm)deviation_magnitude: float 0.0 # 偏差总量tolerance_limit: float 0.015 # 公差限 (mm)timestamp_inspect: float 0.0def __post_init__(self):if self.deviation_magnitude 0.0:self.deviation_magnitude np.sqrt(self.deviation_x**2 self.deviation_y**2 self.deviation_z**2)def to_feature_dict(self) - dict:转为特征字典 (用于 ML)。d {part_id: self.part_id,batch_id: self.batch_id,material_batch: self.material_batch,material_hardness_hrc: self.material_hardness_hrc,is_defective: int(self.is_defective),deviation_x: self.deviation_x,deviation_y: self.deviation_y,deviation_z: self.deviation_z,deviation_magnitude: self.deviation_magnitude,defect_type: self.defect_type or none,defect_feature: self.defect_feature or none,}# 展平工序链 (取最后3道工序的关键信息)for i, step in enumerate(self.process_chain[-3:]):suffix f_step{step.step_no}d[fmachine{suffix}] step.machine_idd[ffixture{suffix}] step.fixture_idd[ftool{suffix}] step.tool_idd[ftool_life{suffix}] step.tool_life_remainingd[foperator{suffix}] step.operator_idd[fspindle_speed{suffix}] step.spindle_speedd[ffeed_rate{suffix}] step.feed_rated[fcut_depth{suffix}] step.cut_depthreturn d/detailsdetailssummary/summaryFMS 质检数据加载与清洗 (pandas)。import pandas as pdimport numpy as npfrom pathlib import Pathfrom typing import Listfrom .models import InspectionRecord, ProcessStepclass DataLoader:CSV 数据加载器。def __init__(self):passdef load_csv(self, csv_path: str) - pd.DataFrame:加载 FMS 质检 CSV。df pd.read_csv(csv_path)required_cols [part_id, batch_id, material_batch,material_hardness_hrc, is_defective,deviation_x, deviation_y, deviation_z,]missing [c for c in required_cols if c not in df.columns]if missing:raise ValueError(fCSV 缺少列: {missing})return dfdef clean_data(self, df: pd.DataFrame) - pd.DataFrame:数据清洗。df df.dropna(subset[part_id, is_defective])# 类型转换df[is_defective] df[is_defective].astype(int)df[material_hardness_hrc] df[material_hardness_hrc].fillna(30.0)# 偏差合理性for col in [deviation_x, deviation_y, deviation_z]:df[col] pd.to_numeric(df[col], errorscoerce).fillna(0.0)# 计算偏差总量if deviation_magnitude not in df.columns:df[deviation_magnitude] np.sqrt(df[deviation_x]**2 df[deviation_y]**2 df[deviation_z]**2)# 工序链解析 (JSON 字符串 → 展开)if process_chain in df.columns:df[process_chain_parsed] df[process_chain].apply(self._parse_process_chain)return df.reset_index(dropTrue)def _parse_process_chain(self, val) - List[dict]:解析工序链 (JSON 字符串)。if pd.isna(val):return []if isinstance(val, str):try:import jsonreturn json.loads(val)except:return []return val if isinstance(val, list) else []def to_records(self, df: pd.DataFrame) - List[InspectionRecord]:DataFrame → 记录对象列表。records []for _, row in df.iterrows():chain []if process_chain_parsed in row and row[process_chain_parsed]:for step_data in row[process_chain_parsed]:chain.append(ProcessStep(step_nostep_data.get(step_no, 0),process_namestep_data.get(process_name, ),machine_idstep_data.get(machine_id, ),fixture_idstep_data.get(fixture_id, ),tool_idstep_data.get(tool_id, ),tool_life_remainingstep_data.get(tool_life_remaining, 100),operator_idstep_data.get(operator_id, ),spindle_speedstep_data.get(spindle_speed, 0),feed_ratestep_data.get(feed_rate, 0),cut_depthstep_data.get(cut_depth, 0),cool_typestep_data.get(cool_type, flood),duration_secstep_data.get(duration_sec, 0),))elif machine_step1 in df.columns:# 展平格式for i in range(1, 6):if fmachine_step{i} in row and pd.notna(row.get(fmachine_step{i})):chain.append(ProcessStep(step_noi,process_namerow.get(fprocess_name_step{i}, ),machine_idstr(row.get(fmachine_step{i}, )),fixture_idstr(row.get(ffixture_step{i}, )),tool_idstr(row.get(ftool_step{i}, )),tool_life_remainingfloat(row.get(ftool_life_step{i}, 100)),operator_idstr(row.get(foperator_step{i}, )),spindle_speedfloat(row.get(fspindle_speed_step{i}, 0)),feed_ratefloat(row.get(ffeed_rate_step{i}, 0)),cut_depthfloat(row.get(fcut_depth_step{i}, 0)),))records.append(InspectionRecord(part_idstr(row[part_id]),batch_idstr(row.get(batch_id, B001)),material_batchstr(row.get(material_batch, M001)),material_hardness_hrcfloat(row[material_hardness_hrc]),process_chainchain,is_defectivebool(row[is_defective]),defect_typerow.get(defect_type, None),defect_featurerow.get(defect_feature, None),deviation_xfloat(row[deviation_x]),deviation_yfloat(row[deviation_y]),deviation_zfloat(row[deviation_z]),deviation_magnitudefloat(row.get(deviation_magnitude, 0)),tolerance_limitfloat(row.get(tolerance_limit, 0.015)),timestamp_inspectfloat(row.get(timestamp_inspect, 0)),))return recordsdef generate_synthetic_data(self, n_samples: int 150) - pd.DataFrame:生成合成 FMS 质检数据 (教学用)。np.random.seed(42)records []# 模拟工序模板process_templates [{name: 粗铣基准面, machines: [MC-01, MC-02, MC-03],fixtures: [FIX-01, FIX-02, FIX-03, FIX-04],tools: [EM-50-01, EM-50-02, EM-63-01]},{name: 精铣定位面, machines: [MC-01, MC-02, MC-03],fixtures: [FIX-01, FIX-02, FIX-03, FIX-04],tools: [EM-20-01, EM-20-02, EM-25-01]},{name: 钻定位孔, machines: [MC-01, MC-02, MC-03],fixtures: [FIX-01, FIX-02, FIX-03, FIX-04],tools: [DR-10-01, DR-10-02, DR-10-03]},{name: 铰定位销孔, machines: [MC-01, MC-02, MC-03],fixtures: [FIX-01, FIX-02, FIX-03, FIX-04],tools: [RE-10-01, RE-10-02, RE-10-03]},{name: 攻螺纹, machines: [MC-01, MC-02, MC-03],fixtures: [FIX-01, FIX-02, FIX-03, FIX-04],tools: [TAP-12-01, TAP-12-02]},]operators [OP-101, OP-102, OP-103, OP-104]material_batches [MB-2401, MB-2402, MB-2403]# 设定罪魁祸首: FIX-04 夹具定位键磨损 MB-2403 材料偏硬bad_fixture FIX-04bad_material MB-2403for i in range(n_samples):part_id fP{i1:05d}batch_id fB{np.random.randint(1, 5):03d}mat_batch np.random.choice(material_batches, p[0.4, 0.4, 0.2])mat_hardness np.random.normal(28, 2)if mat_batch bad_material:mat_hardness 4 # 偏硬process_chain []cumulative_deviation_x 0.0cumulative_deviation_y 0.0will_fail Falsefor j, tmpl in enumerate(process_templates):step_no j 1machine np.random.choice(tmpl[machines])fixture np.random.choice(tmpl[fixtures])tool np.random.choice(tmpl[tools])operator np.random.choice(operators)# 刀具寿命tool_life np.random.uniform(10, 100)if tool.endswith(03):tool_life np.random.uniform(5, 25) # 旧刀spindle np.random.uniform(2000, 6000)feed np.random.uniform(200, 800)cut_depth np.random.uniform(0.5, 3.0)# 偏差累积模型step_dev_x np.random.normal(0, 0.005)step_dev_y np.random.normal(0, 0.005)# 夹具 FIX-04 引入系统性 X 偏移if fixture bad_fixture:step_dev_x np.random.uniform(0.02, 0.04)if step_no 3: # 钻孔开始显现will_fail True# 材料偏硬导致刀具让刀if mat_batch bad_material and 钻 in tmpl[name]:step_dev_x np.random.uniform(0.01, 0.02)step_dev_y np.random.uniform(0.005, 0.015)will_fail True# 刀具寿命低 → 随机偏差增大if tool_life 20:step_dev_x np.random.normal(0, 0.015)step_dev_y np.random.normal(0, 0.015)cumulative_deviation_x step_dev_xcumulative_deviation_y step_dev_yprocess_chain.append({step_no: step_no,process_name: tmpl[name],machine_id: machine,fixture_id: fixture,tool_id: tool,tool_life_remaining: round(tool_life, 1),operator_id: operator,spindle_speed: round(spindle, 0),feed_rate: round(feed, 0),cut_depth: round(cut_depth, 2),cool_type: flood,duration_sec: np.random.uniform(30, 180),})# 最终判定total_dev np.sqrt(cumulative_deviation_x**2 cumulative_deviation_y**2)tolerance 0.015is_defective total_dev tolerance or will_failif is_defective and np.random.rand() 0.15:is_defective False # 部分漏检records.append({part_id: part_id,batch_id: batch_id,material_batch: mat_batch,material_hardness_hrc: round(mat_hardness, 1),is_defective: int(is_defective),defect_type: 尺寸超差 if is_defective else None,defect_feature: 定位销孔Ø10H7 if is_defective else None,deviation_x: round(cumulative_deviation_x, 4),deviation_y: round(cumulative_deviation_y, 4),deviation_z: round(np.random.normal(0, 0.003), 4),deviation_magnitude: round(total_dev, 4),tolerance_limit: tolerance,timestamp_inspect: 1700000000 i * 300,process_chain: str(process_chain), # 简化存储})df pd.DataFrame(records)output_path Path(sample_data)output_path.mkdir(exist_okTrue)df.to_csv(output_path / fms_inspection.csv,indexFalse, encodingutf-8)return df/detailsdetailssummary/summary工序追溯关系图 (networkx)。import networkx as nxfrom typing import Dict, Listfrom .models import InspectionRecordclass TraceabilityGraph:构建零件→工序→工装/刀具/设备 追溯关系图。def __init__(self):self.G nx.Graph()def build(self, records: List[InspectionRecord]) - nx.Graph:构建追溯图。G nx.Graph()# 统计fixture_defect_count {}fixture_total_count {}tool_defect_count {}tool_total_count {}machine_defect_count {}machine_total_count {}material_defect_count {}material_total_count {}for rec in records:# 零件节点part_node fPART:{rec.part_id}G.add_node(part_node, node_typepart,defectiverec.is_defective)# 材料节点mat_node fMAT:{rec.material_batch}G.add_node(mat_node, node_typematerial)G.add_edge(part_node, mat_node, relationmade_from)material_total_count[rec.material_batch] \material_total_count.get(rec.material_batch, 0) 1if rec.is_defective:material_defect_count[rec.material_batch] \material_defect_count.get(rec.material_batch, 0) 1for step in rec.process_chain:# 工序节点step_node fSTEP:{step.step_no}:{step.process_name}G.add_node(step_node, node_typeprocess,namestep.process_name)# 零件-工序边G.add_edge(part_node, step_node, relationprocessed_by)# 设备mach_node fMACHINE:{step.machine_id}G.add_node(mach_node, node_typemachine)G.add_edge(step_node, mach_node, relationuses_machine)machine_total_count[step.machine_id] \machine_total_count.get(step.machine_id, 0) 1if rec.is_defective:machine_defect_count[step.machine_id] \machine_defect_count.get(step.machine_id, 0) 1# 夹具fix_node fFIXTURE:{step.fixture_id}G.add_node(fix_node, node_typefixture)G.add_edge(step_node, fix_node, relationuses_fixture)fixture_total_count[step.fixture_id] \fixture_total_count.get(step.fixture_id, 0) 1if rec.is_def利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
阅读完成 · 觉得有帮助?