feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更: - backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔 - admin-web: ETL 状态页、任务管理、调度配置、登录优化 - miniprogram: 看板页面、聊天集成、UI 组件、导航更新 - etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强 - tenant-admin: 项目初始化 - db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8) - packages/shared: 枚举和工具函数更新 - tools: 数据库工具、报表生成、健康检查 - docs: PRD/架构/部署/合约文档更新 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,8 @@ ETL 数据更新后,直连 ETL 库读取助教服务记录,
|
||||
import json
|
||||
import logging
|
||||
|
||||
from app.trace.decorators import trace_service
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -56,6 +58,7 @@ def _insert_history(
|
||||
)
|
||||
|
||||
|
||||
@trace_service(description_zh="执行维客检测", description_en="Run recall detection")
|
||||
def run(payload: dict | None = None, job_id: int | None = None) -> dict:
|
||||
"""
|
||||
召回完成检测主流程。
|
||||
@@ -178,6 +181,9 @@ def _process_site(conn, site_id: int, last_run_at) -> int:
|
||||
|
||||
# ── 4-7. 逐条服务记录匹配并处理 ──
|
||||
for assistant_id, member_id, service_time in service_records:
|
||||
# 散客过滤(member_id ≤ 0 不参与任务系统)
|
||||
if member_id is None or member_id <= 0:
|
||||
continue
|
||||
try:
|
||||
count = _process_service_record(
|
||||
conn, site_id, assistant_id, member_id, service_time
|
||||
@@ -203,7 +209,13 @@ def _process_service_record(
|
||||
service_time,
|
||||
) -> int:
|
||||
"""
|
||||
处理单条服务记录:匹配 active 任务并标记 completed。
|
||||
处理单条服务记录:匹配 active 任务并标记 completed + 生成回访任务。
|
||||
|
||||
CHANGE 2026-03-30 | 回访任务直接在此生成(不再依赖 note_reclassifier 事件链)。
|
||||
规则:
|
||||
- 有 active 召回任务 → 标记 completed,然后生成回访任务
|
||||
- 有 active 回访任务 → 关闭旧回访,生成新回访(重置 48h 倒计时)
|
||||
- 无任何 active 召回/回访 → 直接生成回访任务
|
||||
|
||||
每条服务记录独立事务,失败不影响其他。
|
||||
返回本次完成的任务数。
|
||||
@@ -213,7 +225,7 @@ def _process_service_record(
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("BEGIN")
|
||||
|
||||
# 查找匹配的 active 召回类任务(仅完成召回任务,回访/关系构建不在此处理)
|
||||
# ── 1. 查找匹配的 active 召回类任务 ──
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT id, task_type
|
||||
@@ -226,14 +238,12 @@ def _process_service_record(
|
||||
""",
|
||||
(site_id, assistant_id, member_id),
|
||||
)
|
||||
active_tasks = cur.fetchall()
|
||||
active_recall_tasks = cur.fetchall()
|
||||
|
||||
if not active_tasks:
|
||||
conn.commit()
|
||||
return 0
|
||||
has_active_recall = len(active_recall_tasks) > 0
|
||||
|
||||
# 将所有匹配的 active 任务标记为 completed
|
||||
for task_id, task_type in active_tasks:
|
||||
# 将所有匹配的 active 召回任务标记为 completed
|
||||
for task_id, task_type in active_recall_tasks:
|
||||
cur.execute(
|
||||
"""
|
||||
UPDATE biz.coach_tasks
|
||||
@@ -260,28 +270,82 @@ def _process_service_record(
|
||||
)
|
||||
completed += 1
|
||||
|
||||
conn.commit()
|
||||
# ── 2. 生成回访任务(CHANGE 2026-03-30) ──
|
||||
# 如果还有 active 召回任务(其他助教的),不生成回访
|
||||
# 注意:上面已经把当前助教的召回任务标记为 completed 了
|
||||
# 这里检查的是当前助教-客户对是否还有未完成的召回任务(不应该有了)
|
||||
|
||||
# ── 7. 触发 recall_completed 事件 ──
|
||||
# 延迟导入 fire_event 避免循环依赖
|
||||
try:
|
||||
from app.services.trigger_scheduler import fire_event
|
||||
# 关闭已有的 active 回访任务
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT id FROM biz.coach_tasks
|
||||
WHERE site_id = %s AND assistant_id = %s AND member_id = %s
|
||||
AND task_type = 'follow_up_visit' AND status = 'active'
|
||||
""",
|
||||
(site_id, assistant_id, member_id),
|
||||
)
|
||||
old_follow_ups = cur.fetchall()
|
||||
for (old_id,) in old_follow_ups:
|
||||
cur.execute(
|
||||
"""
|
||||
UPDATE biz.coach_tasks
|
||||
SET status = 'inactive', updated_at = NOW()
|
||||
WHERE id = %s
|
||||
""",
|
||||
(old_id,),
|
||||
)
|
||||
_insert_history(
|
||||
cur, old_id,
|
||||
action="superseded_by_new_visit",
|
||||
old_status="active", new_status="inactive",
|
||||
old_task_type="follow_up_visit", new_task_type="follow_up_visit",
|
||||
detail={"reason": "new_service_record", "service_time": str(service_time)},
|
||||
)
|
||||
|
||||
fire_event(
|
||||
"recall_completed",
|
||||
{
|
||||
"site_id": site_id,
|
||||
"assistant_id": assistant_id,
|
||||
"member_id": member_id,
|
||||
# 创建新的回访任务(48h 过期)
|
||||
from datetime import timedelta
|
||||
expires_at = service_time + timedelta(hours=48) if hasattr(service_time, '__add__') else None
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO biz.coach_tasks
|
||||
(site_id, assistant_id, member_id, task_type, status, expires_at, created_at, updated_at)
|
||||
VALUES (%s, %s, %s, 'follow_up_visit', 'active', %s, NOW(), NOW())
|
||||
RETURNING id
|
||||
""",
|
||||
(site_id, assistant_id, member_id, expires_at),
|
||||
)
|
||||
new_follow_up_id = cur.fetchone()[0]
|
||||
_insert_history(
|
||||
cur, new_follow_up_id,
|
||||
action="created",
|
||||
old_status=None, new_status="active",
|
||||
new_task_type="follow_up_visit",
|
||||
detail={
|
||||
"reason": "service_record_detected",
|
||||
"service_time": str(service_time),
|
||||
"had_recall": has_active_recall,
|
||||
},
|
||||
)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"触发 recall_completed 事件失败: site_id=%s, assistant_id=%s, member_id=%s",
|
||||
site_id,
|
||||
assistant_id,
|
||||
member_id,
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
|
||||
# ── 3. 触发 recall_completed 事件(仅当有召回任务被完成时) ──
|
||||
if has_active_recall:
|
||||
try:
|
||||
from app.services.trigger_scheduler import fire_event
|
||||
fire_event(
|
||||
"recall_completed",
|
||||
{
|
||||
"site_id": site_id,
|
||||
"assistant_id": assistant_id,
|
||||
"member_id": member_id,
|
||||
"service_time": str(service_time),
|
||||
},
|
||||
)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"触发 recall_completed 事件失败: site_id=%s, assistant_id=%s, member_id=%s",
|
||||
site_id, assistant_id, member_id,
|
||||
)
|
||||
|
||||
return completed
|
||||
|
||||
Reference in New Issue
Block a user