# P5.2：P4 前置依赖修复 — p4-prerequisite-fixes

> 优先级：P5.2（P6 前置，必须在 P6 开发前完成）
> 预估工作量：小（6 个定点修复，无新表/新接口）
> 依赖：P4 已完成
> 来源：P4 Spec vs 实现差异分析（`docs/reports/P4-spec-vs-implementation-gap-analysis.md`）

---

## 背景

P4 核心业务层已实现并通过属性测试，但对比最新 Spec 发现 6 处实现偏差。
这些偏差会影响 P6（前端任务模块）的正常开发，必须前置修复。

参考文档：
- 差异分析：`docs/reports/P4-spec-vs-implementation-gap-analysis.md`
- 生命周期全景：`docs/reports/P4-task-lifecycle-panorama.md`

---

## 需求（Requirements）

### 验收标准

- AC1：任务列表 API 返回 active + abandoned 两种状态，abandoned 排在最后且包含 abandon_reason
- AC2：召回完成检测器仅完成 high_priority_recall / priority_recall 两种任务类型
- AC3：备注回溯重分类器创建回访任务时，已有 active 回访 → 顶替（旧→无效），已完成 → 跳过
- AC4：回访任务完成条件为「助教为该客户提交备注」，不依赖 AI 评分
- AC5：trigger_scheduler 的 last_run_at 更新具备事务安全性
- AC6：任务生成器 cron 时间为 07:00

---

## 任务清单

### T1：任务列表返回已放弃任务（GAP-3）

文件：`apps/backend/app/services/task_manager.py` → `get_task_list()`

修改内容：
1. SQL WHERE 条件：`status = 'active'` → `status IN ('active', 'abandoned')`
2. SQL ORDER BY 增加：`CASE WHEN status = 'abandoned' THEN 1 ELSE 0 END` 排在 `is_pinned DESC` 之前
3. SELECT 增加 `abandon_reason` 字段
4. 返回结构增加 `abandon_reason` 字段

验证：
- 有 abandoned 任务时，列表末尾出现灰化任务且包含 abandon_reason
- 有 active + pinned 任务时，排序为：置顶 → 一般 → 已放弃

---

### T2：召回完成检测器过滤任务类型（GAP-6）

文件：`apps/backend/app/services/recall_detector.py` → `_process_service_record()`

修改内容：
1. SQL 增加 `AND task_type IN ('high_priority_recall', 'priority_recall')`

验证：
- follow_up_visit 和 relationship_building 类型的 active 任务不会被召回检测器完成
- high_priority_recall 和 priority_recall 正常完成

---

### T3：备注回溯重分类器冲突处理（GAP-7）

文件：`apps/backend/app/services/note_reclassifier.py` → `run()`

修改内容：
1. 创建回访任务前，先查询是否已有同 (site_id, assistant_id, member_id) 的 follow_up_visit 任务
2. 已有 completed 的回访任务 → 跳过创建（回访完成 +1 的语义已满足）
3. 已有 active 的回访任务 → 旧任务标记 inactive，创建新任务
4. 不存在 → 正常创建

验证：
- 重复触发不会产生唯一约束冲突
- 已完成的回访任务不会被覆盖

---

### T4：回访完成条件改为「有备注即完成」（新-1）

文件：
- `apps/backend/app/services/note_service.py` → `create_note()`
- `apps/backend/app/services/note_reclassifier.py` → `run()`

修改内容：
1. `note_service.create_note()` 增加逻辑：创建备注后，通过 user_assistant_binding 查 assistant_id，
   再查该 (site_id, assistant_id, member_id) 是否有 active 的 follow_up_visit 任务，有则标记 completed
2. `note_reclassifier.py`：去掉 AI 评分判定逻辑，有备注 → 回访任务直接标记 completed；
   无备注 → 创建 active 回访任务（等待备注）

验证：
- 助教提交备注后，对应客户的 active 回访任务自动完成
- 不依赖 AI 评分返回值

---

### T5：trigger_scheduler last_run_at 事务安全（GAP-9）

文件：`apps/backend/app/services/trigger_scheduler.py`

修改内容：
1. `fire_event()` 和 `check_scheduled_jobs()` 中，将 last_run_at 更新纳入 handler 的事务范围，
   或改为执行前更新（at-most-once 语义，配合 handler 幂等性）

验证：
- handler 成功但 commit 失败的场景不会导致重复处理（或重复处理是幂等的）

---

### T6：任务生成器 cron 改为 07:00（新-2）

文件：
- `db/zqyy_app/migrations/2026-03-15__p52_update_cron_0700.sql`（新建迁移脚本）
- `apps/backend/app/services/trigger_scheduler.py` → `_calculate_next_run()` 默认值

修改内容：
1. 新建迁移脚本：`UPDATE biz.trigger_jobs SET trigger_config = '{"cron_expression": "0 7 * * *"}' WHERE job_name = 'task_generator'`
2. `_calculate_next_run()` 中 cron 默认值从 `"0 4 * * *"` 改为 `"0 7 * * *"`

验证：
- trigger_jobs 表中 task_generator 的 cron_expression 为 `0 7 * * *`
- 下次运行时间计算正确

---

## 执行顺序

```
批次 1（无依赖，可并行）：T1、T2、T6
批次 2（依赖 T2 确认）：T3、T4
批次 3（依赖 T3/T4 验证）：T5
```

---

## 不在本 SPEC 范围

| 项目 | 归属 | 说明 |
|------|------|------|
| GAP-5 备注分页 | P6-T6 | 已写入 P6 Spec |
| GAP-8 问问 AI | P5 | P5 AI 集成层已覆盖 |
| GAP-2 cancel_abandon 重置 is_pinned | 已修复 | 2026-03-14 |
| GAP-4 completed_task_type 快照 | 无差异 | 实现正确 |
