This commit is contained in:
Neo
2026-03-15 10:15:02 +08:00
parent 2dd217522c
commit 72bb11b34f
916 changed files with 65306 additions and 16102803 deletions

View File

@@ -2,9 +2,14 @@
"""
备注回溯重分类器Note Reclassifier
召回完成后,回溯检查是否有普通备注需重分类为回访备注。
查找 service_time 之后的第一条 normal 备注 → 更新为 follow_up →
触发 AI 应用 6 接口(占位)→ 根据 ai_score 生成 follow_up_visit 任务。
召回完成后,回溯检查是否有普通备注需重分类为回访备注,并创建回访任务
流程:
1. 查找 service_time 之后的第一条 normal 备注
2. 若找到 → 重分类为 follow_up任务状态 = completed回溯完成
3. 若未找到 → 任务状态 = active等待备注
4. 冲突检查:已有 completed → 跳过;已有 active → 顶替;否则正常创建
5. 保留 ai_analyze_note() 占位调用,返回值仅更新 ai_score 字段
由 trigger_jobs 中的 note_reclassify_backfill 配置驱动event: recall_completed
"""
@@ -62,21 +67,27 @@ def ai_analyze_note(note_id: int) -> int | None:
return None
def run(payload: dict | None = None) -> dict:
def run(payload: dict | None = None, job_id: int | None = None) -> dict:
"""
备注回溯主流程。
payload 包含: {site_id, assistant_id, member_id, service_time}
1. 查找 biz.notes 中该 (site_id, target_type='member', target_id=member_id)
service_time 之后提交的第一条 type='normal' 的备注
2. 将该备注 type 从 'normal' 更新为 'follow_up'
3. 触发 AI 应用 6 接口P5 实现,本 SPEC 仅定义触发接口):
- 调用 ai_analyze_note(note_id) → 返回 ai_score
4. 若 ai_score >= 6
- 生成 follow_up_visit 任务status='completed'(回溯完成)
5. 若 ai_score < 6
- 生成 follow_up_visit 任务status='active'(需助教重新备注)
流程:
1. 查找 service_time 之后的第一条 normal 备注 → note_id
2. 若 note_id 存在:重分类为 follow_uptask_status = 'completed'(回溯完成)
3. 若 note_id 不存在task_status = 'active'(等待备注)
4. 保留 ai_analyze_note() 占位调用,返回值仅更新 ai_score 字段
5. 冲突检查T3
- 已有 completed → 跳过创建
- 已有 active → 旧任务标记 inactive + superseded 历史,创建新任务
- 不存在(或仅 inactive/abandoned→ 正常创建
6. 创建 follow_up_visit 任务
参数:
payload: 事件载荷(由 trigger_scheduler 传入)
job_id: 触发器 job ID由 trigger_scheduler 传入),用于在最终事务中
更新 last_run_at保证 handler 数据变更与 last_run_at 原子提交
返回: {"reclassified_count": int, "tasks_created": int}
"""
@@ -119,84 +130,166 @@ def run(payload: dict | None = None) -> dict:
note_id = row[0]
conn.commit()
if note_id is None:
logger.info(
"未找到符合条件的 normal 备注: site_id=%s, member_id=%s",
site_id, member_id,
)
return {"reclassified_count": 0, "tasks_created": 0}
# ── 2. 将备注 type 从 'normal' 更新为 'follow_up' ──
with conn.cursor() as cur:
cur.execute("BEGIN")
cur.execute(
"""
UPDATE biz.notes
SET type = 'follow_up', updated_at = NOW()
WHERE id = %s AND type = 'normal'
""",
(note_id,),
)
conn.commit()
reclassified_count = 1
# ── 3. 触发 AI 应用 6 接口(占位,当前返回 None ──
ai_score = ai_analyze_note(note_id)
# ── 4/5. 根据 ai_score 生成 follow_up_visit 任务 ──
if ai_score is not None:
if ai_score >= 6:
# 回溯完成:生成 completed 任务
task_status = "completed"
else:
# 需助教重新备注:生成 active 任务
task_status = "active"
# ── 2. 根据是否找到备注确定任务状态T4 ──
if note_id is not None:
# 找到备注 → 重分类为 follow_up
with conn.cursor() as cur:
cur.execute("BEGIN")
cur.execute(
"""
INSERT INTO biz.coach_tasks
(site_id, assistant_id, member_id, task_type,
status, completed_at, completed_task_type)
VALUES (
%s, %s, %s, 'follow_up_visit',
%s,
CASE WHEN %s = 'completed' THEN NOW() ELSE NULL END,
CASE WHEN %s = 'completed' THEN 'follow_up_visit' ELSE NULL END
)
RETURNING id
UPDATE biz.notes
SET type = 'follow_up', updated_at = NOW()
WHERE id = %s AND type = 'normal'
""",
(
site_id, assistant_id, member_id,
task_status, task_status, task_status,
),
)
new_task_row = cur.fetchone()
new_task_id = new_task_row[0]
# 记录任务创建历史
_insert_history(
cur,
new_task_id,
action="created_by_reclassify",
old_status=None,
new_status=task_status,
old_task_type=None,
new_task_type="follow_up_visit",
detail={
"note_id": note_id,
"ai_score": ai_score,
"source": "note_reclassifier",
},
(note_id,),
)
conn.commit()
tasks_created = 1
reclassified_count = 1
# 保留 AI 占位调用,返回值仅用于更新 ai_score 字段
ai_score = ai_analyze_note(note_id)
if ai_score is not None:
with conn.cursor() as cur:
cur.execute("BEGIN")
cur.execute(
"""
UPDATE biz.notes
SET ai_score = %s, updated_at = NOW()
WHERE id = %s
""",
(ai_score, note_id),
)
conn.commit()
# 有备注 → 回溯完成
task_status = "completed"
else:
# AI 未就绪,跳过任务创建
# 未找到备注 → 等待备注
logger.info(
"AI 接口未就绪,跳过任务创建: note_id=%s", note_id
"未找到符合条件的 normal 备注: site_id=%s, member_id=%s",
site_id, member_id,
)
ai_score = None
task_status = "active"
# ── 3. 冲突检查T3查询已有 follow_up_visit 任务 ──
with conn.cursor() as cur:
cur.execute(
"""
SELECT id, status
FROM biz.coach_tasks
WHERE site_id = %s AND assistant_id = %s AND member_id = %s
AND task_type = 'follow_up_visit'
AND status IN ('active', 'completed')
ORDER BY CASE WHEN status = 'completed' THEN 0 ELSE 1 END
LIMIT 1
""",
(site_id, assistant_id, member_id),
)
existing = cur.fetchone()
conn.commit()
if existing:
existing_id, existing_status = existing
if existing_status == "completed":
# 已完成 → 跳过创建(回访完成语义已满足)
logger.info(
"已存在 completed 回访任务 id=%s,跳过创建: "
"site_id=%s, assistant_id=%s, member_id=%s",
existing_id, site_id, assistant_id, member_id,
)
# 事务安全T5即使跳过创建handler 仍成功,更新 last_run_at
if job_id is not None:
from app.services.trigger_scheduler import (
update_job_last_run_at,
)
with conn.cursor() as cur:
cur.execute("BEGIN")
update_job_last_run_at(cur, job_id)
conn.commit()
return {
"reclassified_count": reclassified_count,
"tasks_created": 0,
}
elif existing_status == "active":
# 顶替:旧任务 → inactive + superseded 历史
with conn.cursor() as cur:
cur.execute("BEGIN")
cur.execute(
"""
UPDATE biz.coach_tasks
SET status = 'inactive', updated_at = NOW()
WHERE id = %s AND status = 'active'
""",
(existing_id,),
)
_insert_history(
cur,
existing_id,
action="superseded",
old_status="active",
new_status="inactive",
detail={
"reason": "new_reclassify_task_supersedes",
"source": "note_reclassifier",
},
)
conn.commit()
logger.info(
"顶替旧 active 回访任务 id=%s → inactive: "
"site_id=%s, assistant_id=%s, member_id=%s",
existing_id, site_id, assistant_id, member_id,
)
# ── 4. 创建 follow_up_visit 任务 ──
with conn.cursor() as cur:
cur.execute("BEGIN")
cur.execute(
"""
INSERT INTO biz.coach_tasks
(site_id, assistant_id, member_id, task_type,
status, completed_at, completed_task_type)
VALUES (
%s, %s, %s, 'follow_up_visit',
%s,
CASE WHEN %s = 'completed' THEN NOW() ELSE NULL END,
CASE WHEN %s = 'completed' THEN 'follow_up_visit' ELSE NULL END
)
RETURNING id
""",
(
site_id, assistant_id, member_id,
task_status, task_status, task_status,
),
)
new_task_row = cur.fetchone()
new_task_id = new_task_row[0]
# 记录任务创建历史
_insert_history(
cur,
new_task_id,
action="created_by_reclassify",
old_status=None,
new_status=task_status,
old_task_type=None,
new_task_type="follow_up_visit",
detail={
"note_id": note_id,
"ai_score": ai_score,
"source": "note_reclassifier",
},
)
# 事务安全T5在最终 commit 前更新 last_run_at
if job_id is not None:
from app.services.trigger_scheduler import update_job_last_run_at
update_job_last_run_at(cur, job_id)
conn.commit()
tasks_created = 1
except Exception:
logger.exception(
@@ -215,3 +308,4 @@ def run(payload: dict | None = None) -> dict:
"reclassified_count": reclassified_count,
"tasks_created": tasks_created,
}