

聚焦高价值:用工AI算法
去年双11凌晨2点,我接到一个电话。
一家日单量峰值12万的物流平台,调度系统在上线第三天凌晨崩了。不是服务器挂了,是算法算不出来了。
5000个分拣员订单同时涌入,系统要给每个人匹配最优的仓库、班次、线路。结果呢?ILP求解器跑了20分钟没跑完,超时熔断,订单全部卡死。
事后复盘,问题不在算力,而在我们对业务复杂度的低估。
真实世界的用工,远比教科书上的"分配问题"要脏、要乱、要不可控——
一个瓦工三年没干防水了,技能还在,但熟练度打折了,你算不算他会? 一个叉车司机有证,但发证机构在A省,用工地在B省,跨省互认还没打通,你派不派? 一个工人上午在浦东仓库干到11点,下午1点要到松江仓库报到,中间通勤要90分钟,你排不排? 劳动法规定连续工作不得超过8小时,一个工人已经接了7.5小时的单,你还能不能给他派一个2小时的急单?
这些不是边界case,这是每天都在发生的常态。
今天这篇文章,我把这套系统里真正在生产环境跑的代码拆给你看。每一行算法代码都在跟真实的业务复杂性肉搏。
【算法与业务复杂性交织图】

一、第一层:规则引擎——不是"过不过",是"怎么算"
大多数系统的规则引擎,就是一个 if-else 堆砌。但在真实灵工场景里,硬约束本身就是一个多维度的评分体系。
我们重新定义一下"合格"的含义——不是"有或没有",而是"在约束条件下,这个人的综合合格分是否超过阈值"。
1.1 技能匹配:不是"有证就行",是"等级+衰减"
真实业务里,技能是有等级的。一个"电工高级"和一个"电工初级"去干同一个活,效率和返工率天差地别。而且,技能会衰减——一个三年没干防水的瓦工,他的防水技能评分应该打折。
class SkillMatcher:"""技能匹配引擎 - 处理等级、衰减、多技能组合"""# 技能等级权重映射SKILL_LEVEL_WEIGHT = {"初级": 0.4, "中级": 0.6, "高级": 0.8, "技师": 0.9, "高级技师": 1.0}# 技能衰减系数:每月未使用衰减2%DECAY_RATE_PER_MONTH = 0.02MAX_DECAY = 0.5 # 最多衰减50%def calculate_skill_score(self, order_skills: dict, worker: dict) -> float:"""order_skills: {"电工": "中级以上", "登高": "必须"}worker["skills"]: [{"name": "电工", "level": "高级", "last_used": "2024-06"}]"""total_score = 0.0for skill_name, required in order_skills.items():worker_skill = self._find_worker_skill(worker, skill_name)if not worker_skill:return 0.0 # 核心技能缺失,直接0分# 等级分level_score = self._calc_level_score(worker_skill, required)if level_score == 0:return 0.0 # 等级不达标,硬约束# 衰减分decay = self._calc_decay(worker_skill.get("last_used"))total_score += level_score * (1 - decay)return total_score / len(order_skills)def _calc_decay(self, last_used_str: str) -> float:"""计算技能衰减:距离上次使用的时间越长,衰减越大"""if not last_used_str:return self.MAX_DECAYlast_used = datetime.strptime(last_used_str, "%Y-%m")months_diff = (datetime.now().year - last_used.year) * 12 + \(datetime.now().month - last_used.month)decay = min(self.MAX_DECAY, months_diff * self.DECAY_RATE_PER_MONTH)return decay
1.2 证书校验:发证机构+跨区域+备案状态
证书不是"有"就完事了。真实场景里,证书有发证机构白名单、有跨区域互认协议、有平台备案状态。
class CertificateValidator:"""证书校验引擎 - 处理发证机构、跨区域、备案"""def validate(self, order: dict, worker: dict) -> tuple[bool, str]:"""返回 (是否通过, 不通过原因)"""for cert_req in order.get("required_certificates", []):cert_name = cert_req["name"]worker_cert = worker.get("certificates", {}).get(cert_name)if not worker_cert:return False, f"缺少证书:{cert_name}"# 1. 有效期检查if worker_cert["expire_date"] < datetime.now():return False, f"证书过期:{cert_name}"# 2. 发证机构白名单issuer = worker_cert["issuer"]if issuer not in cert_req.get("approved_issuers", []):# 3. 跨区域互认检查if not self._check_cross_region_recognition(issuer, cert_req.get("region"), worker_cert.get("region")):return False, f"发证机构不在白名单且未互认:{issuer}"# 4. 平台备案状态(某些高危工种必须在监管平台备案)if cert_req.get("require_registration"):if not self._check_platform_registration(worker["id"], cert_name):return False, f"证书未在监管平台备案:{cert_name}"return True, "OK"def _check_cross_region_recognition(self, issuer_region: str,order_region: str,cert_region: str) -> bool:"""检查跨区域互认协议"""# 查询互认协议表:比如长三角互认、京津冀互认# 这里简化,实际是查数据库或缓存recognition_map = {("上海", "江苏"): True, ("上海", "浙江"): True,("北京", "天津"): True, ("广东", "深圳"): True,}key = (cert_region, order_region)return recognition_map.get(key, False)
1.3 时间冲突:连续工时+通勤时间+跨天
这是最复杂的部分。不是"两个时间段不重叠"就完了,还要考虑:
- 连续工作时长限制
:劳动法规定每天不超过8小时,每周至少休息1天 - 通勤时间
:如果两个订单地点距离远,中间通勤时间不够,也算冲突 - 跨天处理
:夜班跨天,需要特殊处理
class TimeConflictChecker:"""时间冲突检测 - 连续工时+通勤+跨天"""MAX_DAILY_HOURS = 8 # 单日最大工时MIN_REST_HOURS = 10 # 连续工作后最少休息10小时(夜班后)def check(self, order: dict, worker: dict, distance_matrix: dict) -> bool:"""distance_matrix: 地点间距离矩阵,用于计算通勤时间"""new_start = self._parse_time(order["start_time"])new_end = self._parse_time(order["end_time"])new_location = order["location_id"]# 检查已有订单for existing in worker.get("existing_orders", []):exist_start = self._parse_time(existing["start_time"])exist_end = self._parse_time(existing["end_time"])exist_location = existing["location_id"]# 1. 直接时间重叠if new_start < exist_end and new_end > exist_start:return False# 2. 连续工时检查:两个订单之间是否有足够休息if exist_end <= new_start: # 已有订单在前rest_hours = (new_start - exist_end).total_seconds() / 3600commute_hours = self._get_commute_time(exist_location, new_location, distance_matrix)# 休息时间 = 间隔 - 通勤时间effective_rest = rest_hours - commute_hoursif effective_rest < self.MIN_REST_HOURS:return Falseif new_end <= exist_start: # 新订单在前rest_hours = (exist_start - new_end).total_seconds() / 3600commute_hours = self._get_commute_time(new_location, exist_location, distance_matrix)effective_rest = rest_hours - commute_hoursif effective_rest < self.MIN_REST_HOURS:return False# 3. 单日累计工时检查daily_hours = self._calc_daily_hours(worker, new_start.date())if daily_hours + (new_end - new_start).total_seconds() / 3600 > self.MAX_DAILY_HOURS:return Falsereturn Truedef _get_commute_time(self, from_loc: str, to_loc: str, matrix: dict) -> float:"""从距离矩阵获取通勤时间(小时)"""# 实际中这是实时路况API,这里简化key = (from_loc, to_loc)return matrix.get(key, 999) / 60 # 假设速度60km/h
【规则引擎复杂约束图】

