Files
Neo 70324d8542 chore: 文档与 IDE 配置整理
- .kiro/specs/ → docs/specs/(41 个历史需求 spec 迁移,移除 .config.kiro)
- CLAUDE.md 三层拆分:根文件精简 + apps/backend/CLAUDE.md + .claude/commands/
- 新增 /spec-close、/pre-change 两个工作流命令
- DDL 基线刷新(从测试库重新导出 11 个文件,dws 35→38 表,biz 18→21 表)
- BD_Manual → BD_manual 命名统一(48 个文件)
- 修复 3 处文档与数据库不一致(auth.users.status 默认值、scheduled_tasks 字段、RLS 视图数)
- 新增 BD_manual_public_rbac_tables.md(public schema 8 张 RBAC/工作流表)
- 合并 biz.trigger_jobs 文档(10→12 字段,归档独立文档)
- docs/database/README.md 索引更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 00:02:37 +08:00

295 lines
18 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.
# 实施计划NS2 AI Prompt 细化
## 概述
按照设计文档,将实施拆分为:共享数据获取层 → 应用 3/4/5/6/7 Prompt 拼接 → 应用 1 页面上下文 → 前端筛选参数 → 集成联调。每个任务增量构建确保无孤立代码。属性测试Hypothesis和单元测试作为可选子任务紧跟实现步骤。
后端使用 PythonFastAPI + asyncio测试使用 Hypothesis + pytest。
## 任务
- [x] 1. 创建共享数据获取层基础结构
- [x] 1.1 创建 `apps/backend/app/ai/data_fetchers/` 模块骨架
- 创建 `__init__.py`,导出 `fetch_member_consumption_data``fetch_assistant_info``fetch_service_history``fetch_member_notes``build_page_text`
- 创建 `member_data.py``assistant_data.py``page_context.py` 空文件
- _需求: 1.1, 2.1, 8.1_
- [x] 2. 实现客户消费数据获取member_data.py
- [x] 2.1 实现 `fetch_member_consumption_data(site_id, member_id, months=3)`
- 使用 `get_etl_readonly_connection(site_id)` + `SET LOCAL app.current_site_id` 获取 FDW 连接
-`v_dwd_settlement_head` + `v_dwd_table_fee_log` 获取台桌结账(`settle_type IN (1,3)`
-`v_dwd_store_goods_sale` 获取商城订单
-`v_dim_member_card_account` 获取会员卡明细
-`v_dws_member_consumption_summary` + `v_dws_member_visit_detail` 获取汇总和到店数据
-`v_dim_member``scd2_is_current=1`)获取会员昵称
- 消费记录限制 100 条,按 `settle_date DESC` 排序,超出标注 `truncated`
- 金额使用 `items_sum` 口径,拆分 `assistant_pd_money` / `assistant_cx_money`
- 5 秒查询超时,超时抛出 `TimeoutError`
- _需求: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 1.10, 11.1, 11.2, 11.3, 11.4, 11.5, 11.7_
- [x] 2.2 实现 `fetch_member_notes(site_id, member_id, limit=50)`
- 使用 `get_connection()` 查询 `biz.notes`
-`created_at DESC` 排序,最多 `limit`
- 单条备注内容截断 500 字符,超出附加"…(已截断)"
- _需求: 4.3, 4.4, 6.3, 6.4, 14.4_
- [x] 2.3 编写属性测试数据获取返回结构完整性P1
- **Property 1: 数据获取函数返回结构完整性**
- Mock 数据库返回随机行,验证 `fetch_member_consumption_data()` 返回字典包含所有必需键
- 验证每条消费记录包含 `settle_date``settle_type``items_sum` 等 10 个字段
- 测试文件: `tests/test_data_fetchers/test_member_data_props.py`
- **验证: 需求 1.1, 1.3, 1.5, 2.1, 2.2, 2.4**
- [x] 2.4 编写属性测试正向交易过滤P2+ 金额口径P3+ 记录限制与排序P4
- **Property 2: 消费记录仅包含正向交易** — 生成混合 `settle_type` 记录,验证输出仅含 1/3
- **Property 3: 金额口径使用 items_sum** — 验证输出包含 `items_sum` 且不含 `consume_money`
- **Property 4: 消费记录数量限制与排序** — 生成 0-200 条记录,验证输出 ≤100 且 `settle_date` 降序
- 测试文件: `tests/test_data_fetchers/test_member_data_props.py`
- **验证: 需求 1.2, 1.4, 1.9, 3.5, 11.3, 11.4, 14.3**
- [x] 2.5 编写属性测试备注截断P6+ 备注数量限制P17
- **Property 6: 备注截断不变量** — 生成 0-2000 字符字符串,验证截断后 ≤500超长以"…(已截断)"结尾
- **Property 17: 备注数量限制** — 生成 0-100 条备注,验证输出 ≤50 且 `created_at` 降序
- 测试文件: `tests/test_data_fetchers/test_member_data_props.py`
- **验证: 需求 4.4, 6.4, 14.4**
- [x] 2.6 编写单元测试member_data 边界情况
- 客户无消费记录时返回空数组和默认值
- 客户无备注时返回空数组
- FDW 查询超时时抛出 `TimeoutError`
- 会员不存在时 `member_nickname` 为空字符串
- 测试文件: `tests/test_data_fetchers/test_data_fetchers_unit.py`
- _需求: 1.10, 3.4, 6.6, 12.1, 12.2_
- [x] 3. 实现助教数据获取assistant_data.py
- [x] 3.1 实现 `fetch_assistant_info(site_id, assistant_id)`
-`v_dim_assistant` 获取花名、级别、入职日期
-`v_dws_assistant_salary_calc` 获取本月客户数、绩效档位
- 计算 `tenure_months`(入职日期到当前日期)
- 助教不存在时抛出 `ValueError("assistant not found")`
- _需求: 2.1, 2.5, 2.6, 11.1, 11.2_
- [x] 3.2 实现 `fetch_service_history(site_id, assistant_id, member_id, months=3)`
-`v_dwd_assistant_service_log` 获取服务记录,`WHERE is_trash = false`
-`v_dws_member_assistant_relation_index` 获取关系指数
-`v_dws_member_assistant_intimacy` 获取亲密度
-`service_date DESC` 排序
- _需求: 2.2, 2.3, 2.4, 11.1, 11.2_
- [x] 3.3 编写属性测试废单排除P5
- **Property 5: 废单记录排除**
- 生成含 `is_trash` 标记的服务记录,验证输出不含 `is_trash=true` 的记录
- 测试文件: `tests/test_data_fetchers/test_assistant_data_props.py`
- **验证: 需求 2.3**
- [x] 3.4 编写单元测试assistant_data 边界情况
- 助教不存在时抛出 `ValueError`
- 无服务历史时返回空列表
- FDW 查询超时时抛出 `TimeoutError`
- 测试文件: `tests/test_data_fetchers/test_data_fetchers_unit.py`
- _需求: 2.6, 4.5, 12.1_
- [x] 4. 检查点 — 数据获取层完成
- 确保所有测试通过ask the user if questions arise.
- [x] 5. 实现应用 3 Prompt 拼接app3_clue.py
- [x] 5.1 改造 `app3_clue.py``build_prompt()``async def`
- 新增 `site_id` 参数,调用 `fetch_member_consumption_data()` 获取真实消费数据
- 组装 Prompt JSON`current_time``member_nickname``main_data``reference`
- `reference``ai_cache` 获取 `app6_clues` + 最近 2 套 `app8_history`
- 空数据时标注"该客户暂无消费记录"
- `run()` 中调用 `build_prompt()``await`
- _需求: 3.1, 3.2, 3.3, 3.4, 3.5, 12.1, 12.2, 12.3_
- [x] 5.2 编写属性测试App3 Prompt 结构完整性P7 局部)
- **Property 7App3 部分): build_prompt 返回结构完整性**
- Mock 数据获取返回随机数据,验证 Prompt JSON 包含 `current_time``member_nickname``main_data``reference` 顶层键
- 测试文件: `tests/test_ai_apps/test_build_prompt_props.py`
- **验证: 需求 3.1, 3.2**
- [x] 6. 实现应用 4 Prompt 拼接app4_analysis.py
- [x] 6.1 改造 `app4_analysis.py``build_prompt()``async def`
- 使用 `asyncio.gather` 并发调用 `fetch_assistant_info()` + `fetch_service_history()` + `fetch_member_consumption_data()`
- 调用 `fetch_member_notes()` 获取客户备注
- 组装 Prompt JSON`current_time``assistant_info``service_history``task_assignment_basis``customer_data`(含 `system_data` + `notes`)、`reference`
- `reference``ai_cache` 获取 `app8_current` + 最近 2 套 `app8_history`
- 部分数据获取失败时使用 `return_exceptions=True` 降级处理
- _需求: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 11.6, 12.1, 12.4_
- [x] 6.2 编写属性测试App4 Prompt 结构完整性P7 局部)
- **Property 7App4 部分): build_prompt 返回结构完整性**
- Mock 数据获取返回随机数据,验证 Prompt JSON 包含 `current_time``assistant_info``service_history``customer_data``reference` 顶层键
- 测试文件: `tests/test_ai_apps/test_build_prompt_props.py`
- **验证: 需求 4.1, 4.2**
- [x] 7. 实现应用 5 Prompt 拼接app5_tactics.py
- [x] 7.1 改造 `app5_tactics.py``build_prompt()``async def`
- 复用应用 4 的数据获取逻辑(`fetch_assistant_info` + `fetch_service_history` + `fetch_member_consumption_data`
-`context["app4_result"]` 获取 `task_suggestion`,缺失时设为空对象
- 组装 Prompt JSON同 App4 结构 + `task_suggestion`
- _需求: 5.1, 5.2, 5.3, 5.4_
- [x] 7.2 编写属性测试App5 task_suggestion 传递P8
- **Property 8: App5 task_suggestion 传递**
- 生成随机 `app4_result`(含空/缺失),验证 `task_suggestion` 字段正确传递或设为空对象
- 测试文件: `tests/test_ai_apps/test_build_prompt_props.py`
- **验证: 需求 5.3, 5.4**
- [x] 8. 实现应用 6 Prompt 拼接app6_note.py
- [x] 8.1 改造 `app6_note.py``build_prompt()``async def`
- 调用 `fetch_member_consumption_data()` 获取消费数据
- 调用 `fetch_member_notes()` 获取全部备注作为 `all_notes`
- 组装 Prompt JSON`current_time``current_note``reference`(含 `member_nickname``consumption_data``all_notes``app3_clues``app8_history`
- 空备注时 `all_notes` 设为空数组
- _需求: 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 12.1, 12.2_
- [x] 9. 实现应用 7 Prompt 拼接app7_customer.py
- [x] 9.1 改造 `app7_customer.py``build_prompt()``async def`
- 调用 `fetch_member_consumption_data()` 获取客观数据
- 调用 `fetch_member_notes()` 获取备注作为 `subjective_data.notes`
- 每条备注标注"【来源:{recorded_by},请甄别信息真实性】"
- 组装 Prompt JSON`current_time``member_id``member_nickname``objective_data``subjective_data``reference`
- 空备注时标注"该客户暂无主观备注信息"
- _需求: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 12.1, 12.2_
- [x] 9.2 编写属性测试App7 主观信息标注P9
- **Property 9: App7 主观信息来源标注**
- 生成随机备注(含 `recorded_by`),验证 Prompt 中每条备注附带"【来源:{recorded_by},请甄别信息真实性】"
- 测试文件: `tests/test_ai_apps/test_build_prompt_props.py`
- **验证: 需求 7.4**
- [x] 10. 检查点 — 应用 3-7 Prompt 拼接完成
- 确保所有测试通过ask the user if questions arise.
- [x] 11. 实现错误降级与 Token 预算控制
- [x] 11.1 在各应用 `build_prompt()` 中实现统一错误降级模式
- 使用 `asyncio.gather(return_exceptions=True)` 捕获部分失败
- 失败部分使用默认空值(空数组/空对象),附带提示文本
- 确保 Prompt JSON 不含 `None`Python/ `null`JSON使用 `default=str` 处理 `datetime`/`Decimal`
- _需求: 12.1, 12.2, 12.3, 12.4_
- [x] 11.2 实现 Prompt 字符数限制
- 应用 3/4/5/6/7 的 system message `content` ≤ 8000 字符
- 消费记录超 100 条时截断并标注"仅展示最近 100 条,共 {total} 条"
- 备注超 50 条时截断并标注"仅展示最近 50 条备注"
- 单个文本字段不超过 1000 字符
- _需求: 14.1, 14.3, 14.4, 14.5_
- [x] 11.3 编写属性测试错误降级P14+ Token 预算P15
- **Property 14: 错误降级产生合法 Prompt** — 随机让 0-N 个数据获取函数失败,验证 `build_prompt` 返回合法 JSON不含 `null`
- **Property 15: 应用 3-7 Prompt Token 预算** — 生成大量数据,验证 system message ≤ 8000 字符
- 测试文件: `tests/test_ai_apps/test_build_prompt_props.py`
- **验证: 需求 12.1, 12.2, 12.4, 14.1**
- [x] 12. 实现页面上下文文本化page_context.py
- [x] 12.1 实现 `build_page_text(source_page, context_id, site_id, filters)` 框架
- 根据 `source_page` 路由到对应内部函数(`_text_task_detail()``_text_customer_detail()` 等)
- 输出结构化中文描述文本(分段标题 + 缩进),非 JSON
- 输出限制 2000 字符,超出截断并标注
- 未识别的 `source_page` 返回空字符串
- 数据获取失败返回"页面上下文获取失败,请直接描述您的问题"
- _需求: 8.1, 8.2, 8.8, 8.9, 8.11_
- [x] 12.2 实现详情类页面文本化函数
- `_text_task_detail(task_id, site_id)` — 从 `biz.coach_tasks` + 会员信息 + 备注 + `ai_cache` 获取数据
- `_text_customer_detail(member_id, site_id)` — 复用 `fetch_member_consumption_data()` + 维客线索
- `_text_coach_detail(assistant_id, site_id)` — 复用 `fetch_assistant_info()` + 任务统计 + 备注
- `_text_customer_service_records(member_id, site_id)` — 服务记录列表
- `_text_task_list(task_id, site_id)` — 任务摘要 + 客户-助教关系
- 不传入 `member_phone` 等敏感字段
- _需求: 8.3, 8.4, 8.5, 8.12_
- [x] 12.3 实现看板类与其他页面文本化函数
- `_text_board_finance(site_id, filters)` — 财务 DWS 汇总,默认"本月"
- `_text_board_customer(site_id, filters)` — 客户排名 top 列表
- `_text_board_coach(site_id, filters)` — 助教排名
- `_text_performance(site_id, filters)``v_dws_assistant_salary_calc` 绩效数据
- `_text_my_profile(site_id)` — 用户信息 + 助教绑定
- 看板未传筛选参数时使用默认值
- _需求: 8.6, 8.7, 8.10_
- [x] 12.4 编写属性测试页面上下文长度P10+ 类型覆盖P11+ 敏感字段排除P12
- **Property 10: 页面上下文输出长度约束** — 生成大量数据,验证 `build_page_text()` 输出 ≤ 2000 字符
- **Property 11: 页面上下文覆盖所有页面类型** — 枚举 10 种类型,验证不抛异常且返回非空字符串
- **Property 12: 页面上下文不泄露敏感字段** — 生成含手机号的数据,验证输出不含 `member_phone`
- 测试文件: `tests/test_data_fetchers/test_page_context_props.py`
- **验证: 需求 8.8, 8.1, 8.2, 8.12**
- [x] 12.5 编写单元测试page_context 边界情况
- 未识别 `contextType` 时返回空字符串
- 看板无筛选参数时使用默认值
- 数据获取失败时返回降级文本
- `task-detail` 页面完整流程验证
- 测试文件: `tests/test_data_fetchers/test_data_fetchers_unit.py`
- _需求: 8.10, 8.11, 9.5_
- [x] 13. 实现应用 1 页面上下文集成app1_chat.py
- [x] 13.1 改造 `app1_chat.py``_build_page_context()``async def`
- 调用 `page_context.build_page_text(source_page, context_id, site_id, filters)` 获取页面上下文文本
- `source_page` 映射到 `contextType``contextId` 传入 `context_id`
- 看板类页面从请求参数提取筛选参数传入 `filters`
- `contextType` 为空或未识别时跳过页面上下文注入
- 将返回文本作为 system prompt 中的 `page_context` 字段注入
- 确保 `biz_params.user_prompt_params``User_ID``Role``Nickname`)注入后仍正确存在
- system prompt 总字符数控制在 4000 以内
- _需求: 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 14.2_
- [x] 13.2 编写属性测试biz_params 不变量P13+ App1 Token 预算P16
- **Property 13: biz_params 注入后不变量** — 生成随机用户信息,验证 `user_prompt_params` 包含 `User_ID``Role``Nickname` 且值一致
- **Property 16: 应用 1 System Prompt Token 预算** — 生成大量页面上下文,验证 system prompt ≤ 4000 字符
- 测试文件: `tests/test_ai_apps/test_app1_props.py`
- **验证: 需求 9.6, 13.2, 14.2**
- [x] 13.3 编写单元测试App1 页面上下文集成
- `contextType` 为空时跳过页面上下文
- `task-detail` 页面上下文注入后 system prompt 结构正确
- 看板筛选参数正确传递到 `build_page_text()`
- 页面上下文获取失败时使用降级文本
- 测试文件: `tests/test_ai_apps/test_ai_apps_unit.py`
- _需求: 9.1, 9.4, 9.5, 12.5_
- [x] 14. 检查点 — 页面上下文与应用 1 集成完成
- 确保所有测试通过ask the user if questions arise.
- [x] 15. 前端看板筛选参数传递(小程序端)
- [x] 15.1 修改看板页面跳转 chat 的参数传递
- `board-finance` 页面跳转时传入 `timeDimension` + `areaFilter`
- `board-customer` 页面跳转时传入 `dimension` + `typeFilter`
- `board-coach` 页面跳转时传入 `dimension` + `projectFilter` + `timeDimension`
- 将筛选参数编码为 chat 页面 URL 查询参数
- _需求: 10.1, 10.2, 10.3, 10.4_
- [x] 15.2 后端从请求中提取看板筛选参数
- 在 chat 消息接口中解析 URL 查询参数中的筛选参数
- 将筛选参数传入 `build_page_text()``filters` 参数
- _需求: 10.4, 9.4_
- [x] 16. 集成连线与最终验证
- [x] 16.1 确保 dispatcher 调用链中 `build_prompt()``await` 正确
- 消费事件链App3 → App8 → App7 + App4 → App5所有 `build_prompt` 调用加 `await`
- 备注事件链App6 → App8`build_prompt` 调用加 `await`
- 验证 `run()` 方法中 `build_prompt()` 调用已改为 `await`
- _需求: 3.1, 4.1, 5.1, 6.1, 7.1, 11.6_
- [x] 16.2 编写单元测试:各应用完整流程
- App3 完整流程Mock 数据 → `build_prompt` → 验证 JSON 结构和内容
- App4 并发数据获取:验证 `asyncio.gather` 调用
- App5 `task_suggestion` 传递:验证 `context["app4_result"]` 正确传入
- App7 主观信息标注:验证备注标注格式
- RLS 隔离:验证 `get_etl_readonly_connection` 被调用且传入正确 `site_id`
- 测试文件: `tests/test_ai_apps/test_ai_apps_unit.py`
- _需求: 3.1, 4.1, 5.3, 7.4, 11.2, 13.1_
- [x] 17. 最终检查点 — 全部完成
- 确保所有测试通过ask the user if questions arise.
## 备注
- 标记 `*` 的任务为可选,可跳过以加速 MVP
- 每个任务引用具体需求编号,确保可追溯
- 属性测试验证设计文档中的 17 个正确性属性P1-P17
- 检查点确保增量验证
- 测试文件组织:`tests/test_data_fetchers/`(数据获取层)、`tests/test_ai_apps/`(应用层)