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:
Neo
2026-04-06 00:03:48 +08:00
parent 70324d8542
commit 6f8f12314f
515 changed files with 76604 additions and 7456 deletions

View File

@@ -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