二、第二层:ILP整数规划——动态权重+班组约束+连续性
过了规则引擎的候选人,接下来是ILP求全局最优。但真实业务的ILP,约束条件远比教科书复杂。
2.1 动态权重:不同订单类型,权重不同
紧急订单:技能权重提到40%,距离降到10%,因为"人要对,人远点也能来"。
普通订单:价格权重提到25%,因为企业想省钱。
长期订单:接单率权重提到25%,因为"靠谱比便宜重要"。
class DynamicWeightConfig:"""动态权重配置 - 根据订单类型调整"""BASE_WEIGHTS = {"skill_match": 0.30, "distance": 0.20,"rating": 0.20, "accept_rate": 0.15, "price": 0.15}ORDER_TYPE_ADJUSTMENTS = {"emergency": {"skill_match": +0.10, "distance": -0.10},"long_term": {"accept_rate": +0.10, "price": -0.05, "skill_match": -0.05},"batch": {"price": +0.10, "rating": -0.05, "skill_match": -0.05},}def get_weights(self, order_type: str) -> dict:weights = self.BASE_WEIGHTS.copy()adjustments = self.ORDER_TYPE_ADJUSTMENTS.get(order_type, {})for k, v in adjustments.items():weights[k] = max(0, min(1, weights[k] + v)) # 限制在0-1# 归一化total = sum(weights.values())return {k: v/total for k, v in weights.items()}
2.2 班组约束:不是"派一个人",是"派一个团队"
制造业很多订单要求"一拖十"——1个班组长带10个普工。班组长必须是高级技能,普工可以是初级。ILP里要加班组完整性约束。
def add_team_constraints(self, prob, x, orders, candidates):"""班组完整性约束:班组长和组员必须同时分配到同一个订单"""for order in orders:if order.get("require_team"):team_leader_skill = order["team_leader_skill"]member_skill = order["member_skill"]team_size = order["team_size"] # 包括组长# 找出所有班组长候选和组员候选leaders = [c for c in candidates if team_leader_skill in c["skills"]]members = [c for c in candidates if member_skill in c["skills"]]# 约束:每个订单必须恰好分配1个组长和(team_size-1)个组员leader_vars = [x[order["id"]][c["id"]] for c in leaders]prob += pulp.lpSum(leader_vars) == 1, f"TeamLeader_{order['id']}"member_vars = [x[order["id"]][c["id"]] for c in members]prob += pulp.lpSum(member_vars) == team_size - 1, f"TeamMembers_{order['id']}"# 约束:班组长和组员不能是同一个人for c in candidates:if c in leaders and c in members:# 如果既是组长又是组员,不能同时分配pass # 实际中会在候选池里区分
2.3 连续性约束:连续多天的订单,必须保证同一人
有些工地项目要干7天,企业不希望每天换人。ILP里要加跨天连续性约束。
def add_continuity_constraints(self, prob, x, multi_day_orders, candidates):"""连续性约束:同一工人在连续多天的订单中,要么全接,要么全不接"""for worker in candidates:for order_group in multi_day_orders: # 同一项目的多天订单# 如果工人接了其中一天,就必须接所有天vars_for_worker = [x[order["id"]][worker["id"]] for order in order_group]# 所有变量必须相等:要么全1,要么全0for i in range(len(vars_for_worker) - 1):prob += vars_for_worker[i] == vars_for_worker[i+1], \f"Continuity_{worker['id']}_{order_group[0]['id']}_{i}"
【ILP复杂约束图】

