news 2026/8/14 2:05:07

教培SaaS线索分配系统的状态机设计从人工抢单到智能路由的演进

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
教培SaaS线索分配系统的状态机设计从人工抢单到智能路由的演进

背景

教培机构的招生线索管理是一个典型的状态流转场景。一条线索从进入系统到最终成交或流失,会经历分配、跟进、试听、转化等多个状态。我们最初用status字段加if-else实现,随着业务复杂度增加,代码变成了面条式的条件判断,维护噩梦。后来重构为状态机模式,今天分享这个过程。

一、原始实现的问题

最初的线索模型很简单:

class Lead:

status = models.CharField(max_length=20, default='new')

assigned_to = models.ForeignKey('User', null=True)

last_followup = models.DateTimeField(null=True)

# 分配逻辑散落在各处

def assign_lead(lead, user):

if lead.status == 'new':

lead.assigned_to = user

lead.status = 'assigned'

lead.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 = 'following'

lead.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 Enum

from transitions import Machine

class LeadState(Enum):

NEW = 'new'

ASSIGNED = 'assigned'

FOLLOWING = 'following'

TRIAL_SCHEDULED = 'trial_scheduled'

TRIAL_COMPLETED = 'trial_completed'

WON = 'won'

LOST = 'lost'

RECYCLED = 'recycled'

INVALID = 'invalid'

class 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 = lead

self.machine = Machine(

model=self,

states=[s.value for s in LeadState],

transitions=self.transitions,

initial=lead.status,

send_event=True

)

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 = user

self.lead.assigned_at = timezone.now()

def after_assign(self, event):

# 发送通知给被分配人

send_notification.delay(

user_id=self.lead.assigned_to_id,

title='新线索分配',

content=f'您收到一条新线索:{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 = config

async def route(self, lead, candidates):

scores = []

for user in candidates:

score = await self._calculate_score(lead, user)

scores.append((user, score))

scores.sort(key=lambda x: x[1], reverse=True)

# 如果最高分和第二高分差距小于阈值,随机选一个避免总是分给同一个人

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 chosen

async 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(seconds=self.ASSIGN_TIMEOUT)

leads = Lead.objects.filter(

status='assigned',

assigned_at__lt=cutoff

)

for lead in leads:

fsm = LeadStateMachine(lead)

try:

fsm.recycle()

lead.status = fsm.state

lead.assigned_to = None

lead.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,方便后续分析转化漏斗。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/14 2:03:36

数字孪生智慧水利建设方案:数字孪生水利工程建设、典型项目案例、智慧水利解决方案、 信创与市场机会

该方案是一个涵盖政策解读、技术架构、实施路径、项目案例、市场机会的完整智慧水利建设蓝图。其核心是构建以数据为底座、模型为核心、AI为驱动的数字孪生平台&#xff0c;赋能“2N”水利业务&#xff0c;最终实现预报、预警、预演、预案的智慧化管理&#xff0c;全面提升水利…

作者头像 李华
网站建设 2026/8/14 2:03:22

网站建设共享ip:中小站长避坑指南与深度解析

咱们今天不聊那些高大上的云架构,也不扯什么百万并发的大事儿,就聊聊咱们大多数中小站长、个人博主以及初创小团队在搞网站建设时,最头秃、也最容易被忽视的一个角落——IP地址,特别是共享IP这档子事儿。我相信很多刚入行的朋友,甚至是一两年经验的老手,在买服务器或者空…

作者头像 李华
网站建设 2026/8/14 2:02:42

大连网站建设仟亿科技:深耕本土数字化服务,以匠心铸就企业品牌数字基石与长远价值

在这个信息爆炸且竞争日益激烈的互联网时代,对于任何一家身处大连乃至全国的企业来说,拥有一个高质量的专业网站,早已不再是一道“可选项”,而是一道关乎生存与发展的“必答题”。当我们谈论“大连网站建设”时,我们谈论的不仅仅是一堆代码、几页HTML或者一个看似炫酷的模…

作者头像 李华
网站建设 2026/8/14 2:02:06

揭秘紫金网站建设背后的那些事儿:从0到1打造企业的数字化门面,为何它不仅是技术更是态度?

在这个互联网思维渗透进生活每一处细节的时代,如果说线下实体店的招牌是企业的“脸面”,那么网站就是企业在数字世界里的“灵魂栖息地”。很多人对“网站”这两个字有着深深的误解,以为它不过是一堆代码、几张图片和几段文字的简单堆砌,甚至觉得找个模板套一下,半个月就能…

作者头像 李华
网站建设 2026/8/14 2:01:41

深入解析承德建设银行网站功能特色与本地金融服务升级体验指南

在这个数字化浪潮席卷全球的今天,银行不再仅仅是那个我们印象中需要排长队、填单据、还要看脸色办事的繁琐地方。对于咱们生活在承德的市民朋友来说,金融服务早已渗透到了日常生活的方方面面,从早上一杯热腾腾的奶茶付款,到晚上通过手机银行给家里老人转的生活费,再到小微…

作者头像 李华