发布时间:2026/8/15 5:52:00
教培SaaS线索分配系统的状态机设计从人工抢单到智能路由的演进 背景教培机构的招生线索管理是一个典型的状态流转场景。一条线索从进入系统到最终成交或流失会经历分配、跟进、试听、转化等多个状态。我们最初用status字段加if-else实现随着业务复杂度增加代码变成了面条式的条件判断维护噩梦。后来重构为状态机模式今天分享这个过程。一、原始实现的问题最初的线索模型很简单class Lead:status models.CharField(max_length20, defaultnew)assigned_to models.ForeignKey(User, nullTrue)last_followup models.DateTimeField(nullTrue)# 分配逻辑散落在各处def assign_lead(lead, user):if lead.status new:lead.assigned_to userlead.status assignedlead.save()elif lead.status assigned:raise Exception(already assigned)elif lead.status lost:raise Exception(cannot assign lost lead)def followup_lead(lead, user, note):if lead.status assigned and lead.assigned_to user:lead.status followinglead.last_followup timezone.now()lead.save()elif lead.status following:lead.last_followup timezone.now()lead.save()else:raise Exception(invalid state transition)这种写法的问题很明显状态流转逻辑散落在十几个函数里每加一个状态就要改十几处代码。而且没有防止非法状态转换比如有人把lost状态的线索改成following也不会报错。二、状态机建模首先梳理线索的完整状态流转图new - assigned - following - trial_scheduled - trial_completed - won/lost还有几条特殊路径assigned - recycled超时未跟进回收following - recycled长时间未推进回收trial_completed - following试听后继续跟进任何状态 - invalid线索无效用状态机模式重构from enum import Enumfrom transitions import Machineclass LeadState(Enum):NEW newASSIGNED assignedFOLLOWING followingTRIAL_SCHEDULED trial_scheduledTRIAL_COMPLETED trial_completedWON wonLOST lostRECYCLED recycledINVALID invalidclass LeadStateMachine:transitions [# trigger, source, dest, conditions, before, after[assign, new, assigned, can_assign, before_assign, after_assign],[assign, recycled, assigned, can_assign, before_assign, after_assign],[start_followup, assigned, following, None, None, notify_assignee],[schedule_trial, following, trial_scheduled, None, validate_trial_slot, notify_parent],[complete_trial, trial_scheduled, trial_completed, None, record_trial_feedback, None],[continue_followup, trial_completed, following, None, None, notify_assignee],[win, [following, trial_completed], won, None, create_contract, notify_manager],[lose, [following, trial_completed], lost, None, record_loss_reason, None],[recycle, [assigned, following], recycled, None, clear_assignee, add_to_pool],[invalidate, *, invalid, None, record_invalid_reason, None],]def __init__(self, lead):self.lead leadself.machine Machine(modelself,states[s.value for s in LeadState],transitionsself.transitions,initiallead.status,send_eventTrue)def can_assign(self, event):# 检查当前用户是否有分配权限user event.kwargs.get(user)return user.has_perm(assign_lead)def before_assign(self, event):user event.kwargs.get(user)self.lead.assigned_to userself.lead.assigned_at timezone.now()def after_assign(self, event):# 发送通知给被分配人send_notification.delay(user_idself.lead.assigned_to_id,title新线索分配,contentf您收到一条新线索{self.lead.student_name})使用transitions库后状态流转逻辑集中在一处定义非法转换会被自动拦截新增状态只需在transitions列表里加一条。三、智能分配路由状态机解决了怎么转的问题但没解决转给谁的问题。最初是人工抢单后来客户要求智能分配。分配策略需要考虑多个因素销售老师的转化率、当前待跟进线索数、线索来源渠道与销售老师的匹配度、时间段有些老师晚上效率高。class LeadRouter:def __init__(self, config):self.weights {conversion_rate: 0.35,workload: 0.25,channel_match: 0.20,response_speed: 0.20}self.config configasync def route(self, lead, candidates):scores []for user in candidates:score await self._calculate_score(lead, user)scores.append((user, score))scores.sort(keylambda x: x[1], reverseTrue)# 如果最高分和第二高分差距小于阈值随机选一个避免总是分给同一个人if len(scores) 2 and (scores[0][1] - scores[1][1]) 0.05:top_two scores[:2]chosen random.choice(top_two)[0]else:chosen scores[0][0]return chosenasync def _calculate_score(self, lead, user):# 转化率得分conv_rate await self._get_conversion_rate(user)conv_score min(conv_rate / 0.3, 1.0) # 30%转化率得满分# 工作量得分待跟进越少分越高pending await self._get_pending_count(user)workload_score max(1 - pending / 30, 0) # 30条待跟进得0分# 渠道匹配度channel_match await self._get_channel_match(user, lead.channel)# 响应速度avg_response await self._get_avg_response_time(user)response_score max(1 - avg_response / 3600, 0) # 1小时响应得0分total (self.weights[conversion_rate] * conv_score self.weights[workload] * workload_score self.weights[channel_match] * channel_match self.weights[response_speed] * response_score)return total这里踩了一个坑最初把转化率权重设为0.6结果导致所有高转化率的销售老师线索堆积而新老师一直分不到线索能力无法提升。后来把转化率权重降到0.35加了随机选择机制线索分配更均衡了。四、超时回收机制线索分配后如果销售老师不及时跟进需要在超时后自动回收。这需要一个定时任务扫描超时线索class LeadRecycler:ASSIGN_TIMEOUT 2 * 3600 # 分配后2小时未跟进FOLLOWUP_TIMEOUT 48 * 3600 # 跟进后48小时未推进async def run(self):while True:await self._check_assignment_timeout()await self._check_followup_timeout()await asyncio.sleep(300) # 每5分钟检查一次async def _check_assignment_timeout(self):cutoff timezone.now() - timedelta(secondsself.ASSIGN_TIMEOUT)leads Lead.objects.filter(statusassigned,assigned_at__ltcutoff)for lead in leads:fsm LeadStateMachine(lead)try:fsm.recycle()lead.status fsm.statelead.assigned_to Nonelead.save()await self._notify_recycle(lead, assignment_timeout)except MachineError:pass # 状态转换失败跳过五、总结从面条式if-else到状态机加智能路由的演进核心收获1. 状态机模式把散落的状态流转逻辑集中管理新增状态和转换规则只需改一处配置。2. transitions库的send_event模式可以在转换前后插入钩子函数做通知、校验、日志等操作。3. 智能分配需要多维度加权打分不能只看单一指标权重要跟业务方一起调。4. 超时回收是线索管理容易被忽略的一环不回收就等于线索浪费。5. 状态机要有完整的日志记录每次状态转换都记录who、when、from、to、reason方便后续分析转化漏斗。

