ai_scheduler/internal/jobs/advice_task.go

114 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package jobs
import (
"ai_scheduler/internal/biz"
"ai_scheduler/internal/config"
"ai_scheduler/internal/entitys"
"context"
"fmt"
"time"
"github.com/robfig/cron/v3"
)
// AdviceTaskJob 数字销售定时任务
// 1. 客户等级评估:每日 daily_evaluate_hour 点(默认 0 点)评估熟客/意向/沉睡/非客户;
// 2. 熟客超时兜底:每 2 分钟扫描「人工超时未回复」的熟客,执行礼貌性回复与简单解答;
// 3. 对话完成画像迭代:每 10 分钟扫描闲置结束的对话,分析聊天记录完善客户画像(同时抽取时间约定→跟进线索);
// 4. 主动触达:每小时扫描生日/新活动,AI 主动发起对话(受活跃时段与总开关限制);
// 5. 跟进线索到点执行:每分钟扫描到期约定,remind_customer 由 AI 提醒客户、offline_arrival 转后台待办。
type AdviceTaskJob struct {
cfg *config.Config
evaluateBiz *biz.AdviceEvaluateBiz
strategyBiz *biz.AdviceStrategyBiz
proactiveBiz *biz.AdviceProactiveBiz
iterateBiz *biz.AdviceIterateBiz
followBiz *biz.AdviceFollowBiz
cron *cron.Cron
}
func NewAdviceTaskJob(
cfg *config.Config,
evaluateBiz *biz.AdviceEvaluateBiz,
strategyBiz *biz.AdviceStrategyBiz,
proactiveBiz *biz.AdviceProactiveBiz,
iterateBiz *biz.AdviceIterateBiz,
followBiz *biz.AdviceFollowBiz,
) *AdviceTaskJob {
return &AdviceTaskJob{
cfg: cfg,
evaluateBiz: evaluateBiz,
strategyBiz: strategyBiz,
proactiveBiz: proactiveBiz,
iterateBiz: iterateBiz,
followBiz: followBiz,
// SkipIfStillRunning:上一轮未完成时跳过本轮,避免任务堆积
cron: cron.New(cron.WithChain(cron.SkipIfStillRunning(cron.DefaultLogger))),
}
}
// Start 启动调度(非阻塞;服务退出时调用 Stop)
func (j *AdviceTaskJob) Start() {
// 1. 客户等级评估:cron 五段式「分 时 日 月 周」,每天 hour 点整执行
hour := j.cfg.Advicer.DailyEvaluateHour
if hour < 0 || hour > 23 {
hour = 0
}
spec := fmt.Sprintf("0 %d * * *", hour)
if _, err := j.cron.AddFunc(spec, func() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
defer cancel()
_, _ = j.evaluateBiz.EvaluateAll(ctx, &entitys.AdvicerClientEvaluateReq{})
}); err != nil {
// spec 由校验后的 hour 生成,正常不会失败
return
}
// 2. 熟客超时兜底:每 2 分钟扫描一次
if _, err := j.cron.AddFunc("*/2 * * * *", func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
_, _ = j.strategyBiz.RegularTimeoutScan(ctx, false)
}); err != nil {
return
}
// 3. 对话完成画像迭代:每 10 分钟扫描一次
if _, err := j.cron.AddFunc("*/10 * * * *", func() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Minute)
defer cancel()
_, _ = j.iterateBiz.Run(ctx, &entitys.AdvicerIterateRunReq{})
}); err != nil {
return
}
// 4. 主动触达(生日/活动):每小时整点扫描(内部校验活跃时段与开关)
if _, err := j.cron.AddFunc("0 * * * *", func() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
defer cancel()
_, _ = j.proactiveBiz.Scan(ctx, &entitys.AdvicerProactiveRunReq{})
}); err != nil {
return
}
// 5. 跟进线索到点执行:每分钟扫描到期约定(AI 提醒客户 / 线下转后台待办)
if j.followBiz != nil {
if _, err := j.cron.AddFunc("* * * * *", func() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
_, _ = j.followBiz.RunDue(ctx)
}); err != nil {
return
}
}
j.cron.Start()
}
// Stop 停止调度
func (j *AdviceTaskJob) Stop() {
if j.cron != nil {
j.cron.Stop()
}
}