微信小程序页面迁移校验之前 P5任务处理之前

This commit is contained in:
Neo
2026-03-09 01:19:21 +08:00
parent 263bf96035
commit 6e20987d2f
1112 changed files with 153824 additions and 219694 deletions

View File

@@ -0,0 +1,239 @@
# 实现计划小程序核心业务模块miniapp-core-business
## 概述
基于已批准的需求和设计文档,将小程序核心业务模块拆分为增量式编码任务。按照"DDL 建表 → 触发器调度框架 → 任务生成器 → 任务管理 → 有效期轮询 → 召回检测 → 备注系统 → 路由集成"的顺序实现。后端使用 Python + FastAPI数据库使用 PostgreSQL 纯 SQL属性测试使用 hypothesis。所有数据库操作在测试库 `test_zqyy_app` 中进行。
## 任务
- [x] 1. 创建业务数据表和种子数据
- [x] 1.1 创建迁移脚本 `db/zqyy_app/migrations/YYYY-MM-DD__p4_create_biz_tables.sql`
-`biz` Schema 下创建 `coach_tasks``coach_task_history``notes``trigger_jobs` 共 4 张表
- 包含所有字段定义、约束、CHECK 约束(评分 1-5、外键coach_task_history → coach_tasks、notes → coach_tasks、coach_tasks → coach_tasks 自引用)
- 创建部分唯一索引 `idx_coach_tasks_site_assistant_member_type`(仅 status='active'
- 创建查询索引 `idx_coach_tasks_assistant_status``idx_notes_target`
- 使用 `IF NOT EXISTS` 幂等语法
- 包含回滚语句(注释形式)
- _Requirements: 1.1-1.9_
- [x] 1.2 创建种子数据脚本 `db/zqyy_app/migrations/YYYY-MM-DD__p4_seed_trigger_jobs.sql`
- 插入 4 条触发器配置:`task_generator`cron, 0 4 * * *)、`task_expiry_check`interval, 3600s`recall_completion_check`event, etl_data_updated`note_reclassify_backfill`event, recall_completed
- 使用 `ON CONFLICT (job_name) DO NOTHING` 幂等语法
- _Requirements: 2.1-2.5_
- [x] 1.3 在测试库执行迁移脚本并验证
-`test_zqyy_app` 中执行建表脚本和种子数据脚本
- 验证幂等性:连续执行两次无错误
- 验证表结构、约束、索引正确
- 验证种子数据完整4 条触发器配置)
- _Requirements: 11.1-11.5, 12.1_
- [x] 1.4 更新数据库手册和 DDL 基线
- 创建 `docs/database/BD_Manual_biz_tables.md`,包含变更说明、兼容性影响、回滚策略、验证 SQL至少 3 条)
- 运行 `python scripts/ops/gen_consolidated_ddl.py` 刷新 DDL 基线
- 在数据库手册中记录种子数据内容(触发器配置)
- _Requirements: 12.2, 12.3, 12.4_
- [x] 1.5 编写迁移脚本幂等性属性测试
- **Property 13: 迁移脚本幂等性**
- 对 DDL 脚本和种子数据脚本连续执行两次,验证第二次执行无错误且数据库状态不变
- **Validates: Requirements 1.8, 2.5, 11.4, 11.5**
- [x] 2. 检查点 - 确保 DDL 和种子数据正确
- 确保迁移脚本在测试库中执行成功,幂等性验证通过,如有问题请向用户确认。
- [x] 3. 实现 Pydantic 模型和纯函数核心逻辑
- [x] 3.1 创建 Pydantic 模型 `apps/backend/app/schemas/xcx_tasks.py`
- 定义 `TaskListItem`(含 member_name、member_phone、rs_score、heart_icon
- 定义 `AbandonRequest`reason 必填min_length=1
- _Requirements: 8.1, 8.2, 8.4, 8.7_
- [x] 3.2 创建 Pydantic 模型 `apps/backend/app/schemas/xcx_notes.py`
- 定义 `NoteCreateRequest`(含 target_type、target_id、content、task_id、rating_service_willingness、rating_revisit_likelihood评分 ge=1 le=5
- 定义 `NoteOut`(含 type、content、评分、ai_score、ai_analysis
- _Requirements: 9.1, 9.8_
- [x] 3.3 创建任务生成器核心纯函数 `apps/backend/app/services/task_generator.py`
- 定义 `TaskPriority` 枚举、`TASK_TYPE_PRIORITY` 映射
- 定义 `IndexData` 数据类
- 实现 `determine_task_type(index_data)` 纯函数:根据 WBI/NCI/RS 指数确定任务类型
- 实现 `should_replace_task(existing_type, new_type)` 纯函数:判断是否应替换现有任务
- 实现 `compute_heart_icon(rs_score)` 纯函数:根据 RS 指数计算爱心 icon 档位
- _Requirements: 3.1-3.5, 3.8, 8.2_
- [x] 3.4 编写任务类型确定正确性属性测试
- **Property 1: 任务类型确定正确性**
- 生成随机 WBI/NCI/RS 值Decimal, 0-10, 2 位小数),验证 `determine_task_type()` 返回值符合优先级规则
- **Validates: Requirements 3.1, 3.2, 3.3, 3.5**
- [x] 3.5 编写星星评分范围约束属性测试
- **Property 9: 星星评分范围约束**
- 生成随机整数(-100 到 100验证 Pydantic 模型对 1-5 范围外的值拒绝ValidationError
- **Validates: Requirements 9.8, 14.5**
- [x] 3.6 编写爱心 icon 档位计算属性测试
- **Property 11: 爱心 icon 档位计算**
- 生成随机 RS 值Decimal, 0-10, 1 位小数),验证 `compute_heart_icon()` 返回正确 icon
- **Validates: Requirements 8.2**
- [x] 4. 实现触发器调度框架
- [x] 4.1 创建 `apps/backend/app/services/trigger_scheduler.py`
- 实现 `_JOB_REGISTRY` 注册表和 `register_job(job_type, handler)` 函数
- 实现 `fire_event(event_name, payload)` 方法:查找 event 类型触发器并执行
- 实现 `check_scheduled_jobs()` 方法:检查 cron/interval 到期 job 并执行
- 实现 `_calculate_next_run(trigger_condition, trigger_config)` 方法:计算下次运行时间
- 每个 job 独立事务,失败不影响其他触发器
- _Requirements: 10.1-10.7_
- [x] 4.2 编写触发器 next_run_at 计算属性测试
- **Property 12: 触发器 next_run_at 计算**
- 生成随机 cron/interval 配置和当前时间,验证 cron 类型 next_run_at > 当前时间interval 类型 next_run_at = 当前时间 + interval_seconds
- **Validates: Requirements 10.1, 10.2**
- [x] 5. 实现任务生成器完整流程
- [x] 5.1 实现 `TaskGenerator.run()` 主流程
- 通过 `auth.user_assistant_binding` 获取所有已绑定助教
- 对每个助教,通过 FDW 读取 WBI/NCI/RS 指数(`SET LOCAL app.current_site_id`
- 调用 `determine_task_type()` 确定任务类型
- 检查已存在的 active 任务:相同 task_type → 跳过;不同 task_type → 关闭旧任务 + 创建新任务 + 记录 history
- 处理 `follow_up_visit` 的 48 小时滞留机制expires_at 填充)
- 更新 `trigger_jobs` 时间戳
- _Requirements: 3.1-3.10, 4.1-4.5, 5.1-5.4_
- [x] 5.2 编写活跃任务唯一性不变量属性测试
- **Property 2: 活跃任务唯一性不变量**
- 生成随机 (site_id, assistant_id, member_id, task_type) 组合,模拟插入操作,验证 active 任务最多一条
- **Validates: Requirements 1.5, 3.6, 14.1**
- [x] 5.3 编写任务类型变更状态机属性测试
- **Property 3: 任务类型变更状态机**
- 生成随机现有任务 + 新任务类型,执行变更,验证旧任务 inactive + 新任务 active + history 记录
- **Validates: Requirements 3.7, 5.1, 5.4, 14.2**
- [x] 5.4 编写 48 小时滞留机制属性测试
- **Property 4: 48 小时滞留机制**
- 生成随机 follow_up_visit 任务 + 时间偏移,验证 expires_at 填充和过期逻辑
- **Validates: Requirements 4.1, 4.2, 4.3, 4.4, 14.3**
- [x] 6. 检查点 - 确保任务生成器测试通过
- 运行属性测试:`cd C:\NeoZQYY && pytest tests/test_core_business_properties.py -v -k "property_1 or property_2 or property_3 or property_4"`
- 确保所有属性测试通过,如有问题请向用户确认。
- [x] 7. 实现任务管理服务
- [x] 7.1 创建 `apps/backend/app/services/task_manager.py`
- 实现 `get_task_list(user_id, site_id)` 异步方法:查询活跃任务 + FDW 读取客户信息和 RS 指数 + 爱心 icon 计算 + 排序
- 实现 `pin_task(task_id, user_id, site_id)` 异步方法:验证归属 + 设置 is_pinned=TRUE + 记录 history
- 实现 `unpin_task(task_id, user_id, site_id)` 异步方法:验证归属 + 设置 is_pinned=FALSE
- 实现 `abandon_task(task_id, user_id, site_id, reason)` 异步方法:验证 reason 非空 + 设置 abandoned + 记录 history
- 实现 `cancel_abandon(task_id, user_id, site_id)` 异步方法:恢复 active + 清空 abandon_reason + 记录 history
- 实现 `_record_history()` 内部方法
- _Requirements: 8.1-8.8_
- [x] 7.2 编写放弃与取消放弃往返属性测试
- **Property 5: 放弃与取消放弃往返**
- 生成随机 active 任务 + 非空放弃原因,执行放弃→取消放弃,验证状态恢复;空原因应返回 422
- **Validates: Requirements 8.4, 8.6, 8.7, 14.4**
- [x] 7.3 编写任务列表排序正确性属性测试
- **Property 10: 任务列表排序正确性**
- 生成随机任务列表(不同 is_pinned/priority_score/created_at验证排序为 is_pinned DESC, priority_score DESC, created_at ASC
- **Validates: Requirements 8.1**
- [x] 7.4 编写状态变更历史完整性属性测试
- **Property 15: 状态变更历史完整性**
- 生成随机状态变更操作序列(置顶/放弃/取消放弃),验证 history 记录数量和内容正确
- **Validates: Requirements 5.4, 8.3**
- [x] 8. 实现有效期轮询器
- [x] 8.1 创建 `apps/backend/app/services/task_expiry.py`
- 实现 `run()` 方法:查询 expires_at 不为 NULL 且已过期的 active 任务,标记为 inactive记录 history
- _Requirements: 4.3, 4.5_
- [x] 9. 实现召回完成检测器
- [x] 9.1 创建 `apps/backend/app/services/recall_detector.py`
- 实现 `run(payload)` 方法:通过 FDW 读取新增服务记录,匹配 active 任务标记 completed记录 completed_at 和 completed_task_type 快照,触发 `recall_completed` 事件
- _Requirements: 6.1-6.5_
- [x] 9.2 编写召回完成检测与类型快照属性测试
- **Property 6: 召回完成检测与类型快照**
- 生成随机 active 任务 + 服务记录,执行完成检测,验证 completed_task_type 记录了完成时的 task_type 快照
- **Validates: Requirements 6.2, 6.3, 14.6**
- [x] 10. 检查点 - 确保任务管理和召回检测测试通过
- 运行属性测试:`cd C:\NeoZQYY && pytest tests/test_core_business_properties.py -v -k "property_5 or property_6 or property_10 or property_15"`
- 确保所有属性测试通过,如有问题请向用户确认。
- [-] 11. 实现备注系统
- [x] 11.1 创建备注服务 `apps/backend/app/services/note_service.py`
- 实现 `create_note()` 异步方法:验证评分范围 + 确定 note type关联 follow_up_visit 任务 → follow_up否则 normal+ INSERT + 触发 AI 应用 6 接口(占位)+ 若 ai_score >= 6 标记任务 completed
- 实现 `get_notes()` 异步方法:按 created_at DESC 排序,包含评分和 AI 评分
- 实现 `delete_note()` 异步方法:验证归属后硬删除
- _Requirements: 9.1-9.9_
- [x] 11.2 创建备注回溯重分类器 `apps/backend/app/services/note_reclassifier.py`
- 实现 `run(payload)` 方法:查找 service_time 之后的第一条 normal 备注 → 更新为 follow_up → 触发 AI 应用 6 接口(占位)→ 根据 ai_score 生成 follow_up_visit 任务
- 实现 `ai_analyze_note(note_id)` 占位函数(返回 NoneP5 实现后替换)
- _Requirements: 7.1-7.5_
- [x] 11.3 编写备注回溯重分类属性测试
- **Property 7: 备注回溯重分类**
- 生成随机备注列表 + service_time执行回溯验证符合条件的 normal 备注 type 变为 follow_up
- **Validates: Requirements 7.1, 7.2, 14.7**
- [x] 11.4 编写备注类型自动设置属性测试
- **Property 8: 备注类型自动设置**
- 生成随机 task_type + 备注创建,验证关联 follow_up_visit → type=follow_up其他 → type=normal
- **Validates: Requirements 9.2, 9.3**
- [x] 11.5 编写 AI 评分驱动的任务完成判定属性测试
- **Property 14: AI 评分驱动的任务完成判定**
- 生成随机 ai_score + 任务状态,验证 ai_score >= 6 且 active → completedai_score < 6 → 保持 active
- **Validates: Requirements 7.4, 7.5, 9.5**
- [x] 12. 实现 API 路由层
- [x] 12.1 创建小程序任务路由 `apps/backend/app/routers/xcx_tasks.py`
- 实现 `GET /api/xcx/tasks`获取任务列表require_approved
- 实现 `POST /api/xcx/tasks/{id}/pin`:置顶任务
- 实现 `POST /api/xcx/tasks/{id}/unpin`:取消置顶
- 实现 `POST /api/xcx/tasks/{id}/abandon`放弃任务AbandonRequest 校验)
- 实现 `POST /api/xcx/tasks/{id}/cancel-abandon`:取消放弃
- _Requirements: 8.1-8.8_
- [x] 12.2 创建小程序备注路由 `apps/backend/app/routers/xcx_notes.py`
- 实现 `POST /api/xcx/notes`创建备注NoteCreateRequest 校验)
- 实现 `GET /api/xcx/notes`查询备注列表query: target_type, target_id
- 实现 `DELETE /api/xcx/notes/{id}`:删除备注
- _Requirements: 9.1-9.9_
- [x] 12.3 在 `apps/backend/app/main.py` 中注册新路由
- 注册 `xcx_tasks``xcx_notes` 路由
- 验证无路由冲突
- _Requirements: 全部_
- [x] 12.4 注册触发器 job handler
- 在应用启动时调用 `register_job()` 注册 `task_generator``task_expiry_check``recall_completion_check``note_reclassify_backfill` 四个 handler
- _Requirements: 10.1-10.6_
- [x] 13. 检查点 - 确保所有测试通过
- 运行属性测试:`cd C:\NeoZQYY && pytest tests/test_core_business_properties.py -v`
- 26/26 全部通过16.81s
- [x] 14. 最终检查点 - 全量验证
- 运行全部属性测试26/26 通过16.81s
- 验证迁移脚本幂等性Property 133 个测试)通过
- 验证种子数据完整性4 条触发器配置全部存在
- 验证表结构coach_tasks / coach_task_history / notes / trigger_jobs 全部存在
- 验证部分唯一索引idx_coach_tasks_site_assistant_member_type 存在
## 备注
- 标记 `*` 的子任务为可选(属性测试),可跳过以加速 MVP
- 每个任务引用了具体的需求编号,确保可追溯
- 检查点确保增量验证
- 属性测试验证通用正确性属性hypothesis最少 200 次迭代)
- 所有数据库操作在测试库 `test_zqyy_app` 进行
- 迁移脚本放在 `db/zqyy_app/migrations/` 目录
- 属性测试放在 `tests/test_core_business_properties.py`Monorepo 级)
- AI 应用 6 接口为占位实现(返回 None由 P5 AI 集成层替换
- 维客线索功能由独立模块 `routers/member_retention_clue.py` 处理,不在本 SPEC 范围内
- FDW 查询需在事务中 `SET LOCAL app.current_site_id` 设置 RLS 隔离