# Implementation Plan: P4 前置依赖修复

## Overview

6 个定点修复，修正 P4 核心业务层与 Spec 的实现偏差。T1/T2 已在之前修复中完成，仅需属性测试验证；T3/T4/T5/T6 需要代码变更。所有修改限于现有服务层内部逻辑，无新表、无新接口。

测试框架：Hypothesis（项目已有依赖），测试文件位于 `tests/` 目录。

## Tasks

- [x] 1. 验证 T1（任务列表）和 T2（召回检测器）已有实现 + 属性测试
  - [x] 1.1 编写 T1 属性测试：任务列表状态过滤、排序、abandon_reason 一致性
    - 创建 `tests/test_p52_task_list_properties.py`
    - 提取 `get_task_list` 的过滤和排序逻辑为纯函数（或 mock DB 层），用 Hypothesis 生成随机任务集合
    - **Property 1: 任务列表状态过滤** — 仅返回 active/abandoned，不含 completed/inactive
    - **Property 2: 任务列表排序正确性** — abandoned 排在 active 之后，active 内部按 is_pinned DESC, priority_score DESC NULLS LAST, created_at ASC
    - **Property 3: abandon_reason 与 status 一致性不变量** — active → null, abandoned → 非空字符串
    - **Validates: Requirements 1.1, 1.2, 1.3, 1.4**

  - [x] 1.2 编写 T2 属性测试：召回检测器仅完成 recall 类型任务
    - 在 `tests/test_p52_recall_detector_properties.py` 中编写
    - 用 Hypothesis 生成包含四种 task_type 的任务集合和服务记录
    - **Property 4: 召回检测器仅完成 recall 类型任务** — high_priority_recall/priority_recall 被完成，follow_up_visit/relationship_building 状态不变
    - **Validates: Requirements 2.1, 2.2, 2.3**

- [x] 2. Checkpoint — 验证 T1/T2 属性测试通过
  - Ensure all tests pass, ask the user if questions arise.

- [x] 3. 实现 T3：备注回溯重分类器冲突处理
  - [x] 3.1 修改 `apps/backend/app/services/note_reclassifier.py` 的 `run()` 方法
    - 在创建 follow_up_visit 任务前，查询同 (site_id, assistant_id, member_id) 是否已有 follow_up_visit 任务
    - 已有 `completed` → 跳过创建（回访完成语义已满足）
    - 已有 `active` → 旧任务标记 `inactive`，记录 `superseded` 历史，创建新任务
    - 不存在（或仅有 inactive/abandoned）→ 正常创建
    - 确保重复触发相同 payload 不产生唯一约束冲突
    - _Requirements: 3.1, 3.2, 3.3, 3.4_

  - [x] 3.2 编写 T3 属性测试：冲突处理三分支
    - 在 `tests/test_p52_note_reclassifier_properties.py` 中编写
    - **Property 5: 已完成回访任务阻止新建** — 存在 completed 的 follow_up_visit 时跳过创建
    - **Property 6: active 回访任务被顶替** — 旧任务 → inactive + superseded 历史，新任务创建
    - **Property 7: 无冲突时正常创建** — 不存在 follow_up_visit（或仅 inactive/abandoned）时正常创建
    - **Validates: Requirements 3.1, 3.2, 3.3, 3.4**

