# 实现计划:小程序核心业务模块(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)` 占位函数(返回 None,P5 实现后替换) - _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 → completed,ai_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 13(3 个测试)通过 - 验证种子数据完整性: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 隔离