Files
Neo-ZQYY/.kiro/specs/rns1-customer-coach-api/tasks.md

272 lines
19 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
# Implementation Plan: RNS1.2 客户与助教接口
## Overview
基于 design.md 架构,按 T2-1 ~ T2-6 任务结构增量实现 CUST-1、CUST-2、COACH-1 三个接口。先扩展 FDW 查询层,再逐步构建 service → router → 集成测试 → 属性测试。所有金额使用 `items_sum` 口径,助教费用使用 `assistant_pd_money` + `assistant_cx_money` 拆分,会员信息通过 `member_id` JOIN `v_dim_member`
## Tasks
- [x] 1. Pydantic Schema 定义与项目结构搭建
- [x] 1.1 创建 `apps/backend/app/schemas/xcx_customers.py`,定义 CUST-1 和 CUST-2 所有响应模型
- `CustomerDetailResponse``CustomerRecordsResponse` 及所有嵌套模型(`AiInsight``AiStrategy``MetricItem``CoachTask``FavoriteCoach``CoachServiceItem``ConsumptionRecord``RetentionClue``CustomerNote``ServiceRecordItem`
- 所有模型继承 `CamelModel`,确保 camelCase 序列化
- _Requirements: 1.1-1.7, 2.1-2.3, 3.1-3.2, 4.1-4.6, 5.1-5.4, 6.1-6.4, 7.1-7.9_
- [x] 1.2 创建 `apps/backend/app/schemas/xcx_coaches.py`,定义 COACH-1 所有响应模型
- `CoachDetailResponse` 及所有嵌套模型(`PerformanceMetrics``IncomeItem``IncomeSection``CoachTaskItem``AbandonedTask``TopCustomer``CoachServiceRecord``HistoryMonth``CoachNoteItem`
- _Requirements: 8.1-8.5, 9.1-9.4, 10.1-10.7, 11.1-11.5, 12.1-12.6_
- [x] 2. FDW 查询层扩展T2-1 基础)
- [x] 2.1 在 `apps/backend/app/services/fdw_queries.py` 新增客户相关查询函数
- `get_consumption_60d(conn, site_id, member_id)` — 近 60 天消费,使用 `ledger_amount`items_sum过滤 `is_delete=0`
- `get_relation_index(conn, site_id, member_id)` — 关系指数列表,来源 `v_dws_member_assistant_relation_index`,按 `relation_index` 降序
- `get_consumption_records(conn, site_id, member_id, limit, offset)` — 消费记录嵌套查询JOIN `v_dim_assistant`,过滤 `settle_type IN (1,3)` + `is_delete=0`
- `get_total_service_count(conn, site_id, member_id)` — 累计服务总次数
- `get_coach_60d_stats(conn, site_id, assistant_id, member_id)` — 特定助教对特定客户近 60 天统计
- 所有 SQL 使用 AS 别名映射design.md 列名映射表)
- _Requirements: 1.5, 1.6, 4.3-4.6, 5.3, 6.1, 7.4, 7.7-7.8, 13.2-13.7, 13.10_
- [x] 2.2 在 `apps/backend/app/services/fdw_queries.py` 新增助教相关查询函数
- `get_assistant_info(conn, site_id, assistant_id)` — 助教基本信息,来源 `v_dim_assistant`
- `get_salary_calc_multi_months(conn, site_id, assistant_id, months)` — 批量多月绩效数据
- `get_monthly_customer_count(conn, site_id, assistant_id, months)` — 各月不重复客户数,`COUNT(DISTINCT tenant_member_id)`,过滤 `is_delete=0`
- `get_coach_top_customers(conn, site_id, assistant_id, limit=20)` — TOP 客户JOIN `v_dim_member`DQ-6+ `v_dim_member_card_account`DQ-7consume 使用 `ledger_amount`
- `get_customer_service_records(conn, site_id, member_id, year, month, table, limit, offset)` — 按月服务记录 + 月度统计汇总
- _Requirements: 8.2, 8.4, 9.3, 10.2-10.4, 10.6-10.7, 12.3, 12.6, 13.5-13.7, 13.10_
- [x] 2.3 为新增 FDW 查询函数编写单元测试
- 测试文件:`apps/backend/tests/unit/test_fdw_queries_rns12.py`
- 验证 DQ-6 JOIN 正确性、DQ-7 余额查询、`is_delete=0` 排除、`items_sum` 口径
- _Requirements: 13.2-13.7_
- [x] 3. CUST-1 客户详情 Service + RouterT2-1 ~ T2-3
- [x] 3.1 创建 `apps/backend/app/services/customer_service.py`,实现 `get_customer_detail()`
- 核心字段:调用 `fdw_queries.get_member_info()` → 基础信息,`get_member_balance()` → balance`get_consumption_60d()` → consumption60d`get_last_visit_days()` → daysSinceVisit
- 手机号脱敏逻辑(`"139****5678"` 格式)
- Banner 字段查询失败返回 `null`(需求 1.7
- _Requirements: 1.1-1.7, 13.1-13.2_
- [x] 3.2 在 `customer_service.py` 实现 `_build_ai_insight()``_build_retention_clues()``_build_notes()`
- aiInsight查询 `biz.ai_cache` WHERE `cache_type='app4_analysis'` AND `target_id=customerId`,解析 `cache_value` JSON
- retentionClues查询 `public.member_retention_clue`,按 `created_at` 倒序
- notes查询 `biz.notes` WHERE `target_type='member'`,最多 20 条,按 `created_at` 倒序
- 每个模块独立 try/except 优雅降级
- _Requirements: 2.1-2.3, 3.1-3.2, 13.8-13.9_
- [x] 3.3 在 `customer_service.py` 实现 `_build_consumption_records()`
- 调用 `fdw_queries.get_consumption_records()` 获取结算单列表
- 构建 coaches 子数组:`fee` 使用 `assistant_pd_money`(基础课)/ `assistant_cx_money`(激励课)
- `totalAmount` 使用 `items_sum` 口径,`tableFee` 使用 `table_charge_money``foodAmount` 使用 `goods_money`
- 过滤 `settle_type IN (1, 3)` + `is_delete=0`
- _Requirements: 4.1-4.6, 13.3-13.4, 13.7_
- [x] 3.4 在 `customer_service.py` 实现 `_build_coach_tasks()`T2-2
- 查询 `biz.coach_tasks` WHERE `member_id=customerId`
- 对每位助教:`fdw_queries.get_salary_calc()` 获取等级,`get_coach_60d_stats()` 获取近 60 天统计
- 映射 `levelColor`/`taskColor`/`bgClass`
- metrics 返回:服务次数、总时长、次均时长
- _Requirements: 5.1-5.4_
- [x] 3.5 在 `customer_service.py` 实现 `_build_favorite_coaches()`T2-3
- 调用 `fdw_queries.get_relation_index()` 获取关系指数列表,按降序排列
- emoji 映射:`relationIndex >= 0.7``"💖"``< 0.7``"💛"`
- stats 4 项指标:基础课时(`assistant_pd_money`)、激励课时(`assistant_cx_money`)、上课次数、充值金额
- _Requirements: 6.1-6.4_
- [x] 3.6 创建 `apps/backend/app/routers/xcx_customers.py`,注册 CUST-1 端点
- `GET /{customer_id}``customer_service.get_customer_detail()`
- `Depends(require_approved())` 权限检查
-`main.py` 注册 router
- _Requirements: 13.1_
- [x] 3.7 为 CUST-1 编写单元测试
- 测试文件:`apps/backend/tests/unit/test_customer_detail.py`
- 验证完整响应结构、Banner 字段、aiInsight 降级、consumptionRecords 嵌套、coachTasks metrics、favoriteCoaches 排序
- _Requirements: 1.1-6.4_
- [x] 4. Checkpoint — 确保 CUST-1 所有测试通过
- 确保所有测试通过ask the user if questions arise.
- [x] 5. CUST-2 客户服务记录 Service + RouterT2-4
- [x] 5.1 在 `customer_service.py` 实现 `get_customer_records()`
- 接受 `year``month``table`(可选)、`page``page_size` 参数
- 调用 `fdw_queries.get_member_info()` → customerName/customerPhoneDQ-6
- 调用 `fdw_queries.get_customer_service_records()` → 按月分页记录
- 聚合 `monthCount`/`monthHours`
- 调用 `fdw_queries.get_total_service_count()` → totalServiceCount跨月
- 每条记录含 `recordType``course`/`recharge`)和 `isEstimate`
- income 使用 `items_sum` 口径,排除 `is_delete!=0`
-`create_time` 倒序,返回 `hasMore`
- _Requirements: 7.1-7.9_
- [x] 5.2 在 `xcx_customers.py` router 注册 CUST-2 端点
- `GET /{customer_id}/records``customer_service.get_customer_records()`
- Query 参数:`year: int``month: int (ge=1, le=12)``table: str | None``page: int (ge=1)``page_size: int (ge=1, le=100)`
- `Depends(require_approved())` 权限检查
- _Requirements: 7.1, 13.1_
- [x] 5.3 为 CUST-2 编写单元测试
- 测试文件:`apps/backend/tests/unit/test_customer_records.py`
- 验证按月查询、monthCount/monthHours 汇总、totalServiceCount 跨月、hasMore 分页、recordType/isEstimate
- _Requirements: 7.1-7.9_
- [x] 6. COACH-1 助教详情 Service + RouterT2-5
- [x] 6.1 创建 `apps/backend/app/services/coach_service.py`,实现 `get_coach_detail()`
- 基础信息:`fdw_queries.get_assistant_info()` → name/avatar/skills/workYears/hireDate
- 绩效:`fdw_queries.get_salary_calc()` → monthlyHours`effective_hours`/monthlySalary`gross_salary`/perfCurrent/perfTarget
- customerBalance`fdw_queries.get_member_balance()` 聚合该助教所有客户余额
- tasksCompleted`biz.coach_tasks` 当月 `status='completed'` 计数
- _Requirements: 8.1-8.5_
- [x] 6.2 在 `coach_service.py` 实现 `_build_income()``_build_tier_nodes()`
- income`thisMonth`/`lastMonth` 各含 4 项(基础课时费 `assistant_pd_money`/`base_income`、激励课时费 `assistant_cx_money`/`bonus_income`、充值提成、酒水提成)
-`v_dws_assistant_salary_calc` 分别查询当月和上月(`salary_month``YYYY-MM-01`
- tierNodes档位节点数组`[0, 100, 130, 160, 190, 220]`
- _Requirements: 9.1-9.4_
- [x] 6.3 在 `coach_service.py` 实现 `_build_top_customers()``_build_service_records()`
- topCustomers调用 `fdw_queries.get_coach_top_customers()`,最多 20 条
- heartEmoji 三级映射:`score >= 0.7``"❤️"``0.3 <= score < 0.7``"💛"``score < 0.3``"🤍"`
- consume 使用 `items_sum` 口径balance 通过 `v_dim_member_card_account`DQ-7客户姓名通过 `v_dim_member`DQ-6
- serviceRecords近期服务记录income 使用 `ledger_amount`,排除 `is_delete!=0`
- _Requirements: 10.1-10.7_
- [x] 6.4 在 `coach_service.py` 实现 `_build_task_groups()``_build_notes()`
- 查询 `biz.coach_tasks` WHERE `assistant_id=coachId`
- 按 status 分组:`active` → visibleTasks`inactive` → hiddenTasks`abandoned` → abandonedTasks
- visible/hidden关联 `biz.notes` 获取备注列表(`task_id` 关联,按 `created_at` 倒序)
- abandoned`abandon_reason`
- notes助教相关备注最多 20 条
- _Requirements: 11.1-11.5_
- [x] 6.5 在 `coach_service.py` 实现 `_build_history_months()`T2-6
- `fdw_queries.get_salary_calc_multi_months()` → 最近 6 个月工时/工资
- `fdw_queries.get_monthly_customer_count()` → 各月客户数
- `biz.coach_tasks` → 各月回访完成数(`task_type='follow_up_visit' AND status='completed'`)和召回完成数(`task_type IN ('high_priority_recall', 'priority_recall') AND status='completed'`
- 本月 `estimated=True`,历史月份 `estimated=False`
- 格式化customers → `"22人"`hours → `"87.5h"`salary → `"¥6,950"`
- _Requirements: 12.1-12.6_
- [x] 6.6 创建 `apps/backend/app/routers/xcx_coaches.py`,注册 COACH-1 端点
- `GET /{coach_id}``coach_service.get_coach_detail()`
- `Depends(require_approved())` 权限检查
-`main.py` 注册 router
- _Requirements: 13.1_
- [x] 6.7 为 COACH-1 编写单元测试
- 测试文件:`apps/backend/tests/unit/test_coach_detail.py`
- 验证完整响应结构、performance 6 指标、income 本月/上月、topCustomers heartEmoji、historyMonths 排序与 estimated、任务分组
- _Requirements: 8.1-12.6_
- [x] 7. Checkpoint — 确保 CUST-1 + CUST-2 + COACH-1 所有测试通过
- 确保所有测试通过ask the user if questions arise.
- [x] 8. 优雅降级与权限校验测试
- [x] 8.1 为优雅降级编写单元测试
- 测试文件:`apps/backend/tests/unit/test_degradation_rns12.py`
- 验证 aiInsight/coachTasks/favoriteCoaches/consumptionRecords/historyMonths 各模块查询失败时返回空默认值,不影响 HTTP 200
- _Requirements: 13.8-13.9_
- [x] 8.2 为权限校验编写单元测试
- 测试文件:`apps/backend/tests/unit/test_auth_rns12.py`
- 验证未审核用户 403、客户不存在 404、助教不存在 404
- _Requirements: 13.1_
- [x] 9. 属性测试T2-6 PBT
- [x] 9.1 编写属性测试:消费记录金额拆分不变量
- **Property 1: 消费记录金额拆分不变量**
- 测试文件:`tests/test_rns12_properties.py`
- 生成器:`st.floats(min_value=0, max_value=1e5)` 生成 tableFee/foodAmount/coachFees
- 验证:`abs(totalAmount - (tableFee + foodAmount + sum(fees))) < 0.01`
- **Validates: Requirements 14.1, 4.4**
- [x] 9.2 编写属性测试:废单排除一致性
- **Property 2: 废单排除一致性**
- 生成器:`st.lists(st.fixed_dictionaries({is_delete: st.integers(0,2), ...}))`
- 验证:过滤后结果中所有 `is_delete == 0`
- **Validates: Requirements 14.8, 4.6, 7.8, 10.7**
- [x] 9.3 编写属性测试:助教费用拆分正确性
- **Property 3: 助教费用拆分正确性**
- 生成器:`st.floats` 生成 pd_money/cx_money + `st.sampled_from(["基础课","激励课"])`
- 验证:基础课 → pd_money激励课 → cx_money两者之和 = 总额
- **Validates: Requirements 14.2, 4.3, 9.2**
- [x] 9.4 编写属性测试favoriteCoaches 排序不变量
- **Property 4: favoriteCoaches 排序不变量**
- 生成器:`st.lists(st.floats(0, 1))` 生成 relationIndex 列表
- 验证:排序后每项 ≥ 下一项
- **Validates: Requirements 14.5, 6.1**
- [x] 9.5 编写属性测试historyMonths 排序与预估标记
- **Property 5: historyMonths 排序与预估标记**
- 生成器:`st.lists(st.dates(), min_size=1)` 生成月份列表
- 验证:降序排列,首项 `estimated=True`,其余 `False`
- **Validates: Requirements 14.6, 12.5**
- [x] 9.6 编写属性测试:列表上限约束
- **Property 6: 列表上限约束**
- 生成器:`st.integers(0, 100)` 生成记录数
- 验证notes ≤ 20topCustomers ≤ 20
- **Validates: Requirements 3.2, 10.1, 11.5**
- [x] 9.7 编写属性测试:月度汇总聚合正确性
- **Property 7: 月度汇总聚合正确性**
- 生成器:`st.lists(st.fixed_dictionaries({hours: st.floats(0,10), income: st.floats(0,1e4)}))`
- 验证count=lenmonthHours=sum(hours)
- **Validates: Requirements 7.6, 5.3**
- [x] 9.8 编写属性测试daysSinceVisit 计算正确性
- **Property 8: daysSinceVisit 计算正确性**
- 生成器:`st.dates(max_value=date.today())`
- 验证days = (today - date).days非负整数
- **Validates: Requirements 1.6**
- [x] 9.9 编写属性测试emoji 映射正确性
- **Property 9: emoji 映射正确性**
- 生成器:`st.floats(0, 1)` 生成 relationIndex
- 验证CUST-1 两级映射≥0.7→💖,<0.7→💛COACH-1 三级映射≥0.7→❤0.3-0.7→💛,<0.3→🤍)
- **Validates: Requirements 6.4**
- [x] 9.10 编写属性测试:优雅降级
- **Property 10: 优雅降级**
- 生成器:`st.sampled_from(MODULES)` 选择失败模块
- 验证失败模块返回空默认值其他模块正常HTTP 200
- **Validates: Requirements 1.7, 13.8**
- [x] 9.11 编写属性测试:任务分组正确性
- **Property 11: 任务分组正确性**
- 生成器:`st.lists(st.fixed_dictionaries({status: st.sampled_from(STATUSES)}))`
- 验证active→visibleinactive→hiddenabandoned→abandoned无交集并集=原集合
- **Validates: Requirements 11.1**
- [x] 9.12 编写属性测试:数据隔离不变量
- **Property 12: 数据隔离不变量**
- 生成器:`st.integers(1, 1000)` 生成 customerId/coachId
- 验证coachTasks 每条 member_id=customerIdserviceRecords 每条 assistant_id=coachId
- **Validates: Requirements 14.3, 14.4**
- [x] 9.13 编写属性测试:分页与 hasMore 正确性
- **Property 13: 分页与 hasMore 正确性**
- 生成器:`st.integers(1,100)` total + `st.integers(1,10)` page/pageSize
- 验证:记录数 ≤ pageSizehasMore = (total > page*pageSize)
- **Validates: Requirements 7.9**
- [x] 9.14 编写属性测试:幂等性
- **Property 14: 幂等性**
- 生成器:`st.integers(1,12)` month + `st.integers(2020,2026)` year
- 验证f(x) == f(x) 对 monthCount/monthHours
- **Validates: Requirements 14.7**
- [x] 10. Final checkpoint — 确保所有测试通过
- 确保所有测试通过ask the user if questions arise.
- [x] 11. 前端到数据库全链路测试
- [x] 11.1 启动后端服务,使用测试库(`test_zqyy_app`)验证 CUST-1、CUST-2、COACH-1 三个端点的完整请求-响应链路
- 使用真实 FDW 连接(`test_etl_feiqiu`)验证 SQL 查询正确性
- 验证 JSON 响应结构与 Schema 定义一致camelCase 序列化)
- 验证权限校验(`require_approved()`)在真实请求中生效
- [x] 11.2 小程序前端联调验证(如已有对应页面)
- 确认前端页面能正确调用新增 API 并渲染数据
- 验证空数据/降级场景下前端不崩溃
- 如前端页面尚未开发,记录待联调清单供后续 RNS 任务使用
- [x] 12. 项目文档更新与落地
- [x] 12.1 更新 `docs/contracts/openapi/backend-api.json`,补充 CUST-1、CUST-2、COACH-1 三个端点的 OpenAPI 定义
- [x] 12.2 更新 `docs/architecture/backend-architecture.md`,补充新增的 `customer_service``coach_service` 模块及路由注册说明
- [x] 12.3 更新 `docs/database/BD_Manual_biz_tables.md`,补充本次新增/引用的 `biz.coach_tasks``biz.notes``biz.ai_cache` 表的使用说明(如有新增字段或新用法)
- [x] 12.4 更新 `docs/DOCUMENTATION-MAP.md`,确保新增文档条目已索引
- [x] 12.5 更新 `docs/miniprogram-dev/API-contract.md`,补充 CUST-1、CUST-2、COACH-1 的接口契约(请求/响应示例)
- [x] 13. 数据库变更审计与 DDL 合并
- [x] 13.1 审计本次实现中对数据库的改动新建表、新增字段、新增索引、FDW 映射变更等)
- 检查 `biz.coach_tasks``biz.notes``biz.ai_cache``public.member_retention_clue` 是否需要新建或变更
- 检查 FDW 外部表映射是否需要更新(新增视图引用等)
- [x] 13.2 将所有数据库变更合并到主 DDL 文件
- 业务库变更 → `db/zqyy_app/` 对应 DDL 文件
- FDW 变更 → `db/fdw/` 对应 DDL 文件
- 编写日期前缀迁移脚本(如有 schema 变更)
- [x] 13.3 更新 BD 手册记录变更
- 业务库 → `docs/database/BD_Manual_biz_tables.md`
- FDW → `docs/database/BD_Manual_fdw.md`(如有变更)
- 记录变更原因、影响范围、回滚 SQL
## Notes
- Tasks marked with `*` are optional and can be skipped for faster MVP
- 所有金额字段统一使用 `items_sum` 口径DWD-DOC 强制规则 1禁止 `consume_money`
- 助教费用使用 `assistant_pd_money` + `assistant_cx_money` 拆分DWD-DOC 强制规则 2禁止 `service_fee`
- 会员信息通过 `member_id` JOIN `v_dim_member`DQ-6余额通过 `v_dim_member_card_account`DQ-7
- 废单排除统一使用 `is_delete=0`,禁止引用已废弃的 `dwd_assistant_trash_event`
- Property tests validate universal correctness properties from design.md
- Checkpoints ensure incremental validation