# 需求文档：P4 前置依赖修复

## 简介

P4 核心业务层已实现并通过属性测试，但对比 Spec 发现 6 处实现偏差（来源：`docs/reports/P4-spec-vs-implementation-gap-analysis.md`）。这些偏差会影响 P6（前端任务模块）的正常开发，必须前置修复。本需求覆盖 GAP-3、GAP-6、GAP-7、GAP-9 的代码修复，以及两项新增修正（回访完成条件、cron 时间）。

修复范围：6 个定点修复，无新表、无新接口，仅修改现有服务层逻辑和一条迁移脚本。

## 术语表

- **Task_Manager**: 任务管理服务（`task_manager.py`），负责任务列表查询和状态操作
- **Recall_Detector**: 召回完成检测器（`recall_detector.py`），ETL 数据更新后匹配服务记录完成召回任务
- **Note_Reclassifier**: 备注回溯重分类器（`note_reclassifier.py`），召回完成后回溯普通备注为回访备注并创建回访任务
- **Note_Service**: 备注服务（`note_service.py`），负责备注 CRUD 和回访任务自动完成
- **Trigger_Scheduler**: 触发器调度框架（`trigger_scheduler.py`），统一管理 cron/interval/event 三种触发方式
- **coach_tasks**: 助教任务表（`biz.coach_tasks`）
- **trigger_jobs**: 触发器配置表（`biz.trigger_jobs`）
- **active**: 任务有效状态
- **abandoned**: 任务已放弃状态
- **inactive**: 任务无效状态（被顶替）
- **completed**: 任务已完成状态
- **high_priority_recall**: 高优先召回任务类型
- **priority_recall**: 优先召回任务类型
- **follow_up_visit**: 客户回访任务类型
- **relationship_building**: 关系构建任务类型
- **abandon_reason**: 放弃原因字段
- **last_run_at**: 触发器上次运行时间戳
- **ai_analyze_note()**: AI 备注分析占位函数（P5 实现，当前返回 None）

## 需求

### 需求 1：任务列表返回已放弃任务

**用户故事：** 作为助教，我希望在任务列表中看到已放弃的任务及其放弃原因，以便执行「取消放弃」操作恢复任务。

#### 验收标准

1. WHEN 助教请求任务列表, THE Task_Manager SHALL 返回 active 和 abandoned 两种状态的任务
2. THE Task_Manager SHALL 按以下顺序排列任务：abandoned 排在所有 active 任务之后，active 任务内部按 is_pinned DESC、priority_score DESC NULLS LAST、created_at ASC 排序
3. THE Task_Manager SHALL 在返回结构中始终包含 abandon_reason 字段，active 状态任务的 abandon_reason 为 null，abandoned 状态任务的 abandon_reason 为非空字符串
4. WHEN 不存在 abandoned 任务时, THE Task_Manager SHALL 仅返回 active 任务，返回结构不变（abandon_reason 为 null）

### 需求 2：召回完成检测器过滤任务类型

**用户故事：** 作为系统运维人员，我希望召回完成检测器仅完成召回类任务，避免错误地将回访和关系构建任务标记为已完成。

#### 验收标准

1. WHEN ETL 检测到新增服务记录, THE Recall_Detector SHALL 仅匹配 task_type 为 high_priority_recall 或 priority_recall 的 active 任务进行完成标记
2. WHILE 存在 active 的 follow_up_visit 任务, THE Recall_Detector SHALL 跳过该任务，不执行完成标记
3. WHILE 存在 active 的 relationship_building 任务, THE Recall_Detector SHALL 跳过该任务，不执行完成标记

### 需求 3：备注回溯重分类器冲突处理

**用户故事：** 作为系统运维人员，我希望备注回溯重分类器在创建回访任务时正确处理冲突，避免唯一约束违反和重复任务。

#### 验收标准