相关新闻

2026/8/14 2:05:17

从“杀菌”到“养菌”:2026年皮肤管理行业的技术范式转移

引言 2026年,皮肤管理行业正在经历一场技术范式的转移——从传统的 “杀菌”思维转向 “养菌”思维。这场转移的核心,是对皮肤微生态认知的全面升级。 传统范式的局限 传统的皮肤清洁逻辑建立在“杀菌”思维之上——通过物理或化学手段,尽可能…

2026/8/14 2:00:17

口碑好的德州智能晾衣架推荐生产厂家

开篇先说个扎心的事实:很多装修业主在选晾衣架时,面对一堆“承重35kg”“电机质保10年”“智能风干”的参数直接懵圈。更头疼的是,网上虚标严重,同一个配置价格能差出一倍,不同预算到底该选哪个品牌?别急&a…

2026/8/15 5:49:20

ZIP文件结构深度解析:从二进制格式到常见错误修复

1. ZIP格式:无处不在的压缩基石如果你在电脑上工作过,那么你几乎不可能没接触过ZIP文件。从下载一个软件安装包,到同事发来一堆文档,再到备份自己的项目代码,.zip后缀的文件无处不在。它就像一个数字世界的“打包袋”&…

2026/8/15 5:49:20

Linux网络连接状态排查:从netstat到ss的运维实战指南

1. 网络连接状态:运维的“听诊器”在Linux系统运维和开发工作中,网络连接状态是排查问题、分析性能、保障安全的第一手资料。无论是服务器响应变慢、端口冲突,还是怀疑存在异常连接或潜在安全风险,第一时间查看网络连接状态&#…

2026/8/15 5:49:20

性能提升计算与精度权衡:从理论到实践的量化评估指南

1. 项目概述:性能提升的量化与精度权衡在技术迭代和项目优化的日常工作中,我们经常面临一个核心问题:如何客观、准确地评估一项改进措施带来的效果?无论是优化一段代码、升级一个硬件模块,还是调整一个算法参数&#x…

2026/8/15 5:49:20

Win11硬盘分区全攻略:从GPT/SSD原理到实战避坑指南

1. 项目概述:为什么Win11用户需要重新审视硬盘分区?最近帮几个朋友处理新电脑,发现一个挺普遍的现象:很多人拿到预装Win11的新机器,看着那个孤零零的C盘,心里就有点发怵。系统、软件、文档、游戏全挤在一起…

2026/8/15 5:49:20

大模型流式前端,先把连接状态和消息状态分开

大模型流式前端,先把连接状态和消息状态分开 流式输出不是“字符串不断变长”这么简单。连接可能重试,消息可能取消,Markdown 还可能停在半个代码围栏。状态混在一个 loading 里,界面很快失真。 两层状态各自负责 连接层区分连接中…

2026/8/14 4:27:24

如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南 【免费下载链接】chinese_license_plate_generator 中国车牌生成器 项目地址: https://gitcode.com/gh_mirrors/ch/chinese_license_plate_generator 中国车牌生成器是一个基于Python的开源项目&#xff0c…

2026/8/14 4:27:24

当 LLM 遇见大文档:主流开源项目如何处理上下文超限

从 Agentic Loop 到 Repo Map,七种策略与六类陷阱引言:128K vs 10MB 的硬冲突 2026 年的 LLM 上下文窗口已达到 128K ~ 1M token(≈ 0.5MB ~ 4MB 文本),但 LLM 想要处理的真实数据规模远远超过这个量级:真实…

2026/8/15 0:04:00

AI 电动婴儿车智能功率 辅助控制、电源管理的完整选型方案

2026年随着 AI 技术在电动孕婴童用品中的深度渗透(如智能避障、自适应速度控制、能量回收),电动婴儿车对功率器件提出更高要求:高效率、小型化、低功耗、高可靠性。微碧半导体(VBsemi)基于 Trench 及 SGT 工…

2026/8/15 0:04:00

论文AIGC检测不达标完整教程!低门槛用5款工具逐步复检!

论文提交前自己先查一遍AI率,是2026年毕业生的常规动作。学校要求论文AI率低于30%,乃至于20%才能答辩… 很多同学发现一个尴尬的事情:同一篇论文,知网查出来AI率35%,维普查可能是48%,大雅、朱雀又是另外的数…

2026/8/14 4:27:24

实测才敢推 AI论文网站 2026最新测评与推荐

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。一、综…

2026/8/15 4:56:16

2026必备!AI论文网站测评:最新推荐与深度对比

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

2026/8/14 4:27:24

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…