19 KiB
19 KiB
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
-
1. Pydantic Schema 定义与项目结构搭建
- 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
- 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
- 1.1 创建
-
2. FDW 查询层扩展(T2-1 基础)
- 2.1 在
apps/backend/app/services/fdw_queries.py新增客户相关查询函数get_consumption_60d(conn, site_id, member_id)— 近 60 天消费,使用ledger_amount(items_sum),过滤is_delete=0get_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)— 消费记录嵌套查询,JOINv_dim_assistant,过滤settle_type IN (1,3)+is_delete=0get_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
- 2.2 在
apps/backend/app/services/fdw_queries.py新增助教相关查询函数get_assistant_info(conn, site_id, assistant_id)— 助教基本信息,来源v_dim_assistantget_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=0get_coach_top_customers(conn, site_id, assistant_id, limit=20)— TOP 客户,JOINv_dim_member(DQ-6)+v_dim_member_card_account(DQ-7),consume 使用ledger_amountget_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
- 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
- 测试文件:
- 2.1 在
-
3. CUST-1 客户详情 Service + Router(T2-1 ~ T2-3)
- 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
- 核心字段:调用
- 3.2 在
customer_service.py实现_build_ai_insight()和_build_retention_clues()和_build_notes()- aiInsight:查询
biz.ai_cacheWHEREcache_type='app4_analysis'ANDtarget_id=customerId,解析cache_valueJSON - retentionClues:查询
public.member_retention_clue,按created_at倒序 - notes:查询
biz.notesWHEREtarget_type='member',最多 20 条,按created_at倒序 - 每个模块独立 try/except 优雅降级
- Requirements: 2.1-2.3, 3.1-3.2, 13.8-13.9
- aiInsight:查询
- 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
- 调用
- 3.4 在
customer_service.py实现_build_coach_tasks()(T2-2)- 查询
biz.coach_tasksWHEREmember_id=customerId - 对每位助教:
fdw_queries.get_salary_calc()获取等级,get_coach_60d_stats()获取近 60 天统计 - 映射
levelColor/taskColor/bgClass - metrics 返回:服务次数、总时长、次均时长
- Requirements: 5.1-5.4
- 查询
- 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
- 调用
- 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
- 3.7 为 CUST-1 编写单元测试
- 测试文件:
apps/backend/tests/unit/test_customer_detail.py - 验证完整响应结构、Banner 字段、aiInsight 降级、consumptionRecords 嵌套、coachTasks metrics、favoriteCoaches 排序
- Requirements: 1.1-6.4
- 测试文件:
- 3.1 创建
-
4. Checkpoint — 确保 CUST-1 所有测试通过
- 确保所有测试通过,ask the user if questions arise.
-
5. CUST-2 客户服务记录 Service + Router(T2-4)
- 5.1 在
customer_service.py实现get_customer_records()- 接受
year、month、table(可选)、page、page_size参数 - 调用
fdw_queries.get_member_info()→ customerName/customerPhone(DQ-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
- 接受
- 5.2 在
xcx_customers.pyrouter 注册 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
- 5.3 为 CUST-2 编写单元测试
- 测试文件:
apps/backend/tests/unit/test_customer_records.py - 验证按月查询、monthCount/monthHours 汇总、totalServiceCount 跨月、hasMore 分页、recordType/isEstimate
- Requirements: 7.1-7.9
- 测试文件:
- 5.1 在
-
6. COACH-1 助教详情 Service + Router(T2-5)
- 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
- 基础信息:
- 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
- income:
- 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
- topCustomers:调用
- 6.4 在
coach_service.py实现_build_task_groups()和_build_notes()- 查询
biz.coach_tasksWHEREassistant_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
- 查询
- 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
- 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
- 6.7 为 COACH-1 编写单元测试
- 测试文件:
apps/backend/tests/unit/test_coach_detail.py - 验证完整响应结构、performance 6 指标、income 本月/上月、topCustomers heartEmoji、historyMonths 排序与 estimated、任务分组
- Requirements: 8.1-12.6
- 测试文件:
- 6.1 创建
-
7. Checkpoint — 确保 CUST-1 + CUST-2 + COACH-1 所有测试通过
- 确保所有测试通过,ask the user if questions arise.
-
8. 优雅降级与权限校验测试
- 8.1 为优雅降级编写单元测试
- 测试文件:
apps/backend/tests/unit/test_degradation_rns12.py - 验证 aiInsight/coachTasks/favoriteCoaches/consumptionRecords/historyMonths 各模块查询失败时返回空默认值,不影响 HTTP 200
- Requirements: 13.8-13.9
- 测试文件:
- 8.2 为权限校验编写单元测试
- 测试文件:
apps/backend/tests/unit/test_auth_rns12.py - 验证未审核用户 403、客户不存在 404、助教不存在 404
- Requirements: 13.1
- 测试文件:
- 8.1 为优雅降级编写单元测试
-
9. 属性测试(T2-6 PBT)
- 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
- 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
- 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
- 9.4 编写属性测试:favoriteCoaches 排序不变量
- Property 4: favoriteCoaches 排序不变量
- 生成器:
st.lists(st.floats(0, 1))生成 relationIndex 列表 - 验证:排序后每项 ≥ 下一项
- Validates: Requirements 14.5, 6.1
- 9.5 编写属性测试:historyMonths 排序与预估标记
- Property 5: historyMonths 排序与预估标记
- 生成器:
st.lists(st.dates(), min_size=1)生成月份列表 - 验证:降序排列,首项
estimated=True,其余False - Validates: Requirements 14.6, 12.5
- 9.6 编写属性测试:列表上限约束
- Property 6: 列表上限约束
- 生成器:
st.integers(0, 100)生成记录数 - 验证:notes ≤ 20,topCustomers ≤ 20
- Validates: Requirements 3.2, 10.1, 11.5
- 9.7 编写属性测试:月度汇总聚合正确性
- Property 7: 月度汇总聚合正确性
- 生成器:
st.lists(st.fixed_dictionaries({hours: st.floats(0,10), income: st.floats(0,1e4)})) - 验证:count=len,monthHours=sum(hours)
- Validates: Requirements 7.6, 5.3
- 9.8 编写属性测试:daysSinceVisit 计算正确性
- Property 8: daysSinceVisit 计算正确性
- 生成器:
st.dates(max_value=date.today()) - 验证:days = (today - date).days,非负整数
- Validates: Requirements 1.6
- 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
- 9.10 编写属性测试:优雅降级
- Property 10: 优雅降级
- 生成器:
st.sampled_from(MODULES)选择失败模块 - 验证:失败模块返回空默认值,其他模块正常,HTTP 200
- Validates: Requirements 1.7, 13.8
- 9.11 编写属性测试:任务分组正确性
- Property 11: 任务分组正确性
- 生成器:
st.lists(st.fixed_dictionaries({status: st.sampled_from(STATUSES)})) - 验证:active→visible,inactive→hidden,abandoned→abandoned,无交集,并集=原集合
- Validates: Requirements 11.1
- 9.12 编写属性测试:数据隔离不变量
- Property 12: 数据隔离不变量
- 生成器:
st.integers(1, 1000)生成 customerId/coachId - 验证:coachTasks 每条 member_id=customerId,serviceRecords 每条 assistant_id=coachId
- Validates: Requirements 14.3, 14.4
- 9.13 编写属性测试:分页与 hasMore 正确性
- Property 13: 分页与 hasMore 正确性
- 生成器:
st.integers(1,100)total +st.integers(1,10)page/pageSize - 验证:记录数 ≤ pageSize,hasMore = (total > page*pageSize)
- Validates: Requirements 7.9
- 9.14 编写属性测试:幂等性
- Property 14: 幂等性
- 生成器:
st.integers(1,12)month +st.integers(2020,2026)year - 验证:f(x) == f(x) 对 monthCount/monthHours
- Validates: Requirements 14.7
- 9.1 编写属性测试:消费记录金额拆分不变量
-
10. Final checkpoint — 确保所有测试通过
- 确保所有测试通过,ask the user if questions arise.
-
11. 前端到数据库全链路测试
- 11.1 启动后端服务,使用测试库(
test_zqyy_app)验证 CUST-1、CUST-2、COACH-1 三个端点的完整请求-响应链路- 使用真实 FDW 连接(
test_etl_feiqiu)验证 SQL 查询正确性 - 验证 JSON 响应结构与 Schema 定义一致(camelCase 序列化)
- 验证权限校验(
require_approved())在真实请求中生效
- 使用真实 FDW 连接(
- 11.2 小程序前端联调验证(如已有对应页面)
- 确认前端页面能正确调用新增 API 并渲染数据
- 验证空数据/降级场景下前端不崩溃
- 如前端页面尚未开发,记录待联调清单供后续 RNS 任务使用
- 11.1 启动后端服务,使用测试库(
-
12. 项目文档更新与落地
- 12.1 更新
docs/contracts/openapi/backend-api.json,补充 CUST-1、CUST-2、COACH-1 三个端点的 OpenAPI 定义 - 12.2 更新
docs/architecture/backend-architecture.md,补充新增的customer_service、coach_service模块及路由注册说明 - 12.3 更新
docs/database/BD_Manual_biz_tables.md,补充本次新增/引用的biz.coach_tasks、biz.notes、biz.ai_cache表的使用说明(如有新增字段或新用法) - 12.4 更新
docs/DOCUMENTATION-MAP.md,确保新增文档条目已索引 - 12.5 更新
docs/miniprogram-dev/API-contract.md,补充 CUST-1、CUST-2、COACH-1 的接口契约(请求/响应示例)
- 12.1 更新
-
13. 数据库变更审计与 DDL 合并
- 13.1 审计本次实现中对数据库的改动(新建表、新增字段、新增索引、FDW 映射变更等)
- 检查
biz.coach_tasks、biz.notes、biz.ai_cache、public.member_retention_clue是否需要新建或变更 - 检查 FDW 外部表映射是否需要更新(新增视图引用等)
- 检查
- 13.2 将所有数据库变更合并到主 DDL 文件
- 业务库变更 →
db/zqyy_app/对应 DDL 文件 - FDW 变更 →
db/fdw/对应 DDL 文件 - 编写日期前缀迁移脚本(如有 schema 变更)
- 业务库变更 →
- 13.3 更新 BD 手册记录变更
- 业务库 →
docs/database/BD_Manual_biz_tables.md - FDW →
docs/database/BD_Manual_fdw.md(如有变更) - 记录变更原因、影响范围、回滚 SQL
- 业务库 →
- 13.1 审计本次实现中对数据库的改动(新建表、新增字段、新增索引、FDW 映射变更等)
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_idJOINv_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