三、第三层:遗传算法——多目标+公平性+技能衰减
当订单量上千时,ILP算不过来。遗传算法要处理的是更复杂的适应度函数。
3.1 染色体编码:二维编码(订单×天×班次)
不是简单的列表,而是三维结构:solution[order_id][day][shift] = worker_id
3.2 适应度函数:多目标+公平性+技能衰减
class ComplexGeneticScheduler:"""复杂遗传算法调度器 - 多目标+公平性+技能衰减"""def _fitness(self, individual, orders, candidates, distance_matrix):"""适应度 = 匹配分 - 惩罚项 + 公平性奖励 - 技能衰减惩罚"""total_score = 0penalty = 0worker_assign_count = {} # 工人被分配的总次数worker_daily_hours = {} # 工人每天的工作时长for order_idx, assignment in enumerate(individual):order = orders[order_idx]for worker_id in assignment:worker = candidates[worker_id]# 1. 匹配分(考虑技能衰减)match_score = self._calc_match_with_decay(order, worker)total_score += match_score# 2. 统计分配次数worker_assign_count[worker_id] = worker_assign_count.get(worker_id, 0) + 1# 3. 统计每日工时day = order["start_time"][:10] # 日期hours = self._calc_hours(order)key = (worker_id, day)worker_daily_hours[key] = worker_daily_hours.get(key, 0) + hours# 惩罚项1:同一工人时间冲突(重叠)penalty += self._calc_conflict_penalty(individual, orders, candidates)# 惩罚项2:工人负载不均衡(用基尼系数)if worker_assign_count:counts = list(worker_assign_count.values())gini = self._calc_gini(counts)penalty += gini * 1000 # 基尼系数越大,惩罚越重# 惩罚项3:超时工作for (worker_id, day), hours in worker_daily_hours.items():if hours > 8:penalty += (hours - 8) * 500 # 超时惩罚# 惩罚项4:技能衰减过大(工人被分配去做很久没做的活)penalty += self._calc_decay_penalty(individual, orders, candidates)# 公平性奖励:技能高的工人不应该总被分配去做低级活fairness_bonus = self._calc_skill_fairness(individual, orders, candidates)total_score += fairness_bonusreturn total_score - penaltydef _calc_gini(self, values):"""计算基尼系数,衡量分配公平性"""if not values or sum(values) == 0:return 0sorted_values = sorted(values)n = len(sorted_values)cumsum = [sum(sorted_values[:i+1]) for i in range(n)]return (2 * sum((i+1) * sorted_values[i] for i in range(n)) / (n * sum(sorted_values))) - (n+1)/ndef _calc_decay_penalty(self, individual, orders, candidates):"""技能衰减惩罚:如果工人被分配去做很久没用的技能,扣分"""penalty = 0for order_idx, assignment in enumerate(individual):order = orders[order_idx]for worker_id in assignment:worker = candidates[worker_id]for skill in order.get("required_skills", []):last_used = worker.get("skills", {}).get(skill, {}).get("last_used")if last_used:months = self._months_since(last_used)if months > 12: # 超过1年没用penalty += (months - 12) * 10 # 每多一个月扣10分return penalty
3.3 遗传操作:针对复杂编码的PMX交叉
def _crossover_pmx(self, parent1, parent2):"""部分匹配交叉(PMX),适合排列编码"""size = len(parent1)cx1, cx2 = sorted(random.sample(range(size), 2))child1 = [-1] * sizechild2 = [-1] * size# 复制交叉段for i in range(cx1, cx2 + 1):child1[i] = parent1[i]child2[i] = parent2[i]# 映射关系mapping1 = {parent1[i]: parent2[i] for i in range(cx1, cx2 + 1)}mapping2 = {parent2[i]: parent1[i] for i in range(cx1, cx2 + 1)}# 填充剩余位置for i in range(size):if i < cx1 or i > cx2:# 处理child1val1 = parent2[i]while val1 in child1:val1 = mapping1.get(val1, val1)child1[i] = val1# 处理child2val2 = parent1[i]while val2 in child2:val2 = mapping2.get(val2, val2)child2[i] = val2return child1, child2
【遗传算法复杂适应度图】