1. WHEN Note_Reclassifier 准备创建 follow_up_visit 任务且同 (site_id, assistant_id, member_id) 已存在 completed 的 follow_up_visit 任务, THEN THE Note_Reclassifier SHALL 跳过创建（回访完成语义已满足）
2. WHEN Note_Reclassifier 准备创建 follow_up_visit 任务且同 (site_id, assistant_id, member_id) 已存在 active 的 follow_up_visit 任务, THEN THE Note_Reclassifier SHALL 将旧任务标记为 inactive 并记录变更历史，然后创建新的 follow_up_visit 任务（顶替方案）
3. WHEN Note_Reclassifier 准备创建 follow_up_visit 任务且同 (site_id, assistant_id, member_id) 不存在任何 follow_up_visit 任务, THE Note_Reclassifier SHALL 正常创建新任务
4. IF Note_Reclassifier 重复触发相同 payload, THEN THE Note_Reclassifier SHALL 不产生唯一约束冲突错误

### 需求 4：回访任务完成条件改为「有备注即完成」

**用户故事：** 作为助教，我希望为客户提交备注后对应的回访任务自动完成，无需等待 AI 评分。

#### 验收标准

1. WHEN 助教通过 Note_Service 为某客户创建备注, THE Note_Service SHALL 查询该客户是否存在 active 的 follow_up_visit 任务（通过 user_assistant_binding 获取 assistant_id，再匹配 site_id、assistant_id、member_id）
2. WHEN 存在 active 的 follow_up_visit 任务且助教提交了备注, THE Note_Service SHALL 将该任务标记为 completed 并记录变更历史，完成判定不依赖 ai_analyze_note() 的返回值
3. THE Note_Service SHALL 保留 ai_analyze_note() 占位调用（P5 接入时调用链不变），但 ai_analyze_note() 的返回值不作为回访任务完成的判定条件
4. WHEN Note_Reclassifier 回溯发现已有备注, THE Note_Reclassifier SHALL 直接创建 status='completed' 的回访任务（回溯完成），不依赖 AI 评分
5. WHEN Note_Reclassifier 回溯未发现备注, THE Note_Reclassifier SHALL 创建 status='active' 的回访任务（等待助教提交备注）

### 需求 5：trigger_scheduler last_run_at 事务安全

**用户故事：** 作为系统运维人员，我希望触发器的 last_run_at 更新与 handler 执行在同一事务中，避免 handler 成功但 last_run_at 更新失败导致重复处理。

#### 验收标准

1. WHEN Trigger_Scheduler 执行 event 类型触发器（fire_event）, THE Trigger_Scheduler SHALL 将 last_run_at 更新纳入 handler 执行的同一事务范围，handler 成功与 last_run_at 更新要么一起提交要么一起回滚
2. WHEN Trigger_Scheduler 执行 cron/interval 类型触发器（check_scheduled_jobs）, THE Trigger_Scheduler SHALL 将 last_run_at 和 next_run_at 更新纳入 handler 执行的同一事务范围
3. IF handler 执行成功但事务提交失败, THEN THE Trigger_Scheduler SHALL 回滚整个事务（包括 handler 的数据变更和 last_run_at 更新），下次重跑时 handler 的幂等性保证不会产生副作用

### 需求 6：任务生成器 cron 时间改为 07:00

**用户故事：** 作为运营人员，我希望任务生成器在每天早上 7 点运行，与门店营业节奏匹配。

#### 验收标准

1. THE 迁移脚本 SHALL 将 trigger_jobs 表中 task_generator 的 cron_expression 从 `0 4 * * *` 更新为 `0 7 * * *`
2. THE Trigger_Scheduler SHALL 在 _calculate_next_run() 中使用 `0 7 * * *` 作为 cron 默认值
3. WHEN task_generator 触发器下次运行时间被计算, THE Trigger_Scheduler SHALL 基于 `0 7 * * *` 计算正确的 next_run_at