- [x] 4. 实现 T4：回访完成条件改为「有备注即完成」
  - [x] 4.1 修改 `apps/backend/app/services/note_service.py` 的 `create_note()` 方法
    - 当 `note_type == "follow_up"` 且 `task_id is not None` 时：
      - 保留 `ai_analyze_note()` 占位调用，返回值仅用于更新 `ai_score` 字段
      - 不论 `ai_score` 如何，有备注即标记关联的 active follow_up_visit 任务为 `completed`
      - 记录 `completed_by_note` 历史
    - _Requirements: 4.1, 4.2, 4.3_

  - [x] 4.2 修改 `apps/backend/app/services/note_reclassifier.py` 的 `run()` 方法（T4 部分）
    - 去掉 AI 评分判定逻辑（`ai_score >= 6` 条件）
    - 保留 `ai_analyze_note()` 占位调用
    - 找到备注（`note_id is not None`）→ 创建 `status='completed'` 的回访任务（回溯完成）
    - 未找到备注（`note_id is None`）→ 创建 `status='active'` 的回访任务（等待备注）
    - 注意：此步骤需与 3.1 的冲突处理逻辑协同，冲突检查在任务创建前执行
    - _Requirements: 4.4, 4.5_

  - [x] 4.3 编写 T4 属性测试：有备注即完成
    - 在 `tests/test_p52_note_service_properties.py` 中编写
    - **Property 8: 有备注即完成回访任务，不依赖 AI 评分** — 对任意 ai_score（None/0-5/6-10），提交备注后 active follow_up_visit 任务都标记 completed
    - **Property 9: 回溯有备注时创建 completed 回访任务** — note_reclassifier 找到备注 → status='completed'
    - **Property 10: 回溯无备注时创建 active 回访任务** — note_reclassifier 未找到备注 → status='active'
    - **Validates: Requirements 4.2, 4.3, 4.4, 4.5**

- [x] 5. Checkpoint — 验证 T3/T4 实现和测试通过
  - Ensure all tests pass, ask the user if questions arise.

- [x] 6. 实现 T5：trigger_scheduler last_run_at 事务安全
  - [x] 6.1 修改 `apps/backend/app/services/trigger_scheduler.py`
    - `fire_event()`：handler 接受可选 `conn` 参数，在 handler 最终 commit 前附带更新 `last_run_at`；handler 失败时整个事务回滚（last_run_at 不更新）
    - `check_scheduled_jobs()`：同理，将 `last_run_at` 和 `next_run_at` 更新纳入 handler 事务范围
    - 需同步修改 `recall_detector.run()` 和 `note_reclassifier.run()` 的签名，接受可选 `conn` 参数
    - _Requirements: 5.1, 5.2, 5.3_

  - [x] 6.2 编写 T5 属性测试：事务一致性
    - 在 `tests/test_p52_trigger_scheduler_properties.py` 中编写
    - **Property 11: 触发器 last_run_at 事务一致性** — handler 成功 → last_run_at 更新；handler 异常 → last_run_at 不变（整个事务回滚）
    - **Validates: Requirements 5.1, 5.2**

- [x] 7. 实现 T6：cron 默认值改为 07:00 + 迁移脚本
  - [x] 7.1 修改 `apps/backend/app/services/trigger_scheduler.py` 的 `_calculate_next_run()`
    - 将 `trigger_config.get("cron_expression", "0 4 * * *")` 改为 `"0 7 * * *"`
    - _Requirements: 6.2, 6.3_

  - [x] 7.2 创建迁移脚本 `db/zqyy_app/migrations/2026-03-15__p52_update_cron_0700.sql`
    - 幂等 UPDATE：`UPDATE biz.trigger_jobs SET trigger_config = jsonb_set(trigger_config, '{cron_expression}', '"0 7 * * *"') WHERE job_name = 'task_generator'`
    - 包含回滚注释
    - _Requirements: 6.1_

- [x] 8. Final checkpoint — 全部测试通过
  - Ensure all tests pass, ask the user if questions arise.
  - 运行 `pytest tests/test_p52_*.py -v` 验证所有 P5.2 属性测试通过

## Notes

- Tasks marked with `*` are optional and can be skipped for faster MVP
- T1/T2 已在之前修复中实现，任务 1 仅编写属性测试验证覆盖
- T3 和 T4 对 `note_reclassifier.py` 有交叉修改，任务 3.1 和 4.2 需协同实施
- T5 涉及 handler 签名变更，需同步修改 recall_detector 和 note_reclassifier
- T6 种子数据已是 `0 7 * * *`，迁移脚本为幂等更新确保生产环境一致
- 属性测试使用 Hypothesis，每个属性最少 100 次迭代