四、分层混合调度:真实场景的算法编排
场景 | 算法组合 | 业务原因 |
|---|---|---|
小批量紧急调度(<50单) | 规则引擎 + ILP(动态权重) | 求精确最优,响应时间可接受 |
大批量日排班(>100单) | 规则引擎 + 遗传算法(PMX交叉) | 数据量大,遗传算法秒级出结果 |
动态补单/调单 | 启发式贪心 + 局部搜索(2-opt) | 秒级响应,只调整变动部分 |
长期预测排班(7-30天) | Prophet预测 + ILP(连续性约束) | 先预测需求,再求多天最优 |
跨企业共享用工 | 联盟博弈 + ILP | 多企业资源池共享,成本分摊 |
一个真实场景串起来:
某汽车零部件厂,双11前14天启动长期预测排班——用Prophet模型预测未来两周每天每小时的用工需求,结合历史数据、订单预测、季节因素,生成需求曲线。
然后按天调用ILP,带连续性约束——同一个工人如果被分配到某项目,必须保证连续7天都能来。如果某天有冲突,整个方案重新调整。
双11当天早上,有30个工人临时请假。系统启动动态补单——用启发式贪心算法,从备选池里按"技能等级优先+距离最近+接单率最高"秒级拉出30个替补,然后用2-opt局部搜索优化这30个人的分配,不影响全局方案。
三层算法,加上预测和博弈,构成了一个完整的调度生态。
【分层混合调度策略图】

