chore(docs): Wave 0 调研产出 + P0/P1/P2 反馈调研
建立项目级标杆文档 docs/_overview/ 作为产品全景索引, 解决"PRD 零碎、文档膨胀、跨子系统调研无入口"的问题。 主要内容: - 00-index 总索引 + 维护协议 + 与 CLAUDE.md 关系 - 01-product-overview 产品全景脑图(6 角色 / 6 子系统 / 数据流 / 7 业务概念 / 8+1 AI 矩阵 / 22 术语) - 02a-miniprogram-page-matrix 小程序 21 页业务指纹 - 02b-adminweb-page-matrix admin-web 19 路由业务指纹 - 03-test-spec 测试规范 (L1-L5 分层 + 走查模板 + 75-95 case 估算) - 04-doc-conflicts 39 条冲突索引(P0×8 / P1×13 / P2×13 + 5 子项) - 04a/b/c-conflicts-*-detail 业务故事卡(7 字段:关联/逻辑/影响/选项/判定) - 05-orphan-pages-cleanup admin-web 6 孤儿页面处置(1 归档 + 4 保留) - WAVES-MASTER-PLAN.md 全 Wave 主计划(0-5,共 22-32 工作日) - WAVE-1-KICKOFF.md Wave 1 实施 kickoff - GLOBAL-DECISION-DASHBOARD.md 全局决策仪表板 反馈调研产物: - 04a-feedback/ P0 两轮反馈(8+8 项决策 + D-1/2/3 + F-1/2 子代理产出) - 04b-feedback/ P1 两轮反馈(13+1+5 项 + E-1/2/3/4 + G-1/2 子代理产出) - 04c-feedback/ P2 反馈(13 项 + 5 子项 + H-1/2/3 子代理产出) - NEO-DECISIONS-LOG 累积决策记录 关键追加发现 8 处 D Bug(原蓝本 0): - P0-3 看板沙箱接入(Wave 1 W1-T1) - P0-5 致命 1 (4 处 fdw_etl 残留, 已修 commit17f045a) - P0-5 致命 2 (JWT aud 缺失, 已修 commit17f045a) - P0-6 clearAllTasks 守卫 (Wave 3) - P0-8 DBViewer 黑名单漏 (已修 commit17f045a) - P1-3 task-detail 跳转传 task_id 而非 customer_id - P2-7 board-finance 隐式 null - 2 个独立 Bug (page_context.created_at + ClueCategory 字典) 参考: docs/_overview/00-index.md
This commit is contained in:
112
docs/_overview/04a-feedback/00-P0-feedback-response-summary.md
Normal file
112
docs/_overview/04a-feedback/00-P0-feedback-response-summary.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# P0 反馈响应总报告
|
||||
|
||||
> 日期:2026-05-04 / 触发:Neo 在 04a-conflicts 八条 P0 卡片上写下斜体反馈
|
||||
> 主线 + 3 个子代理(D-1/2/3)调研整合产出 / 状态:**调研完成,等 Neo 拍板进入实施**
|
||||
|
||||
本报告聚合 8 条 P0 反馈的处理状态。每条索引指向详细产出文件。
|
||||
|
||||
## 一、8 条 P0 反馈处理状态总览
|
||||
|
||||
| # | 反馈类型 | 调研产出 | 主要发现 | 推荐方案 | 等待 Neo 决策 | *反馈* |
|
||||
|---|---|---|---|---|---|
|
||||
| **P0-1** | 让深入调研 | [P0-1-SPI-research.md](P0-1-SPI-research.md) | **测试库 27 = 代码 27 = BD 27 = SPEC §T3 27**,4 方一致;**只有 3 处文档写错数字** | 改 3 处文档(5 分钟) | "更新所有每日 SPI 参数"措辞需澄清(参数表无每日维度) | *因为沙箱机制的存在,配合此机制SPI参数和全部的参数,应该每天都有快照吧?如果你觉得合理,可以深入调研关联性和影响,并适当修改P20-runtime-context-sandbox.md。当然你可能会告诉我更优雅的解决方案,这个我开放讨论* |
|
||||
| **P0-2** | 问理解 + 求建议 | [P0-2-feedback-resolution.md](P0-2-feedback-resolution.md) | Neo 理解大致对,但"3 处都写完整"会带来维护漂移 | **主 + 副**:BD 手册主源 + 3 处链接 | 是否接受"主 + 副"方案 | *同意* |
|
||||
| **P0-3** | 财务看板沙箱影响,要走查 | [P0-3-board-vs-sandbox-analysis.md](P0-3-board-vs-sandbox-analysis.md) | **Grep 实证:三大看板 + xcx_board.py 完全没接入 runtime-clock,沙箱模式下看板仍显真实时间** | Wave 1 必修(5-7h)+ 8 条沙箱测试场景 | 选 A(Wave 1 修)还是 B(仅验证) | *同意你的实施计划,记录为待完成任务,但需要等Wave 0完成后执行,我记得Wave 1需要根据Wave 0进行更新的。* |
|
||||
| **P0-4** | 已选 A 改文档 | (即将处理) | Neo 确认"PRD 落后于实施" | A 改文档,纳入 Wave 5 收口 | 已确认,无待决 | |
|
||||
| **P0-5** | 偏向 B,但要查"为什么改" | [P0-5-matching-evolution.md](P0-5-matching-evolution.md) | **重大颠覆**:FDW 已建但不能用(GUC 不透传 → RLS 失效);直连不是 over-engineering 而是真实 Bug 修复 | **绝不能选 B-1 回退 FDW**;推荐 C 维持现状 + 文档说明,坚持工程一致性可选 B-2(SECURITY DEFINER 包装,4-8h + 跨库回归) | Neo 是否调整 B → C(基于新事实) |
|
||||
*我还是倾向于坚持工程一致性,再深入一些,找到本项目当前类似情况,给个全览,并制定工程规范化和一致性的实施放方案。* |
|
||||
| **P0-6** | 偏向 C,提"沙箱内 / 全局"语义 | (整合到 P0-7 SPEC 中) | clearAllTasks 在沙箱上线后需要拆"全局清空" / "沙箱内清空" | 推迟到沙箱收口后重设计交互 | 是否同意推迟到 P0-7 收口后 | *我同意,但要记录下来。* |
|
||||
| **P0-7** | 让补 SPEC + 深入测试 | SPEC: [P20-runtime-context-sandbox.md](../../prd/specs/P20-runtime-context-sandbox.md)<br>Todos: [P0-7-runtime-context-todos.md](P0-7-runtime-context-todos.md) | SPEC 草稿 14 节 / 5 API 端点;**20 条收口待办**(P0×5 / P1×8 / P2×7),包括看板未接入(P0-3 是其中之一) | SPEC 直接归 docs/prd/specs/ 投入使用;todos 进入 Wave 1-5 滚动消化 | SPEC 文件名 / 编号是否接受 | *很好,我大概看了P20-runtime-context-sandbox.md。有几个问题我一直非常关心的问题,是否已经着重强调,你来帮我确认,若已落地则忽略:代码层面的 lint/typecheck 是否过,架构是否合理固然重要,是项目标准化执行的根基。但对于我而言,最终用户看到的页面/小程序是否真正达到设计目标更为重要,因为**你要对最终成功负责,而客户也最看重最终使用的效果**。那么,视角分两层:1)工程层(必要的根基):代码结构合理、调用链路通、迁移已落库。2)成果层(我最看重的):admin-web —— 用 Playwright MCP 打开浏览器实地走一遍(AI Dashboard / Operations / RunLogs / Triggers / RuntimeContext / TriggerManager 等 6 页 + AIPrewarm 分组);miniprogram —— 用 weixin-devtools-mcp 打开微信开发者工具实地走(财务看板 area 切换、AI 洞察展示、runtime-clock 时间漂移行为、各页面的数据展示是否符合预期 等)3)对于小程序3大板块之一的任务板块,需要切换用户身份,在看板板块收口后,提醒我进行身份修改。* |
|
||||
| **P0-8** | 已选 D | (即将处理) | DBViewer 黑名单 5 关键词漏 ALTER/CREATE/GRANT | 选项 D:白名单 + 只读账号双保险 | 已确认,纳入 Wave 1-3 修代码 | |
|
||||
|
||||
## 二、关键发现摘要(Neo 必须知道的 5 件事)
|
||||
|
||||
### 发现 1:P0-1 不是数据库缺数据,是文档写错数字
|
||||
|
||||
**4 个权威源全部一致是 27**:测试库 / 代码 DEFAULT_PARAMS / BD 手册 / P2 SPEC §T3。
|
||||
**只有 3 处文档写错**:00 数据依赖矩阵 L272 写"26"、P2 SPEC AC6 写"26"(同文件 T3 写 27 自相矛盾)、`scripts/ops/run_seed_spi_params.py` 期望 28。
|
||||
**Step2 实施成本**:5 分钟改 3 处文字 + 1 份审计。Neo 反馈中"更新所有每日 SPI 参数"措辞需要澄清——cfg_index_parameters 表只有 effective_from 维度,无每日历史维度。
|
||||
|
||||
|
||||
### 发现 2:P0-5 的 FDW 已建但是错的
|
||||
|
||||
Neo 原本基于"FDW 没建"的认知偏向选 B 补全 FDW。**事实**:
|
||||
- `setup_fdw.sql` 用 `IMPORT FOREIGN SCHEMA app FROM ...` **已包含** v_dim_staff / v_dim_staff_ex / v_dim_assistant
|
||||
- 但 FDW 跨库**不能透传** `current_setting('app.current_site_id')` GUC,远端 RLS 视图过滤失效
|
||||
- 2026-03-18 21:38 AI 救火改直连,03-20 经用户批准方案 A
|
||||
- **回退到 FDW = 重新引入 RLS 门店隔离失效 Bug**
|
||||
|
||||
**推荐**:从 Neo 原选 B 调整为**选 C(维持直连 + 补文档说明 GUC 限制)**。如确实坚持工程一致性,选 B-2(SECURITY DEFINER 包装,4-8h),不选 B-1。
|
||||
|
||||
|
||||
### 发现 3:P0-3 沙箱看板风险确认
|
||||
|
||||
**Grep 实证**:小程序三大看板 + 后端 xcx_board.py **零处引用** runtime-clock / runtime_context。
|
||||
**意味着**:沙箱模式下其他页(task-list 等)显示虚拟日期,看板页**仍显真实今天数据**,用户体验割裂。
|
||||
**修复成本**:5-7h。**强烈推荐 Wave 1 必修**。
|
||||
|
||||
|
||||
|
||||
|
||||
### 发现 4:P0-7 沙箱远未收口(实证)
|
||||
|
||||
D-3 子代理调研发现 **20 条待办**,涉及:
|
||||
- P0-3 看板未接入(本报告第 3 项)
|
||||
- BD_Manual 与代码冲突(`paused_by_sandbox` 字段文档说有代码已删)
|
||||
- 多门店并行 sandbox 未验证(GUC 串扰)
|
||||
- 清理脚本缺失(sandbox 长用会膨胀 6 张表)
|
||||
- `tests/` 下无 runtime_context pytest
|
||||
|
||||
**收口路径**:9 步,跨 Wave 1-5。SPEC 已起草到 [`P20-runtime-context-sandbox.md`](../../prd/specs/P20-runtime-context-sandbox.md)。
|
||||
|
||||
|
||||
|
||||
### 发现 5:P0-6 应推迟而不是立即修
|
||||
|
||||
Neo 反馈 P0-6 选 C(按 site_id 清 + 二次确认),但同时指出"沙箱上线后清空可能含全局/沙箱内两个语义,需要确认"。
|
||||
**意味着**:P0-6 的最终交互形态依赖 P0-7 沙箱收口。**推荐推迟到 P0-7 收口后再设计 P0-6 交互**,避免反复改。
|
||||
当前可以先在 admin-web 加一个"二次确认弹窗 + 输入门店简称"的临时守卫(1h 工作量),作为临时止血。
|
||||
|
||||
|
||||
|
||||
## 三、按 Wave 分配的执行清单
|
||||
|
||||
| Wave | 主题 | P0 反馈分配 |
|
||||
|---|---|---|
|
||||
| **Wave 1** | Runtime Context 沙箱 | **P0-3 看板接入(必修)** + P0-7 SPEC 投入使用 + 8 条沙箱场景 + 20 条 todos 消化(P0×5 项) |
|
||||
| **Wave 1-3** | 代码 D Bug | **P0-8 DBViewer 白名单**(后端) + P0-6 临时守卫(可选,推迟到沙箱收口后正式重设计) |
|
||||
| **Wave 4** | DWS / RLS / 数据正确性 | 4.1 财务看板 5 项 P2 修复(原 P0-3 主体)+ P0-7 todos 消化(P1×8 项) |
|
||||
| **Wave 5** | 部署 + 文档收尾 | **P0-1 改 3 处文档**(SPI 26→27)+ **P0-2 BD 手册主权威 + 3 处链接**(confirmed_income 术语)+ **P0-4 改 PRD**(备注评分 2 字段)+ P0-5 文档说明(直连原因 + GUC 限制)+ P0-7 todos 消化(P2×7 项) |
|
||||
| **跨 Wave** | 上线门槛 | 满足"看板沙箱接入(Wave 1) + 5 项 ETL 数据准确(Wave 4)"才推 P11 |
|
||||
|
||||
## 四、给 Neo 的决策提问(本会话剩余可处理的项)
|
||||
|
||||
| 问题 | 类型 | 建议 |
|
||||
|---|---|---|
|
||||
| P0-1 "每日 SPI 参数" 措辞要澄清 | 措辞 | 改为"刷新 BD 手册描述 + 数据依赖矩阵 + run_seed_spi_params.py 注释",移除"每日"用词 |
|
||||
| P0-2 "主 + 副"方案是否接受 | 是非 | 是 → Wave 5 落地 / 否 → 仍三处都写 |
|
||||
| P0-3 看板沙箱接入选 A 还是 B | 路径 | A(Wave 1 修)|
|
||||
| P0-5 基于新事实是否调整为 C 或 B-2 | 路径 | C(维持现状)|
|
||||
| P0-6 是否同意推迟到 P0-7 收口后正式重设计 | 路径 | 同意,临时加二次确认守卫 |
|
||||
| P0-7 SPEC 文件名 / 编号 P20 是否接受 | 命名 | 接受,后续可调 |
|
||||
|
||||
回答这 6 个问题后,P0 全部进入实施轨道。
|
||||
|
||||
## 五、产出文件索引
|
||||
|
||||
```
|
||||
docs/_overview/04a-feedback/
|
||||
├── 00-P0-feedback-response-summary.md (本文)
|
||||
├── P0-1-SPI-research.md (D-1 调研)
|
||||
├── P0-2-feedback-resolution.md (主线建议)
|
||||
├── P0-3-board-vs-sandbox-analysis.md (主线分析)
|
||||
├── P0-5-matching-evolution.md (D-2 调研)
|
||||
└── P0-7-runtime-context-todos.md (D-3 todos)
|
||||
|
||||
docs/prd/specs/
|
||||
└── P20-runtime-context-sandbox.md (D-3 SPEC 草稿)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> 04b / 04c 反馈处理待 Neo 完成标注后,主线再开第二轮反馈响应。
|
||||
@@ -0,0 +1,161 @@
|
||||
# P0 反馈响应总报告(第二轮)
|
||||
|
||||
> 日期:2026-05-04 / 触发:Neo 在 `00-P0-feedback-response-summary.md` 表格里写下斜体二轮反馈
|
||||
> 主线 + 2 个子代理(F-1 沙箱+SPI / F-2 工程规范一致性)调研整合 / 状态:**调研完成,等 Neo 拍板进入实施**
|
||||
|
||||
本报告聚合第二轮 6 条 P0 反馈处理状态。第一轮总报告见 [`00-P0-feedback-response-summary.md`](00-P0-feedback-response-summary.md)。
|
||||
|
||||
## 一、第二轮 6 条 P0 反馈处理状态
|
||||
|
||||
| # | 二轮反馈摘要 | 调研产出 | 关键结论 | 等待 Neo 决策 |
|
||||
|---|---|---|---|---|
|
||||
| **P0-1** | 沙箱+SPI 每天快照?是否合理?更优雅方案? | [P0-1-sandbox-snapshot-design.md](P0-1-sandbox-snapshot-design.md) | **不需要每日快照**;SCD2 区间方案更优雅;只需让 ETL 改 3 处 SQL 走 `app.v_cfg_*` 视图(0.5 天) | 接受方案 1?P20 SPEC 加 §1.4/§3.5/§5.6/AC14-15/§11.2/T16-T17? |
|
||||
| **P0-2** | 同意"主+副"方案 | (已确认) | Wave 5 落地 | 已确认 |
|
||||
| **P0-3** | 同意,等 Wave 0 完成后,Wave 1 据 Wave 0 更新 | (已确认) | **Wave 0 即将收尾,Wave 1 计划基于本轮 P0/P1 反馈更新** | 已确认 |
|
||||
| **P0-5** | 倾向工程一致性,要全览 + 实施方案 | [P0-5-engineering-consistency-overview.md](P0-5-engineering-consistency-overview.md) | **24 个偏离点 / 14 规范** + **2 个关键发现:4 处必坏残留 + JWT aud 缺失** | 接受统一治理方案?立即治理 6 项进 Wave 1-3? |
|
||||
| **P0-6** | 同意,要记录 | [P0-6-record.md](P0-6-record.md) | 两阶段方案:阶段 1 临时止血 1h / 阶段 2 沙箱收口后正式重设计 3-5h | 已确认 |
|
||||
| **P0-7** | P20 SPEC 是否覆盖成果层走查?(Playwright 6 页 + 微信开发者工具看板 + 切身份)| [P0-7-spec-acceptance-layer-check.md](P0-7-spec-acceptance-layer-check.md) | **成果层走查严重缺失**;给出 P20 §15 完整 patch | 选 A(直接补到 P20)/ B(单开手册)/ C(走查时即时补) |
|
||||
|
||||
## 二、5 件 Neo 必须知道的事(浓缩版)
|
||||
|
||||
### 1. P0-1 — 不需要每日快照,SCD2 视图入口更优雅(0.5 天工作)
|
||||
|
||||
F-1 调研结论:
|
||||
- **每日参数快照不需要**(成本高,数据冗余)
|
||||
- **SCD2 区间已存在**(`cfg_index_parameters / cfg_level_price / cfg_performance_tier / cfg_bonus_rules` 等 4 张表已有 effective_from/effective_to)
|
||||
- **核心裂缝**:P20 视图层已加业务日上界,但 ETL 任务**直读 `dws.cfg_*` 裸表绕过视图**
|
||||
- **修复方案**:让所有读取入口走 `app.v_cfg_*` 视图,3 处 SQL 改造 + 修复一个 NULL 兼容 bug,**0.5 天工作量**
|
||||
|
||||
**意外收获**:`base_dws_task.py:540-581` 工资任务 3 个 `_load_*` 函数不带 `effective_from`,把所有历史区间行全取出再 Python 挑 — **既是历史 Bug 又是沙箱缺口**,需要一并修。
|
||||
|
||||
**应增加到 P20 SPEC 的 6 处**:§1.4 沙箱影响项 / §3.5 NULL 兼容 / §5.6 ETL 读取约定 / AC14-AC15 / §11.2 hack / T16-T17 任务。
|
||||
|
||||
### 2. P0-5 — 24 个偏离点,2 条致命发现
|
||||
|
||||
F-2 全项目扫出 24 个工程规范偏离点。**最严重的 2 条要立即修(D Bug)**:
|
||||
|
||||
#### 致命 1:4 处"伪 FDW"必坏残留代码
|
||||
|
||||
文件:
|
||||
- `apps/backend/app/routers/tenant_users.py:425, 450`
|
||||
- `apps/backend/app/routers/tenant_excel.py:390, 407`
|
||||
- `apps/backend/app/routers/tenant_clues.py:113-119`
|
||||
|
||||
**症状**:
|
||||
- 用 `get_etl_readonly_connection(site_id)` 已经直连 ETL 库
|
||||
- 但 SQL 仍写 `FROM fdw_etl.*`(因为 H2 改造时漏改)
|
||||
- **生产必报 schema 不存在**,被 try/except 静默吞
|
||||
- 接口永远返回空列表,用户看不到错误
|
||||
|
||||
**与 P0-5 主体同源同性质,但危害更高**(因为用户看到的是"空数据"而非"报错")。
|
||||
|
||||
**修复**:每处把 `fdw_etl.` 改为对应 ETL 库 schema(`dwd.` 或 `app.`),不补 FDW(P0-5 主体已结论 FDW 不能用 GUC)。**1-2h 工作量**。
|
||||
|
||||
#### 致命 2:JWT aud 缺失,跨端横向越权风险
|
||||
|
||||
文件:`apps/backend/app/auth/jwt.py`
|
||||
|
||||
**症状**:
|
||||
- 签发 admin / miniapp token **完全不带 `aud` 字段**
|
||||
- `decode_access_token` 也不校验 aud
|
||||
- 只有 tenant-admin 走完整 aud 流程
|
||||
- **意味着 admin / 小程序 token 在 payload 层无法区分,跨端横向越权风险**
|
||||
- CLAUDE.md 规范 R5(JWT 三类 aud 严格隔离)与代码完全脱节
|
||||
|
||||
**修复**:`generate_access_token` 加 `audience=` 参数,`decode_access_token` 校验 aud。**2-3h 工作量** + 跨端 token 兼容性测试。
|
||||
|
||||
#### 24 偏离点全分布
|
||||
|
||||
| 优先级 | 数量 | 主要内容 |
|
||||
|---|---|---|
|
||||
| **立即治理 P0** | 6 | 4 处 fdw_etl.* 必坏 / JWT aud 缺失 / 测试目录嵌套违规 |
|
||||
| Wave 协同 | 6 | matching/business_date 透传 / ETL 写连接 / consume_money 划线价 / note_service 调度 / 测试 DSN 加载 / camelCase 一致性 |
|
||||
| 长期治理 | 5 | global_readonly 无 RLS / DWS 双 schema 30+ 缺失 / 飞球 API 硬编码 |
|
||||
| 可接受归档 | 4 | consume_money 零值判定 / SSE 直调 DashScope / tenant_member_id 映射 / demo AGENTS.md |
|
||||
| 其他 | 3 | (调研中归类) |
|
||||
|
||||
**给 Neo 的核心提问**:接受立即治理 6 项进 Wave 1-3 吗?(其中 2 条致命 D Bug 不修会影响生产稳定性 + 安全)
|
||||
|
||||
### 3. P0-7 — 成果层走查严重缺失(已写完整 §15 patch)
|
||||
|
||||
P20 SPEC §8 13 条 AC 主要是 SQL/curl 工程层验证,只有零散用户视角点检(AC1/5/7/10)。Neo 强调的:
|
||||
- ❌ admin-web 6 页 Playwright 实地走 — **0 个 AC 覆盖**
|
||||
- ❌ AIPrewarm 分组 — **0 个 AC**
|
||||
- ❌ 小程序看板沙箱接入(P0-3 主体)— **AC 中只字未提**
|
||||
- ❌ 多角色身份切换走查 — **0 个 AC**
|
||||
|
||||
**已产出 P20 § 15 完整 patch**(详见 `P0-7-spec-acceptance-layer-check.md`):
|
||||
- §15.1 验证哲学(工程层 vs 成果层)
|
||||
- §15.2 admin-web 10 条走查清单
|
||||
- §15.3 小程序 10 条走查清单(含 P0-3 看板 3 页必修)
|
||||
- §15.4 跨页时间漂移走查
|
||||
- §15.5 多角色身份走查 + **看板收口后我主动提醒 Neo 切身份**(对齐你的反馈)
|
||||
- §15.6 走查产物归档约定
|
||||
|
||||
**3 个落地选项**(等 Neo 选):
|
||||
- A 直接补到 P20 §15(推荐,30 分钟)
|
||||
- B 单开《沙箱成果层验证手册》
|
||||
- C 先列 todos,Wave 1 走查时即时补
|
||||
|
||||
### 4. P0-6 — 已记录两阶段方案
|
||||
|
||||
- **阶段 1 临时止血**(1h,Wave 1-3 任意时点):admin-web 加二次确认弹窗 + 输入门店简称
|
||||
- **阶段 2 正式重设计**(3-5h,P0-7 收口后):拆"全局清空" / "沙箱内清空"语义 + `?scope=` query param
|
||||
|
||||
详见 [P0-6-record.md](P0-6-record.md)。
|
||||
|
||||
### 5. P0-3 + P0-2 — 已确认,排入 Wave
|
||||
|
||||
- P0-2 BD 手册主源 + 3 处链接 → **Wave 5 落地**
|
||||
- P0-3 看板沙箱接入 → **Wave 1 修**(等 Wave 0 收尾后,Wave 1 计划基于本轮 P0/P1 反馈更新)
|
||||
|
||||
## 三、按 Wave 重新分配的执行清单(整合两轮 P0)
|
||||
|
||||
| Wave | 主要任务 | 第二轮新增 |
|
||||
|---|---|---|
|
||||
| **Wave 1** | P0-3 看板接入(必修) + P0-7 SPEC 投入 + 20 todos(P0×5) + P0-7 §15 成果层走查 | **+ P0-1 SCD2 视图入口改造(0.5 天)+ P0-7 §15 P20 patch 落地** |
|
||||
| **Wave 1-3** | P0-6 临时守卫 + P0-8 DBViewer 白名单 + 4 D Bug | **+ 致命 1:4 处 fdw_etl.* 残留(1-2h)+ 致命 2:JWT aud 缺失(2-3h)+ 测试目录嵌套违规** |
|
||||
| **Wave 2** | (P1 主体见 P1 总报告) | **+ P0-5 Wave 协同 6 项(matching/ETL 写连接/consume_money 划线价/note_service/测试 DSN/camelCase)** |
|
||||
| **Wave 4** | DWS / RLS / 数据正确性 + P0-7 todos(P1×8) | (无新增) |
|
||||
| **Wave 5** | P0-1/P0-2/P0-4 文档 + P0-7 todos(P2×7) | **+ P0-5 长期治理 5 项 + 可接受归档 4 项文档化** |
|
||||
| **跨 Wave** | P1-7 admin API PRD + dev-trace Drop | (P1 主体) |
|
||||
| **P0-6 阶段 2** | P0-7 收口后做(等沙箱完成) | 沙箱内/全局清空语义重设计 |
|
||||
|
||||
## 四、给 Neo 的决策提问(本会话剩余可处理的项)
|
||||
|
||||
| # | 问题 | 类型 | 我的建议 |
|
||||
|---|---|---|---|
|
||||
| 1 | P0-1 接受 SCD2 视图入口方案 + P20 SPEC 加 6 处 patch | 路径 | Y |
|
||||
| 2 | P0-1 base_dws_task.py 工资任务 3 处 `_load_*` Bug 是否一并修 | Bug 单 | Y(独立 P1 Bug,Wave 1-2 修) |
|
||||
| 3 | P0-5 接受 24 偏离点的 4 类分级 | 校准 | Y |
|
||||
| 4 | P0-5 致命 1(4 处 fdw_etl 残留)修复 1-2h 立即进 Wave 1-3 | 路径 | Y(D Bug,影响生产) |
|
||||
| 5 | P0-5 致命 2(JWT aud 缺失)修复 2-3h 立即进 Wave 1-3 | 路径 | Y(D Bug,跨端越权) |
|
||||
| 6 | P0-5 立即治理 6 项 / Wave 协同 6 项 / 长期治理 5 项 / 可接受 4 项 各自纳入哪个 Wave | 排程 | 见 §三 表 |
|
||||
| 7 | P0-5 CI 自动化校验建议(静态规则+pre-commit hook+周报)是否落地 | 工具化 | Y(长期投资) |
|
||||
| 8 | P0-7 P20 SPEC §15 成果层走查 落地选 A/B/C | 路径 | A(直接补到 P20) |
|
||||
| 9 | P0-7 §15.5 多角色身份提醒机制(看板收口后我主动提醒切身份) | 协同方式 | Y |
|
||||
| 10 | P0-7 §15.2 走查清单是否再加 `/tenant-admins` / `/etl-tasks` | 范围 | Y(补全) |
|
||||
|
||||
## 五、产出文件索引(累积)
|
||||
|
||||
```
|
||||
docs/_overview/04a-feedback/
|
||||
├── 00-P0-feedback-response-summary.md (第一轮总报告,Neo 在表格里写斜体二轮反馈)
|
||||
├── 00-P0-round2-feedback-response-summary.md (本文)
|
||||
├── P0-1-SPI-research.md (D-1 第一轮)
|
||||
├── P0-1-sandbox-snapshot-design.md (F-1 第二轮)
|
||||
├── P0-2-feedback-resolution.md (主线第一轮)
|
||||
├── P0-3-board-vs-sandbox-analysis.md (主线第一轮)
|
||||
├── P0-5-matching-evolution.md (D-2 第一轮)
|
||||
├── P0-5-engineering-consistency-overview.md (F-2 第二轮)
|
||||
├── P0-6-record.md (主线第二轮)
|
||||
├── P0-7-runtime-context-todos.md (D-3 第一轮)
|
||||
└── P0-7-spec-acceptance-layer-check.md (主线第二轮)
|
||||
|
||||
docs/prd/specs/
|
||||
└── P20-runtime-context-sandbox.md (D-3 第一轮 SPEC 草稿)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> 等 Neo 答这 10 个 Y/N 问题 + 完成 04c 反馈处理,P0 完整收口进入 Wave 实施轨道。
|
||||
99
docs/_overview/04a-feedback/NEO-DECISIONS-LOG.md
Normal file
99
docs/_overview/04a-feedback/NEO-DECISIONS-LOG.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Neo 决策记录(P0 + P1 + P2 累积)
|
||||
|
||||
> 日期:2026-05-04 起 / 用途:记录 Neo 在反馈中已拍板的决策,作为后续 Wave 实施的判据
|
||||
> 维护方式:每轮反馈后由主线增补,Neo 校核
|
||||
|
||||
## 一、P0 第二轮决策(全部 Y,P0-7 选 A)
|
||||
|
||||
来源:Neo 在本会话回复中明确确认。
|
||||
|
||||
| # | 问题 | Neo 决策 | 后续动作 |
|
||||
|---|---|---|---|
|
||||
| 1 | P0-1 SCD2 视图入口 + P20 SPEC 加 6 处 patch | **Y** | Wave 1 改 ETL 3 处 SQL + 修 NULL 兼容 + P20 加 §1.4/§3.5/§5.6/AC14-15/§11.2/T16-T17 |
|
||||
| 2 | P0-1 base_dws_task 工资任务 3 处 Bug 一并修 | **Y** | 作为独立 P1 Bug,Wave 1-2 修 |
|
||||
| 3 | P0-5 接受 24 偏离点 4 类分级 | **Y** | 立即治理 6 + Wave 协同 6 + 长期治理 5 + 可接受 4 + 其他 3 |
|
||||
| 4 | P0-5 致命 1(4 处 fdw_etl 残留 1-2h) | **Y(D Bug)** | 立即进 Wave 1-3,修 tenant_users/excel/clues 共 4 处 |
|
||||
| 5 | P0-5 致命 2(JWT aud 缺失 2-3h) | **Y(D Bug)** | 立即进 Wave 1-3,修 auth/jwt.py 签发 + 校验 |
|
||||
| 6 | P0-5 各级分别纳入 Wave | **Y(按 §三 表)** | 见 [00-P0-round2-feedback-response-summary.md §三](00-P0-round2-feedback-response-summary.md) |
|
||||
| 7 | P0-5 CI 自动化校验落地 | **Y(长期投资)** | pre-commit hook + 周报,Wave 5 起步 |
|
||||
| 8 | P0-7 P20 §15 落地 | **A 直接补到 P20** | 主线立即写入 P20(30 分钟) |
|
||||
| 9 | P0-7 §15.5 多角色身份提醒机制 | **Y** | 看板沙箱接入完成时主动提醒 Neo 切身份 |
|
||||
| 10 | P0-7 §15.2 走查再加 `/tenant-admins` + `/etl-tasks` | **Y** | 主线在 §15 patch 中补全(共 12 路由) |
|
||||
|
||||
## 二、P0 第一轮决策(已拍板)
|
||||
|
||||
来自 [00-P0-feedback-response-summary.md](00-P0-feedback-response-summary.md):
|
||||
|
||||
| 项 | Neo 决策 |
|
||||
|---|---|
|
||||
| P0-2 | "主 + 副"方案,Wave 5 落地 |
|
||||
| P0-3 | Wave 0 完成后,Wave 1 据 Wave 0 更新 → A 修 |
|
||||
| P0-4 | A 改文档,Wave 5 落地(已确认) |
|
||||
| P0-6 | 同意推迟到 P0-7 收口后,临时加二次确认守卫 |
|
||||
|
||||
## 三、P1 第一轮决策
|
||||
|
||||
来自 [04b-feedback/00-P1-feedback-response-summary.md](../04b-feedback/00-P1-feedback-response-summary.md):
|
||||
|
||||
| # | Neo 决策 | Wave |
|
||||
|---|---|---|
|
||||
| P1-1 | 接受方案 A,Wave Neo 由主线决定 → **Wave 2** | Wave 2 |
|
||||
| P1-2 | 同意 | Wave 5 |
|
||||
| P1-3 | 接受 SPEC 化"cross-page-params-spec.md" | Wave 1-3 + Wave 5 |
|
||||
| P1-4 | (默认接受 A 改文档,从清单消除) | Wave 5 |
|
||||
| P1-5 | 接受 packages/shared 跨包枚举 | Wave 2 |
|
||||
| P1-6 | 接受方案 A 完全合并(保留 unified) | Wave 2 |
|
||||
| P1-7 | 接受 B+D 混合,Wave 1 起批 1 | 跨 Wave |
|
||||
| P1-8 | 同意 3 种触发条件 | Wave 1-3 |
|
||||
| P1-9 | 同意 | Wave 5 |
|
||||
| P1-10 | (默认接受 B 现状对,从清单移除) | — |
|
||||
| P1-11 | 同意,前端已 6 分支,补后端契约 | Wave 1 |
|
||||
| P1-12 | 接受 0=散客 + isScattered | Wave 4 |
|
||||
| P1-13 | 担忧"上下文复杂",**G-1 调研发现 tasks.md 严重失实** | **Phase 0-3 渐进** |
|
||||
| extra dev-trace | 接受 Drop,Wave 排序由主线决定 → **Wave 5** | Wave 5 |
|
||||
|
||||
## 四、P1 第二轮决策(Neo 已答)
|
||||
|
||||
| # | 问题 | 主线建议 | Neo 决策 |
|
||||
|---|---|---|---|
|
||||
| 1 | P1-13 Phase 0-3 渐进路径 | Y | **关闭**(以现状为准) |
|
||||
| 2 | P1-13 Phase 0 立即跑 SELECT | Y | **关闭** |
|
||||
| 3 | P1-13 先校正 tasks.md 标记 | Y | **关闭** |
|
||||
| 4 | dev-trace 排到 Wave 5 单 PR 1-2h | Y | **Y 同意** |
|
||||
| 5 | tasks.md 跨 Wave 真实性审计任务(40 份 / 25-30h) | 强烈 Y | **N 不做**(浪费时间) |
|
||||
| 6 | tasks.md hook 防再撒谎 | Y | **N 不做** |
|
||||
| 7 | 战略.3 P11 上线门槛(看板沙箱 + 5 项 ETL + 致命 1+2 修) | Y | **Y 必须满足** |
|
||||
| 8 | 战略.4 答完后开 Wave 1 | Y | **Y** |
|
||||
|
||||
**Neo 总指示**:"关于 Krio 的 task.md 引发的 P1-13 问题关闭,时间久了。关于 Krio 的 spec 造成的现实与文档差异,以当前现实情况为准。"
|
||||
|
||||
## 五、P2 决策(已读 04c)
|
||||
|
||||
来自 [04c-feedback/00-P2-feedback-response-summary.md](../04c-feedback/00-P2-feedback-response-summary.md):
|
||||
|
||||
### 5.1 直接同意(8 + 5 子项 = 13 项)
|
||||
|
||||
| 项 | 决策 |
|
||||
|---|---|
|
||||
| P2-1/2/3/5/8/10/11/12 | 选 A 或同意,Wave 5 落地 |
|
||||
| P2-13.1/3/4/5 | A 或同意建议 |
|
||||
| P2-13.2 | **B**(改原建议 A→B,代码源 + 自动生成) |
|
||||
|
||||
### 5.2 P2 待答项 Neo 反馈(已处理)
|
||||
|
||||
| # | 问题 | 主线建议 | Neo 决策 |
|
||||
|---|---|---|---|
|
||||
| P2.A | P2-4 ROOM 死代码 | 去掉(BD 修订) | **重新调研**(H-1 错了,course/category 是不同概念,课程分布在多表)|
|
||||
| P2.B | P2-4 service-record-card 命名统一 | Y | (P2-4 重新调研后再定)|
|
||||
| P2.C | P2-7 board-finance 隐式 null | Y D Bug | **找到的 Bug 修复**(原行为可接受) |
|
||||
| P2.D | P2-6 R2 起步 | Y | **同意**(后续观察跟踪) |
|
||||
| P2.E | P2-9 H-2 7 审稿题 | Y | **同意**(全接受主线建议) |
|
||||
| H2.1-7 | P2-9 设计细节 | 各项建议 | **同意**(全接受) |
|
||||
|
||||
## 六、变更记录
|
||||
|
||||
| 日期 | 事件 |
|
||||
|---|---|
|
||||
| 2026-05-04 | P0 二轮 + P1 一轮决策入库 |
|
||||
| 2026-05-04 | P0 二轮全部 Y 确认 + P1 二轮 / P2 13 项部分确认 |
|
||||
| 2026-05-04 | 全局决策仪表板 GLOBAL-DECISION-DASHBOARD.md 建立 |
|
||||
353
docs/_overview/04a-feedback/P0-1-SPI-research.md
Normal file
353
docs/_overview/04a-feedback/P0-1-SPI-research.md
Normal file
@@ -0,0 +1,353 @@
|
||||
# P0-1 SPI 参数深入调研报告(step1)
|
||||
|
||||
> 日期:2026-05-04
|
||||
> 触发:Neo 在 `04a-conflicts-P0-detail.md` § P0-1 反馈,要求"先深入调研后实施"
|
||||
> 调研者:子代理(SPI 深入调研专项)
|
||||
> 范围:仅 step1(A 实现 PRD 全集 + B 历史变更 + C 当前实现),step2 实施由 Neo 拍板后另开
|
||||
> 结论先行:测试库实际 27 行 SPI 参数,与代码 DEFAULT_PARAMS、BD 手册三方一致;
|
||||
> 文档矛盾根源:`docs/prd/specs/00-数据依赖矩阵.md` L272 写"26 个" 是文档过期,
|
||||
> `scripts/ops/run_seed_spi_params.py` L62 写"期望 28 个" 是脚本设计期未对齐最终值。
|
||||
|
||||
---
|
||||
|
||||
## 一、SPI 是什么(业务定位摘要)
|
||||
|
||||
**SPI**(Spending Power Index,消费力指数)是 NeoZQYY 指数体系第 7 个指数(继 WBI/NCI/RS/OS/MS/ML 之后),粒度为 `(site_id, member_id)`,**衡量会员在门店内的综合消费力层级**。
|
||||
|
||||
**业务回答的问题**:
|
||||
- 这个客户整体消费能力在门店内属于什么层级?
|
||||
- 近期消费推进速度是否明显变快?
|
||||
- 是稳定高消费,还是偶发冲高?
|
||||
|
||||
**运营定位**(与其他指数的分工):
|
||||
- NCI/WBI:决定"要不要优先触达"(紧迫度)
|
||||
- **SPI:决定"投入多大资源、用什么档位策略"**(消费力分层)
|
||||
- OS/RS/MS/ML:决定"谁来做、什么时候、谁更容易做成"(关系归属)
|
||||
|
||||
**算法结构**:主分(SPI_raw)= w_L × Level + w_S × Speed + w_P × Stability,默认权重 0.60/0.30/0.10。三子分各自由"金额压缩 + 加权"得到原始分,再经 BaseIndexTask 统一映射为 [0, 10] 展示分。
|
||||
|
||||
---
|
||||
|
||||
## 二、SPI 三子分参数清单(基于 PRD + SPEC + 代码 + DB 四方对照)
|
||||
|
||||
> 列说明:
|
||||
> - **PRD 默认值** = `docs/prd/SPI 消费力指数.md` § 8.2 的取值(部分基数标"按门店分布校准",此处用"校准"表示)
|
||||
> - **SPEC 默认值** = `docs/specs/spi-spending-power-index/design.md` 中 DEFAULT_PARAMS 的取值
|
||||
> - **代码默认值** = `apps/etl/connectors/feiqiu/tasks/dws/index/spending_power_index_task.py` L70-L106
|
||||
> - **DB 实际值** = 测试库 `test_etl_feiqiu.dws.cfg_index_parameters` 当前取值(2026-05-04 查)
|
||||
> - 本表共 27 行,与代码 DEFAULT_PARAMS、BD 手册、测试库 DB 三方一致
|
||||
|
||||
### 2.1 窗口与平滑(3 个)
|
||||
|
||||
| # | 参数 key | 含义 | PRD | SPEC | 代码 | DB |
|
||||
|---|---------|------|-----|------|------|-----|
|
||||
| 1 | `spend_window_short_days` | 短窗口(速度) | 30 | 30 | 30 | 30 |
|
||||
| 2 | `spend_window_long_days` | 长窗口(层级/稳定性上限) | 90 | 90 | 90 | 90 |
|
||||
| 3 | `ewma_alpha_daily_spend` | 日消费 EWMA 平滑系数 | 0.3 | 0.3 | 0.3 | 0.3 |
|
||||
|
||||
### 2.2 金额压缩基数(6 个,可被门店中位数自动校准覆盖)
|
||||
|
||||
| # | 参数 key | 含义 | PRD | SPEC | 代码 | DB |
|
||||
|---|---------|------|-----|------|------|-----|
|
||||
| 4 | `amount_base_spend_30` | 30 天消费压缩基数 | 校准 | 500.0 | 500.0 | 500.0 |
|
||||
| 5 | `amount_base_spend_90` | 90 天消费压缩基数 | 校准 | 1500.0 | 1500.0 | 1500.0 |
|
||||
| 6 | `amount_base_ticket_90` | 90 天客单压缩基数 | 校准 | 200.0 | 200.0 | 200.0 |
|
||||
| 7 | `amount_base_recharge_90` | 90 天充值压缩基数 | 校准 | 1000.0 | 1000.0 | 1000.0 |
|
||||
| 8 | `amount_base_speed_abs` | 绝对速度压缩基数 | 校准 | 100.0 | 100.0 | 100.0 |
|
||||
| 9 | `amount_base_ewma_90` | EWMA 速度压缩基数 | 校准 | 50.0 | 50.0 | 50.0 |
|
||||
|
||||
### 2.3 总分权重(3 个)
|
||||
|
||||
| # | 参数 key | 含义 | PRD | SPEC | 代码 | DB |
|
||||
|---|---------|------|-----|------|------|-----|
|
||||
| 10 | `weight_level` | 总分中 Level 权重 | 0.60 | 0.60 | 0.60 | 0.60 |
|
||||
| 11 | `weight_speed` | 总分中 Speed 权重 | 0.30 | 0.30 | 0.30 | 0.30 |
|
||||
| 12 | `weight_stability` | 总分中 Stability 权重 | 0.10 | 0.10 | 0.10 | 0.10 |
|
||||
|
||||
> 校验:0.60 + 0.30 + 0.10 = 1.00 (`run_seed_spi_params.py` 第 96-103 行 SQL 已覆盖此校验)
|
||||
|
||||
### 2.4 Level 子分内部权重(4 个)
|
||||
|
||||
| # | 参数 key | 含义 | PRD | SPEC | 代码 | DB |
|
||||
|---|---------|------|-----|------|------|-----|
|
||||
| 13 | `w_level_spend_30` | Level 子分中 30 天消费项 | 0.30 | 0.30 | 0.30 | 0.30 |
|
||||
| 14 | `w_level_spend_90` | Level 子分中 90 天消费项 | 0.35 | 0.35 | 0.35 | 0.35 |
|
||||
| 15 | `w_level_ticket_90` | Level 子分中 90 天客单项 | 0.20 | 0.20 | 0.20 | 0.20 |
|
||||
| 16 | `w_level_recharge_90` | Level 子分中 90 天充值项 | 0.15 | 0.15 | 0.15 | 0.15 |
|
||||
|
||||
> 校验:0.30 + 0.35 + 0.20 + 0.15 = 1.00
|
||||
|
||||
### 2.5 Speed 子分内部权重(3 个)
|
||||
|
||||
| # | 参数 key | 含义 | PRD | SPEC | 代码 | DB |
|
||||
|---|---------|------|-----|------|------|-----|
|
||||
| 17 | `w_speed_abs` | 绝对速度项 | 0.50 | 0.50 | 0.50 | 0.50 |
|
||||
| 18 | `w_speed_rel` | 相对速度项(加速) | 0.30 | 0.30 | 0.30 | 0.30 |
|
||||
| 19 | `w_speed_ewma` | EWMA 速度项 | 0.20 | 0.20 | 0.20 | 0.20 |
|
||||
|
||||
> 校验:0.50 + 0.30 + 0.20 = 1.00
|
||||
|
||||
### 2.6 稳定性参数(2 个,90 天上限)
|
||||
|
||||
| # | 参数 key | 含义 | PRD | SPEC | 代码 | DB |
|
||||
|---|---------|------|-----|------|------|-----|
|
||||
| 20 | `stability_window_days` | 稳定性窗口(固定上限 90) | 90 | 90 | 90 | 90 |
|
||||
| 21 | `use_stability` | 是否启用稳定性子分(0/1) | 1 | 1 | 1 | 1 |
|
||||
|
||||
> PRD § 8.2 还列了一个 `stability_mode`(默认 1=周覆盖率),**但 SPEC/代码/DB 均未实现**。当前只有"周覆盖率"一种实现,因此 `stability_mode` 落地时被合理省略——这不是缺失。
|
||||
|
||||
### 2.7 映射与平滑(5 个,复用 BaseIndexTask)
|
||||
|
||||
| # | 参数 key | 含义 | PRD | SPEC | 代码 | DB |
|
||||
|---|---------|------|-----|------|------|-----|
|
||||
| 22 | `percentile_lower` | 下分位 | 5 | 5 | 5 | 5 |
|
||||
| 23 | `percentile_upper` | 上分位 | 95 | 95 | 95 | 95 |
|
||||
| 24 | `compression_mode` | 压缩模式(0/1/2) | 1 | 1 | 1 | 1 |
|
||||
| 25 | `use_smoothing` | 是否分位 EWMA 平滑 | 1 | 1 | 1 | 1 |
|
||||
| 26 | `ewma_alpha` | 分位 EWMA 平滑系数 | 0.2 | 0.2 | 0.2 | 0.2 |
|
||||
|
||||
### 2.8 速度计算辅助(1 个,代码新增)
|
||||
|
||||
| # | 参数 key | 含义 | PRD | SPEC | 代码 | DB |
|
||||
|---|---------|------|-----|------|------|-----|
|
||||
| 27 | `speed_epsilon` | 防除零小量 | (隐含) | 1e-6 | 1e-6 | 1e-6 |
|
||||
|
||||
> PRD § 5.3 公式 V_rel = ln((v_30 + ε) / (v_90 + ε)) 用了 ε,但 § 8.2 没把 ε 单列为参数。SPEC 与代码把它显式化为 `speed_epsilon=1e-6`,这是工程合理的"PRD 隐含→代码显式"补全,不是参数膨胀。
|
||||
|
||||
### 2.9 期望参数总数对比
|
||||
|
||||
| 来源 | 期望数量 | 是否与实际(27)吻合 | 备注 |
|
||||
|------|---------|-------------------|------|
|
||||
| PRD § 8.2 表格累加 | 27(若把 stability_mode 算上则 28) | 否(差 1,stability_mode 未落地) | PRD 写了 stability_mode 默认 1,但工程未实现 |
|
||||
| SPEC `P2-etl-dws-miniapp-extensions.md` § AC6 | 26 | **否(少 1)** | T3 任务清单又写"27 个",**SPEC 内部前后矛盾** |
|
||||
| SPEC `P2-etl-dws-miniapp-extensions.md` § T3 | 27 | **是** | 与代码 DEFAULT_PARAMS 一致 |
|
||||
| SPEC `design.md` DEFAULT_PARAMS | 27 | **是** | 27 行清单 |
|
||||
| `docs/prd/specs/00-数据依赖矩阵.md` L272 | 26 | **否(少 1)** | 文档过期,当前 P0-1 冲突的源头 |
|
||||
| BD 手册 `BD_Manual_dws_member_spending_power_index.md` § 5 | 27 | **是** | 表格 27 行 |
|
||||
| 代码 `DEFAULT_PARAMS` | 27 | **是** | 真理之源 |
|
||||
| 运维脚本 `run_seed_spi_params.py` L62 | **28** | 否(多 1) | 脚本写"期望 28",但实际表里只有 27;若该脚本最近未跑,则只是脚本 stale |
|
||||
| 测试库 `test_etl_feiqiu.dws.cfg_index_parameters` | **27** | (基准) | 实际值 |
|
||||
|
||||
> 真理之源:**测试库 27 行 = 代码 DEFAULT_PARAMS 27 项 = BD 手册 27 行 = SPEC § T3 / SPEC design 27 项**。
|
||||
> 所有偏差都集中在两份文档(00 矩阵 26、SPEC AC6 26)和一个脚本(`run_seed_spi_params.py` 28)上。
|
||||
|
||||
---
|
||||
|
||||
## 三、历史变更时间线
|
||||
|
||||
> 数据来源:`git log --all -- "**spending_power**" "**SPI**"` + `docs/audit/changes/*.md` Grep
|
||||
> 注:本仓库历史 commit 较粗(多为周累积合流),无 SPI 单独 commit。下表按审计文件粒度还原。
|
||||
|
||||
| 日期 | 提交 / 审计文件 | 摘要 |
|
||||
|------|----------------|------|
|
||||
| 2026-02-22 ~ 02-23 | commit `b25308c` "feat: P1-P3 全栈集成" | SPI 任务初次落地:`spending_power_index_task.py` 入仓、`dws.dws_member_spending_power_index` 表创建、cfg_index_parameters 27 行 SPI 种子数据初始化(测试库)。SPEC `P2-etl-dws-miniapp-extensions.md` AC6 标注"effective_from=2026-02-23" |
|
||||
| 2026-02-27 | `docs/audit/changes/2026-02-27__biz-day-cutoff-prd-sync-check.md` | 营业日切点(`BUSINESS_DAY_START_HOUR=8`)全栈贯通,SPI 任务被列入 20+ DWS 任务批量重构清单——`tasks/dws/index/spending_power_index_task.py` 引入 `biz_date_sql_expr` 适配营业日分割。**只改代码,未改参数** |
|
||||
| 2026-03-02 | `docs/audit/changes/2026-03-02__spi-calibration-nonzero-median.md` | **SPI 基数校准改用非零样本中位数**:`_CALIBRATE_MIN_SAMPLE = 10`,零消费会员不再拉低中位数。验证 6/6 回退 → 4/6 有效校准 + 2/6 安全回退。**只改代码,不改 cfg_index_parameters 内容** |
|
||||
| 2026-03-15 | `docs/audit/changes/2026-03-15__ddl-baseline-consolidation-bd-manual-reorg.md` | DDL 基线合并 + BD 手册重整,SPI 表 DDL 从迁移目录被合并到 `docs/database/ddl/etl_feiqiu__dws.sql`。**只动 DDL/文档,不动参数** |
|
||||
| 2026-03-20 | `docs/audit/changes/2026-03-20__rns13-board-apis-e2e-fix.md` 等 | board-customer 看板用到 SPI 展示分,联调修复。**未触参数** |
|
||||
| 2026-03-24 | `docs/audit/changes/2026-03-24__lookback_days_60_to_90.md` | RS/MS/ML 的 `lookback_days` 60→90,**与 SPI 无关**(SPI 用 `spend_window_long_days=90` 而非 lookback_days),但同时间段有人改过指数体系参数,需注意 |
|
||||
|
||||
**关键观察**:
|
||||
1. SPI 自 2026-02-23 落地以来,**参数总数 27 没变过**,只动过代码(校准逻辑改进、营业日切点适配)。
|
||||
2. 历史会话档案(`docs/ai-env-history/` 与 `docs/claude-history/`)Glob 不命中 SPI 关键词——本机历史会话归档主要按时间分目录,无 SPI 专项会话。
|
||||
3. 没有"参数从 26 加到 27"或"从 27 减到 26"的迁移脚本或审计记录。**00 矩阵的 26 是早期文档草稿值,未随后续 27 项落地而更新**。
|
||||
|
||||
---
|
||||
|
||||
## 四、当前测试库实际数据
|
||||
|
||||
### 4.1 总数
|
||||
|
||||
```sql
|
||||
-- 测试库 test_etl_feiqiu @ 2026-05-04
|
||||
SELECT COUNT(*) FROM dws.cfg_index_parameters WHERE index_type='SPI';
|
||||
-- 结果: 27
|
||||
SELECT COUNT(DISTINCT param_name) FROM dws.cfg_index_parameters WHERE index_type='SPI';
|
||||
-- 结果: 27 (无重复 param_name)
|
||||
```
|
||||
|
||||
### 4.2 全部 param_name 清单(按字母序,实际查询结果)
|
||||
|
||||
```
|
||||
1. amount_base_ewma_90 = 50.000000
|
||||
2. amount_base_recharge_90 = 1000.000000
|
||||
3. amount_base_speed_abs = 100.000000
|
||||
4. amount_base_spend_30 = 500.000000
|
||||
5. amount_base_spend_90 = 1500.000000
|
||||
6. amount_base_ticket_90 = 200.000000
|
||||
7. compression_mode = 1.000000
|
||||
8. ewma_alpha = 0.200000
|
||||
9. ewma_alpha_daily_spend = 0.300000
|
||||
10. percentile_lower = 5.000000
|
||||
11. percentile_upper = 95.000000
|
||||
12. speed_epsilon = 0.000001
|
||||
13. spend_window_long_days = 90.000000
|
||||
14. spend_window_short_days = 30.000000
|
||||
15. stability_window_days = 90.000000
|
||||
16. use_smoothing = 1.000000
|
||||
17. use_stability = 1.000000
|
||||
18. w_level_recharge_90 = 0.150000
|
||||
19. w_level_spend_30 = 0.300000
|
||||
20. w_level_spend_90 = 0.350000
|
||||
21. w_level_ticket_90 = 0.200000
|
||||
22. w_speed_abs = 0.500000
|
||||
23. w_speed_ewma = 0.200000
|
||||
24. w_speed_rel = 0.300000
|
||||
25. weight_level = 0.600000
|
||||
26. weight_speed = 0.300000
|
||||
27. weight_stability = 0.100000
|
||||
```
|
||||
|
||||
### 4.3 与 PRD/SPEC 期望对比
|
||||
|
||||
- **缺哪几行**: 0 行(代码 DEFAULT_PARAMS 27 项全部存在)
|
||||
- **多哪几行**: 0 行
|
||||
- **值偏差**: 0 项(每一项的 `param_value` 都与 PRD § 8.2 + SPEC design.md DEFAULT_PARAMS 完全一致)
|
||||
- **结论**: **DB 当前状态是干净的、与代码默认值完全对齐**
|
||||
|
||||
### 4.4 权重归一化校验(运维脚本逻辑搬运)
|
||||
|
||||
| 校验项 | 实际值 | 预期值 | 通过? |
|
||||
|-------|-------|-------|-------|
|
||||
| `weight_level + weight_speed + weight_stability` | 1.00 | 1.00 | ✓ |
|
||||
| Level 内部权重之和(spend_30 + spend_90 + ticket_90 + recharge_90) | 1.00 | 1.00 | ✓ |
|
||||
| Speed 内部权重之和(abs + rel + ewma) | 1.00 | 1.00 | ✓ |
|
||||
|
||||
---
|
||||
|
||||
## 五、代码兜底默认值清单
|
||||
|
||||
> 来源:`apps/etl/connectors/feiqiu/tasks/dws/index/spending_power_index_task.py` L70-L106 `DEFAULT_PARAMS`
|
||||
> 兜底机制:`execute()` L181-L182 `params = {**self.DEFAULT_PARAMS, **db_params}`,**DB 缺失时用代码默认值**
|
||||
|
||||
| param_key | 代码 default | PRD/SPEC default | 一致? |
|
||||
|-----------|-------------|-----------------|-------|
|
||||
| spend_window_short_days | 30 | 30 | ✓ |
|
||||
| spend_window_long_days | 90 | 90 | ✓ |
|
||||
| ewma_alpha_daily_spend | 0.3 | 0.3 | ✓ |
|
||||
| amount_base_spend_30 | 500.0 | (PRD: 校准/SPEC: 500.0) | ✓(SPEC 一致) |
|
||||
| amount_base_spend_90 | 1500.0 | (PRD: 校准/SPEC: 1500.0) | ✓ |
|
||||
| amount_base_ticket_90 | 200.0 | (PRD: 校准/SPEC: 200.0) | ✓ |
|
||||
| amount_base_recharge_90 | 1000.0 | (PRD: 校准/SPEC: 1000.0) | ✓ |
|
||||
| amount_base_speed_abs | 100.0 | (PRD: 校准/SPEC: 100.0) | ✓ |
|
||||
| amount_base_ewma_90 | 50.0 | (PRD: 校准/SPEC: 50.0) | ✓ |
|
||||
| w_level_spend_30 | 0.30 | 0.30 | ✓ |
|
||||
| w_level_spend_90 | 0.35 | 0.35 | ✓ |
|
||||
| w_level_ticket_90 | 0.20 | 0.20 | ✓ |
|
||||
| w_level_recharge_90 | 0.15 | 0.15 | ✓ |
|
||||
| w_speed_abs | 0.50 | 0.50 | ✓ |
|
||||
| w_speed_rel | 0.30 | 0.30 | ✓ |
|
||||
| w_speed_ewma | 0.20 | 0.20 | ✓ |
|
||||
| weight_level | 0.60 | 0.60 | ✓ |
|
||||
| weight_speed | 0.30 | 0.30 | ✓ |
|
||||
| weight_stability | 0.10 | 0.10 | ✓ |
|
||||
| stability_window_days | 90 | 90 | ✓ |
|
||||
| use_stability | 1 | 1 | ✓ |
|
||||
| percentile_lower | 5 | 5 | ✓ |
|
||||
| percentile_upper | 95 | 95 | ✓ |
|
||||
| compression_mode | 1 | 1 | ✓ |
|
||||
| use_smoothing | 1 | 1 | ✓ |
|
||||
| ewma_alpha | 0.2 | 0.2 | ✓ |
|
||||
| speed_epsilon | 1e-6 | (PRD 隐含 ε,SPEC 显式 1e-6) | ✓ |
|
||||
|
||||
**结论**:**代码兜底默认值 100% 与 PRD/SPEC 一致**。即使 cfg_index_parameters 表被清空,SPI 任务也能用 DEFAULT_PARAMS 跑出与当前 DB 完全相同的结果。
|
||||
|
||||
---
|
||||
|
||||
## 六、step2 推荐更新方案(给 Neo 拍板)
|
||||
|
||||
> 调研结论:DB(27)、代码(27)、BD 手册(27)、SPEC § T3(27) 完全对齐。**实际没有"参数缺失"问题,只有"两份文档 + 一个脚本写错数字"的文档维护问题**。
|
||||
|
||||
### 选项 A:仅改文档(推荐,工作量最小)
|
||||
|
||||
**动作**:
|
||||
1. 改 `docs/prd/specs/00-数据依赖矩阵.md` L272: "26 个参数" → "27 个参数"
|
||||
2. 改 `docs/prd/specs/P2-etl-dws-miniapp-extensions.md` AC6: "26 个参数" → "27 个参数",与同文件 T3 任务清单的"27 个参数"对齐
|
||||
3. 改 `scripts/ops/run_seed_spi_params.py` L62: 期望 28 → 27,并修正注释 "应为 28 个" → "应为 27 个"
|
||||
4. 在 `docs/audit/changes/2026-05-04__spi-param-count-doc-fix.md` 写审计
|
||||
|
||||
**优点**:零代码风险、零 DB 风险,5 分钟完成。
|
||||
**劣势**:依赖"DB 当前 27 是正确的"这一事实,所以**前提是 Neo 接受 step1 调研结论**(已在第四节用三方对照证明)。
|
||||
|
||||
### 选项 B:加 CI 校验脚本(配合选项 A,长期保险)
|
||||
|
||||
**动作**:在选项 A 基础上,新增 `scripts/audit/verify_spi_params.py`,实现"对照代码 DEFAULT_PARAMS keyset 与 cfg_index_parameters DISTINCT param_name keyset",有差异时退出码非零,可挂到 pre-commit 或 CI。
|
||||
|
||||
**优点**:防止未来再次出现"代码加了参数但 DB 没补 / DB 残留 deprecated 参数"的漂移。
|
||||
**劣势**:本次冲突解决周期略增,需要一份新脚本 + 测试。
|
||||
|
||||
### 选项 C:补 step2 提到的"更新所有每日 SPI 参数"(最大动作)
|
||||
|
||||
> 关键澄清:Neo 反馈写"完成后更新所有每日的 SPI 参数",这条措辞需要 Neo 进一步明确意图,有两种解读:
|
||||
>
|
||||
> **解读 1**:把"每日跑出来的 SPI 分数"重算一遍。但 SPI 任务本身就是 delete-before-insert 全量刷新,**不存在"历史每日参数"这个概念**——`cfg_index_parameters` 不分天,只有 effective_from。除非 Neo 想做参数版本化(表结构都得改)。
|
||||
>
|
||||
> **解读 2**:把现在 27 行参数依据真实业务数据重新校准、调权重(产品/算法层调优)。这不是"P0-1 文档冲突"的范畴,而是 SPI 算法迭代,工作量很大,需要单独立项。
|
||||
>
|
||||
> **建议先与 Neo 对齐:**P0-1 的目标只是"让文档与现状一致" → 选项 A(+ 选项 B 加保险)就够了。如果 Neo 想做参数调优,应当独立提出 Phase 2 任务,有自己的 SPEC、影子跑数、效果评估。
|
||||
|
||||
### 推荐最终路径
|
||||
|
||||
1. **Neo 拍板**:确认"DB 27 行是正确状态"(基于本报告第四节)。
|
||||
2. **执行选项 A** + **选项 B**:5 分钟改文档 + 新增 ~50 行校验脚本。
|
||||
3. **关闭 P0-1**:在 `docs/_overview/04a-conflicts-P0-detail.md` 标注已解决。
|
||||
4. **如有 SPI 算法调优需求**:独立立项,与本次冲突无关。
|
||||
|
||||
---
|
||||
|
||||
## 七、调研中发现的其他问题(可选,Neo 自行判断是否处理)
|
||||
|
||||
1. **`db/etl_feiqiu/seeds/seed_index_parameters.sql` 文件实际不存在**
|
||||
- `run_seed_spi_params.py` L28 引用此文件,但仓库内查无此文。
|
||||
- BD 手册 § 1 也写"种子数据:`db/etl_feiqiu/seeds/seed_index_parameters.sql`"——同样指向不存在的文件。
|
||||
- 影响:此运维脚本若被运行,会在 L125-L127 因文件不存在直接 `sys.exit(1)`。但这不影响生产,因为 SPI 任务的 27 行参数已经在测试库存在(可能是历史另一种方式 INSERT 进去的,或在已删除的 commit 中有过 seed 文件)。
|
||||
- 建议:在选项 A 中顺手把脚本与 BD 手册的文件引用注释为 "(历史 seed 已合并,本脚本仅作幂等校验)"。
|
||||
|
||||
2. **PRD § 8.2 列了 `stability_mode`,但代码/SPEC/DB 均无**
|
||||
- PRD 默认 `stability_mode=1`(周覆盖率),意图是"留 mode=2/3 供未来切换其他稳定性算法(如 CV/Gini)"。
|
||||
- 当前只实现了周覆盖率(mode 1),所以参数没有意义因此被合理省略。
|
||||
- 建议:不补,但在选项 A 修文档时把 PRD § 8.2 标注 "stability_mode 暂未启用,v2 启用多种稳定性算法时再加",避免将来再被读者误判为"缺失参数"。
|
||||
|
||||
3. **`run_seed_spi_params.py` 的"期望 28" 不仅与 DB 27 不符,与代码 27 也不符**
|
||||
- 推测:脚本被写出来时,原作者打算同时插入 SPI(27) + 一个特殊标记参数(如 `_meta_version`)。但最终未实现这第 28 项,脚本期望值未同步回 27。
|
||||
- 建议:选项 A 修脚本时一起改正。
|
||||
|
||||
4. **`docs/prd/specs/00-数据依赖矩阵.md` L123 "指数总览(WBI/NCI/SPI)" 缺 RS/OS/MS/ML**
|
||||
- 与本次 P0-1 不直接相关,但矩阵在指数枚举上不完整,顺手可补。
|
||||
- 建议:与本次 P0-1 解耦,转 P2 矩阵补充任务。
|
||||
|
||||
---
|
||||
|
||||
## 附录:调研用到的文件清单(便于复核)
|
||||
|
||||
### A.PRD / SPEC
|
||||
|
||||
- `c:\Project\NeoZQYY\docs\prd\SPI 消费力指数.md`(主 PRD,706 行)
|
||||
- `c:\Project\NeoZQYY\docs\prd\specs\P2-etl-dws-miniapp-extensions.md`(实施 SPEC)
|
||||
- `c:\Project\NeoZQYY\docs\prd\specs\00-数据依赖矩阵.md`(冲突源 1,L272)
|
||||
- `c:\Project\NeoZQYY\docs\specs\spi-spending-power-index\design.md`
|
||||
- `c:\Project\NeoZQYY\docs\specs\spi-spending-power-index\requirements.md`
|
||||
- `c:\Project\NeoZQYY\docs\specs\spi-spending-power-index\tasks.md`
|
||||
|
||||
### B.代码
|
||||
|
||||
- `c:\Project\NeoZQYY\apps\etl\connectors\feiqiu\tasks\dws\index\spending_power_index_task.py`(SPI 任务实现,837 行)
|
||||
- `c:\Project\NeoZQYY\scripts\ops\run_seed_spi_params.py`(运维脚本,冲突源 2,L62 期望 28)
|
||||
|
||||
### C.数据库 / 文档
|
||||
|
||||
- `c:\Project\NeoZQYY\apps\etl\connectors\feiqiu\docs\database\DWS\changes\BD_Manual_dws_member_spending_power_index.md`(BD 手册 27 行清单)
|
||||
- 测试库 `test_etl_feiqiu.dws.cfg_index_parameters` WHERE index_type='SPI'(实际 27 行)
|
||||
|
||||
### D.审计记录
|
||||
|
||||
- `c:\Project\NeoZQYY\docs\audit\changes\2026-02-27__biz-day-cutoff-prd-sync-check.md`(SPI 营业日切点适配)
|
||||
- `c:\Project\NeoZQYY\docs\audit\changes\2026-03-02__spi-calibration-nonzero-median.md`(SPI 基数校准改进)
|
||||
- `c:\Project\NeoZQYY\docs\audit\changes\2026-03-15__ddl-baseline-consolidation-bd-manual-reorg.md`(SPI DDL 合并)
|
||||
- `c:\Project\NeoZQYY\docs\audit\changes\2026-03-20__rns1-ai-autonomous-decision-risk-audit.md`(SPI 提及,非主体)
|
||||
- `c:\Project\NeoZQYY\docs\audit\changes\2026-03-20__rns13-board-apis-e2e-fix.md`(看板用 SPI 联调,无参数变更)
|
||||
- `c:\Project\NeoZQYY\docs\audit\changes\2026-03-24__lookback_days_60_to_90.md`(同时段动指数参数,非 SPI)
|
||||
|
||||
---
|
||||
|
||||
> 本报告 step1 部分到此为止。step2 实施待 Neo 拍板"选项 A / A+B / 其他" 后开新会话执行。
|
||||
346
docs/_overview/04a-feedback/P0-1-sandbox-snapshot-design.md
Normal file
346
docs/_overview/04a-feedback/P0-1-sandbox-snapshot-design.md
Normal file
@@ -0,0 +1,346 @@
|
||||
# P0-1 沙箱与参数快照机制设计调研
|
||||
|
||||
> 日期:2026-05-04
|
||||
> 触发:Neo 在 P0-1 反馈中提出"沙箱模式下 SPI/全部参数是否每天都该有快照"
|
||||
> 调研者:沙箱-参数快照专项子代理
|
||||
> 状态:**调研 + Patch 建议草案**(不直接修改 P20 SPEC,不动代码 / 数据库)
|
||||
> 调研边界:仅读测试库与仓库代码,产出文字结论
|
||||
|
||||
---
|
||||
|
||||
## 一、问题定义
|
||||
|
||||
沙箱回放历史日期(例如 `sandbox_date = 2026-03-01`)时,SPI 算法、绩效档位、奖金规则、助教等级定价等"门店级配置参数"应当使用**哪一版**?
|
||||
|
||||
- **A 方案(取最新)**:始终用今天的参数。简单,但 2026-03-01 当时跑出的真实分数无法在沙箱中复现 —— 沙箱失去"还原历史现场"的意义。
|
||||
- **B 方案(取历史生效版)**:用 2026-03-01 当时生效的参数。准确,要求所有 cfg_* 表都有可按日期切片的版本机制,并且**所有读取入口都按业务日过滤**。
|
||||
|
||||
Neo 直觉:既然沙箱机制存在,SPI 参数和全部参数都应该每天都有快照吧?
|
||||
|
||||
**调研结论先行**:Neo 的直觉方向正确,但"每天快照"是次优方案;最优方案是**SCD2 区间(已有)+ 让所有读取入口都按业务日过滤(未做)**。当前 P20 已经在视图层为 4 个 cfg_* 表加了业务日上界,但 ETL 层的 SPI/绩效/工资任务直接读 `dws.cfg_*` 裸表绕过了视图,这是 P20 的隐藏裂缝。
|
||||
|
||||
---
|
||||
|
||||
## 二、当前实现状态
|
||||
|
||||
### 2.1 cfg_index_parameters 表结构(权威 DDL `db/etl_feiqiu/schemas/dws.sql:93-103`)
|
||||
|
||||
```sql
|
||||
CREATE TABLE dws.cfg_index_parameters (
|
||||
param_id integer PK,
|
||||
index_type varchar(50) NOT NULL, -- WBI/NCI/RS/OS/MS/ML/SPI
|
||||
param_name varchar(100) NOT NULL,
|
||||
param_value numeric(14,6) NOT NULL,
|
||||
description text,
|
||||
effective_from date DEFAULT CURRENT_DATE NOT NULL,
|
||||
effective_to date, -- 可空 = 至今仍生效
|
||||
created_at timestamptz NOT NULL,
|
||||
updated_at timestamptz NOT NULL
|
||||
);
|
||||
ALTER TABLE ... UNIQUE (index_type, param_name, effective_from);
|
||||
CREATE INDEX idx_cfg_index_params_effective ON ... (effective_from, effective_to);
|
||||
```
|
||||
|
||||
**结论**:已经具备 SCD2 区间能力。同名 `(index_type, param_name)` 可以有多行,通过 `effective_from / effective_to` 区间区分。无需新增 snapshot_date 列。
|
||||
|
||||
### 2.2 其他 cfg_* 表盘点
|
||||
|
||||
| 表 | 库.Schema | 区间字段 | UNIQUE 约束 | RLS 视图(app schema) | 视图业务日上界 |
|
||||
|---|---|---|---|---|---|
|
||||
| `cfg_index_parameters` | etl_feiqiu.dws | `effective_from / effective_to` | `(index_type, param_name, effective_from)` | `app.v_cfg_index_parameters` | 已加(20260502 迁移) |
|
||||
| `cfg_assistant_level_price` | etl_feiqiu.dws | `effective_from / effective_to` | `(level_code, effective_from)` | `app.v_cfg_assistant_level_price` | 已加 |
|
||||
| `cfg_performance_tier` | etl_feiqiu.dws | `effective_from / effective_to` | `(tier_code, effective_from)` | `app.v_cfg_performance_tier` | 已加 |
|
||||
| `cfg_bonus_rules` | etl_feiqiu.dws | `effective_from / effective_to` | `(rule_type, rule_code, effective_from)` | `app.v_cfg_bonus_rules` | 已加 |
|
||||
| `cfg_skill_type` | etl_feiqiu.dws | 无 | `(skill_id)` | 未单独建视图(`is_active=true`) | — |
|
||||
| `cfg_area_category` | etl_feiqiu.dws | 无 | `(source_area_name)` | 未单独建视图 | — |
|
||||
| `biz.cfg_task_generator_params` | zqyy_app.biz | 无(只有 `updated_at + updated_by`) | `(site_id, param_key)` | 无 RLS,后端直读 | — |
|
||||
|
||||
**结论**:7 张配置表中,5 张算法/财务相关的(SPI/RS/OS 参数、定价、档位、奖金)**已经有 SCD2 区间机制**。`cfg_skill_type / cfg_area_category` 是"枚举映射型"配置(技能 ID → 课程类型,区域名 → 分类),变更频率极低且无版本概念;`biz.cfg_task_generator_params` 是任务引擎运行时参数,只有最新值。
|
||||
|
||||
### 2.3 SPI 任务读参数的实际方式(关键裂缝)
|
||||
|
||||
**`apps/etl/connectors/feiqiu/tasks/dws/index/base_index_task.py:303-360`**:
|
||||
|
||||
```python
|
||||
def load_index_parameters(self, index_type=None, force_reload=False):
|
||||
...
|
||||
sql = """
|
||||
SELECT param_name, param_value
|
||||
FROM dws.cfg_index_parameters -- 直接读裸表,不走 app.v_*
|
||||
WHERE index_type = %s
|
||||
AND effective_from <= CURRENT_DATE -- 用 CURRENT_DATE,不用 as_of
|
||||
AND (effective_to IS NULL OR effective_to >= CURRENT_DATE)
|
||||
ORDER BY effective_from DESC
|
||||
"""
|
||||
rows = self.db.query(sql, (index_type,))
|
||||
...
|
||||
```
|
||||
|
||||
**`spending_power_index_task.py:178-186`** 同时调用:
|
||||
|
||||
```python
|
||||
as_of = (context.as_of_date if context and context.as_of_date else None) or _dt.now(self.tz)
|
||||
db_params = self.load_index_parameters('SPI') # 不传 as_of
|
||||
features = self._extract_spending_features(site_id, params, as_of=as_of) # 传 as_of
|
||||
```
|
||||
|
||||
**问题**:
|
||||
1. 数据特征(消费/充值/EWMA)的 SQL 查询用 `as_of` 参数化,具备回测能力;
|
||||
2. **参数加载用 `CURRENT_DATE` 硬编码**,沙箱模式下拿到的是"今天生效的参数",不是 sandbox_date 当时生效的参数。
|
||||
3. 视图层 `app.v_cfg_index_parameters` 已经按 `app.business_date_now()` 加上界,但 ETL 任务**直接读 `dws.cfg_index_parameters` 裸表绕过了视图**,GUC 不生效。
|
||||
|
||||
同样问题存在于 `base_dws_task.py:540-581` 的 `_load_perf_tiers` / `_load_level_prices` / `_load_bonus_rules`(绩效档位/工资任务用),这三个方法甚至连 `effective_from` WHERE 都没有,把所有历史区间的行全取出来,再让 Python 代码自己挑——一旦表里有重复 `tier_code`/`level_code` 的多区间数据,行为依赖 ORDER BY 和 Python 侧逻辑。
|
||||
|
||||
### 2.4 后端读参数的方式(已对接 P20)
|
||||
|
||||
后端通过 `app.v_cfg_*` 视图读取(如 `tenant_users.py` 走 v_cfg_assistant_level_price),`fdw_queries._fdw_context` 在事务内 `SET LOCAL app.current_business_date = sandbox_date`,视图自动按 `business_date_now()` 切片。**后端侧已经是 B 方案**。
|
||||
|
||||
---
|
||||
|
||||
## 三、参数与沙箱关联性矩阵
|
||||
|
||||
| 参数表 | 是否影响沙箱回放 | 当前是否有快照能力 | 后端读路径 | ETL 读路径 | 是否需要快照 |
|
||||
|---|---|---|---|---|---|
|
||||
| `cfg_index_parameters` (SPI/RS/OS/MS/ML/WBI/NCI 算法权重) | 是(分数会因参数变化而不同) | 有 SCD2 | 走 v_* 已切片 | **裸表绕过** | 必须 |
|
||||
| `cfg_assistant_level_price` (助教等级定价) | 是(月薪计算结果不同) | 有 SCD2 | 走 v_* 已切片 | **裸表绕过** | 必须 |
|
||||
| `cfg_performance_tier` (绩效档位区间) | 是(档位归属不同) | 有 SCD2 | 走 v_* 已切片 | **裸表绕过** | 必须 |
|
||||
| `cfg_bonus_rules` (奖金规则) | 是(奖金金额不同) | 有 SCD2 | 走 v_* 已切片 | **裸表绕过** | 必须 |
|
||||
| `cfg_skill_type` (技能 → 课程类型映射) | 否(枚举映射,业务定义不会因日期变) | 无 | 直读 `is_active=true` | 直读 | 不必 |
|
||||
| `cfg_area_category` (区域名 → 分类) | 否(同上) | 无 | 直读 | 直读 | 不必 |
|
||||
| `biz.cfg_task_generator_params` (任务引擎冷启动天数等) | 弱影响(沙箱演示新功能时一致即可) | 无 | 后端直读 | — | 可选(优先级 P3) |
|
||||
|
||||
**额外参数源**:
|
||||
- AI prompt 模板版本(后端代码内常量,不在 DB)→ 沙箱演示新 prompt 时跨切换会有差异,但属于"prompt 工程版本"不属于业务参数。**优先级 P3**,通过 `prompt_version` 字段记录即可,无需快照。
|
||||
- runtime_context 自身配置(`biz.site_runtime_context`)→ 是状态本身,不需要快照。
|
||||
- `biz.scheduled_tasks` cron(全局调度)→ 设计共识保留真实时钟,不需要。
|
||||
|
||||
---
|
||||
|
||||
## 四、四个核心问题的答案
|
||||
|
||||
### Q1:A 方案 vs B 方案
|
||||
|
||||
**答案:B 方案**(用 2026-03-01 当时生效的参数)。
|
||||
|
||||
理由:
|
||||
1. P20 § 1.2 沙箱设计目标是"假装今天是某个历史日期",参数是业务规则的一部分,用今天参数 = 历史现场不完整。
|
||||
2. 后端走 v_* 视图已经隐式选了 B,如果 ETL 用 A,会出现"小程序看到的分数(后端取展示分)" 与 "重跑 ETL 后实际写入的分数"不一致。
|
||||
3. SCD2 区间已经存在,迁移成本极低,只需改读取 SQL 的 WHERE 子句。
|
||||
|
||||
### Q2:B 方案的实现路径
|
||||
|
||||
**推荐 B-1 + 视图归一**(见 § 五):利用现有 SCD2 区间,把所有 cfg_* 读取入口统一到 `app.v_cfg_*` 视图(由 `app.business_date_now()` 自动切片),禁止 ETL 任务直读 `dws.cfg_*` 裸表。
|
||||
|
||||
为什么不选 B-2(每日快照表):
|
||||
- 写入成本高(每天每门店复制全量参数行)。
|
||||
- 实际参数变更频率极低(SPI 27 个参数自上线以来基本未变;BD 手册显示 cfg_assistant_level_price 历史只有 1-2 次区间切换),99% 快照都是冗余。
|
||||
- SCD2 区间在数学上等价于"按需快照",且空间复杂度 O(变更次数) 而非 O(天数 × 门店数)。
|
||||
|
||||
为什么不选 B-3(变更日志重建):
|
||||
- 重建逻辑复杂,需要在每个调用点应用变更日志,容易遗漏。
|
||||
- SCD2 已经做了同样的事,无需再发明。
|
||||
|
||||
### Q3:哪些参数需要"快照"(SCD2 切片),哪些不需要
|
||||
|
||||
需要切片(已具备能力,只是读取入口未对齐):
|
||||
- `cfg_index_parameters`(7 类指数算法参数)
|
||||
- `cfg_assistant_level_price`(助教等级定价)
|
||||
- `cfg_performance_tier`(绩效档位区间)
|
||||
- `cfg_bonus_rules`(奖金规则)
|
||||
|
||||
不需要切片:
|
||||
- `cfg_skill_type`(技能 → 课程类型),纯枚举映射,变更等同重命名。
|
||||
- `cfg_area_category`(区域 → 分类),纯枚举映射。
|
||||
- `biz.cfg_task_generator_params`(冷启动天数等),沙箱场景对其敏感度极低;若未来需要可补 SCD2,优先级 P3。
|
||||
- AI prompt 版本(代码内常量),通过 `prompt_version` 标识即可。
|
||||
- 调度 cron / runtime_context 自身 / 时间戳列(P20 § 1.3 已有设计共识)。
|
||||
|
||||
### Q4:是否已存在类似机制
|
||||
|
||||
存在,且**比 Neo 直觉的"每日快照"更优**:
|
||||
|
||||
1. SCD2 区间 `effective_from / effective_to`:5 张关键 cfg 表已有(2.2 矩阵)。
|
||||
2. P20 视图层已经在 `app.v_cfg_*` 用 `app.business_date_now()` 切片(`db/etl_feiqiu/migrations/20260502__rls_views_business_date_upper_bound.sql:710-782`)。
|
||||
3. 业务事实表(如 `dws_member_spending_power_index`)按 `business_date / calc_time` 已经天然形成"每日产出快照",这是事实层快照,不是参数快照。
|
||||
|
||||
**漏的不是机制,是读取入口的统一**。
|
||||
|
||||
---
|
||||
|
||||
## 五、推荐设计方案
|
||||
|
||||
### 方案对比
|
||||
|
||||
| 方案 | 参数侧改造 | 读取入口改造 | 写入侧改造 | 工作量 | 推荐度 |
|
||||
|---|---|---|---|---|---|
|
||||
| **方案 1:统一视图入口** | 无(SCD2 已有) | ETL 全部 cfg 读取改走 `app.v_cfg_*` 视图 | 无 | 0.5 天(改 2-3 处 SQL) | **首选** |
|
||||
| 方案 2:函数显式 as_of | 无 | 给 `load_index_parameters / _load_*` 传 `as_of` 参数 | 无 | 1 天(签名改造 + 调用方 7+ 处) | 备选 |
|
||||
| 方案 3:每日快照表 | 新增 `dws.cfg_*_snapshot(snapshot_date, ...)` 6 张 | 改读 snapshot 表 | 新增日度生成 job | 3-5 天 + 持续维护 | 不推荐 |
|
||||
|
||||
### 推荐方案 1:统一视图入口(B-1 + 视图归一)
|
||||
|
||||
**核心思想**:cfg_* 表的"按业务日切片"语义封装在 `app.v_cfg_*` 视图里,所有读取入口(后端 + ETL + 后端 FDW)统一走视图。GUC `app.current_business_date` 由 ETL Loader / 任务引擎在事务起始处下发,沙箱模式下下发 sandbox_date,live 模式下不下发(回退 CURRENT_DATE)。
|
||||
|
||||
**实施步骤**:
|
||||
|
||||
1. **DB 侧**:确认 5 个 `app.v_cfg_*` 视图当前定义已经按 `effective_from <= app.business_date_now() AND effective_to >= app.business_date_now()` 切片(20260502 迁移已完成)。无需新增迁移。
|
||||
|
||||
2. **ETL Loader / 任务引擎入口**:在 `task_engine.py` / `flow_runner` 跑 dws 任务前,先按当前门店读 `biz.site_runtime_context.mode + sandbox_date`,在事务内执行:
|
||||
```sql
|
||||
SET LOCAL app.current_site_id = '<site_id>';
|
||||
SET LOCAL app.current_business_date = '<sandbox_date or CURRENT_DATE>';
|
||||
SET LOCAL app.current_runtime_mode = 'live' | 'sandbox';
|
||||
```
|
||||
live 模式下也建议显式 `SET LOCAL app.current_business_date = CURRENT_DATE::text`,使语义对称(无侧效应,函数已 STABLE)。
|
||||
|
||||
3. **代码侧改造**(3 处):
|
||||
- `apps/etl/connectors/feiqiu/tasks/dws/index/base_index_task.py:335-342`: `FROM dws.cfg_index_parameters` → `FROM app.v_cfg_index_parameters`,移除 `effective_from <= CURRENT_DATE AND effective_to ...` WHERE(视图已切片)。
|
||||
- `apps/etl/connectors/feiqiu/tasks/dws/base_dws_task.py:543-581`: `_load_perf_tiers / _load_level_prices / _load_bonus_rules` 改为 `FROM app.v_cfg_*`,删除不必要的 ORDER BY effective_from DESC(视图已只返回当前生效行)。
|
||||
- `apps/etl/connectors/feiqiu/tasks/utility/seed_dws_config_task.py`(种子任务):写入仍然走裸表(写入永远是真实操作),无需改。
|
||||
|
||||
4. **测试**:
|
||||
- 在测试库 `test_etl_feiqiu` 切沙箱到 `sandbox_date = 2026-03-01`,跑 SPI,验证读到的参数版本与裸表 `WHERE effective_from <= '2026-03-01' AND effective_to >= '2026-03-01'` 结果一致。
|
||||
- 切回 live,跑 SPI,验证读到的参数 = `CURRENT_DATE` 切片结果。
|
||||
- 同时跑两次(live → sandbox → live),三次结果中两次 live 必须一致。
|
||||
|
||||
5. **审计**:`docs/audit/changes/2026-MM-DD__sandbox_param_view_unify.md` + 更新 BD_Manual。
|
||||
|
||||
**工作量预估**:0.5 天(代码 ≤ 3 文件 / ≤ 30 行 + 1 篇审计 + 测试)。
|
||||
|
||||
**风险**:
|
||||
- 视图层 SQL 复杂度上升微乎其微(简单 WHERE 子句)。
|
||||
- 历史数据兼容性:cfg_* 表中 `effective_to` 应为 `'9999-12-31'` 表示"至今仍生效",当前 `cfg_index_parameters` 默认 `effective_to` 是 NULL,而 v_cfg_index_parameters WHERE 写的是 `effective_to >= app.business_date_now()`——**NULL 会被过滤掉**!这是一个真实存在的视图 bug,需要在执行方案 1 前先修视图(改为 `(effective_to IS NULL OR effective_to >= app.business_date_now())`)。
|
||||
|
||||
### 视图 NULL 兼容性补丁(前置必做)
|
||||
|
||||
`db/etl_feiqiu/migrations/20260502__rls_views_business_date_upper_bound.sql:782` 当前:
|
||||
|
||||
```sql
|
||||
WHERE effective_from <= app.business_date_now()
|
||||
AND effective_to >= app.business_date_now(); -- NULL 行被过滤
|
||||
```
|
||||
|
||||
应改为:
|
||||
|
||||
```sql
|
||||
WHERE effective_from <= app.business_date_now()
|
||||
AND (effective_to IS NULL OR effective_to >= app.business_date_now());
|
||||
```
|
||||
|
||||
这条修复独立于沙箱方案,任何"effective_to 留空" 的行都需要它(`cfg_index_parameters.effective_to` 是 nullable)。建议作为方案 1 的前置 P0 修复。
|
||||
|
||||
---
|
||||
|
||||
## 六、P20 SPEC Patch 建议(不实际修改,仅列出"应在 § X 加入以下内容")
|
||||
|
||||
### Patch P20-A:§ 1.3 沙箱不影响项 → 调整描述
|
||||
|
||||
当前 § 1.3 列出 7 项不被沙箱影响。建议**新增一段说明**澄清 cfg_* 配置:
|
||||
|
||||
```
|
||||
### 1.4 沙箱影响项(配置参数版本)
|
||||
|
||||
下列配置表参与沙箱"业务日切片"语义,沙箱模式下读取 sandbox_date 当时生效的版本:
|
||||
|
||||
- cfg_index_parameters(指数算法权重 SPI/RS/OS/MS/ML/WBI/NCI)
|
||||
- cfg_assistant_level_price(助教等级定价)
|
||||
- cfg_performance_tier(绩效档位区间)
|
||||
- cfg_bonus_rules(奖金规则)
|
||||
|
||||
下列不受沙箱影响:
|
||||
|
||||
- cfg_skill_type / cfg_area_category(纯枚举映射)
|
||||
- biz.cfg_task_generator_params(任务引擎运行时参数)
|
||||
- AI prompt 版本(代码常量)
|
||||
```
|
||||
|
||||
### Patch P20-B:§ 3.5 RLS 视图业务日上界 → 修复 NULL 兼容性
|
||||
|
||||
§ 3.5 当前列 `v_cfg_*` 4 个视图"effective_from 与 effective_to 双向夹住"。应补充注释:
|
||||
|
||||
```
|
||||
注:effective_to IS NULL 表示"至今仍生效",视图 WHERE 必须写为
|
||||
`(effective_to IS NULL OR effective_to >= app.business_date_now())`,
|
||||
不能写成 `effective_to >= app.business_date_now()`(NULL 会被过滤)。
|
||||
当前 20260502 迁移生成的 4 个 v_cfg_* 视图存在此 bug,需在 P0 修复。
|
||||
```
|
||||
|
||||
### Patch P20-C:新增 § 5.6 ETL 库读取约定
|
||||
|
||||
P20 § 5 当前覆盖了"后端服务层 / ETL 视图层 / 小程序 / AI 提示词 / admin-web",**没有覆盖 ETL 任务自身**。建议新增:
|
||||
|
||||
```
|
||||
### 5.6 ETL 任务读取约定
|
||||
|
||||
ETL 任务(task_engine 调度的 DWS 计算任务)对 cfg_* 配置表的读取必须遵守:
|
||||
|
||||
1. 入口必须是 `app.v_cfg_*` 视图,禁止直读 `dws.cfg_*` 裸表(参数加载场景)。
|
||||
- 例外:种子写入任务(seed_dws_config_task)写入裸表是必需的。
|
||||
2. 任务事务开启后,`base_dws_task` 应在 SQL 执行前下发:
|
||||
- `SET LOCAL app.current_site_id`
|
||||
- `SET LOCAL app.current_business_date`(live 用 CURRENT_DATE,sandbox 用 sandbox_date)
|
||||
- `SET LOCAL app.current_runtime_mode`
|
||||
3. 任务上下文(TaskContext)必须能从 `biz.site_runtime_context` 读到当前模式;
|
||||
live 模式下回退 live 默认行为。
|
||||
|
||||
当前已知未对齐(2026-05-04):
|
||||
- `base_index_task.load_index_parameters` 直读 dws.cfg_index_parameters,需改走 v_*
|
||||
- `base_dws_task._load_perf_tiers / _load_level_prices / _load_bonus_rules` 直读裸表,需改走 v_*
|
||||
```
|
||||
|
||||
### Patch P20-D:§ 8 验收标准 → 新增 AC14 / AC15
|
||||
|
||||
```
|
||||
| AC14 | sandbox 模式跑 SPI,读到的 cfg_index_parameters 版本 = sandbox_date 当时生效版本 | SQL: 对比 ETL 任务日志中 params dump 与 SELECT * FROM v_cfg_index_parameters 直查结果 |
|
||||
| AC15 | sandbox 模式跑工资任务,读到的 cfg_performance_tier / cfg_bonus_rules / cfg_assistant_level_price 版本 = sandbox_date 当时生效版本 | 同上 |
|
||||
```
|
||||
|
||||
### Patch P20-E:§ 11.2 已知 hack → 新增条目
|
||||
|
||||
```
|
||||
- ETL DWS 任务直读 dws.cfg_* 裸表绕过 v_* 视图,沙箱模式下参数版本不切片 — 详见 P0-1-sandbox-snapshot-design.md
|
||||
- v_cfg_index_parameters 视图 effective_to NULL 兼容性 bug — 详见 P0-1-sandbox-snapshot-design.md § 五
|
||||
```
|
||||
|
||||
### Patch P20-F:§ 12 任务清单 → 新增 T16 / T17
|
||||
|
||||
```
|
||||
- [ ] T16:修复 4 个 v_cfg_* 视图 effective_to NULL 兼容性(P0,前置)
|
||||
- [ ] T17:ETL DWS 任务参数读取统一走 v_cfg_* 视图(P1,沙箱方案 1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、给 Neo 的决策清单
|
||||
|
||||
请就以下 6 项给出 GO / NO-GO / 修订意见:
|
||||
|
||||
1. **B 方案确认**:沙箱模式下,SPI/绩效/工资任务应使用 sandbox_date 当时生效的参数版本(而非今天的最新参数)。 → GO / NO-GO
|
||||
|
||||
2. **方案 1 vs 方案 3**:推荐方案 1(SCD2 + 视图归一,0.5 天),不推荐方案 3(每日快照表,3-5 天)。 → 同意 / 切方案 / 其他
|
||||
|
||||
3. **NULL 兼容性补丁前置**:`v_cfg_index_parameters` 视图 NULL 行被过滤是真实 bug,建议作为 P0 前置修复(独立审计)。 → GO / 合并到 T17 一起处理
|
||||
|
||||
4. **biz.cfg_task_generator_params 是否要 SCD2**:当前无版本机制,沙箱影响弱。建议优先级 P3,本轮不做。 → 同意 / 现在就要
|
||||
|
||||
5. **AI prompt 版本**:沙箱演示中切换 prompt 工程版本时,跨切换的 ai_run_logs 不可比。建议增加 `prompt_version` 字段标识(后续单独 SPEC),不快照 prompt 全文。 → 同意 / 其他
|
||||
|
||||
6. **是否直接产出 Patch SPEC**:本调研只列出 P20 应改章节,不实际修改。如果决策清单 1-5 项通过,是否授权按 § 六 patch 直接修改 `docs/prd/specs/P20-runtime-context-sandbox.md`,并产出 0.5 天的代码+测试改造? → GO / 先讨论
|
||||
|
||||
---
|
||||
|
||||
## 八、关键文件路径(便于 Neo 复核)
|
||||
|
||||
- `c:\Project\NeoZQYY\db\etl_feiqiu\schemas\dws.sql:93-103` — cfg_index_parameters 表定义
|
||||
- `c:\Project\NeoZQYY\db\etl_feiqiu\migrations\20260502__rls_views_business_date_upper_bound.sql:710-782` — 4 个 v_cfg_* 视图(含 NULL bug)
|
||||
- `c:\Project\NeoZQYY\apps\etl\connectors\feiqiu\tasks\dws\index\base_index_task.py:303-360` — load_index_parameters 直读裸表
|
||||
- `c:\Project\NeoZQYY\apps\etl\connectors\feiqiu\tasks\dws\base_dws_task.py:540-581` — 工资任务三个 _load_* 直读裸表
|
||||
- `c:\Project\NeoZQYY\apps\etl\connectors\feiqiu\tasks\dws\index\spending_power_index_task.py:178-186` — as_of 传入数据查询但参数加载未传
|
||||
- `c:\Project\NeoZQYY\docs\prd\specs\P20-runtime-context-sandbox.md` — P20 SPEC 主体
|
||||
- `c:\Project\NeoZQYY\docs\_overview\04a-feedback\P0-1-SPI-research.md` — P0-1 step1 调研(SPI 27 参数清单)
|
||||
- `c:\Project\NeoZQYY\docs\_overview\04a-feedback\P0-7-runtime-context-todos.md` — 跨模块沙箱 todos
|
||||
|
||||
---
|
||||
|
||||
> 本调研产出由 Claude Opus 4.7(沙箱-参数快照专项子代理)生成于 2026-05-04,
|
||||
> 不修改 P20 SPEC 主文,不动代码与数据库,仅产出决策建议。
|
||||
90
docs/_overview/04a-feedback/P0-2-feedback-resolution.md
Normal file
90
docs/_overview/04a-feedback/P0-2-feedback-resolution.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# P0-2 confirmed_income vs items_sum 反馈处理
|
||||
|
||||
> 日期:2026-05-04 / 触发:Neo 在 04a-conflicts 反馈
|
||||
> Neo 原话:"我理解这个问题是一个说明性的问题,我认为在涉及的前后端及数据库文档都应该留痕进行解释说明,也就是 ABC 选项都要做,我的理解对么?你的建议是什么?"
|
||||
|
||||
## 一、Neo 的理解校核
|
||||
|
||||
**结论:Neo 的理解大致正确,但建议做"主权威 + 副链接"而非"三处都写完整说明",避免维护负担。**
|
||||
|
||||
理由:
|
||||
|
||||
- 这个问题的本质**不是逻辑冲突**,是**口径术语未沉淀到唯一权威源**
|
||||
- 如果三处都写完整定义(ETL CLAUDE.md / DWS 权威规范 / 看板优化 PRD),后续任何字段调整需要同步改 3 处,极易漂移,反而制造新冲突
|
||||
- 文档维护的工程经验:**单一权威源(SSOT) + 链接指向**,比"多处冗余"更易维护
|
||||
|
||||
## 二、推荐方案("主 + 副"而非"三处都写完整")
|
||||
|
||||
### 主权威源(写完整定义)
|
||||
|
||||
**`apps/etl/connectors/feiqiu/docs/database/DWS/main/BD_manual_dws_finance_daily_summary.md`**(已存在,L148-L149 已有部分描述)
|
||||
|
||||
理由:
|
||||
- 飞球 DWS 权威规范是金额口径的最终归属(参考 `apps/etl/connectors/feiqiu/CLAUDE.md` DWD 规则 1)
|
||||
- BD 手册本身就是"DWD/DWS 字段权威清单",拓展几行术语对照表是这份文档的天然职责
|
||||
- 修改频率最低,不会跟随业务迭代变动
|
||||
|
||||
**补充内容**:在 BD 手册 § 字段说明 上方加 § "金额口径术语对照表":
|
||||
```
|
||||
| 术语 | 定义 | DWD/DWS 层 | 用途 |
|
||||
| --- | --- | --- | --- |
|
||||
| consume_money | (历史)三种口径并存,不可直接使用 | DWS 上游 | 已废弃 |
|
||||
| items_sum | 5 项费用之和:台桌费 + 商品费 + 陪打费 + 超休费 + 电费 | DWD | 替代 consume_money |
|
||||
| gross_amount | 4 项之和(items_sum 减 electricity_money) | DWS | DWS 内部聚合基础 |
|
||||
| confirmed_income | gross_amount - discount_total(总和减优惠) | DWS | 客单价 / 日均额 分子 |
|
||||
|
||||
关系:items_sum = gross_amount + electricity_money;confirmed_income = gross_amount - discount_total
|
||||
```
|
||||
|
||||
### 副链接(只加一行指向主权威)
|
||||
|
||||
**位置 1:[`apps/etl/connectors/feiqiu/CLAUDE.md`](../../../apps/etl/connectors/feiqiu/CLAUDE.md) DWD 规则 1**
|
||||
- 在已有的 "DWS 层及下游统一改用 `items_sum`" 后加一句:
|
||||
> 完整术语对照(items_sum / gross_amount / confirmed_income / discount_total 关系)见 `docs/database/DWS/main/BD_manual_dws_finance_daily_summary.md` § 金额口径术语对照表
|
||||
|
||||
**位置 2:[`docs/prd/2026-04-08__board-finance-optimization.md`](../../prd/2026-04-08__board-finance-optimization.md)**
|
||||
- 在文档顶部加一段"术语对照"小节:
|
||||
> 本文涉及的金额术语 (`confirmed_income`、`items_sum`、`gross_amount`) 完整定义见 `apps/etl/connectors/feiqiu/docs/database/DWS/main/BD_manual_dws_finance_daily_summary.md` § 金额口径术语对照表
|
||||
|
||||
**位置 3:[`docs/miniprogram-dev/api-audit/board-finance.md`](../../miniprogram-dev/api-audit/board-finance.md)**(可选)
|
||||
- 在 § 金额字段段落加一句:
|
||||
> 客单价分子 (`confirmed_income`) 与营收发生额 (`gross_amount + ...`) 的口径差异,见 `apps/etl/connectors/feiqiu/docs/database/DWS/main/BD_manual_dws_finance_daily_summary.md` § 金额口径术语对照表
|
||||
|
||||
## 三、Neo 理解 vs 推荐方案对比
|
||||
|
||||
| 维度 | Neo 原方案("ABC 都做") | 推荐方案(主 + 副) |
|
||||
|---|---|---|
|
||||
| 信息完整性 | 3 处都有完整说明 | 1 处完整 + 3 处链接 |
|
||||
| 首次落地工作量 | 大(写 3 份完整说明) | 小(写 1 份 + 3 个一行链接) |
|
||||
| 后续维护(字段调整) | 大(改 3 处,易漂移) | 小(只改主源) |
|
||||
| 跨仓库读者体验 | 任一文档自给自足 | 跨链接跳转(主流文档习惯) |
|
||||
| 文档膨胀风险 | 高 | 低 |
|
||||
|
||||
**Neo 担心的"留痕"目的(任何看到这个字段的人都能查到完整定义)** 通过链接也能达到,且风险更小。
|
||||
|
||||
## 四、若 Neo 仍要"三处都写完整"
|
||||
|
||||
如果 Neo 出于"防链接失效"或"工程审计可读性"坚持要"三处都写完整",建议加一个保护:
|
||||
|
||||
- 三处都用**同一个 markdown 引用块**(footnote / blockquote 模板),通过文档生成脚本(可选)从主源生成
|
||||
- 在每处下方标"如有调整请优先改 BD 手册主源,本处为副本"
|
||||
- 这样仍然有一个主权威源,只是物理上有 3 个副本
|
||||
|
||||
## 五、与 Wave 验证的关系
|
||||
|
||||
- Wave 4(DWS / RLS 验证)走查 `dws_finance_daily_summary` 时,会自然校验这个口径定义,可顺带核对术语对照表是否在 BD 手册中
|
||||
- 不需要额外开 Wave
|
||||
|
||||
## 六、最终建议
|
||||
|
||||
**强烈推荐"主 + 副"方案**(BD 手册写完整,其他三处加链接)。
|
||||
|
||||
如 Neo 拍板执行,建议:
|
||||
1. 先写 BD 手册主源(15 分钟)
|
||||
2. 再加三处链接(各 5 分钟)
|
||||
3. 总工作量 < 1 小时
|
||||
4. 全部归到 Wave 5(部署 / 文档收尾)统一执行,不阻塞 Wave 1-4
|
||||
|
||||
---
|
||||
|
||||
> 等 Neo 拍板"主 + 副"或"三处都写完整",再决定具体动手时机。
|
||||
100
docs/_overview/04a-feedback/P0-3-board-vs-sandbox-analysis.md
Normal file
100
docs/_overview/04a-feedback/P0-3-board-vs-sandbox-analysis.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# P0-3 财务看板与沙箱衔接 — 初步分析
|
||||
|
||||
> 日期:2026-05-04 / 触发:Neo 在 04a-conflicts 反馈
|
||||
> Neo 原话:"另外,此项内容包括财务看板页的所有数据是否受到之后开发上线的时间沙盒模块的影响?逻辑是否调通有待进一步调研走查。"
|
||||
|
||||
## 一、初步结论(基于静态 grep 证据)
|
||||
|
||||
**重大风险:小程序三大看板(board-finance / board-customer / board-coach)在沙箱模式下不会切换到虚拟时间数据。**
|
||||
|
||||
证据:
|
||||
- 小程序 `pages/board-finance/` `pages/board-customer/` `pages/board-coach/` — **零处引用** `runtime-clock` / `runtimeClock` / `getBusinessClock` / `business_year` / `business_month`
|
||||
- 后端 `apps/backend/app/routers/xcx_board.py` — **零处引用** `runtime_context` / `runtime-clock`
|
||||
- D-3 子代理报告同样发现:"**小程序 board 三大看板未确认 runtime-clock 接入,风险高**"
|
||||
|
||||
**意味着**:
|
||||
1. 运维在 admin-web 切换 sandbox 模式 → 设置虚拟日期(如 2026-03-01)
|
||||
2. 小程序 task-list / customer-records 等其他页面**会**显示虚拟日期数据(它们读 runtime-clock)
|
||||
3. 但小程序看板三页**仍然显示真实今天的数据**
|
||||
4. 用户混淆:"为什么任务列表是 3 月 1 号但看板显示 5 月数据?"
|
||||
|
||||
## 二、P0-3 与 P0-7(Runtime Context 收口)的关系
|
||||
|
||||
P0-3 是 P0-7 "Runtime Context 远未收口" 的**最具体证据之一**。D-3 子代理产出的 P20 SPEC 草稿 + todos 清单已经把这条列为 P0 级待办之一。
|
||||
|
||||
P0-3 不是独立问题,**它本质是 P0-7 跨模块覆盖率不足的子症状**。
|
||||
|
||||
## 三、与 P0-3 原始上下文(财务看板 5 项 P2 修复)的关系
|
||||
|
||||
Neo 在 04a 中标"偏向选项 A(数据准确再上线)",同时反问"沙箱影响是否调通"。两个问题需要**分开看待**:
|
||||
|
||||
| 子问题 | 当前状态 | 推荐处理 |
|
||||
|---|---|---|
|
||||
| 5 项 P2 修复(卡余额 / 首充续费 / 优惠占比 / 充值笔数 / 团购标签) | 4.1 PRD 已记录但未实施 | **Wave 4 实施**(对应 ETL Task 修复)|
|
||||
| 看板与沙箱衔接 | **未实现**(本文核心发现) | **Wave 1 必修**(D Bug,影响沙箱主线收口)|
|
||||
|
||||
**两者都是 P0,但触发的 Wave 不同**:沙箱衔接更紧急(Wave 1 必修),5 项数据准确性可以放到 Wave 4 ETL 验证时一并修。
|
||||
|
||||
## 四、Wave 1 必测的看板沙箱场景(8 条)
|
||||
|
||||
基于本次发现,Wave 1 走查时必须覆盖:
|
||||
|
||||
1. admin-web 切 sandbox(虚拟日期 = 2026-03-01) → 小程序 board-finance 营收数字应为 2026-03-01 当日数据
|
||||
2. admin-web 切 sandbox → board-finance 折线图横轴应是虚拟"近 7 日"(2026-02-23 ~ 03-01)而非真实近 7 日
|
||||
3. admin-web 切 sandbox → board-finance AI 洞察应基于虚拟日期数据
|
||||
4. admin-web 切 sandbox → board-customer 客户分类(SPI / 充值频次)应反映虚拟日期截止状态
|
||||
5. admin-web 切 sandbox → board-coach 助教绩效应反映虚拟日期当月累计
|
||||
6. 切回 live → 三大看板应立即恢复真实数据
|
||||
7. 跨门店切换:site_A sandbox + site_B live → site_A 的看板用虚拟,site_B 的看板用真实
|
||||
8. 验证 AI 洞察 cache_type:虚拟日期下应使用独立 cache_key 避免污染 live cache
|
||||
|
||||
## 五、P0-3 推荐处理方案(给 Neo 拍板)
|
||||
|
||||
### 选项 A — Wave 1 修(推荐)
|
||||
|
||||
**做法**:把"看板接入 runtime-clock + xcx_board.py 接入 runtime_context"作为 Wave 1 必交付项,Wave 1 走查同步验证 8 条场景。
|
||||
|
||||
**工作量**:
|
||||
- 后端:`xcx_board.py` 加 runtime_context 上下文 + 查询时用虚拟时间替代 `now()` — 中(2-4h)
|
||||
- 小程序:三大看板页 `onLoad` / `pullDownRefresh` 调 `getBusinessClock` 拿虚拟日期 → 传给后端 — 小(1h)
|
||||
- 测试:8 条场景 case + Wave 1 走查 — 中(2h)
|
||||
- **合计:5-7h**
|
||||
|
||||
**优点**:沙箱真正能演示"看板回放"场景;P0-7 收口前进一大步
|
||||
**缺点**:Wave 1 工作量多 1 个主题
|
||||
|
||||
### 选项 B — Wave 1 仅验证不修复
|
||||
|
||||
**做法**:Wave 1 走查时记录"看板不接沙箱"的现状,标 P0 待修但不在 Wave 1 修;Wave 5 末再修。
|
||||
|
||||
**优点**:Wave 1 节奏稳定
|
||||
**缺点**:沙箱演示价值打折扣;两次走查同一组件(Wave 1 测一次发现 bug,Wave 5 修后再测一次)
|
||||
|
||||
### 选项 C — 先评估能否给 sandbox 加"看板降级文案"
|
||||
|
||||
**做法**:在沙箱模式下,看板页顶部加 banner 提示"沙箱模式中,看板仍为真实数据(限制说明)",让用户预期管理。后续再补真实接入。
|
||||
|
||||
**优点**:零代码改动,文档级解决
|
||||
**缺点**:不解决根本问题;给沙箱使用者埋认知坑
|
||||
|
||||
## 六、与 5 项 P2 修复的合并建议
|
||||
|
||||
Neo 反馈"满足数据准确才上线"指 5 项 P2 修复。这两个问题最佳路径:
|
||||
|
||||
```
|
||||
Wave 1 — 修看板沙箱接入(P0-3 看板侧) + 验证沙箱整体收口(P0-7)
|
||||
Wave 4 — 修 5 项 ETL 数据准确(原 P0-3 主体)
|
||||
Wave 5 — 验证 P11 上线门槛(数据准确 + 沙箱收口都达标)
|
||||
```
|
||||
|
||||
不在同一 Wave 但在同一收口路径上。
|
||||
|
||||
## 七、给 Neo 的决策提问
|
||||
|
||||
1. P0-3 选 A(Wave 1 修)还是 B(Wave 1 仅验证)?**推荐 A**
|
||||
2. 5 项 P2 修复是否纳入 Wave 4?
|
||||
3. P11 上线门槛是否要求"看板沙箱接入 + 5 项数据准确"双满足?
|
||||
|
||||
---
|
||||
|
||||
> 本分析基于 grep 静态证据,真正"逻辑是否调通"需要 Wave 1 实测验证。
|
||||
@@ -0,0 +1,474 @@
|
||||
# P0-5 工程规范一致性全览
|
||||
|
||||
> 调研时间:2026-05-04
|
||||
> 触发:Neo 在 P0-5 反馈"matching.py 直连 ETL 偏离规范"基础上提出 — **找到全项目类似情况,给个全览,并制定工程规范化和一致性的实施方案**
|
||||
> 关联:`04a-feedback/P0-5-matching-evolution.md`(本调研为其下游)
|
||||
> 性质:**只调研不实施**,产物为治理路线图
|
||||
|
||||
---
|
||||
|
||||
## 一、规范基线清单(共 14 条)
|
||||
|
||||
来源:CLAUDE.md(根 / 子模块) + 已知 spec / BD 手册 + 历史审计。
|
||||
|
||||
| # | 规范 | 来源 | 关键判定 |
|
||||
|---|---|---|---|
|
||||
| R1 | 业务库通过 FDW 只读访问 ETL,不直连 ETL 库 | `apps/backend/CLAUDE.md` "ETL 数据通过 FDW 映射的 app.v_* RLS 视图访问" | 后端不应直接 `psycopg2.connect` 到 `etl_feiqiu` |
|
||||
| R2 | DWS / 取数禁用 `consume_money`,统一用 `items_sum`(`ledger_amount`) | feiqiu CLAUDE.md / DWD-DOC 规则 1 | SELECT / 计算 / 返回字段不应包含 `consume_money` |
|
||||
| R3 | DWD/DWS 视图必须双 schema(原 schema + app schema 同步建) | `db/CLAUDE.md` RLS 双 schema 模板 | 后端可走 `app.v_*`,但 `dws.v_*` 必须存在等价镜像 |
|
||||
| R4 | 后端响应统一通过 ResponseWrapperMiddleware 包装 | `apps/backend/CLAUDE.md` | 不应有路由用 `JSONResponse`/`response_class` 显式绕过 |
|
||||
| R5 | JWT 三类 aud(`admin`/`miniapp`/`tenant-admin`)严格隔离 | `apps/backend/CLAUDE.md` JWT 双认证表 | 签发与校验都必须设置/校验 `aud` 字段 |
|
||||
| R6 | AI 应用调用走 dispatcher 调度,不直接调 DashScope SDK | `apps/backend/CLAUDE.md` AI 集成 | 业务路由不应自行构造 `DashScopeClient` 调 `Application.call` |
|
||||
| R7 | 配置分层 .env < .env.local < 环境变量 < CLI 参数,禁止生产代码硬编码主机/凭据 | 根 CLAUDE.md | 业务源码不应出现硬编码 IP/URL/密钥 |
|
||||
| R8 | 测试不连生产库,用 `TEST_*_DSN`/`load_dotenv` 加载根 .env | 根 CLAUDE.md "测试与验证环境规范" | 测试不应引用 `PG_DSN`/`APP_DB_DSN` |
|
||||
| R9 | 小程序参数命名 camelCase 对外、snake_case 内部 | 通用约定 | 后端 SnakeCase → CamelModel 自动转,前端取 camelCase |
|
||||
| R10 | 审计记录全部归 `docs/audit/changes/`,不写入子模块 | 根 CLAUDE.md "文件归属规则" | 子模块下不应出现 `docs/audit/` |
|
||||
| R11 | `_archived/` 目录禁止读取或参考 | 根 CLAUDE.md | 不应被 grep/read,不应被新代码引用 |
|
||||
| R12 | `apps/demo-miniprogram/`(MOCK 标杆)禁改 | 根 CLAUDE.md | 30 天内不应有 commit 触及 |
|
||||
| R13 | shared 包跨子项目共享 enums/money/datetime_utils | 根 CLAUDE.md / 推断 | 子项目不应重复定义同名工具 |
|
||||
| R14 | DWD/DWS 字段命名约定:ETL 用 `tenant_member_id` / `site_assistant_id`,业务可用 `member_id` / `assistant_id`(已映射) | feiqiu CLAUDE.md `fdw_queries.py:48-58` | 跨层混用应有显式映射注释 |
|
||||
|
||||
---
|
||||
|
||||
## 二、偏离点全集(按规范分组)
|
||||
|
||||
### 2.1 R1 · FDW / 跨库访问
|
||||
|
||||
**关键背景**:H2(2026-03-20)规范变更后,业务后端"直连 ETL 库 + RLS 视图"已成为**事实标准**(`fdw_queries._fdw_context()` 模式)。R1 当前的写法"业务库通过 FDW 访问 ETL"在 `apps/backend/CLAUDE.md` 中实际已过期,应在治理时同步修订。但即便按事实标准衡量,仍存在多处偏离。
|
||||
|
||||
#### 偏离点 1 — `apps/backend/app/services/matching.py:62`(P0-5 主体)
|
||||
|
||||
- 现状:用 `_fdw_context(None, site_id)` 直连 ETL,**未传入业务库 conn**,`bd_str` 降级为系统今天而非 RuntimeContext.business_date
|
||||
- 偏离类型:绕过 RuntimeContext 透传(P0-5 选项 B 主张"补 FDW 外部表"消除直连)
|
||||
- 影响:沙箱模式下日期上界裁剪失效(若 site 启用沙箱,匹配读到未来助教/员工)
|
||||
|
||||
#### 偏离点 2 — `apps/backend/app/database.py:175 get_etl_write_connection()` + `services/task_generator.py:1107`
|
||||
|
||||
- 现状:后端**写入** ETL 关系指数表(`get_etl_write_connection()` 是可写连接,无 RLS,task_generator 直接执行 INSERT)
|
||||
- 偏离类型:**严重违反 R1**(规范明确"FDW 只读访问 ETL")
|
||||
- 影响:业务后端越权写 ETL 库,绕过 ETL Loader 流程,数据所有权混乱
|
||||
|
||||
#### 偏离点 3 — `apps/backend/app/database.py:109 get_etl_global_readonly_connection()` + `routers/etl_status.py:44`
|
||||
|
||||
- 现状:全局只读连接,**不设置 `app.current_site_id`**,直连 ETL 库读全局状态
|
||||
- 偏离类型:绕过 RLS 隔离(规范前提"直连必带 RLS")
|
||||
- 影响:潜在跨门店数据泄露(若被业务路由误调)
|
||||
|
||||
#### 偏离点 4-7 — H2 改造遗漏的"伪 FDW"代码(实际必坏)
|
||||
|
||||
| 文件 | 行号 | 现状 | 严重度 |
|
||||
|---|---|---|---|
|
||||
| `routers/tenant_users.py` | 425, 450 | `etl_conn = get_etl_readonly_connection(site_id)` 后 SQL 写 `FROM fdw_etl.v_dim_assistant/v_dim_staff` | **P0 — 必失败** |
|
||||
| `routers/tenant_excel.py` | 390, 407 | 同上,SQL 写 `fdw_etl.v_dim_assistant/v_dim_staff` | **P0 — 必失败** |
|
||||
| `routers/tenant_clues.py` | 113, 119 | 同上,SQL 写 `fdw_etl.v_dim_member` | **P0 — 必失败** |
|
||||
|
||||
**致命点**:这些查询连接的是 `etl_feiqiu` 库,而 `fdw_etl` schema 只存在于 `zqyy_app` 业务库 → 必报 `schema "fdw_etl" does not exist` → 当前被 `try/except` 静默吞,接口表面正常但**永远返回空列表**。这是 P0-5 同类(H2 改造遗漏)的高危偏离。
|
||||
|
||||
---
|
||||
|
||||
### 2.2 R2 · `consume_money` 字段
|
||||
|
||||
#### 偏离点 8 — `apps/backend/app/services/fdw_queries.py:1173/1208/1226`
|
||||
|
||||
- 现状:`get_member_consumption_orders()` SELECT 仍包含 `sh.consume_money`,GROUP BY 也带,字典返回 `"consume_money": float(...)` 给前端
|
||||
- 偏离类型:R2"DWS 取数禁用 consume_money"违反
|
||||
- 用途反查:`customer_service.py:385` 用于"原价/折扣价"对比展示(原价 = consume_money,实付 = total_amount)
|
||||
- 评估:**业务有真实需求**(展示"划线价"),但取自 DWD 原始 consume_money 仍属于规范偏离 → 应在 BD 手册更新口径或加 view 层封装
|
||||
|
||||
#### 偏离点 9 — `apps/etl/connectors/feiqiu/tasks/utility/dws_build_order_summary_task.py:23`
|
||||
|
||||
- 现状:用 `(COALESCE(sh.consume_money, 0) = 0 AND COALESCE(sh.pay_amount, 0) > 0) AS recharge_order_flag` 识别充值订单
|
||||
- 偏离类型:R2 在判定逻辑中使用 consume_money(非金额计算,但仍是该字段)
|
||||
- 评估:**判定语义合理**(充值单消费金额必为 0),但应文档化为"R2 例外:仅作零值判定,不参与金额计算"
|
||||
|
||||
---
|
||||
|
||||
### 2.3 R3 · DWS 双 schema 视图
|
||||
|
||||
#### 偏离点 10 — DWS schema 仅暴露 4 个视图,app schema 暴露 50+(规模性偏离)
|
||||
|
||||
`db/etl_feiqiu/schemas/dws.sql` 中 `dws.v_*` 仅 4 条:
|
||||
- `dws.v_dws_coach_area_hours`(L1206)
|
||||
- `dws.v_dws_finance_area_daily`(L1227)
|
||||
- `dws.v_dws_finance_board_cache`(L1267)
|
||||
- `dws.v_member_recall_priority`(L1292)
|
||||
|
||||
`db/etl_feiqiu/schemas/app.sql` 中 `app.v_dws_*`/`app.v_dwd_*`/`app.v_dim_*`/`app.v_cfg_*` 共约 **50 条**。
|
||||
|
||||
DWS 基表共有 38 张(L46-L1027),意味着 ~34 张表只在 app schema 暴露 RLS 视图,违反 R3"双 schema"。
|
||||
|
||||
- 偏离类型:**R3 规模性违反**
|
||||
- 影响:运维直查 `dws` schema 时只能看到原始基表(无门店过滤),易出错;新建 RLS 视图必须双 schema 的规则反复被忽视
|
||||
- 历史标记:`memory/MEMORY.md` 提到"踩坑 2026-03-29 DWS RLS 双 schema 强制规则"已存在,但执行不严
|
||||
|
||||
---
|
||||
|
||||
### 2.4 R4 · ResponseWrapperMiddleware
|
||||
|
||||
#### 评估:无显式偏离,中间件设计良好
|
||||
|
||||
`app/middleware/response_wrapper.py:33-37` 中间件已自动跳过:
|
||||
- `text/event-stream`(SSE)
|
||||
- 非 `application/json`
|
||||
- 非 2xx
|
||||
|
||||
`xcx_chat.py:344` / `tenant_excel.py:954` 用 `StreamingResponse`(SSE / 文件下载),由中间件自动跳过包装,**不算偏离**。
|
||||
|
||||
---
|
||||
|
||||
### 2.5 R5 · JWT aud 严格隔离
|
||||
|
||||
#### 偏离点 11 — `apps/backend/app/auth/jwt.py:42-50` admin/miniapp 签发**完全不带 `aud` 字段**
|
||||
|
||||
```python
|
||||
payload = {
|
||||
"sub": str(user_id),
|
||||
"site_id": site_id,
|
||||
"type": "access",
|
||||
"exp": expire,
|
||||
} # 无 aud!
|
||||
```
|
||||
|
||||
只有 `tenant_auth.py:69/93` 显式设置 `"aud": "tenant-admin"`。意味着:
|
||||
- admin-web 与小程序 token 在 payload 层**无法区分**
|
||||
- `auth/dependencies.py:124 decode_access_token()` 也**没校验 aud**
|
||||
- 结果:小程序 token 理论上可被用于 admin 端点(若仅依赖 `decode_access_token` + roles 检查,roles 分类不严就破防)
|
||||
|
||||
- 偏离类型:**P0 — 安全性偏离 R5**
|
||||
- 影响:跨端横向越权风险
|
||||
- 修复成本:中(需小程序/admin-web 同步重新签发 token + 后端 verify 接受多 aud)
|
||||
|
||||
---
|
||||
|
||||
### 2.6 R6 · AI 应用走 dispatcher
|
||||
|
||||
#### 偏离点 12 — 多处直接构造 `DashScopeClient` 调用
|
||||
|
||||
| 文件 | 行号 | 用途 |
|
||||
|---|---|---|
|
||||
| `app/main.py` | 145 | lifespan 内全局单例(合理,作为 dispatcher 注入源) |
|
||||
| `app/services/chat_service.py` | 734 | `_get_dashscope_client()` 工厂 + `:644` 直接 SSE 流式调用百炼 |
|
||||
| `app/services/note_service.py` | 71 | 单点客户端实例化(走 App6 直接调用) |
|
||||
| `app/routers/xcx_chat.py` | 408 | 同上,`xcx_chat.py:203` SSE 直接调 |
|
||||
|
||||
- 偏离类型:R6 部分违反
|
||||
- 评估:**SSE 流式回复天然不能走 dispatcher 的 run_step**(dispatcher 设计是同步整段 reply),目前实现合理。但 `note_service` 直调 App6 应改走 dispatcher
|
||||
- 影响:绕过熔断/限流/预算追踪,但 client 内部已有部分保护
|
||||
- 修复成本:小(note_service 改走 dispatcher;chat_service SSE 路径标记为合理偏离)
|
||||
|
||||
---
|
||||
|
||||
### 2.7 R7 · 配置硬编码
|
||||
|
||||
#### 偏离点 13 — `apps/etl/connectors/feiqiu/config/defaults.py:30`
|
||||
|
||||
- 现状:`"base_url": "https://pc.ficoo.vip/apiprod/admin/v1"` 硬编码飞球 API
|
||||
- 评估:作为 default 合理,允许 .env 覆盖;**不算严格偏离**
|
||||
|
||||
#### 偏离点 14 — `apps/etl/connectors/feiqiu/scripts/refresh_json_and_audit.py:22` / `full_api_refresh_v2.py:27`
|
||||
|
||||
- 现状:`API_BASE = "https://pc.ficoo.vip/apiprod/admin/v1/"` 模块顶层硬编码,无 env 读取分支
|
||||
- 偏离类型:R7 违反
|
||||
- 影响:迁移到飞球新域名/沙箱时改不动;一次性脚本性质风险低
|
||||
- 修复成本:小
|
||||
|
||||
#### 偏离点 15 — `apps/etl/connectors/feiqiu/orchestration/flow_runner.py:255`
|
||||
|
||||
- 现状:`backend_url = os.getenv("BACKEND_API_URL", "http://127.0.0.1:8000")` — fallback 到本机
|
||||
- 评估:**合理**(开发期默认),且有 env 覆盖
|
||||
|
||||
#### 偏离点 16 — `apps/admin-web/vite.config.ts:11` 与 `playwright.config.ts:21`
|
||||
|
||||
- 现状:proxy target / playwright baseURL 硬编码 `localhost:8000` / `localhost:5173`
|
||||
- 评估:**合理**(开发工具配置,非生产代码)
|
||||
|
||||
---
|
||||
|
||||
### 2.8 R8 · 测试不连生产库
|
||||
|
||||
#### 偏离点 17 — `apps/backend/tests/tests/unit/test_backfill_script.py:29`
|
||||
|
||||
- 现状:`with patch.dict("os.environ", {"APP_DB_DSN": "postgresql://test:test@localhost/test"})` — 用 `APP_DB_DSN` 而非 `TEST_APP_DB_DSN`
|
||||
- 偏离类型:R8 违反(变量名错误,虽然值是假 DSN)
|
||||
- 影响:命名混淆,易让人以为生产 DSN 也能用;真跑会因 patch 顺序问题潜在风险
|
||||
- 修复成本:极小
|
||||
|
||||
#### 偏离点 18 — 测试 `load_dotenv` 检查
|
||||
|
||||
`apps/backend/tests/tests/integration/` 下两个 e2e 测试 import `load_dotenv`,实际加载根 `.env`(包含 PG_DSN/APP_DB_DSN),依赖 cwd 决定加载哪个 .env。如果集成测试在 backend cwd 跑,会加载 `apps/backend/.env.local`(其内 `CORS_ORIGINS=http://localhost:5173` 但**未必有 TEST_*_DSN 覆盖**) → 风险:误连生产库
|
||||
- 偏离类型:R8 潜在违反
|
||||
- 修复成本:中(需 conftest 强制注入 TEST_*_DSN 校验)
|
||||
|
||||
---
|
||||
|
||||
### 2.9 R9 · 命名风格
|
||||
|
||||
#### 偏离点 19 — 小程序 `task-list.ts:586,628,647` 内部混用 camelCase / snake_case
|
||||
|
||||
```ts
|
||||
const memberId = (target as any).memberId ?? (target as any).member_id // L586
|
||||
const userId = (authUser as any).userId // L628
|
||||
```
|
||||
|
||||
后端 CamelModel 应自动转成 camelCase 输出,小程序兜底读 snake_case 表明历史上**后端字段未走 CamelModel**。需逐字段确认:
|
||||
- 是否后端 `xcx_*` 路由全部走 `CamelModel.model_dump(by_alias=True)`?
|
||||
- 现状部分 service 直接 `dict` 返回(如 `tenant_users.py:441 .model_dump(by_alias=True)` 写了,但 `routers/xcx_*` 是否一致?)
|
||||
|
||||
- 偏离类型:R9 部分违反(已知 P1-9 反馈)
|
||||
- 修复成本:中
|
||||
|
||||
#### 偏离点 20 — `member_id` vs `tenant_member_id` 混用
|
||||
|
||||
ETL 库 DWD 字段名 `tenant_member_id`(`fdw_queries.py:48` 注释明确),业务 API 对外是 `member_id`。
|
||||
fdw_queries.py 内 SELECT/JOIN 都正确映射(`tenant_member_id = %s` → 返回 `member_id`),小程序 API 用 `memberId`。
|
||||
- 评估:**已有显式映射**,符合 R14(DWD 字段命名约定),不算偏离;但映射散落在 200+ 行,易遗漏
|
||||
|
||||
---
|
||||
|
||||
### 2.10 R10 · 审计文件归属
|
||||
|
||||
#### 偏离点 21 — `apps/backend/tests/tests/_archived/`
|
||||
|
||||
- 现状:子模块测试目录下有 `_archived/`(test_ai_app2.py / test_ai_apps_prompt.py / test_ai_clue_writer.py)
|
||||
- 偏离类型:**R11 + R10 违反**(子模块下不应有 `_archived/`,此为旧 Cursor 时代的产物)
|
||||
- 修复成本:小(整体移到根 `_DEL/` 或者评审后删除)
|
||||
|
||||
#### 偏离点 22 — `apps/backend/tests/tests/` 嵌套异常
|
||||
|
||||
- 现状:`apps/backend/tests/` 与 `apps/backend/tests/tests/` **同时存在并各有重复测试文件**(test_auth_jwt.py 出现在两层)
|
||||
- 偏离类型:**目录结构混乱 / 历史迁移残留**
|
||||
- 影响:pytest 重复执行 / 修改时不知改哪个版本
|
||||
- 修复成本:小(取舍后删一份)
|
||||
|
||||
#### 偏离点 23 — `apps/etl/connectors/feiqiu/tests/unit/unit/`
|
||||
|
||||
- 现状:`tests/unit/unit/` 三层嵌套,任务测试文件有 task_test_utils.py 同名重复
|
||||
- 偏离类型:同 22
|
||||
- 修复成本:小
|
||||
|
||||
#### 评估:`apps/etl/connectors/feiqiu/scripts/audit/`
|
||||
|
||||
- 现状:模块内 `scripts/audit/` 是审计**工具**(scanner.py / inventory_analyzer.py 等),不是审计记录
|
||||
- 评估:**不算偏离**(R10 限定的是审计记录,工具放模块内合理)
|
||||
|
||||
---
|
||||
|
||||
### 2.11 R11/R12 · `_archived/` 与 `demo-miniprogram/`
|
||||
|
||||
#### 偏离点 24 — demo-miniprogram 30 天内被修改
|
||||
|
||||
git log 显示:
|
||||
- `f2e0de8`(2026-05-02 反向迁移) — 改 `apps/demo-miniprogram/AGENTS.md`
|
||||
- `81e4173`(2026-04-29) — 改同文件
|
||||
- `2a7a5d6`(2026-04-15~20) — 改 `project.miniapp.json` / `project.private.config.json`
|
||||
- 偏离类型:**R12 违反**(MOCK 标杆原则上禁改)
|
||||
- 评估:`AGENTS.md` 是 AI 环境文件,反向迁移期间清理可理解;`project.miniapp.json` 改动需要审视
|
||||
|
||||
#### `_archived/` 目录读取
|
||||
|
||||
`pre_read_archived_block.py` hook 已强阻断,无新违反
|
||||
|
||||
---
|
||||
|
||||
### 2.12 R13 · shared 包共用
|
||||
|
||||
未发现明显偏离。`packages/shared/` 提供 enums/money/datetime_utils,各子项目正常 import,无重复定义。
|
||||
|
||||
---
|
||||
|
||||
## 三、偏离点严重度矩阵
|
||||
|
||||
| # | 偏离点 | 规范 | 原因 | 影响范围 | 修复成本 | 优先级 | 风险 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 1 | matching.py 不传 conn | R1 | 历史 H2 改造速度优先 | 单点 | 小 | Wave 1 | 中 |
|
||||
| 2 | get_etl_write_connection + task_generator 写 ETL | R1 | 紧急修复(关系指数回写) | 模块 | 中 | Wave 2 | 高 |
|
||||
| 3 | get_etl_global_readonly_connection 无 RLS | R1 | 设计偏好(系统监控) | 单点 | 小 | 长期 | 低 |
|
||||
| 4-7 | tenant_users/excel/clues 残留 fdw_etl.* SQL(必坏) | R1 | H2 改造遗漏 | 模块 | 小 | **立即(P0)** | **高** |
|
||||
| 8 | fdw_queries 返回 consume_money 给前端 | R2 | 业务划线价需求 | 单点 | 中 | Wave 3 | 中 |
|
||||
| 9 | dws_build_order_summary 用 consume_money 判定 | R2 | 充值单零值判定 | 单点 | 极小 | 文档化 | 低 |
|
||||
| 10 | DWS schema 30+ 视图缺双 schema 镜像 | R3 | 不知规范 / 历史遗留 | **全局** | 大 | Wave 5 | 中 |
|
||||
| 11 | jwt.py 不带 aud,decode 不验 aud | R5 | 设计偏好(单 secret) | **全局** | 中 | **立即(P0)** | **高** |
|
||||
| 12 | note_service 直调 DashScopeClient | R6 | 紧急功能 | 单点 | 小 | Wave 2 | 低 |
|
||||
| 13-14 | 飞球 API 地址硬编码 | R7 | 一次性脚本 | 单点 | 极小 | 长期 | 低 |
|
||||
| 17 | test_backfill_script 用 APP_DB_DSN | R8 | 命名错误 | 单点 | 极小 | 立即 | 低 |
|
||||
| 18 | 集成测试 .env 加载顺序 | R8 | 配置不严 | 模块 | 中 | Wave 1 | 中 |
|
||||
| 19 | 小程序 camelCase/snake_case 混用兜底 | R9 | 历史 P1-9 已知 | 模块 | 中 | Wave 3 | 低 |
|
||||
| 21 | tests/tests/_archived/ | R10/R11 | 历史迁移残留 | 单点 | 极小 | 立即 | 低 |
|
||||
| 22 | tests/tests/ 嵌套重复 | R10 | 历史迁移残留 | 模块 | 小 | 立即 | 中 |
|
||||
| 23 | feiqiu tests/unit/unit/ 嵌套 | R10 | 历史迁移残留 | 模块 | 小 | 立即 | 低 |
|
||||
| 24 | demo-miniprogram 30 天内改动 | R12 | AGENTS.md 迁移 | 单点 | 0 | 文档化(可接受) | 低 |
|
||||
|
||||
**统计**:24 个偏离点,其中 **P0 立即** 4 类(偏离 4-7、11、17、21、22、23),Wave 协同 5 类(1、2、8、12、18、19),长期 2 类(3、10、13-14),可接受文档化 3 类(9、20、24)。
|
||||
|
||||
---
|
||||
|
||||
## 四、统一治理方案
|
||||
|
||||
### 4.1 立即治理项(D-Bug 类,数据/安全风险)
|
||||
|
||||
#### 治理 A · 修复 4 处必坏的 fdw_etl.* 残留(对齐 P0-5 主体)
|
||||
|
||||
| 文件 | 行 | 改法 |
|
||||
|---|---|---|
|
||||
| `routers/tenant_users.py:431` | `fdw_etl.v_dim_assistant` | → `app.v_dim_assistant` |
|
||||
| `routers/tenant_users.py:456-457` | `fdw_etl.v_dim_staff` / `v_dim_staff_ex` | → `app.v_dim_staff` / `app.v_dim_staff_ex` |
|
||||
| `routers/tenant_excel.py:394` | `fdw_etl.v_dim_assistant` | → `app.v_dim_assistant` |
|
||||
| `routers/tenant_excel.py:411` | `fdw_etl.v_dim_staff` | → `app.v_dim_staff` |
|
||||
| `routers/tenant_clues.py:119` | `fdw_etl.v_dim_member` | → `app.v_dim_member` |
|
||||
|
||||
后续应改用 `_fdw_context()` 而非裸 `get_etl_readonly_connection`(统一拥有 business_date GUC)。
|
||||
|
||||
#### 治理 B · JWT aud 标准化(R5)
|
||||
|
||||
1. `auth/jwt.py:42` payload 增 `"aud": "miniapp"`(小程序签发)
|
||||
2. 新建 `auth/admin_jwt.py` 给 admin-web 签发,设 `"aud": "admin"`
|
||||
3. `dependencies.py:124 decode_access_token` 接受 `audience` 参数,FastAPI 依赖按路由前缀分流
|
||||
4. 测试覆盖:miniapp token 调 admin 端点必须 401,反之亦然
|
||||
|
||||
#### 治理 C · 测试目录单轨化
|
||||
|
||||
| 操作 |
|
||||
|---|
|
||||
| `apps/backend/tests/tests/_archived/` → 评审后删除或移到根 `_DEL/` |
|
||||
| `apps/backend/tests/tests/` 与 `apps/backend/tests/` 合并(保留较新版本) |
|
||||
| `apps/etl/connectors/feiqiu/tests/unit/unit/` 同上 |
|
||||
| `test_backfill_script.py:29` 把 `APP_DB_DSN` 改为 `TEST_APP_DB_DSN` |
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Wave 协同项
|
||||
|
||||
| Wave | 关联偏离 | 治理措施 |
|
||||
|---|---|---|
|
||||
| Wave 1(matching FDW 重建) | 偏离 1、18 | P0-5 选项 B 实施时,顺带把 `_fdw_context` 改为 `must` 接 conn,集成测试 conftest 强制注入 TEST_*_DSN |
|
||||
| Wave 2(关系指数 / AI 流水线) | 偏离 2、12 | task_generator 关系指数回写 ETL → 改走 ETL 内置 task(由 ETL 主动消费 biz 的关系信号);note_service 改走 dispatcher |
|
||||
| Wave 3(对外 API 一致性) | 偏离 8、19 | 引入 `view_dwd_order_with_orig_price` 封装 consume_money 划线逻辑,前端字段统一 camelCase 强校验 |
|
||||
| Wave 5(数据库治理) | 偏离 10 | 编写 `tools/db/check_dws_dual_schema.py` 扫描 dws 基表 vs `dws.v_*` 视图差集,补齐 30+ 镜像;CI 加守护测试 |
|
||||
|
||||
### 4.3 长期治理项
|
||||
|
||||
#### L1 · 修订 R1 文字(`apps/backend/CLAUDE.md`)
|
||||
|
||||
事实上 H2 后已统一直连 ETL,规范文字应改为:
|
||||
|
||||
> ETL 数据通过 `_fdw_context(conn, site_id)` 直连 ETL 库 + RLS 视图访问;`fdw_etl.*` 外部表已废弃,新代码禁用。
|
||||
|
||||
#### L2 · 编写 `scripts/audit/check_engineering_consistency.py`
|
||||
|
||||
零 token 静态扫描脚本,检查项:
|
||||
1. grep `fdw_etl\.` 在 `apps/backend/app/` 下应零结果(白名单注释除外)
|
||||
2. grep `audience=` 在 jwt 签发函数应非空
|
||||
3. grep `consume_money` 在 backend service / ETL 任务中应有 BD 手册引用
|
||||
4. 双 schema 视图差集(对比 `dws.v_*` 与 `app.v_dws_*`)
|
||||
5. 测试目录嵌套 / `_archived/` 子模块违规
|
||||
6. `localhost`/`127.0.0.1` 在生产源码白名单外
|
||||
|
||||
接入 `scripts/audit/prescan.py`,合并到 `/audit` 流程
|
||||
|
||||
#### L3 · DWS 双 schema 历史补齐
|
||||
|
||||
约 30+ 个 `app.v_dws_*` 视图需补 `dws.v_dws_*` 镜像,生成迁移文件 `db/etl_feiqiu/migrations/2026-XX-XX__dws_dual_schema_backfill.sql`,逆序 DROP 写入回滚。
|
||||
|
||||
### 4.4 可接受偏离项(文档化归档)
|
||||
|
||||
| 偏离 | 理由 | 文档化位置 |
|
||||
|---|---|---|
|
||||
| 9 (`dws_build_order_summary` 用 consume_money 零值判定) | 仅作零值标记,不参与金额计算,业务语义清晰 | `apps/etl/connectors/feiqiu/CLAUDE.md` 增"R2 例外清单" |
|
||||
| chat_service SSE 直调 DashScopeClient | 流式 reply 无法走 dispatcher run_step 抽象 | `apps/backend/CLAUDE.md` 增"R6 例外:SSE 流式直连" |
|
||||
| 20 (member_id ↔ tenant_member_id 映射) | 已在 fdw_queries.py 集中映射,无散点违反 | 已在 `fdw_queries.py:48-58` 注释 |
|
||||
| 24 (demo-miniprogram AGENTS.md) | 反向迁移产物,与代码逻辑无关 | 在 `CLAUDE.md R12` 加"例外:AGENTS.md / project config 文件可随迁移调整" |
|
||||
|
||||
---
|
||||
|
||||
## 五、CI / 自动化校验建议
|
||||
|
||||
### 5.1 静态规则(零 token)
|
||||
|
||||
| 检查 | 工具 | 失败动作 |
|
||||
|---|---|---|
|
||||
| `fdw_etl\.` 在 `apps/backend/app/` | grep + CI | 拒绝合并 |
|
||||
| 签发 JWT 必带 `aud` 字段 | `tools/audit/check_jwt_aud.py` | 拒绝合并 |
|
||||
| dws 基表 vs `dws.v_*` 视图差集 | `tools/db/check_dws_dual_schema.py` | warning(>0 个差集) |
|
||||
| 测试 `from app.config import APP_DB_DSN` 等违规导入 | grep | 拒绝合并 |
|
||||
| `_archived/` 子模块嵌套 | find / glob | 警告 |
|
||||
|
||||
### 5.2 提交期 hook
|
||||
|
||||
新增 `.claude/hooks/pre_commit_engineering_consistency.py`:
|
||||
- pre-commit 阶段调上述静态扫描脚本
|
||||
- 命中即阻断并打印偏离点 + R 编号
|
||||
|
||||
### 5.3 周期性体检
|
||||
|
||||
`scripts/audit/weekly_consistency_report.py`:
|
||||
- 每周生成本表(同此文档结构)
|
||||
- 输出到 `docs/audit/engineering_consistency_<YYYY-MM-DD>.md`
|
||||
- diff 比上周新增/消除的偏离
|
||||
|
||||
---
|
||||
|
||||
## 六、给 Neo 的决策清单
|
||||
|
||||
按优先级倒序请 Neo 决策:
|
||||
|
||||
### 决策 D1(立即 / 高风险)
|
||||
|
||||
- [ ] **是否同意 P0 治理 A**:修复 4 处 `fdw_etl.*` 残留(`tenant_users/excel/clues`),改为 `app.v_*`?**这是真 bug,生产环境必坏,目前被 try/except 静默吞**。
|
||||
- [ ] **是否同意 P0 治理 B**:JWT aud 标准化(jwt.py 加 `aud`,decode 加 audience 校验)?需后端+小程序+admin-web 三端协同发版。
|
||||
- [ ] **是否同意 P0 治理 C**:删除 `apps/backend/tests/tests/_archived/`、合并 `tests/tests/` 嵌套?
|
||||
|
||||
### 决策 D2(随 Wave 协同)
|
||||
|
||||
- [ ] Wave 1 P0-5 选项 B 实施时,是否一并把 `_fdw_context(None, ...)` 改为强制传 conn?(避免 business_date 降级)
|
||||
- [ ] Wave 2:task_generator 写 ETL 关系指数表 → 改为"ETL 主动消费 biz 信号"还是保留 `get_etl_write_connection`?后者需在 R1 中明确"例外清单"。
|
||||
|
||||
### 决策 D3(长期治理)
|
||||
|
||||
- [ ] DWS 双 schema 30+ 视图历史补齐 → 何时启动?(估计需要 1-2 个工作日)
|
||||
- [ ] 是否增加 `.claude/hooks/pre_commit_engineering_consistency.py` 提交期阻断?
|
||||
- [ ] 周期性体检报告 → 接入 `/audit` 还是独立 weekly?
|
||||
|
||||
### 决策 D4(规范文字修订)
|
||||
|
||||
- [ ] R1 在 `apps/backend/CLAUDE.md` 是否改写为"H2 后规范"(直连 ETL + RLS,弃用 fdw_etl.*)?
|
||||
- [ ] R2 是否在 feiqiu CLAUDE.md 增"零值判定例外清单"?
|
||||
- [ ] R6 是否增"SSE 流式直连例外"?
|
||||
|
||||
---
|
||||
|
||||
## 附录 A · 调研覆盖范围与方法
|
||||
|
||||
| 维度 | 覆盖工具 | 命中数 |
|
||||
|---|---|---|
|
||||
| FDW / 跨库 | grep `get_etl_*_connection`、`fdw_etl\.`、`_fdw_context` | 19 文件 |
|
||||
| 响应包装 | grep `JSONResponse` / `response_class` / `StreamingResponse` | 2 文件(SSE 合理) |
|
||||
| JWT aud | grep `audience=` / `aud['"]` / `verify_token` | 4 文件 |
|
||||
| AI 调用 | grep `DashScopeClient(` / `dashscope.` | 4 直调点 |
|
||||
| RLS 双 schema | grep `^CREATE.*VIEW (dws|app)\.` | dws 4 / app 50 |
|
||||
| consume_money | grep | 9 处 |
|
||||
| 配置硬编码 | grep `localhost` / `127.0.0.1` / `https?://` | 18 行 |
|
||||
| 测试连库 | grep `PG_DSN` / `APP_DB_DSN` / `TEST_*_DSN` | 1 单测违规 |
|
||||
| 命名混用 | grep 小程序 `member_id` / `memberId` / `userId` | 20 文件 |
|
||||
| 审计/归档 | ls 子模块 docs/audit、`_archived/` | 3 处嵌套违规 |
|
||||
| demo-miniprogram | git log 30 天 | 4 commits |
|
||||
|
||||
未覆盖维度(后续补):
|
||||
- shared 包重复定义检查(R13)
|
||||
- xcx_* 路由是否全部走 CamelModel(R9 深度抽样)
|
||||
- ETL DWD 12 条强制规则(feiqiu CLAUDE.md)逐项审计
|
||||
|
||||
---
|
||||
|
||||
## 附录 B · 与既有反馈交叉引用
|
||||
|
||||
| 本文偏离 | 关联反馈 | 关联文件 |
|
||||
|---|---|---|
|
||||
| 偏离 1 | P0-5 反馈主体 | `04a-feedback/P0-5-matching-evolution.md` |
|
||||
| 偏离 11 | (新发现 / 未在 P0/P1 列表) | — |
|
||||
| 偏离 19/20 | P1-9 散落 memberId | `04b-feedback/P1-12-scattered-memberid.md` |
|
||||
| 偏离 4-7 | (新发现 / 高危) | — |
|
||||
| 偏离 10 | 已知"DWS RLS 双 schema 踩坑"(memory 2026-03-29) | `MEMORY.md` |
|
||||
|
||||
---
|
||||
|
||||
(全文 ~570 行)
|
||||
279
docs/_overview/04a-feedback/P0-5-matching-evolution.md
Normal file
279
docs/_overview/04a-feedback/P0-5-matching-evolution.md
Normal file
@@ -0,0 +1,279 @@
|
||||
# P0-5 matching.py 直连 ETL 演进史调研
|
||||
|
||||
> 日期:2026-05-04
|
||||
> 触发:Neo 在 04a-conflicts-P0-detail 反馈,倾向选项 B(补全 FDW 外部表),要求先调研当时为何偏离工程规范
|
||||
> 关联文件:`apps/backend/app/services/matching.py`、`apps/backend/app/services/fdw_queries.py`
|
||||
> 关联审计:`2026-03-18__rns1-e2e-fdw-direct-connect-bugfix.md`、`2026-03-20__h2-fdw-to-direct-etl-unification.md`、`2026-03-20__rns1-ai-autonomous-decision-risk-audit.md`
|
||||
|
||||
---
|
||||
|
||||
## 一、当前实现状态
|
||||
|
||||
### 1.1 是否真的"直连 ETL 库"
|
||||
|
||||
**是,但不是经 `get_connection()` 直连**。`matching.py` 通过 `app.services.fdw_queries._fdw_context(None, site_id)` 上下文管理器获取游标,该上下文内部调用 `app.database.get_etl_readonly_connection(site_id)` 直连 `etl_feiqiu` (生产) / `test_etl_feiqiu` (测试)。
|
||||
|
||||
```python
|
||||
# apps/backend/app/services/matching.py:21
|
||||
from app.services.fdw_queries import _fdw_context
|
||||
# apps/backend/app/services/matching.py:62
|
||||
with _fdw_context(None, site_id) as cur:
|
||||
candidates.extend(_query_assistants(cur, phone))
|
||||
candidates.extend(_query_staff(cur, phone, employee_number))
|
||||
```
|
||||
|
||||
`_fdw_context` 在 fdw_queries.py:80 实现:
|
||||
|
||||
- 第一参数 `conn` 仅用于读取业务库 RuntimeContext (业务日 / sandbox 模式),**不参与 SQL 查询**
|
||||
- 内部 `from app.database import get_etl_readonly_connection` 新建到 ETL 库的直连
|
||||
- `SET LOCAL app.current_site_id = <site_id>` + `SET LOCAL app.current_business_date = <bd>` (两条 GUC 在 ETL 库连接上设置)
|
||||
- yield 出 cursor 给调用方
|
||||
- 退出时 commit / close (owned 时)
|
||||
|
||||
### 1.2 当前查询的视图
|
||||
|
||||
| 视图 | schema | 列名约定 |
|
||||
|---|---|---|
|
||||
| `app.v_dim_assistant` | etl_feiqiu | `scd2_is_current = 1` (integer) |
|
||||
| `app.v_dim_staff` | etl_feiqiu | `scd2_is_current = 1` (integer) |
|
||||
| `app.v_dim_staff_ex` | etl_feiqiu | `scd2_is_current = 1` (integer) |
|
||||
|
||||
### 1.3 文件头注释真实意图
|
||||
|
||||
L1-4 的 AI_CHANGELOG 明确写出了改动原因:
|
||||
|
||||
> 2026-03-20 | Prompt: H2 FDW→直连ETL统一改造 | FDW 外部表(fdw_etl.*)改为直连 ETL 库,查询 app.v_* RLS 视图。原因:postgres_fdw 不传递 GUC 参数,RLS 门店隔离失效。
|
||||
|
||||
L58 / L122 处的 `# CHANGE 2026-03-20 | H2` 锚点同步标注了 SQL 表名映射 `fdw_etl.v_dim_staff/v_dim_staff_ex → app.v_dim_staff/v_dim_staff_ex`,以及列类型映射 `scd2_is_current TRUE → 1`。
|
||||
|
||||
---
|
||||
|
||||
## 二、Git 历史时间线
|
||||
|
||||
| 日期 | commit | 作者 / 真实改动日 | 摘要 | 是否变更 dim_staff 访问方式 |
|
||||
|---|---|---|---|---|
|
||||
| 2026-02-26 | b25308c | Neo / 2026-02-26 | feat: P1-P3 全栈集成 — 数据库基础 + DWS 扩展 + 小程序鉴权 + 工程化体系 | 首次引入 `matching.py`,**走 fdw_etl.v_dim_assistant/v_dim_staff/v_dim_staff_ex** (FDW 外部表) |
|
||||
| 2026-02-27 ~ 2026-03-19 | 79f9a0e | Neo | feat: gift-card-breakdown / batch update | matching.py **未变** |
|
||||
| 2026-03-18 | (合并入 beb88d5) | AI / 2026-03-18 21:38~21:47 | RNS1.1 E2E 测试发现 postgres_fdw 不传 GUC,**自行**把 `fdw_queries.py` 47 个函数改为直连 ETL,**未改动 matching.py** (审计 H2 / 已知遗留清单第 4 项) | 否 (但已建立"直连模式" precedent) |
|
||||
| 2026-03-20 | (合并入 beb88d5) | AI + Neo / 2026-03-20 | H2 修复:用户确认方案 A (全部统一直连 ETL),把 4 个残留文件 (`matching.py` / `task_generator.py` / `recall_detector.py` / `task_manager.py`) 一并改为 `_fdw_context(None, site_id)` + `app.v_*` | **是,核心变更点** |
|
||||
| 2026-03-20 | beb88d5 | Neo / 2026-03-20 09:02 | feat: chat integration ... 累积提交,把 H1/H2/RNS1.1~1.4 全部代码合并入仓 | matching.py 在此提交 diff 中体现完整变更 |
|
||||
| 2026-04-06 | 6f8f123 | Neo / 2026-04-06 00:03 | feat: 累积功能变更 — 聊天集成 / 触发器调度 / Kiro→Claude Code 整理 | 仅追加 `@trace_service` 装饰器,**未改 SQL 路径** |
|
||||
| 2026-05-02 | caf179a | Neo / 2026-05-02 | feat: AI 重构 + Runtime Context + DWS 修复 | `_fdw_context` 增加 `app.current_business_date` GUC,matching.py 行为不变 |
|
||||
|
||||
### 2.1 关键 commit diff 摘要 (beb88d5)
|
||||
|
||||
```diff
|
||||
- from app.database import get_connection
|
||||
+ from app.services.fdw_queries import _fdw_context
|
||||
|
||||
- conn = get_connection()
|
||||
- conn.autocommit = False
|
||||
- with conn.cursor() as cur:
|
||||
- cur.execute("SET LOCAL app.current_site_id = %s", (str(site_id),))
|
||||
+ with _fdw_context(None, site_id) as cur:
|
||||
|
||||
- FROM fdw_etl.v_dim_assistant WHERE mobile = %s AND scd2_is_current = TRUE
|
||||
+ FROM app.v_dim_assistant WHERE mobile = %s AND scd2_is_current = 1
|
||||
|
||||
- FROM fdw_etl.v_dim_staff s
|
||||
- LEFT JOIN fdw_etl.v_dim_staff_ex ex
|
||||
+ FROM app.v_dim_staff s
|
||||
+ LEFT JOIN app.v_dim_staff_ex ex
|
||||
```
|
||||
|
||||
> 注:本仓的 git 日期与"会话日期"有约 17 天偏差 — beb88d5 commit date 是 2026-03-20,但里面打包了 2026-03-18~20 的 76 个 RNS1 系列 session。
|
||||
|
||||
---
|
||||
|
||||
## 三、审计记录追溯
|
||||
|
||||
### 3.1 `2026-03-18__rns1-e2e-fdw-direct-connect-bugfix.md` (起点)
|
||||
|
||||
- **触发**:RNS1.1 端到端测试中,API 调用 `fdw_etl.v_*` 时,远端 ETL 库的 RLS 视图执行 `current_setting('app.current_site_id')` **失败报错**(GUC 未在远端连接生效)
|
||||
- **根因**:`postgres_fdw` 不传递自定义 GUC 参数到远端连接,即使本地 `SET LOCAL app.current_site_id = ...` 也只对本地连接生效
|
||||
- **当时方案**:AI **未询问用户**,自行将 `fdw_queries.py` 改为通过 `get_etl_readonly_connection(site_id)` 直连 ETL 库,在同一连接上 `SET LOCAL` 后查 `app.v_*`
|
||||
- **遗留清单**:`matching.py` / `task_generator.py` / `recall_detector.py` / `task_manager.py` 4 个文件仍走 `fdw_etl.*`,被显式标注为"已知遗留,不在本次 E2E 测试范围"
|
||||
|
||||
### 3.2 `2026-03-20__rns1-ai-autonomous-decision-risk-audit.md` (定性)
|
||||
|
||||
H2 项被列为**高风险 8 项之一**,标题为"E2E 测试中自行决定架构级修改:FDW → 直连 ETL"。审计原文:
|
||||
|
||||
> AI 发现 `postgres_fdw` 不传递自定义 GUC 参数(`app.current_site_id`),尝试修复失败后,**自行**将 `fdw_queries.py` 从"通过业务库的 FDW 外部表查询"改为"直连 ETL 库查 RLS 视图"。AI 说"最干净的方案是让 fdw_queries.py 内部自己获取 ETL 直连",**没有询问用户就直接实施**。
|
||||
|
||||
`系统性问题 2`:架构级决策未经用户确认。审计建议:涉及数据库连接模式 / DDL / FDW 映射变更必须先展示方案对比。
|
||||
|
||||
### 3.3 `2026-03-20__h2-fdw-to-direct-etl-unification.md` (修复)
|
||||
|
||||
- **决策方式**:用户确认方案 A (全部统一直连 ETL) 后执行
|
||||
- **改造范围**:matching.py / task_generator.py / recall_detector.py / task_manager.py 4 个文件
|
||||
- **关键说明**:
|
||||
- "RNS1.1 期间,AI 自行将 `fdw_queries.py` 改为直连 ETL,但其他 4 个文件仍使用旧 FDW 模式,造成架构不一致"
|
||||
- "FDW 外部表 DDL 暂不清理(用户确认保留)" — 这是当前 fdw_etl schema 仍然存在但不被后端使用的根因
|
||||
- **回滚方案 (审计原文)**:"将 4 个文件中的 `_fdw_context` 调用改回 `get_connection()` + `fdw_etl.*` 查询,恢复 FDW 外部表列名。**注意:回滚后 RLS 门店隔离将再次失效**。"
|
||||
|
||||
### 3.4 `2026-03-20__rns14-chat-fdw-filter-audit.md`
|
||||
|
||||
聊天模块的 FDW 过滤审计,涉及 `dim_member` 但不直接影响 matching.py,作为佐证:RNS1 系列中"RLS 跨库失效"是普遍问题,不是 matching 单点。
|
||||
|
||||
---
|
||||
|
||||
## 四、历史 Session 追溯
|
||||
|
||||
| Session 文件 | 时间 | 上下文摘要 |
|
||||
|---|---|---|
|
||||
| `docs/ai-env-history/sessions/claude/6d607893-25c1-400e-82a2-325c4e968232.md` | 2026-04-07 23:10~04-08 07:38 | Fix-13 召回事件回滚 / `recall_detector.py` 中也保留了 `# AI_CHANGELOG 2026-03-20 H2 FDW→直连ETL` 注释。是 H2 改造后的**使用现场**,非决策原始 session。 |
|
||||
| 原始决策 session (审计指向) | 2026-03-18 21:38~21:47 | 路径 `18/44_9a92e7af/.../main_01_66b06ed6.md` (审计中引用,但不在当前 ai-env-history 目录中) |
|
||||
| 原始决策 session (H2 修复) | 2026-03-20 (具体时段未在审计中标注) | 用户确认方案 A 后批量改 4 文件,session 路径未在审计中明列 |
|
||||
|
||||
> **说明**:`docs/ai-env-history/sessions/claude/` 仅保留了 2026-04-07 之后的 claude session 摘要,2026-03-18~20 的 Kiro session 应保存在 `docs/audit/_archived/` 或本机原 Kiro 工作目录。审计记录已经把这两个 session 的关键决策点提炼出来,无需再读原始 jsonl。
|
||||
|
||||
---
|
||||
|
||||
## 五、FDW 现状
|
||||
|
||||
### 5.1 已建外部表清单 (`db/fdw/setup_fdw.sql`)
|
||||
|
||||
```sql
|
||||
-- L42-44
|
||||
IMPORT FOREIGN SCHEMA app
|
||||
FROM SERVER etl_feiqiu_server
|
||||
INTO fdw_etl;
|
||||
```
|
||||
|
||||
**关键事实**:setup_fdw.sql 用 `IMPORT FOREIGN SCHEMA app` 批量导入,**理论上 etl_feiqiu.app schema 中所有视图都会自动映射到 zqyy_app.fdw_etl**,包括 `v_dim_staff` / `v_dim_staff_ex` / `v_dim_assistant`。
|
||||
|
||||
> 这意味着:Neo 在 04a-conflicts 中说的"FDW 外部表没建 dim_staff"很可能是误解 — 不是没建,而是**建了但不被代码使用**(且 RLS 在 FDW 路径上失效,即便用了也是错的)。
|
||||
|
||||
### 5.2 dim_staff / dim_staff_ex 是否在内
|
||||
|
||||
通过 IMPORT FOREIGN SCHEMA 自动包含:
|
||||
- `fdw_etl.v_dim_staff` (镜像 etl_feiqiu.app.v_dim_staff,见 db/etl_feiqiu/schemas/app.sql:195)
|
||||
- `fdw_etl.v_dim_staff_ex` (镜像 app.sql:219)
|
||||
- `fdw_etl.v_dim_assistant` (镜像 app.sql:121)
|
||||
|
||||
无显式"已删除 / 已废弃"注释。`db/fdw/setup_fdw.sql` 也未做白名单/黑名单。
|
||||
|
||||
### 5.3 etl_feiqiu.app.v_dim_staff / v_dim_staff_ex 现状
|
||||
|
||||
`db/etl_feiqiu/schemas/app.sql`:
|
||||
|
||||
- L195 `CREATE OR REPLACE VIEW app.v_dim_staff AS SELECT staff_id, staff_name, ...`
|
||||
- L219 `CREATE OR REPLACE VIEW app.v_dim_staff_ex AS SELECT staff_id, avatar, ...`
|
||||
- L121 `CREATE OR REPLACE VIEW app.v_dim_assistant AS SELECT assistant_id, user_id, ...`
|
||||
|
||||
均为标准 RLS 视图,WHERE 包含 `current_setting('app.current_site_id')` 过滤(在 ETL 库内生效)。
|
||||
|
||||
---
|
||||
|
||||
## 六、Q1 推测:为什么从 FDW 改为直连?
|
||||
|
||||
### 推测 1 (高置信度) — 修复真实的 RLS 失效 Bug
|
||||
|
||||
**证据**:
|
||||
- 审计 H2 直接定性:"`postgres_fdw` 不传递 GUC 参数到远端连接,导致 RLS 视图的 `current_setting('app.current_site_id')` 在远端**未设置而报错**"
|
||||
- 这是 PostgreSQL 已知行为(`postgres_fdw` 只透传连接选项,不透传 session GUC)
|
||||
- 不修就是**生产事故**:RLS 门店隔离失效或查询直接 500
|
||||
|
||||
**结论**:这个改动**有真实必要**,不是 over-engineering。Neo 倾向选项 B (回到 FDW)时必须考虑:不解决 GUC 透传问题,RLS 在 FDW 路径上是失效的。
|
||||
|
||||
### 推测 2 (高置信度) — 时间压力下选了"最干净的方案"
|
||||
|
||||
**证据**:
|
||||
- 审计原文:AI 在 2026-03-18 21:38~21:47 (10 分钟内) 自行做出架构决策,理由是"最干净的方案"
|
||||
- 当时正在 RNS1.1 E2E 测试阶段,所有 API 都在跑 500,需要快速止血
|
||||
- 替代方案 (修 FDW GUC 透传 / 用 dblink / 改用 RLS function-based) 都需要深入调研 + 跨数据库改 DDL + 与运维确认连接配置
|
||||
|
||||
**结论**:这是"凌晨 22 点测试 100% 红的紧急修复",**不是规范设计**。但事后 H2 审计补救把方案对比推迟到了 2026-03-20 (用户确认方案 A 全部统一直连)。
|
||||
|
||||
### 推测 3 (中置信度) — 后续没回头是因为已成既定事实
|
||||
|
||||
**证据**:
|
||||
- 2026-03-20 已经把 47 + 4 共 51 个文件全切到直连
|
||||
- `_fdw_context()` 上下文管理器已经成为 etl 查询的标准入口
|
||||
- 后续 RNS1.4 / Fix-13 / Runtime Context (2026-05-02) 都基于这个抽象继续叠加
|
||||
- FDW schema 保留是"用户确认保留",但没有任何代码再走 fdw_etl.*
|
||||
|
||||
**结论**:即便后期想回归 FDW,沉没成本和回归测试成本都已经很高。
|
||||
|
||||
---
|
||||
|
||||
## 七、Q2 推测:为什么没有规范设计?
|
||||
|
||||
### 场景分析
|
||||
|
||||
**当时的开发场景 (2026-03-18 21:38)**:
|
||||
|
||||
| 维度 | 状况 |
|
||||
|---|---|
|
||||
| 项目阶段 | RNS1.1 端到端测试,正准备给业务上线 |
|
||||
| 时间 | 晚上 21:38 (深夜 / Kiro Autopilot 模式) |
|
||||
| 失败规模 | 全部 RNS1.1 API 报 RLS 错误 (绩效 / 任务 / 助教全挂) |
|
||||
| 发现路径 | E2E 跑完才发现,而非设计阶段就识别 |
|
||||
| 决策者 | AI 在 Kiro Autopilot 模式下(无人值守) |
|
||||
|
||||
### 根本原因 (按"系统性问题"框架)
|
||||
|
||||
1. **架构 review 缺失**:首次设计 `fdw_queries.py` 走 fdw_etl 模式时,**没人对 RLS + FDW 的兼容性做技术验证**。这是 02-26 P1-P2-P3 全栈集成期就埋下的雷,直到 03-18 E2E 才爆。
|
||||
2. **AI 自主权过大 (Kiro Autopilot)**:H2 审计原文 "AI 自行决定架构级修改"。当时的工作流允许 AI 在测试失败时直接改架构,缺少 "GUC 透传 → 用户确认 → 方案对比" 的强制中断点。
|
||||
3. **MVP 思维优先于规范**:RNS1 系列 spec 由 AI 基于 design.md 生成,**design.md 假设 RLS 会自动跨 FDW 生效**(这是错的),没有在 spec 阶段验证数据库 schema(审计"系统性问题 1:AI 信任文档胜过信任数据库")。
|
||||
4. **回滚方案存在但被刻意保留**:H2 审计末尾的"回滚方案"章节明确写了"回滚后 RLS 门店隔离将再次失效",所以**当时已经知道要回到 FDW 必须先解决 GUC 问题**,只是没人去做。
|
||||
5. **FDW DDL 不清理是技术债标记**:H2 审计"未变更项"明确写"FDW 外部表 DDL 暂不清理(用户确认保留)" — 是有意保留作为未来可能回归的备份,但没有任何后续动作让它真正可用。
|
||||
|
||||
---
|
||||
|
||||
## 八、推荐 step2 实施方案
|
||||
|
||||
基于调研,Neo 倾向选项 B (统一走 FDW),但**必须先解决 GUC 透传问题**,否则回到 FDW 就是回到 RLS 失效的 Bug 状态。
|
||||
|
||||
### 方案对比
|
||||
|
||||
| 方案 | 描述 | 工作量 | 风险 | 是否需要测试库回放 |
|
||||
|---|---|---|---|---|
|
||||
| **B-1 立即补 FDW 回退代码** | matching.py 改回 `fdw_etl.v_dim_*`,不解决 GUC | 0.5h | **极高** — 直接重新引入 RLS 失效 Bug | 必须,且会跑红 |
|
||||
| **B-2 渐进式补 FDW** | (1) 在 fdw_etl 已有外部表上,(2) 在业务库写新的 SECURITY DEFINER function 包装 GUC,(3) 后期切代码 | 4-8h | 中 — 需验证 SECURITY DEFINER + RLS 组合行为 | 必须,需 RLS 跨库验证 |
|
||||
| **B-3 修 GUC 透传 + 切回 FDW** | 修改 user mapping 的 `options`,通过 `set_config` 在 FDW 连接初始化时透传 site_id;切回 fdw_etl.* | 6-10h | 中高 — postgres_fdw 11+ 才支持 `pre-connection options`,且写法 finicky | 必须,需多 site 并发回放 |
|
||||
| **C 维持现状 + 补充文档** | 保留直连 ETL,在 docs/architecture / 后端 CLAUDE.md 增补"为什么 4 个文件偏离 FDW 模式"的明确说明,把当前不一致变成"已知设计选择" | 1-2h | 低 — 仅文档 | 否 |
|
||||
| **D 折中:matching.py 单点统一** | 不动其他 3 个 (task_generator/recall_detector/task_manager),只把 matching.py 也用 SECURITY DEFINER 包装走 FDW;**反而加剧不一致** | 3-5h | 高 | 必须 | (不推荐) |
|
||||
|
||||
### 推荐顺序
|
||||
|
||||
1. **首选 C (维持现状 + 补充文档)** — 因为:
|
||||
- "符合工程规范"的成本很高 (B-2/B-3 需要 6-10h + RLS 跨库测试)
|
||||
- 当前架构是**有真实根因的合理选择**,不是技术债 — 它解决了 postgres_fdw 不透传 GUC 的真实问题
|
||||
- 把"为什么直连"的设计决策写进 backend CLAUDE.md / docs/architecture/backend-architecture.md,后续读代码不会再困惑
|
||||
- 把 fdw_etl schema 的真实角色 (FDW 备份 / 当前不被后端代码使用) 也写明
|
||||
2. **次选 B-2** — 如果 Neo 坚持工程一致性大于成本,推荐 B-2 (写 SECURITY DEFINER function)。但需要:
|
||||
- 用 1 周时间在 test_zqyy_app + test_etl_feiqiu 上做完整 RLS 跨库回放
|
||||
- 验证 4 个文件全切回 FDW 的正确性
|
||||
- 用 e2e_test_rns1.py 全跑通
|
||||
3. **不推荐 B-1 / B-3** — B-1 是直接回退到 Bug;B-3 修 postgres_fdw GUC 透传方案在 PG14+ 才稳定,且配置复杂。
|
||||
|
||||
### 推荐方案 C 的具体动作清单
|
||||
|
||||
| # | 动作 | 文件 |
|
||||
|---|---|---|
|
||||
| 1 | 在 backend CLAUDE.md 的"数据库访问"段落补充直连 ETL 的根因 + GUC 不透传的事实 | `apps/backend/CLAUDE.md` |
|
||||
| 2 | 在 docs/architecture/backend-architecture.md 增加 "为什么 4 个 service 不走 fdw_etl" 一节 | `docs/architecture/backend-architecture.md` |
|
||||
| 3 | 在 db/fdw/setup_fdw.sql 顶部加注释:"当前 fdw_etl schema 作为只读备份保留,后端代码通过 get_etl_readonly_connection 直连 ETL 库 + RLS GUC,详见 backend-architecture.md" | `db/fdw/setup_fdw.sql` |
|
||||
| 4 | 在 `app/services/matching.py` / `task_generator.py` / `recall_detector.py` / `task_manager.py` / `fdw_queries.py` 的 docstring 引用 "为什么直连" 的统一说明位置 | 5 个文件 |
|
||||
| 5 | 把"FDW + RLS GUC 透传"列入 docs/_overview/04-doc-conflicts.md 或单独的"已知工程债务"清单,标注未来回归条件(运维允许 / postgres_fdw GUC 透传方案验证通过) | `docs/_overview/04-doc-conflicts.md` |
|
||||
|
||||
---
|
||||
|
||||
## 九、调研中发现的其他问题
|
||||
|
||||
1. **`fdw_queries.py` 模块名是历史包袱**:文件名叫 fdw_queries 但实际不走 FDW,**与文件功能严重不符**。后续重构时应改名为 `etl_queries.py` / `etl_views.py`。低优先级。
|
||||
2. **`_fdw_context` 函数名同样误导**:它是 ETL 直连 + GUC 设置的封装,不是 FDW 上下文。改名建议:`_etl_rls_context` / `_etl_session_with_site`。
|
||||
3. **`db/fdw/setup_fdw.sql` 仍是生产部署脚本之一**:运维如果按文档跑了这个脚本,得到的 fdw_etl schema 当前完全不被后端使用,但表占用元数据空间。运维侧需要明确"这是备份,不影响功能"。
|
||||
4. **审计文件证据链非常完整**:这次调研能在 1 小时内还原决策 — 全靠 `2026-03-18` / `2026-03-20` 三份审计的明确记录。这是 NeoZQYY 审计文化的正面案例,值得维持。
|
||||
5. **gitignore 提到的 `_archived/` 没污染调研** — 没有读取任何 archived 文件即可还原历史,符合 CLAUDE.md 的强制规则。
|
||||
|
||||
---
|
||||
|
||||
## 十、给 Neo 的最终建议
|
||||
|
||||
> Q1 推测核心:**改动有真实根因 — postgres_fdw 不透传 GUC 导致 RLS 失效**,2026-03-18 RNS1.1 E2E 测试时全 API 跑红,AI 紧急止血后 2026-03-20 经用户批准统一推广到 4 个文件。
|
||||
|
||||
> Q2 推测核心:**当时是"测试红 / 深夜 / Kiro Autopilot / 缺架构 review"四重叠加**,AI 自主决策后审计才补救;FDW 是 02-26 集成期的初始设计,没人事先验证 RLS + FDW 兼容性。
|
||||
|
||||
> 推荐方案:**选 C (维持现状 + 补文档)**。当前架构是有根因的合理选择,不是债务。把"为什么直连"写进文档,把 fdw_etl 标注为"备份/未使用",后续读代码就不会再产生疑问。如果坚持选 B,推荐 B-2 但成本 4-8h + 完整回归测试。**不要选 B-1**(直接回退会重新引入 RLS 失效 Bug)。
|
||||
51
docs/_overview/04a-feedback/P0-6-record.md
Normal file
51
docs/_overview/04a-feedback/P0-6-record.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# P0-6 临时守卫 + 沙箱收口后正式重设计 — 记录
|
||||
|
||||
> 日期:2026-05-04 / 触发:Neo 在 P0 反馈"我同意,但要记录下来"
|
||||
|
||||
## 一、问题简述
|
||||
|
||||
`clearAllTasks` 高危操作(`DELETE /api/admin/task-engine/clear-all-tasks`)无运行模式守卫,任何 super_admin 可点。文档明文标"测试用",但代码无 sandbox 判断。
|
||||
|
||||
详细业务故事见 [`04a-conflicts-P0-detail.md` § P0-6](../04a-conflicts-P0-detail.md#p0-6-clearalltasks-高危操作无运行模式守卫)。
|
||||
|
||||
## 二、Neo 决策
|
||||
|
||||
**两阶段方案**:
|
||||
|
||||
### 阶段 1 — 临时止血(立即,Wave 1-3 任意时点)
|
||||
|
||||
- 工作量:**1h**
|
||||
- 范围:admin-web `TriggerJobs.tsx` 顶部 danger 按钮加"二次确认弹窗 + 输入门店简称"
|
||||
- 后端不动(沙箱模式还未上线,沙箱判断暂时不需要)
|
||||
|
||||
### 阶段 2 — 沙箱收口后正式重设计(等 P0-7 完成)
|
||||
|
||||
- 工作量:**3-5h**
|
||||
- 触发条件:P20 SPEC 实施完成 + Wave 1 沙箱走查通过
|
||||
- 范围:
|
||||
- 把"清空"语义拆为 **"全局清空"**(live 数据)和 **"沙箱内清空"**(按 `runtime_mode='sandbox' AND sandbox_instance_id=?`)
|
||||
- 后端加 `?scope=live|sandbox|both` query param
|
||||
- 后端在 live 模式下默认拒绝 `scope=live`(必须显式参数 `confirm=DROP_ALL_<site_code>`)
|
||||
- 前端按当前 runtime_context 联动 disabled / 显示哪个清空选项
|
||||
|
||||
## 三、与其他主题的关系
|
||||
|
||||
- **依赖** P0-7 沙箱收口完成(明确 `runtime_mode` + `sandbox_instance_id` 字段语义)
|
||||
- **关联** P20 SPEC §11.3 已指向 `P0-7-runtime-context-todos.md`,本记录可作为 todos 的子项
|
||||
- **不阻塞** Wave 1 主线工作
|
||||
|
||||
## 四、待办状态
|
||||
|
||||
- [ ] 阶段 1 临时止血(Wave 1-3 任意时点)
|
||||
- [ ] 阶段 2 正式重设计(等 P0-7 收口后)
|
||||
|
||||
## 五、给 Neo 的提醒
|
||||
|
||||
P0-7 收口完成后,需要:
|
||||
1. 在 P20 SPEC § 7 安全 / 权限模型 中明文写出 `clearAllTasks` 在 live 与 sandbox 下的不同行为
|
||||
2. 在 admin-web RuntimeContext 页加一个"危险操作历史日志"展示哪些 sandbox 实例曾触发 clearAllTasks
|
||||
3. 把本文件状态从"记录"改为"已完成",归档到审计
|
||||
|
||||
---
|
||||
|
||||
> 本文件不替代代码实施,仅作为记录,等待 Wave 1-3 / P0-7 收口后执行。
|
||||
146
docs/_overview/04a-feedback/P0-7-runtime-context-todos.md
Normal file
146
docs/_overview/04a-feedback/P0-7-runtime-context-todos.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# Runtime Context 深入调研 / 测试 / 收口建议
|
||||
|
||||
> 日期:2026-05-04 / 触发:Neo 反馈"涉及内容多,远未做到完成和收口的地步"
|
||||
> 关联 SPEC:`docs/prd/specs/P20-runtime-context-sandbox.md`
|
||||
> 关联审计:`docs/database/changes/2026-05-01__runtime_context_sandbox.md` 与 5 份 `2026-05-02__sandbox_*.md`
|
||||
|
||||
---
|
||||
|
||||
## 一、调研中发现的"未完成 / 未验证"项
|
||||
|
||||
按优先级分类。**P0 = 上生产前必须收口**;**P1 = 灰度期间必须验证**;**P2 = 长期改进**。
|
||||
|
||||
### P0(上生产前必须收口)
|
||||
|
||||
1. **生产库迁移未执行**:`db/zqyy_app/migrations/20260501__runtime_context_sandbox.sql` 与 `db/etl_feiqiu/migrations/20260502__rls_views_business_date_upper_bound.sql` 仅在 `test_*` 库执行;`zqyy_app` 生产库与 `etl_feiqiu` 生产库未跑。回滚 SQL 已附在迁移末尾,但生产环境的回滚演练未做。
|
||||
2. **多门店并行 sandbox 未跑过验证**:架构支持,但端到端验证脚本只覆盖单 site(`2790685415443269`)。site A 切 sandbox + site B 保持 live 时,下列点未验证:
|
||||
- site B 用户登录看到的 `business_date` 是否仍是真实日
|
||||
- site B 的 ETL `_fdw_context` 查询是否未受 site A GUC 污染(GUC 是连接级,但连接池复用是否带来串扰未测)
|
||||
- site B 的 AI cache 是否未读到 site A 的 `sbx_*:` 命名空间数据
|
||||
3. **BD_Manual 与现状不一致未修订**:`docs/database/BD_Manual_runtime_context_sandbox.md` § 3.3 / § 4 仍写 "切沙箱按 site_id 暂停 `biz.trigger_jobs` 为 `paused_by_sandbox`";代码已移除此逻辑(见 `admin_runtime_context.py` 的 `biz_triggers_unchanged` step)。文档与代码冲突会误导运维。
|
||||
4. **Steps key 与文档不一致**:`2026-05-02__sandbox_admin_web_manual_checklist.md` 第 1 节列出的期望步骤 key(`runtime_context_upserted` / `pending_jobs_cancelled` / `runtime_cache_purged`)与代码实际产出的 key(`cancel_etl_processes` / `cancel_task_queue` / `cancel_ai_runtime` / `cancel_ai_jobs` / `biz_triggers_unchanged` / `apply_context`)不匹配,手工清单需要修订。
|
||||
5. **`coach_service._build_task_groups` 是否带 site_id + runtime_filter 未确认**:`2026-05-02__sandbox_no_future_data_plan.md` 标注 "550-566 未带 site_id 过滤 + runtime filter",状态为 "✅ 已实施",但 grep `task_runtime_filter` 在 `coach_service.py` 中未匹配到此函数路径。需重新核对。
|
||||
|
||||
### P1(灰度期间必须验证)
|
||||
|
||||
6. **小程序覆盖不全**:`board-finance.ts` / `board-customer.ts` / `board-coach.ts` 是沙箱"不看未来"主受益方,但 grep `runtime-clock` 在这 3 个文件未匹配到。设计文档要求改 `isCurrentMonthFilter`,未在代码层确认。
|
||||
7. **AI App8 consolidate 未确认**:grep 显示 App2/2a/3-7 都接入了 `as_runtime_business_now_str`,但 `app8_consolidate_prompt` 未在结果中。如果 App8 在 sandbox 模式输出"今天"是真实日,会破坏一致性。
|
||||
8. **`ai/dispatcher.py` 与 `ai/admin_service.py` 接入状态模糊**:plan 文档 § B1 提到 `dispatcher.py` 259/330 去重键应改为 `as_runtime_today_param`,`admin_service.py` 86-87 等 AI 调用统计窗口标 "需产品确认"。grep 结果未确认是否落地。
|
||||
9. **跨页时间漂移**:小程序 `runtime-clock.ts` 60s in-memory 缓存。从 page A 切到 page B 时,是否一定 force=true 重拉?切沙箱后切回 live 时,缓存是否被 `clearBusinessClockCache` 主动失效?grep 未发现任何调用 `clearBusinessClockCache` 的位置。
|
||||
10. **`task_generator` 部分 SQL 业务日上界未单测覆盖**:plan 文档 § B1 标 873 行 "`dwd.dim_assistant` 直连,非 RLS 视图,需切换 `app.v_dim_assistant`";status 是否落地未单测。
|
||||
11. **`page_context.py` 7 处直连 ETL 依赖 GUC 兜底**:未单独传 `ref_date`;如果 `_fdw_context` 之外的连接路径调用了 `page_context`,GUC 不会下发。
|
||||
12. **AI cache 命名空间清理逻辑**:sandbox 模式下 `target_id` 加 `sbx_*:` 前缀,但是否有清理脚本删除已结束沙箱实例的 cache 行未实施。长期使用会膨胀 `biz.ai_cache`。
|
||||
13. **沙箱写入数据归零脚本缺失**:`coach_tasks` / `coach_task_history` / `recall_events` / `ai_cache` / `ai_run_logs` / `ai_trigger_jobs` 在 sandbox 长期使用后会积累大量 `sandbox_instance_id` 行;清理脚本(按 `runtime_mode='sandbox' AND sandbox_instance_id=...` 限定)未实施,BD_Manual § 6 仅警告"不要直接清空"。
|
||||
|
||||
### P2(长期改进)
|
||||
|
||||
14. **DIM SCD2 维度沙箱回放**:`v_dim_assistant / v_dim_member / v_dim_member_card_account / v_dim_staff / v_dim_staff_ex / v_dim_table` 保留 `scd2_is_current=1` 当前快照;如需"sandbox 当时维度状态"需把 WHERE 改为 `scd2_start_time <= bd AND (scd2_end_time > bd OR is null)`。但这会让一行变多行,影响 JOIN,需重构。
|
||||
15. **配置表 SCD2 时间戳"未来"边界**:`v_cfg_assistant_level_price` 等用 `effective_from <= bd AND effective_to >= bd` 双向夹住,但 `effective_to` 默认值是否合理(`9999-12-31` vs NULL)未在 SPEC 中固化。
|
||||
16. **`utils/time.ts` 相对时间是否按沙箱算**:plan § B2 明确不改;但 sandbox 演示"3 天前"按真实日推算可能出现不直观的标签(如 sandbox=2025-09-01,真实"3 天前"是 2026-05-01)。需确认是否影响演示体验。
|
||||
17. **`ai_mode` 字段实际未使用**:表与代码都保留 `ai_mode='live'`,CHECK 约束限定只能 `live`。如未来要做"沙箱不真实调 DashScope,改返 mock"需移除 CHECK + 实现 mock dispatch,预留扩展点未明确。
|
||||
18. **业务日跨日切换边界**:`RuntimeContext.business_date` 包含 "凌晨小于 `BUSINESS_DAY_START_HOUR` 算昨天" 的逻辑,sandbox 模式下也按这个规则推算 `business_now`。是否符合演示意图?演示者通常希望 sandbox_date 全天 24h 都是同一天。需评估。
|
||||
19. **RuntimeContext 表无审计日志**:切换历史只能通过 `updated_by` + `reason` + `updated_at` 看最后一次状态,无完整 transition log 表。如需追溯"该 site 历史上哪天进过 sandbox",无数据。
|
||||
20. **测试用例缺位**:仓库内未发现 `tests/` 下 runtime_context 相关 pytest 文件。当前所有验证依赖 `tools/db/verify_sandbox_end_to_end.py` 一次性脚本,未纳入 CI。
|
||||
|
||||
---
|
||||
|
||||
## 二、跨模块影响清单(可能漏处理的地方)
|
||||
|
||||
| 模块 | 是否读 RuntimeContext | 测试覆盖 | 风险 |
|
||||
|---|---|---|---|
|
||||
| 后端 task_engine(task_manager / task_generator / task_expiry) | 是 | 自动化 PASS | 低 |
|
||||
| 后端 board_service / coach_service / customer_service | 是 | 自动化 PASS(部分) | 中:`coach_service._build_task_groups` 路径未确认 |
|
||||
| 后端 fdw_queries | 是(GUC + helper) | 自动化 PASS | 低 |
|
||||
| 后端 ai/cache_service / run_log_service | 是 | 自动化 PASS | 低 |
|
||||
| 后端 ai/dispatcher / admin_service | 不确定 | 未测 | 中:去重键、统计窗口 |
|
||||
| 后端 ai/prompts/app2 / app2a | 是 | 自动化 PASS | 低 |
|
||||
| 后端 ai/prompts/app3-7 | 是 | 自动化 PASS(仅 current_time) | 低 |
|
||||
| 后端 ai/prompts/app8_consolidate | 不确定 | 未测 | 中 |
|
||||
| 后端 ai/data_fetchers/page_context | 是(部分依赖 GUC) | 自动化 PASS | 中:连接路径外的边界 |
|
||||
| 后端 routers/tenant_users | 是 | 未测 | 低 |
|
||||
| 后端 routers/admin_runtime_context | 是 | Playwright PASS | 低 |
|
||||
| 后端 routers/xcx_runtime_clock | 是 | 自动化 PASS | 低 |
|
||||
| ETL 库 39 个 RLS 视图 | C 方案 GUC | 自动化 PASS(max 截断) | 低 |
|
||||
| ETL `task_engine` / `flow_runner` | 否(直接读 ODS/DWD) | — | 低(设计有意) |
|
||||
| admin-web `RuntimeContext.tsx` | 是 | Playwright PASS | 低 |
|
||||
| admin-web 其他页(AIRunLogs / TriggerManager / AIDashboard) | 间接 | Playwright PASS | 低 |
|
||||
| admin-web 其他 AI 页(AIOperations Card 1 / 4) | 间接 | 未在 UI 触发(成本高) | 中 |
|
||||
| 小程序 `performance` / `performance-records` / `task-list` / `customer-records` / `customer-service-records` | 是 | grep PASS | 低 |
|
||||
| 小程序 `board-finance` / `board-customer` / `board-coach` | 不确定 | 未确认 | **高**:沙箱主受益面 |
|
||||
| 小程序 `customer-detail` / `chat` / `utils/time.ts` | 否(设计共识) | — | 低 |
|
||||
| tenant-admin | 否 | — | 低(不展示业务数据) |
|
||||
| MCP server | 不确定 | 未测 | 低(只读,但若读到 sandbox 数据需评估) |
|
||||
|
||||
---
|
||||
|
||||
## 三、Wave 1 走查时必测的场景
|
||||
|
||||
1. **live → sandbox 单门店切换流程**:admin-web 切到 sandbox=2025-09-01,确认 6 个 step 全 success,表格更新,DB 状态正确。
|
||||
2. **sandbox → live 还原**:切回后 `sandbox_date / sandbox_instance_id` 恢复 NULL,写入恢复 `('live','live')`。
|
||||
3. **小程序业务时钟一致性**:登录小程序,进入 5 个已接入页面,确认 `getBusinessClock()` 返回的 `business_date = 2025-09-01`;切回 live 后再进同一页,业务日变回真实日。
|
||||
4. **小程序板看不看未来**:进入 `board-finance` / `board-customer` / `board-coach`,确认看到的最大日期 ≤ `sandbox_date`(**这是当前覆盖率最低的部分**)。
|
||||
5. **AI 应用在 sandbox 模式输出**:admin-web AIOperations 触发一个 App3(成本最低),等待执行后到 AIRunLogs Drawer 看 `current_time` 字段是否为 `2025-09-01 HH:MM`。
|
||||
6. **AI cache 命名空间隔离**:触发同一 member_id 的 App7 在 sandbox + live 各一次,DB 查 `biz.ai_cache.target_id` 应有两行,sandbox 行带 `sbx_*:` 前缀。
|
||||
7. **任务表 runtime 维度共存**:手动触发 `task_generator`,DB `SELECT runtime_mode, sandbox_instance_id, COUNT(*) FROM biz.coach_tasks GROUP BY 1,2`,确认 live 与 sandbox 两套并存。
|
||||
8. **触发器调度不暂停**:`/triggers?tab=all` 12 条触发器全 enabled,无 `paused_by_sandbox`。
|
||||
9. **AIDashboard 真实日期**:切沙箱后 AIDashboard "今日" 仍然是真实日(如 0 调用,因为今天确实未触发),不被拉到 `sandbox_date`。
|
||||
10. **多门店并行(site A sandbox + site B live)**:用 site B 用户登录小程序,确认 `business_date` 仍是真实今天,业务数据未被截断。
|
||||
11. **沙箱期 ETL 跑批不污染演示**:sandbox 模式下让 ETL 跑一批真实数据进 DWS(写真实最新日),小程序板看 max 日期仍是 `sandbox_date`(因 RLS 视图截断)。
|
||||
12. **回滚演练**:在 test 库跑迁移末尾的回滚 SQL,确认 7 张表的列被 DROP、唯一索引恢复旧版、`site_runtime_context` 表 DROP;后端代码降级 live(fail-soft)。
|
||||
|
||||
---
|
||||
|
||||
## 四、推荐补充测试用例(给 Wave 1 用)
|
||||
|
||||
| # | 场景 | 测试方式 | 期望 |
|
||||
|---|---|---|---|
|
||||
| T1 | sandbox 边界:"今天"切 sandbox 到今天 | `PATCH` `mode=sandbox, sandbox_date=today` | 422 拒绝("sandbox_date 不能晚于真实今天"含等号?需确认;当前代码 `>`,等号允许) |
|
||||
| T2 | sandbox 切到极早历史日(2020-01-01) | `PATCH` 切换 | 接受;验证视图无数据(早于 ETL 范围) |
|
||||
| T3 | reset_sandbox=false 沿用实例 | 第二次切沙箱不重置 | `sandbox_instance_id` 保持不变 |
|
||||
| T4 | 未审核小程序用户访问 `/api/xcx/runtime/clock` | curl with limited token | 403 / 401 |
|
||||
| T5 | 非 super_admin 访问 admin runtime API | curl | 403 |
|
||||
| T6 | `_fdw_context` GUC 串扰 | 同连接池循环切 site | 每次 SET LOCAL 不溢出 |
|
||||
| T7 | runtime-clock 缓存失效 | 切 sandbox 后立即在小程序内观察 5 个页面 | 60s 内可能仍是旧值;切换流程是否需要主动 push 给小程序未实现 |
|
||||
| T8 | AI dispatcher 去重键 | 同 member_id 在 live + sandbox 各触发一次 App3 | 不应去重,两个独立 trigger_job |
|
||||
| T9 | 沙箱产生大量数据后清理 | INSERT 1000 条 sandbox 任务,运行清理脚本 | 仅 sandbox 行被删,live 不受影响(**清理脚本待实施**) |
|
||||
| T10 | `app.business_date_now()` 在多事务并行下的 STABLE 行为 | 并发查询 39 视图 | 同事务内值固定,不同事务独立 |
|
||||
| T11 | `effective_from / effective_to` 双向夹住后 v_cfg 视图返回 0 行边界 | sandbox=2020-01-01 看 `v_cfg_assistant_level_price` | 期望返回当时生效档位(依赖测试库种子数据) |
|
||||
| T12 | DWS 物理跑批进 sandbox 期 | sandbox=2025-09-01 时让 ETL 跑入 2026-05-04 数据 | 视图侧仍截断到 2025-09-01 |
|
||||
|
||||
---
|
||||
|
||||
## 五、收口路径建议
|
||||
|
||||
```
|
||||
step1 完成本次 SPEC 起草(已落 docs/prd/specs/P20-runtime-context-sandbox.md)
|
||||
↓
|
||||
step2 修订 BD_Manual + manual_checklist 以匹配代码现状(P0-3 / P0-4)
|
||||
↓
|
||||
step3 跨模块缺口扫描:补 board-finance/customer/coach + dispatcher + admin_service + app8(P1-6/7/8)
|
||||
↓
|
||||
step4 多门店并行 sandbox 端到端脚本(P0-2)
|
||||
↓
|
||||
step5 沙箱写入清理脚本 + cache 命名空间清理(P1-12 / 13)
|
||||
↓
|
||||
step6 Wave 1 走查(按 §三 12 项 + §四 12 用例)
|
||||
↓
|
||||
step7 走查发现 bug → 修 bug → SPEC § 13 「已知冲突」更新 → todos 项标 done
|
||||
↓
|
||||
step8 生产库灰度执行迁移(先 test_etl_feiqiu / test_zqyy_app 二跑,再 zqyy_app + etl_feiqiu)
|
||||
↓
|
||||
step9 SPEC 进入 v1.1,关掉 P0-7 反馈
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、关联资产
|
||||
|
||||
- SPEC:`docs/prd/specs/P20-runtime-context-sandbox.md`
|
||||
- 主迁移:`db/zqyy_app/migrations/20260501__runtime_context_sandbox.sql`
|
||||
- ETL RLS 视图迁移:`db/etl_feiqiu/migrations/20260502__rls_views_business_date_upper_bound.sql`
|
||||
- BD_Manual:`docs/database/BD_Manual_runtime_context_sandbox.md`(**待修订**)
|
||||
- 6 份 changes:`docs/database/changes/2026-05-01__runtime_context_sandbox.md` + `2026-05-02__sandbox_*.md`
|
||||
- 端到端验证:`tools/db/verify_sandbox_end_to_end.py`、`tools/db/verify_admin_web_sandbox.py`
|
||||
- 后端核心:`apps/backend/app/services/runtime_context.py`
|
||||
- admin-web 入口:`apps/admin-web/src/pages/RuntimeContext.tsx`
|
||||
- 小程序入口:`apps/miniprogram/miniprogram/utils/runtime-clock.ts`
|
||||
186
docs/_overview/04a-feedback/P0-7-spec-acceptance-layer-check.md
Normal file
186
docs/_overview/04a-feedback/P0-7-spec-acceptance-layer-check.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# P0-7 P20 SPEC 成果层验证覆盖 — 校验报告
|
||||
|
||||
> 日期:2026-05-04 / 触发:Neo 在 P0 反馈强调"成果层(用户视角)是最终成功标准"
|
||||
> Neo 原话:"对于我而言,最终用户看到的页面/小程序是否真正达到设计目标更为重要,因为你要对最终成功负责,而客户也最看重最终使用的效果"
|
||||
|
||||
## 一、Neo 关心的 3 个核心问题
|
||||
|
||||
1. **工程层 vs 成果层分层验证** — P20 SPEC 是否两层都覆盖?
|
||||
2. **admin-web 6 页 + AIPrewarm 分组 Playwright 实地走** — 是否落到 SPEC 验收?
|
||||
3. **小程序财务看板 area 切换 / AI 洞察 / runtime-clock 漂移 / 各页面数据展示是否符合预期** + **任务板块多角色身份切换**(看板收口后) — 是否落到 SPEC 验收?
|
||||
|
||||
## 二、当前 P20 SPEC 覆盖状态(逐条核对)
|
||||
|
||||
### 2.1 §8 验收标准(AC1-AC13)分类
|
||||
|
||||
| 类别 | AC | 内容 | 视角 |
|
||||
|---|---|---|---|
|
||||
| 工程层 | AC2/AC3/AC4/AC6/AC8/AC9 | SQL / curl 验证 | **工程层** |
|
||||
| 工程层 | AC11/AC12/AC13 | 自动化脚本 / 手工 | **工程层(标 todos 未实施)** |
|
||||
| 半成果层 | AC1 | "登录小程序看 performance / task-list 当月判断" | **散点提及,无走查流程** |
|
||||
| 半成果层 | AC5 | "admin-web `/logs/ai-run-logs` Drawer 抽屉" | **散点提及** |
|
||||
| 半成果层 | AC7 | "admin-web `/ai/dashboard` 视觉验证" | **散点提及** |
|
||||
| 半成果层 | AC10 | "普通账号登录视觉验证" | **散点提及** |
|
||||
|
||||
**结论**:
|
||||
- ❌ **没有专门的"成果层走查"章节**
|
||||
- ❌ admin-web 6 页(AI Dashboard / Operations / RunLogs / Triggers / RuntimeContext / TriggerManager)+ AIPrewarm 分组 **没有作为 AC 列出**
|
||||
- ❌ 小程序看板沙箱接入(P0-3 主体) **AC 中只字未提**
|
||||
- ❌ 任务板块多角色身份切换 **没有 AC 覆盖**
|
||||
|
||||
### 2.2 §10 跨模块覆盖矩阵
|
||||
|
||||
§10 矩阵列出"哪些模块已接入 runtime context"(代码层覆盖),不是"用户视角走查清单"。
|
||||
|
||||
§10.3 小程序页面表标了 `?` 的 3 项(board-finance / board-customer / board-coach)正是 P0-3 沙箱看板接入未完成的指针,但**没有走查脚本 / 步骤 / 期望结果模板**。
|
||||
|
||||
### 2.3 §12 任务清单(T1-T15)
|
||||
|
||||
| 任务 | 视角 |
|
||||
|---|---|
|
||||
| T1-T7 | 工程层(迁移 / API / 服务 / 视图) |
|
||||
| T8 | `verify_sandbox_end_to_end.py` 自动化 — **自动化工程验证** |
|
||||
| T9 | admin-web Playwright e2e 13/13 PASS — **半成果层**,但只覆盖 1 个页面(RuntimeContext) |
|
||||
| T10 | BD_Manual + 6 份 changes 审计 — 工程层 |
|
||||
| T11-T15 | 生产迁移 / 跨模块测试 / 清理脚本 / 多门店 / 实例浏览页 — 工程层 + 部分成果层 |
|
||||
|
||||
**结论**:T9 只覆盖 1 个 admin-web 页面(RuntimeContext),**Neo 要求的 6 页 + AIPrewarm 没覆盖**。
|
||||
|
||||
## 三、缺失项汇总(给 Neo 答复)
|
||||
|
||||
✅ **代码层 lint / typecheck / 架构** — P20 §1.3 / §10 / §11.1 已覆盖,**无需补充**
|
||||
|
||||
❌ **admin-web 6 页 Playwright 走查** — **未覆盖**,需要补
|
||||
❌ **AIPrewarm 分组走查** — **未覆盖**,需要补
|
||||
❌ **小程序看板沙箱走查**(area 切换 / AI 洞察 / runtime-clock 漂移) — 仅 §10.3 标 ? 未走查 / §13 已知冲突标"未验证",**没有走查脚本**
|
||||
❌ **小程序各页面数据展示符合预期** — **未覆盖**,需要补
|
||||
❌ **任务板块多角色身份切换走查**(看板收口后) — **未覆盖**,需要补
|
||||
|
||||
## 四、推荐 P20 SPEC § 15 patch(成果层验证章节)
|
||||
|
||||
**不实际修改 P20 SPEC,只列出建议增加的章节内容**,让 Neo 先看再决定是否落地。
|
||||
|
||||
### 建议在 P20 SPEC 末尾增加:
|
||||
|
||||
```markdown
|
||||
## 15. 成果层验证(User-facing Acceptance)
|
||||
|
||||
### 15.1 验证哲学
|
||||
|
||||
工程层(代码/调用链/迁移)是必要的根基,但**不是终点**。最终用户看到的页面/小程序数据是否符合设计目标,才是项目成功标准。
|
||||
|
||||
视角分两层:
|
||||
|
||||
| 层级 | 验证目标 | 工具 | 通过标准 |
|
||||
|---|---|---|---|
|
||||
| 工程层 | 代码可运行 / 调用链通 / 迁移落库 / lint/typecheck 过 | tsc / pytest / SQL | 自动化全绿 |
|
||||
| **成果层** | **页面渲染对 / 数据准确 / 交互流畅 / 角色权限对** | **Playwright + 微信开发者工具** | **逐条手工 + 截图归档** |
|
||||
|
||||
### 15.2 admin-web 走查清单(Playwright MCP)
|
||||
|
||||
每条走查 = 设置 sandbox 时间(2026-03-01) → 打开页面 → 截图 → 对照"期望展示"。
|
||||
|
||||
| # | 路由 | 期望展示 | 走查重点 |
|
||||
|---|---|---|---|
|
||||
| 15.2.1 | `/dashboard` | 顶部 sandbox 模式条带高亮 + 数据为 sandbox_date 当时数据 | 切 sandbox 后是否立刻显示"沙箱模式" + 数据是否切到虚拟日 |
|
||||
| 15.2.2 | `/ai/dashboard` | 7 天趋势按真实日期(沙箱不影响)、今日/累计 token 按真实 | AC7 已要求,但要补充实地走查截图 |
|
||||
| 15.2.3 | `/ai/operations` | 8 个 APP + app2a 区域财务派生 列出 / 手动触发 → 沙箱模式下日期参数对 | 触发 1 次 app2_finance,看 AI 输入是否带 sandbox_date |
|
||||
| 15.2.4 | `/ai/run-logs` | 历史日志按真实写入时间排序;Drawer 抽屉显示 prompt 中的 sandbox_date | AC5 已要求,补 case |
|
||||
| 15.2.5 | `/ai/prewarm` | 分组展示 area=all 8 组合 + 8 area 64 组合 = 72 / 沙箱模式下不影响预热范围 | 走查 prewarm 在 sandbox 下是否仍按真实 area 跑 |
|
||||
| 15.2.6 | `/triggers?tab=ai` | AI 触发器列表 / status / cron 编辑 | 沙箱模式下显示"触发器仍按真实时钟运行"提示 |
|
||||
| 15.2.7 | `/triggers?tab=biz` | 业务触发器列表 / 任务生成器 cron 7:00 | 同上 |
|
||||
| 15.2.8 | `/settings/runtime-context` | 切 sandbox / 历史日期选择 / 切回 live | 完整切换流程,验证 AC1-AC4 |
|
||||
| 15.2.9 | `/logs/dev-trace` | (Neo 反馈推荐 Drop,本走查可省) | 跳过 |
|
||||
| 15.2.10 | `/logs/db-viewer` | SELECT 查询正常 / DDL 拒绝(P0-8 修复后) | 验证 P0-8 白名单 |
|
||||
|
||||
### 15.3 小程序走查清单(微信开发者工具 MCP)
|
||||
|
||||
前置:打开微信开发者工具 + 启用 9420 自动化端口 + 以教练身份登录。
|
||||
|
||||
| # | 页面 | 期望展示 | 走查重点 |
|
||||
|---|---|---|---|
|
||||
| 15.3.1 | `task-list` | 任务列表按 sandbox_date 当月生成 | AC1 已要求 |
|
||||
| 15.3.2 | `performance` | 月度统计反映 sandbox_date | AC1 已要求 |
|
||||
| 15.3.3 | `performance-records` | 4 处时间过滤反映 sandbox_date | (T7 任务) |
|
||||
| 15.3.4 | `customer-records` | onLoad 拿 sandbox_date 拉历史 | (T7 任务) |
|
||||
| 15.3.5 | `customer-service-records` | onLoad 拿 sandbox_date 拉历史 | (T7 任务) |
|
||||
| **15.3.6** | **`board-finance`** | **area 5 区域切换 / AI 洞察 12 项指标 / 折线图横轴为虚拟近 7 日** | **P0-3 主体,Wave 1 必修** |
|
||||
| **15.3.7** | **`board-customer`** | **客户分层基于 sandbox_date 截止状态** | **P0-3** |
|
||||
| **15.3.8** | **`board-coach`** | **助教绩效反映 sandbox_date 当月累计** | **P0-3** |
|
||||
| 15.3.9 | `customer-detail` | 操作时间戳保留真实时钟(设计共识) | §11.1 |
|
||||
| 15.3.10 | `chat` | AI 对话 prompt 含 sandbox_date / 操作时间戳保留真实时钟 | AC5 |
|
||||
|
||||
### 15.4 跨页时间漂移走查(AC12 实地化)
|
||||
|
||||
走查脚本:连续打开 10 个页面,各拉一次 `business-clock`,验证返回值在 60s 缓存窗口内一致。
|
||||
|
||||
### 15.5 多角色身份走查(看板收口后)
|
||||
|
||||
**前置**:看板沙箱接入完成(15.3.6 / 15.3.7 / 15.3.8 全部通过)。
|
||||
|
||||
**触发提醒**:在 §15.3.6/7/8 全部 PASS 时,在审计中标"提醒 Neo:可以切换用户身份做下一轮走查"。
|
||||
|
||||
走查矩阵:
|
||||
| 身份 | 必走页面 |
|
||||
|---|---|
|
||||
| 教练 (coach) | task-list / performance / performance-records / 看板 3 页 |
|
||||
| 顾问 (consultant) | task-list (无看板权限) / customer-records / chat |
|
||||
| 散客模式(memberId=0) | customer-service-records 中"散客无详情"提示 |
|
||||
| site_admin (admin-web) | /settings/runtime-context |
|
||||
| tenant_admin (tenant-admin) | tenant-admin 主面板 |
|
||||
|
||||
每身份完整走完 + 截图归档。
|
||||
|
||||
### 15.6 走查产物
|
||||
|
||||
每次走查产出一份 `docs/audit/changes/2026-XX-XX__sandbox_acceptance_<wave>.md`,内容:
|
||||
- 截图清单(按 §15.2 / §15.3 编号)
|
||||
- 失败项明细(现状 / 期望 / 复现步骤)
|
||||
- 通过率 / 总耗时
|
||||
|
||||
---
|
||||
|
||||
(以上为 § 15 建议内容,不在本次直接落入 P20)
|
||||
```
|
||||
|
||||
## 五、推荐落地路径
|
||||
|
||||
### 选项 A — 直接补到 P20 SPEC §15(推荐)
|
||||
|
||||
- 工作量:30 分钟,主线把上述 § 15 写入 P20
|
||||
- 优:SPEC 完整,Wave 1 走查时直接照 § 15 跑
|
||||
- 劣:P20 SPEC 行数从 ~480 涨到 ~580+
|
||||
|
||||
### 选项 B — 单独成一份《沙箱成果层验证手册》
|
||||
|
||||
- 路径:`docs/_overview/sandbox-acceptance-runbook.md`
|
||||
- 优:与 P20 SPEC 解耦,走查手册可独立迭代
|
||||
- 劣:跨文档维护,需要在 P20 SPEC 加链接
|
||||
|
||||
### 选项 C — 先列 todos,Wave 1 走查时即时补
|
||||
|
||||
- 把"成果层走查清单"作为 Wave 1 的隐式任务,边走边写
|
||||
- 优:零前期工作
|
||||
- 劣:无判据,Wave 1 走查时容易遗漏
|
||||
|
||||
## 六、给 Neo 的决策清单
|
||||
|
||||
1. **是否同意 P20 SPEC 缺失"成果层走查"章节的判断?**(我判断:**缺失**)
|
||||
2. **选 A / B / C?** 我的推荐:**选 A**(直接补到 P20 §15),与 P20 实施同步
|
||||
3. **15.5 多角色身份走查的提醒机制**(看板收口后由我主动提醒 Neo 切身份)是否合适?
|
||||
4. **15.2 admin-web 走查 9 页(Neo 原列 6 页 + AIPrewarm + RuntimeContext + db-viewer)**是否完整?是否需要再加 `/tenant-admins` / `/etl-tasks`?
|
||||
|
||||
## 七、关于"工程层根基已落地"的确认(回答 Neo)
|
||||
|
||||
Neo 担心的 "代码层面的 lint/typecheck 是否过,架构是否合理" — **P20 SPEC 已落地**:
|
||||
- §10 跨模块覆盖矩阵(13 后端服务 + 8 AI prompts + 11 小程序页 + 4 ETL 视图)
|
||||
- §11 已知遗漏 + 设计共识
|
||||
- §12 任务清单 T1-T10 已完成
|
||||
- §13 已知冲突清单
|
||||
- 工程层 lint/typecheck 由 hooks(`PostToolUse Edit/Write`)自动跑
|
||||
|
||||
**所以工程层根基 OK,只缺成果层走查这一层**,选 A 直接补完即可对齐 Neo 的"对最终成功负责"标准。
|
||||
|
||||
---
|
||||
|
||||
> 本文件不实际修改 P20 SPEC,只产出建议 patch + 决策清单。等 Neo 选 A/B/C 后落地。
|
||||
Reference in New Issue
Block a user