Files

240 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 实现计划小程序核心业务模块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 隔离