总结升华
写到这里,我想起一句话——
好的算法,不是把世界简化成数学,而是把数学扩展到能容纳世界的复杂。
规则引擎处理的是"底线"——技能等级、证书链、连续工时、通勤时间,这些不是简单的if-else,而是多维度的业务规则网络。
ILP处理的是"最优"——动态权重、班组约束、连续性约束,这些不是教科书上的标准模型,而是为真实业务定制的数学模型。
遗传算法处理的是"效率"——技能衰减、负载均衡、基尼系数、公平性,这些不是简单的目标函数,而是对平台长期健康发展的深度考量。
算法不是炫技。算法是业务复杂性的数学表达。【文中展示的代码算法只是我‘用工平台’AI调度的冰山一角】
收尾金句
没有处理过技能衰减的调度算法,不配叫"智能"。
没有考虑过连续工时的匹配系统,不配叫"合规"。
真正的AI调度,是在每一个业务暗坑上都铺好了算法的路。
关注⭐订阅《AI 产业落地录》下一期免费分享:
业务架构实战篇 :AI 智能调度算法详解与代码实现
......敬请期待!!!
🔗 如果你正在规划用工数字化升级,或者对AI调度系统有具体需求,欢迎留言交流,我们一起把"活找人"变成现实。
📌 关注本公众号,持续获取灵活用工 × AI技术 的深度干货。
👇 觉得有用?转发给身边做用工管理的朋友,一起降本增效,有想要了解咨询的评论区留言。粉丝们后续想看想学什么评论区留言,评论点赞过百者优先

图:四模块联动的系统架构全景

图:智能问答,对话式交互

如果这篇文章对你有帮助,欢迎点赞、在看、转发。
关注我,后续还会分享更多企业级 AI 落地的实战经验。
持续关注订阅:AI 产业落地录
互动提问:你所在的公司深耕哪个行业?有没有遇到制造业龙头抢单?评论区聊聊你的行业痛点! 关注引导:点赞收藏转发给同行,看懂产业互联网壁垒,避开行业洗牌大坑;持续更新制造业与产业互联网等实战干货。
📣 深度互动
欢迎点赞+关注我的公众号,技术搭建不懂落地,商业规划难以执行?关注我,后台发消息与我联系交流:帮您一站式搞定平台、业务、全流程运营!
AI数智时代,大有可为。如果你对AI工具、智能体或AI+赚钱感兴趣,欢迎添加学习交流群 和 我的微信交流。备注“行业(专业)+昵称(姓名)。”
学习交流群:
QQ群

微信群

🔥 立即行动,未来已来! 🔥
👉 关注下方公众号,点击关注,开启智能学习使用新篇章!
🔗 价值延伸
只需关注本公众号,并回复“AI学习”,即可加入我们的学习社群,获取最新学习资料和互动机会!




夜雨聆风