
简介基于物料分类的差异化供应商选择研究资源面向供应链管理人员、采购经理及从事供应链优化研究的专业人士旨在建立结合Kraljic矩阵和支持向量机的物料分类模型将物料细分为战略、杠杆、一般和瓶颈四类再依据类别特性构建差异化评价指标采用TOPSIS方法完成供应商优选从而降低采购成本、提高交货准时率并缩短评估周期。包内共1个docx文档容量58KB虽然包体不大但内容完整涵盖从理论基础、体系设计到实证验证的全过程。已有45人学习下载。文档不仅提供理论框架还附有详细Python代码与解释具体包括数据预处理、SVM网格搜索调参、模型评估与分类边界可视化、TOPSIS综合评价计算等模块并嵌入C公司实际案例说明。读者既可快速理解方法思路也能直接参考或扩展代码应用于企业的差异化供应商选择与供应链优化场景。1. 物料分类与差异化供应商选择先分类再定权重的采购系统思路“基于物料分类的差异化供应商选择”这标题看起来像一篇学术论文落到实际系统里它是采购数字化里最容易做成一锅粥的模块。物料分类是把企业采购的上千种物料按采购金额占比和供应风险分成关键、杠杆、瓶颈、一般四类差异化供应商选择则是针对不同类别的物料采用不同的评估维度、权重和候选池策略而不是一套评分表打天下。它解决的是采购负责人最头疼的问题同一个供应商在同一套权重下既可能被高估也可能被埋没。适合正在搭供应商管理系统或采购平台的开发人员也适合想给供应商评估规则找到落地路径的业务人员。2. 为什么物料分类是差异化供应商选择的前置条件Kraljic矩阵与权重设计2.1 四象限分类的两个打分维度怎么定供应商评估系统最常见的错误是上来就建一张“供应商评分表”把所有供应商放进同一套质量、交付、成本、服务指标里打分排序。但不同物料的供应策略完全不同比如螺丝钉和汽车芯片前者可以随时换供应商后者可能因为缺货导致产线停线。混在一起评分排序结果没有任何业务意义。物料分类最常用的理论底座是 Kraljic 矩阵。它把物料放到两个维度里看一是利润影响通常用“该物料年度采购金额占总采购金额的比例”来量化二是供应风险通常用“可用供应商数量、质量稳定性、交付周期、可替代性”综合打分。分类时我用两个阈值把平面切成四块采购金额占比大于等于 10% 且风险分大于等于 3 的归为关键物料K金额占比高但风险低的归为杠杆物料L金额占比低但风险高的归为瓶颈物料B其余归为一般物料G。这里有个容易踩的细节占比阈值和风险阈值不是拍脑袋固定的需要拿历史采购数据先跑一遍看每类物料的数量分布是否合理。比如有些企业物料种类多、金额分散10% 的阈值会导致关键物料只有两三种这时候就要把阈值降到 5% 再看。常见做法是先用 SQL 统计近一年的采购明细算出每个物料的金额占比和风险分再画一个散点图人工复核边界最后才把阈值落到代码里。2.2 差异化权重关键物料重质量杠杆物料重成本分类完成后差异化供应商选择的核心动作就是“按类设权重”。同一套供应商评估基础数据可以复用但每个物料类别的指标权重必须不同。我一般用下面这张表作为初始配置物料类别质量交付成本服务技术关键物料 K40%25%20%10%5%杠杆物料 L20%20%40%15%5%瓶颈物料 B25%35%5%20%15%一般物料 G20%20%30%30%0%关键物料质量权重最高是因为这类物料直接决定产品核心性能质量事故代价远高于价差杠杆物料成本权重最高因为这类物料可替代供应商多比价空间大选型的核心目标就是降本瓶颈物料交付权重最高因为卖方市场下能稳定供货比便宜重要得多一般物料则简化评估服务权重高一些目的是筛掉售后差的供应商。权重设计有一条铁律先定“一票否决项”再谈权重。比如关键物料只要质量分低于及格线比如 60 分无论其他项多高都直接淘汰。一票否决项应该在评估规则配置表里单独存一个字段而不是塞进权重里。否则就会出现质量权重 40% 但总分仍然被成本拉平的尴尬局面。2.3 试点顺序为什么先从杠杆物料切进去差异化供应商选择不是一次性把所有物料类别的规则都上线的。业务对分类结果本来就有争议如果最复杂的类别一上来就搞错整个项目会被打回。我一般从杠杆物料开始试点。理由有三杠杆物料金额占比高、供应商数量多样本数据充分排序结果容易被业务验证它的策略目标单一降本评估维度少权重设置不容易起争议即使评估结果不理想替换供应商的空间也大试错成本低。关键物料反而要在后面做因为关键物料的供应风险高、涉及部门多需要和研发、质量、生产反复确认评估维度适合在杠杆物料跑通后再推进。试点落地路径一般分三步先把目标类别的物料主数据清洗一遍跑分类并人工复核再把该类别的权重配置表配好用历史供应商数据回放一遍评分最后才让业务在系统里发起供应商评估流程。3. 数据建模先行物料分类与供应商评估的 MySQL 数据设计表3.1 物料主表与分类结果表把分类结果存成字段避免每次重算代码实现之前先要把 MySQL 数据设计表定下来这是整个方案的地基。物料分类不能每次评估时现算那样性能差而且规则一旦调整历史数据就没法追溯。我通常的做法是把分类结果直接落到物料主表里同时记录分类规则的版本号。CREATE TABLE material_master ( id BIGINT AUTO_INCREMENT PRIMARY KEY, material_code VARCHAR(32) NOT NULL UNIQUE COMMENT 物料编码, material_name VARCHAR(128) NOT NULL COMMENT 物料名称, annual_purchase_amount DECIMAL(16,2) NOT NULL DEFAULT 0.00 COMMENT 年度采购金额, total_purchase_amount DECIMAL(16,2) NOT NULL DEFAULT 0.00 COMMENT 同期采购总金额, supplier_count INT NOT NULL DEFAULT 0 COMMENT 可用供应商数量, risk_score DECIMAL(3,1) NOT NULL DEFAULT 0.0 COMMENT 供应风险综合分1-5分, classification CHAR(1) DEFAULT NULL COMMENT 分类结果K关键/L杠杆/B瓶颈/G一般, classify_version VARCHAR(32) DEFAULT NULL COMMENT 分类规则版本号, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_classification (classification), KEY idx_material_name (material_name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT物料主表;这里的核心设计是classification和classify_version两个字段。分类结果落库后供应商评估模块直接读这个字段不需要每个页面都跑一遍分类算法。classify_version用来记录这次分类是哪个版本的规则算出来的规则调整后历史数据还能通过版本号反查避免“以前的结果被新规则默默覆盖”的情况。索引方面material_code建了唯一索引因为业务上物料编码就是天然唯一键classification建普通索引因为后面要根据类别批量查询物料。annual_purchase_amount和total_purchase_amount用 DECIMAL(16,2)采购金额不用 FLOAT避免浮点精度问题。3.2 类别权重配置表和评估项表差异化策略的数据底座差异化供应商选择的差异化靠的是“评估项表 类别权重配置表”两张表支撑。评估项表存系统里有哪些评分维度权重配置表存每个物料类别在每个维度上的权重。CREATE TABLE evaluate_item ( id BIGINT AUTO_INCREMENT PRIMARY KEY, item_code VARCHAR(32) NOT NULL UNIQUE COMMENT 评估项编码如QUALITY, item_name VARCHAR(64) NOT NULL COMMENT 评估项名称如质量, score_type VARCHAR(16) NOT NULL DEFAULT PERCENT COMMENT 评分类型PERCENT百分制/LEVEL等级制, sort_order INT DEFAULT 0 COMMENT 展示顺序 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT供应商评估项表; CREATE TABLE category_weight ( id BIGINT AUTO_INCREMENT PRIMARY KEY, category CHAR(1) NOT NULL COMMENT 物料类别K/L/B/G, item_code VARCHAR(32) NOT NULL COMMENT 评估项编码, weight DECIMAL(5,2) NOT NULL COMMENT 权重如0.40表示40%, pass_score DECIMAL(5,1) DEFAULT NULL COMMENT 一票否决及格线NULL表示不启用, UNIQUE KEY uk_category_item (category, item_code), KEY idx_item_code (item_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT物料类别权重配置表;category_weight表是差异化策略的核心表达。同一套evaluate_item不同类别可以配置不同的weight和pass_score。pass_score字段就是前面说的“一票否决项”的落地位置关键物料质量项可以配 60 分低于 60 直接淘汰成本项可以不配及格线。uk_category_item唯一键很重要它保证每个类别下同一个评估项只有一条权重记录不会出现重复数据导致评分时权重加起来超过 100%。3.3 评估明细与审批状态字段支撑流程审批的 MySQL 设计供应商评分不是算完总分就结束的它要进入审批流程评估人提交、采购主管审核、最后归档。所以评估明细表要把“评分明细”和“审批状态”放在同一张表里前后端才能用一个流程串起来。CREATE TABLE supplier_evaluation ( id BIGINT AUTO_INCREMENT PRIMARY KEY, supplier_id BIGINT NOT NULL COMMENT 供应商ID, material_code VARCHAR(32) NOT NULL COMMENT 关联物料编码, category CHAR(1) NOT NULL COMMENT 评估时使用的物料类别, total_score DECIMAL(5,1) DEFAULT NULL COMMENT 加权总分, approval_status TINYINT NOT NULL DEFAULT 0 COMMENT 审批状态0待提交/1审批中/2通过/3驳回, flow_node VARCHAR(32) DEFAULT DRAFT COMMENT 流程节点DRAFT/SUBMITTED/APPROVED/REJECTED, approval_comment VARCHAR(255) DEFAULT NULL COMMENT 审批意见, evaluate_time DATETIME DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_supplier (supplier_id), KEY idx_category (category), KEY idx_status (approval_status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT供应商评估主表; CREATE TABLE supplier_evaluation_detail ( id BIGINT AUTO_INCREMENT PRIMARY KEY, evaluation_id BIGINT NOT NULL COMMENT 评估主表ID, item_code VARCHAR(32) NOT NULL COMMENT 评估项编码, item_score DECIMAL(5,1) NOT NULL COMMENT 该项得分, KEY idx_evaluation (evaluation_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT供应商评估明细表;两张表分开设计主表存结果和审批状态明细表存每个评估项的原始分。这样审批页面上只需要查询主表就能拿到列表点击进入详情时再按evaluation_id查明细。审批状态字段我同时留了approval_status和flow_node但这俩字段容易让前后端对不上后面避坑章节会专门说。整体设计上评估明细表要在插入时就把category字段写入因为供应商可能在多个类别下被评估只有带上物料类别才能区分这次评估用的是哪套权重。4. 用 Python 把分类与评分跑通从计算到 web 页面与 layui 流程审批4.1 读取采购数据并按规则判定物料类别有了表结构接下来就是具体实现。分类计算的代码不复杂但要注意数据读取时不要用一条 SQL 把所有物料几千万行明细拉到 Python 里应该先按物料编码聚合好金额再传到内存里算。import pymysql import pandas as pd conn pymysql.connect( host127.0.0.1, userroot, passwordyour_password, databasesupplier_db, charsetutf8mb4 ) sql SELECT material_code, annual_purchase_amount, total_purchase_amount, supplier_count, risk_score FROM material_master WHERE classify_version IS NULL OR classify_version v1.0 df pd.read_sql(sql, conn) # 计算采购金额占比占比是相对值不能用绝对值直接比 df[purchase_ratio] df[annual_purchase_amount] / df[total_purchase_amount] def classify_material(row): ratio row[purchase_ratio] risk row[risk_score] if ratio 0.10 and risk 3.0: return K # 关键物料金额高、风险高 elif ratio 0.10 and risk 3.0: return L # 杠杆物料金额高、风险低 elif ratio 0.10 and risk 3.0: return B # 瓶颈物料金额低、风险高 else: return G # 一般物料金额低、风险低 df[classification] df.apply(classify_material, axis1)代码里的purchase_ratio是关键不同物料的采购金额绝对值差异很大不用占比就会把高价低频物料全部误判为关键物料。分类阈值 0.10 和 3.0 与前面定义的规则保持一致实际落地时这两个参数应该从配置表读取而不是写死在代码里。classify_version的条件是用来支持增量重算的只有上次分类版本不是 v1.0 的物料才重新计算避免全表刷新影响线上数据。如果业务要求全量重算去掉这个 WHERE 条件即可。4.2 按类别加载差异化权重并计算供应商差异化得分分类完成后供应商评估计算分三步取评估明细、按类别关联权重、加权求和。这里最容易出错的是按“评估时使用的物料类别”关联权重而不是按供应商主数据里的某个默认类别。# 读取权重配置 weight_df pd.read_sql(SELECT category, item_code, weight FROM category_weight, conn) # 读取评分明细先做归一化再加权 detail_df pd.read_sql( SELECT evaluation_id, supplier_id, item_code, item_score FROM supplier_evaluation_detail , conn ) # 归一化把原始分统一映射到0-100区间 detail_df[normalized_score] detail_df[item_score] merged detail_df.merge(weight_df, onitem_code, howinner) merged[weighted_score] merged[normalized_score] * merged[weight] result ( merged.groupby([evaluation_id, supplier_id]) .agg( total_score(weighted_score, sum), category(category, first), ) .reset_index() ) result result.sort_values([category, total_score], ascending[True, False])这段代码的逻辑要点在merge和groupby。merge的howinner保证只在权重配置中存在评估项的记录参与计算权重表漏配的评估项会被自动忽略这样能快速发现配置缺失。groupby按evaluation_id和supplier_id聚合保证同一个供应商在不同类别下的评分互不干扰。归一化这步我特意写成了直接赋值因为实际系统里评估项得分录入时就应该用统一的分制。如果历史数据有五分制和百分制混用的情况必须在这里先做转换否则加权后成本类的分数会被质量类碾压。4.3 把结果写回数据库并用 layui 渲染 web 页面做流程审批评分算完后要写回supplier_evaluation表再通过一个简单的 web 接口把数据吐给前端页面。前端我用 layui 的 table 模块渲染列表用 flow 的流程状态按钮做审批操作这是目前最省事的组合后端只需返回固定 JSON 格式页面表格自动渲染。from flask import Flask, jsonify, request import pymysql app Flask(__name__) app.route(/api/evaluation/list) def evaluation_list(): conn pymysql.connect( host127.0.0.1, userroot, passwordyour_password, databasesupplier_db, charsetutf8mb4 ) page int(request.args.get(page, 1)) limit int(request.args.get(limit, 10)) offset (page - 1) * limit with conn.cursor() as cur: cur.execute( SELECT id, material_code, category, total_score, approval_status FROM supplier_evaluation ORDER BY id DESC LIMIT %s OFFSET %s , (limit, offset) ) rows cur.fetchall() cur.execute(SELECT COUNT(*) FROM supplier_evaluation) total cur.fetchone()[0] conn.close() return jsonify({ code: 0, msg: , count: total, data: rows }) app.run(host0.0.0.0, port5000)对应 layui 的前端表格渲染代码很简单关键是把url指到上面这个接口cols里的字段名和后端返回的 key 对齐table.render({ elem: #demo-table, url: /api/evaluation/list, page: true, cols: [[ { field: material_code, title: 物料编码 }, { field: category, title: 物料类别 }, { field: total_score, title: 加权总分 }, { field: approval_status, title: 审批状态 }, { title: 操作, templet: function (d) { if (d.approval_status 0) { return button classlayui-btn layui-btn-xs lay-eventsubmit提交审批/button; } return span已提交/span; }} ]] });layui 这边有两个参数要注意page: true开启分页后layui 会自动向接口传page和limit所以后端接口必须接收这两个参数并返回count和datatemplet里的d是当前行数据对象字段名必须和后端返回的 key 一致否则按钮渲染不出来。流程审批的完整链路是页面上点击“提交审批”前端用table.on(tool)监听事件把当前行 ID 通过 AJAX 发到后端更新approval_status字段。这也是热词里“用 layui 设计流程审批”最常见的实现方式状态字段在数据库里改表格重新加载后自动显示最新状态。5. 避坑从物料主数据到 layui 审批流程的五个翻车现场5.1 物料分类翻车同名异物把采购金额占比算偏现象分类跑完业务看到“关键物料”列表里混进了一堆单价不到一毛钱的螺丝金额占比算出来却高得离谱整个分类结果被质疑。原因物料主数据里存在大量同名异物。同一个物料名称规格型号不同、ERP 编码不同采购金额被分散到多个编码下或者反过来不同物料共用一个名称导致聚合金额虚高。解决分类前先做物料主数据清洗。常见做法是用“物料编码 规格型号”拼成唯一键先聚合编码级数据再通过物料名称做模糊归并。分类结果不要直接发布先导出一份给采购员人工抽检抽样比例不低于 10%确认无误后再更新classify_version。5.2 权重没有归一化成本永远打不过质量现象杠杆物料评分排序结果里成本占比最高的供应商反而排到了最后业务直接说系统有问题。原因评估明细表里质量分是百分制成本分是采购单价比如几千几万两项直接乘权重再加总成本项数值天然碾压其他项。解决所有评估项在进入加权计算前统一映射到 0-100 分。成本项反向打分单价最低的供应商给 100 分最高的给 0 分中间按线性插值。归一化逻辑要写在计算服务里不要依赖前端录入人员自己控制分制。5.3 瓶颈物料候选不足排序结果没法用现象瓶颈物料的评估列表只有两三个供应商排序后第一名和第二名分数差不到 1 分业务认为排序没有意义。原因瓶颈物料本身就处于卖方市场候选池小甚至独家供应。差异化供应商选择里“排序选优”的逻辑只适用于候选充足的类别对瓶颈物料硬套排序公式结果就是矮子里拔将军。解决对瓶颈物料类别的评估改走准入制不排序。只评估是否达到供货门槛通过后全部进入合格供应商名单再配合备用供应商储备策略。这个逻辑在supplier_evaluation表里用一个is_qualified字段标记而不是用total_score排序。5.4 layui 表格一直 loading返回格式与后端接口没对齐现象页面打开后表格一直转圈数据没渲染出来打开浏览器控制台发现接口报 500 或者返回的 JSON 里没有count字段。原因layui 的 table 模块对接口返回格式有固定要求必须包含code、msg、count、data四个字段而且code必须是 0 才算成功。后端口随便返回一个数组或者code返回 200前端就不认一直停在 loading 状态。解决统一接口返回结构。Flask 接口里固定用 JSON 封装code固定为 0count返回总数data返回当前页列表。同时检查数据表字段名是否和cols配置里的field一一对应字段名不匹配时表格能渲染出来但所有列都是空的。5.5 审批状态字段语义不统一流程审批串台现象评估人刚点击“提交审批”列表里显示“已通过”单据直接跳过了主管审批环节流程审批串台。原因数据库里approval_status用 TINYINT 存 0/1/2/3前端页面又用flow_node字符串判断状态两套枚举在中间接口层没有做转换导致状态判断逻辑前后不一致。解决在数据库设计阶段只保留一个审批状态字段我后来统一保留approval_status作为唯一状态源flow_node只做展示用不在业务逻辑里参与判断。同时把所有枚举值的含义写进表字段注释里接口返回时后端做一次字段映射前端只认转换后的字符串状态。6. 进阶把分类规则和权重做成配置让评估结果可回放6.1 把分类阈值和权重做成配置表不写代码也能调前面的分类阈值 10% 和风险分 3.0 是写死在 Python 里的权重也在数据库表里但改起来要刷 SQL。进阶做法是把分类阈值也纳入配置体系建一张classify_rule_config表存ratio_threshold、risk_threshold、rule_version三个字段。分类程序启动时先读配置再执行分类计算。这样业务调整阈值时只需要在页面上改配置并生成一个新的rule_version程序按新版本重算分类旧版本的数据还能通过classify_version追溯。权重配置同理category_weight表加上valid_from和valid_to两个时间字段每次调整权重都插入一条新记录而不是更新旧记录评估明细表里记录weight_version保证历史评估结果可回放。6.2 用历史数据回放验证模型排序靠不靠谱评估模型上线前我习惯做一个“历史数据回放”验证把过去半年已经人工选定供应商的采购记录捞出来用当时的分类版本和权重版本重算模型排序对比模型给的排序和人工实际选择是否一致。如果有超过三成的结果不一致说明评估维度或权重的方向有偏差这时候不要急着调权重先把不一致的样本拉出来看是哪个指标拉低了排名。验证通过后再推广到其他物料类别。这个技巧能让你在业务面前有底气模型不是玄学是能拿历史结果解释的。我做这个方案到现在最大的教训就是把规则尽快配置化越早上配置后续调整成本越低。最后希望帮到你。本文还有配套的精品资源点击获取