1
This commit is contained in:
@@ -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_up,task_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,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user