微信小程序页面迁移校验之前 P5任务处理之前

This commit is contained in:
Neo
2026-03-09 01:19:21 +08:00
parent 263bf96035
commit 6e20987d2f
1112 changed files with 153824 additions and 219694 deletions

View File

@@ -0,0 +1,266 @@
# -*- coding: utf-8 -*-
"""
召回完成检测器Recall Completion Detector
ETL 数据更新后,通过 FDW 读取助教服务记录,
匹配活跃任务标记为 completed记录 completed_at 和 completed_task_type 快照,
触发 recall_completed 事件通知备注回溯重分类器。
由 trigger_jobs 中的 recall_completion_check 配置驱动event: etl_data_updated
"""
import json
import logging
logger = logging.getLogger(__name__)
def _get_connection():
"""延迟导入 get_connection避免模块级导入失败。"""
from app.database import get_connection
return get_connection()
def _insert_history(
cur,
task_id: int,
action: str,
old_status: str | None = None,
new_status: str | None = None,
old_task_type: str | None = None,
new_task_type: str | None = None,
detail: dict | None = None,
) -> None:
"""在 coach_task_history 中记录变更。"""
cur.execute(
"""
INSERT INTO biz.coach_task_history
(task_id, action, old_status, new_status,
old_task_type, new_task_type, detail)
VALUES (%s, %s, %s, %s, %s, %s, %s)
""",
(
task_id,
action,
old_status,
new_status,
old_task_type,
new_task_type,
json.dumps(detail) if detail else None,
),
)
def run(payload: dict | None = None) -> dict:
"""
召回完成检测主流程。
1. 从 trigger_jobs 读取 last_run_at 作为增量过滤基准
2. 获取所有 distinct site_id从 active 任务中)
3. 对每个 site_idSET LOCAL app.current_site_id 后
通过 FDW 读取 v_dwd_assistant_service_log 中 service_time > last_run_at 的新增服务记录
4. 对每条服务记录,查找 biz.coach_tasks 中匹配的
(site_id, assistant_id, member_id) 且 status='active' 的任务
5. 将匹配任务标记为 completed
- status = 'completed'
- completed_at = 服务时间
- completed_task_type = 当前 task_type快照
6. 记录 coach_task_history
7. 触发 fire_event('recall_completed', {site_id, assistant_id, member_id, service_time})
返回: {"completed_count": int}
"""
completed_count = 0
conn = _get_connection()
try:
# ── 1. 读取 last_run_at ──
with conn.cursor() as cur:
cur.execute(
"""
SELECT last_run_at
FROM biz.trigger_jobs
WHERE job_name = 'recall_completion_check'
"""
)
row = cur.fetchone()
last_run_at = row[0] if row else None
conn.commit()
# ── 2. 获取所有有 active 任务的 distinct site_id ──
with conn.cursor() as cur:
cur.execute(
"""
SELECT DISTINCT site_id
FROM biz.coach_tasks
WHERE status = 'active'
"""
)
site_ids = [r[0] for r in cur.fetchall()]
conn.commit()
# ── 3. 逐 site_id 读取新增服务记录 ──
for site_id in site_ids:
try:
count = _process_site(conn, site_id, last_run_at)
completed_count += count
except Exception:
logger.exception(
"处理门店召回检测失败: site_id=%s", site_id
)
conn.rollback()
finally:
conn.close()
logger.info("召回完成检测完成: completed_count=%d", completed_count)
return {"completed_count": completed_count}
def _process_site(conn, site_id: int, last_run_at) -> int:
"""
处理单个门店的召回完成检测。
通过 FDW 读取新增服务记录,匹配 active 任务并标记 completed。
返回本门店完成的任务数。
"""
completed = 0
# 通过 FDW 读取新增服务记录(需要 SET LOCAL 启用 RLS
with conn.cursor() as cur:
cur.execute("BEGIN")
cur.execute(
"SET LOCAL app.current_site_id = %s", (str(site_id),)
)
if last_run_at is not None:
cur.execute(
"""
SELECT DISTINCT assistant_id, member_id, service_time
FROM fdw_etl.v_dwd_assistant_service_log
WHERE service_time > %s
ORDER BY service_time ASC
""",
(last_run_at,),
)
else:
# 首次运行,读取所有服务记录
cur.execute(
"""
SELECT DISTINCT assistant_id, member_id, service_time
FROM fdw_etl.v_dwd_assistant_service_log
ORDER BY service_time ASC
"""
)
service_records = cur.fetchall()
conn.commit()
# ── 4-7. 逐条服务记录匹配并处理 ──
for assistant_id, member_id, service_time in service_records:
try:
count = _process_service_record(
conn, site_id, assistant_id, member_id, service_time
)
completed += count
except Exception:
logger.exception(
"处理服务记录失败: site_id=%s, assistant_id=%s, member_id=%s",
site_id,
assistant_id,
member_id,
)
conn.rollback()
return completed
def _process_service_record(
conn,
site_id: int,
assistant_id: int,
member_id: int,
service_time,
) -> int:
"""
处理单条服务记录:匹配 active 任务并标记 completed。
每条服务记录独立事务,失败不影响其他。
返回本次完成的任务数。
"""
completed = 0
with conn.cursor() as cur:
cur.execute("BEGIN")
# 查找匹配的 active 任务
cur.execute(
"""
SELECT id, task_type
FROM biz.coach_tasks
WHERE site_id = %s
AND assistant_id = %s
AND member_id = %s
AND status = 'active'
""",
(site_id, assistant_id, member_id),
)
active_tasks = cur.fetchall()
if not active_tasks:
conn.commit()
return 0
# 将所有匹配的 active 任务标记为 completed
for task_id, task_type in active_tasks:
cur.execute(
"""
UPDATE biz.coach_tasks
SET status = 'completed',
completed_at = %s,
completed_task_type = %s,
updated_at = NOW()
WHERE id = %s AND status = 'active'
""",
(service_time, task_type, task_id),
)
_insert_history(
cur,
task_id,
action="completed",
old_status="active",
new_status="completed",
old_task_type=task_type,
new_task_type=task_type,
detail={
"service_time": str(service_time),
"completed_task_type": task_type,
},
)
completed += 1
conn.commit()
# ── 7. 触发 recall_completed 事件 ──
# 延迟导入 fire_event 避免循环依赖
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