简介:基于物料分类的差异化供应商选择研究资源,面向供应链管理人员、采购经理及从事供应链优化研究的专业人士,旨在建立结合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 差异化权重:关键物料重质量,杠杆物料重成本
分类完成后,差异化供应商选择的核心动作就是“按类设权重”。同一套供应商评估基础数据可以复用,但每个物料类别的指标权重必须不同。我一般用下面这张表作为初始配置:
| 物料类别 | 质量 | 交付 | 成本 | 服务 | 技术 |
|---|---|---|---|---|---|
| 关键物料 K | 40% | 25% | 20% | 10% | 5% |
| 杠杆物料 L | 20% | 20% | 40% | 15% | 5% |
| 瓶颈物料 B | 25% | 35% | 5% | 20% | 15% |
| 一般物料 G | 20% | 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) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 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 '展示顺序' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 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) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 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) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 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) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 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( host="127.0.0.1", user="root", password="your_password", database="supplier_db", charset="utf8mb4" ) 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, axis=1)代码里的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, on="item_code", how="inner") 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的how="inner"保证只在权重配置中存在评估项的记录参与计算,权重表漏配的评估项会被自动忽略,这样能快速发现配置缺失。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( host="127.0.0.1", user="root", password="your_password", database="supplier_db", charset="utf8mb4" ) 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(host="0.0.0.0", port=5000)对应 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 class="layui-btn layui-btn-xs" lay-event="submit">提交审批</button>'; } return '<span>已提交</span>'; }} ]] });layui 这边有两个参数要注意:page: true开启分页后,layui 会自动向接口传page和limit,所以后端接口必须接收这两个参数并返回count和data;templet里的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固定为 0,count返回总数,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 用历史数据回放验证模型排序靠不靠谱
评估模型上线前,我习惯做一个“历史数据回放”验证:把过去半年已经人工选定供应商的采购记录捞出来,用当时的分类版本和权重版本重算模型排序,对比模型给的排序和人工实际选择是否一致。如果有超过三成的结果不一致,说明评估维度或权重的方向有偏差,这时候不要急着调权重,先把不一致的样本拉出来看是哪个指标拉低了排名。
验证通过后再推广到其他物料类别。这个技巧能让你在业务面前有底气:模型不是玄学,是能拿历史结果解释的。我做这个方案到现在,最大的教训就是把规则尽快配置化,越早上配置,后续调整成本越低。最后希望帮到你。
本文还有配套的精品资源,点击获取