From 1e803e23cd4e1656ca85d9da2fde18e8447d6adc Mon Sep 17 00:00:00 2001 From: Neo Date: Tue, 5 May 2026 22:12:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(db,docs):=20F1-5b=20MP-2=20prep=20+=20?= =?UTF-8?q?=E6=B2=99=E7=AE=B1=E6=97=B6=E5=85=89=E6=9C=BA=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=20spec=20(W1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MP-2 经 4 轮调研 + Neo 反馈,采纳方案 C(推迟到 F1-6 沙箱时光机阶段 B): - 第 1 轮原方案 D(双口径) → 第 2 轮 D'(单口径) - 第 3 轮 Neo 架构纠正:不读 DWD,走 Core/DWS/app - 第 4 轮 DWS 视图靠谱性审计:dws_assistant_daily_detail 是计费明细 (ledger_amount),不是助教工资(gross_salary 需等级时薪 + 抽成 + 罚分),且缺 effective_hours / work_days - 结论:MP-2 真正实施需要新建 dws_assistant_daily_salary 表(ETL 改造),跟其他 14 个 P1 指标一起做更高效 → 推迟到 F1-6 本次 Wave B 只做 prep:DB schema + 模块 spec + tasks.md 状态调整。 DB 迁移(zqyy_app): - db/zqyy_app/migrations/20260505__add_effective_date_for_excel_adjustments.sql - 3 张 Excel 暂存表(全空,Neo 确认尚无 Excel 上传)ADD COLUMN effective_date DATE NOT NULL(无 DEFAULT,强制未来 Excel 上传必须带): * biz.salary_adjustments(助教薪资扣款/奖励) * biz.stg_finance_expense(月度支出) * biz.stg_platform_income(平台结算收入) - 3 个复合索引 (site_id, effective_date) 支持后续 daily 截断查询 - biz.stg_recharge_commission 已有 recharge_date,无需改造 测试库执行 + 5/5 校验 PASS: - 字段存在(NOT NULL DATE 无 default) - 复合索引存在 + 列序正确 - 字段注释含 'F1-5b MP-2 prep' - INSERT 不带 effective_date 触发 NotNullViolation docs/database/ 同步: - docs/database/changes/2026-05-05__add_effective_date_for_excel_adjustments.md 完整变更说明 + 兼容性 + 回滚 + 5 条校验 SQL + 正式库执行说明 沙箱时光机模块 spec(主干任务排期登记): - docs/_overview/sandbox-replay-engine-spec.md - 22 个相关指标分 P1/P2/P3 优先级: * P1 14 项(daily 视图已有,后端切换) * P2 5 项(算法重算,含 MP-2 完整 daily salary) * P3 3 项(状态算法 + sandbox_audit_log 用户行为) - 4 阶段实施路径: * 阶段 0(本次 prep) * 阶段 A(F1-5a/b 已完成) * 阶段 B(F1-6,2-3 周)— MP-2 真正实施在此 * 阶段 C(F1-7+,1-2 周) - sandbox_replay 模块结构 + runtime_aware decorator 接口契约 - 性能 + 测试 + 前置依赖清单 F1-5b-tasks.md 状态调整: - §4.3 顺序 15:MP-2 从"待开始/C4" → "延期 F1-6" - §6 进度表 MP-2 行同步标"延期 F1-6 + 改方向说明" - 关联到 mp2_prep.md 审计 业务影响: - board-coach sandbox 行为暂遗留(F1-6 解决) - 旧 Excel 模板上传将因 NOT NULL 失败,需 F1-6 同期 ETL UI 改造 + 操作员培训 - 跨页面已 audit:board-finance / customer-records / coach-service-records / customer-service-records 等已合规(F1-5b A1/A3 + MP-1/3/5 收益) 审计:docs/audit/changes/2026-05-05__wave1_f1_5b_mp2_prep.md Co-Authored-By: Claude Opus 4.7 (1M context) --- ...d_effective_date_for_excel_adjustments.sql | 72 ++++++++ docs/_overview/sandbox-replay-engine-spec.md | 168 +++++++++++++++++ docs/_overview/wave1-findings/F1-5b-tasks.md | 4 +- .../2026-05-05__wave1_f1_5b_mp2_prep.md | 121 ++++++++++++ ...dd_effective_date_for_excel_adjustments.md | 173 ++++++++++++++++++ 5 files changed, 536 insertions(+), 2 deletions(-) create mode 100644 db/zqyy_app/migrations/20260505__add_effective_date_for_excel_adjustments.sql create mode 100644 docs/_overview/sandbox-replay-engine-spec.md create mode 100644 docs/audit/changes/2026-05-05__wave1_f1_5b_mp2_prep.md create mode 100644 docs/database/changes/2026-05-05__add_effective_date_for_excel_adjustments.md diff --git a/db/zqyy_app/migrations/20260505__add_effective_date_for_excel_adjustments.sql b/db/zqyy_app/migrations/20260505__add_effective_date_for_excel_adjustments.sql new file mode 100644 index 0000000..cf50514 --- /dev/null +++ b/db/zqyy_app/migrations/20260505__add_effective_date_for_excel_adjustments.sql @@ -0,0 +1,72 @@ +-- 2026-05-05 +-- F1-5b MP-2 prep: 3 张 Excel 导入暂存表加 effective_date 字段 +-- +-- 背景: F1-5b MP-2 调研发现,sandbox 模式下需要按业务日上界过滤 +-- Excel 修正(扣款/奖励/支出/收入),但当前 schema 只有 YYYY-MM 月度精度, +-- 无法 daily 截断。 +-- +-- 三张目标表: +-- biz.salary_adjustments — 助教薪资扣款/奖励 +-- biz.stg_finance_expense — 月度支出(房租/水电/成本) +-- biz.stg_platform_income — 平台结算收入 +-- +-- (biz.stg_recharge_commission 已有 recharge_date DATE 字段,无需改造) +-- +-- 决策(Neo 2026-05-05): 截至当前三张表均为空(尚无任何 Excel 上传), +-- 直接 ADD COLUMN effective_date DATE NOT NULL 不带 DEFAULT, +-- 强制未来 Excel 上传必须传入 effective_date 列(操作员业务规范)。 +-- +-- 本次仅做 schema 准备,后端 board_service 改造延期到 F1-6 沙箱时光机 +-- 阶段 B(详见 docs/_overview/sandbox-replay-engine-spec.md)。 +-- +-- 兼容性: +-- - 三表当前为空,ADD COLUMN NOT NULL 不引发既有数据迁移失败 +-- - ETL Excel 上传 UI 后续(F1-6)需要在导入流程中解析 effective_date 列 +-- - 后端 SQL 暂未使用 effective_date,无即时影响 +-- +-- 索引: 加 (site_id, effective_date) 复合索引,支持后续 daily 截断查询 +-- +-- 回滚: +-- DROP INDEX IF EXISTS biz.idx_salary_adj_site_eff_date; +-- DROP INDEX IF EXISTS biz.idx_stg_finance_expense_site_eff; +-- DROP INDEX IF EXISTS biz.idx_stg_platform_income_site_eff; +-- ALTER TABLE biz.salary_adjustments DROP COLUMN IF EXISTS effective_date; +-- ALTER TABLE biz.stg_finance_expense DROP COLUMN IF EXISTS effective_date; +-- ALTER TABLE biz.stg_platform_income DROP COLUMN IF EXISTS effective_date; + +BEGIN; + +-- 1. salary_adjustments(助教薪资扣款/奖励) +ALTER TABLE biz.salary_adjustments + ADD COLUMN effective_date DATE NOT NULL; + +COMMENT ON COLUMN biz.salary_adjustments.effective_date IS + '生效日期(F1-5b MP-2 prep): Excel 上传时强制带,' + '用于 sandbox 模式下按业务日上界 (effective_date <= business_date) 过滤'; + +CREATE INDEX idx_salary_adj_site_eff_date + ON biz.salary_adjustments (site_id, effective_date); + +-- 2. stg_finance_expense(月度支出) +ALTER TABLE biz.stg_finance_expense + ADD COLUMN effective_date DATE NOT NULL; + +COMMENT ON COLUMN biz.stg_finance_expense.effective_date IS + '生效日期(F1-5b MP-2 prep): Excel 上传时强制带,' + '用于 sandbox 模式下按业务日上界 (effective_date <= business_date) 过滤'; + +CREATE INDEX idx_stg_finance_expense_site_eff + ON biz.stg_finance_expense (site_id, effective_date); + +-- 3. stg_platform_income(平台结算收入) +ALTER TABLE biz.stg_platform_income + ADD COLUMN effective_date DATE NOT NULL; + +COMMENT ON COLUMN biz.stg_platform_income.effective_date IS + '生效日期(F1-5b MP-2 prep): Excel 上传时强制带,' + '用于 sandbox 模式下按业务日上界 (effective_date <= business_date) 过滤'; + +CREATE INDEX idx_stg_platform_income_site_eff + ON biz.stg_platform_income (site_id, effective_date); + +COMMIT; diff --git a/docs/_overview/sandbox-replay-engine-spec.md b/docs/_overview/sandbox-replay-engine-spec.md new file mode 100644 index 0000000..f3aa0b3 --- /dev/null +++ b/docs/_overview/sandbox-replay-engine-spec.md @@ -0,0 +1,168 @@ +# 沙箱时光机引擎 (Sandbox Replay Engine) — 模块 Spec + +> 版本:v1.0 · 创建日期:2026-05-05 +> 决策来源:F1-5b Wave B MP-2 调研收尾(Neo 同意分阶段实现,加入主干任务排期) +> 主要落地依赖:F1-5a runtime_context 框架 + F1-5b 业务日上界裁剪基础设施 +> +> **状态**:Spec 阶段(未启动实施) + +## 一、设计意图 + +P20 沙箱(`runtime_mode='sandbox' + sandbox_business_date`)的核心承诺是 **"设定某个历史日期 X 后,系统所有数据/统计/AI 生成内容呈现的状态都是 X 那天的样子"**,即"时光机"语义。 + +当前 F1-5a/F1-5b 已建立基础设施,但**业务读取层只覆盖了"daily 累计型"指标**(财务流水 / 服务记录),**未覆盖"月度结算型 / 状态算法型 / Excel 修正型"指标**。沙箱设到 4-20 时,部分页面仍能"看见未来"或显示已知最终结果,违反时光机语义。 + +本模块旨在**统一所有业务读取按 sandbox_business_date 重算**,达成完整时光机体验。 + +## 二、当前已就位的基础设施(继承) + +| 层 | 已做 | Wave / Commit | +|----|------|--------------| +| **RuntimeContext 框架** | site_runtime_context 表 + apply_runtime_session_vars() | F1-5a 421e193 | +| **数据写入隔离** | ai_run_logs / coach_tasks / 各业务写入表带 runtime_mode + sandbox_instance_id | F1-5a + F1-5b A1 af02446 | +| **app 视图业务日上界裁剪** | 39 个 app.v_* 视图加 `WHERE stat_date <= business_date_now()`(2026-05-02 迁移) | 2026-05-02 ETL 迁移 | +| **后端读取**(daily 累计型指标) | board-finance / customer-detail / customer-records / coach-service-records 等 | F1-5b MP-1/3/5 | +| **后端读取**(权限路由) | manager 角色权限隔离 | F1-5b BE-1 | +| **admin-web sandbox 透出** | UI-1/2/3/4/5 全套 runtime 字段 + 提示条 + 全局徽章 | F1-5b Wave A/B | +| **Excel 修正表 schema 准备** | salary_adjustments / stg_finance_expense / stg_platform_income 加 effective_date NOT NULL | F1-5b MP-2 prep(本次) | + +## 三、缺失能力清单(本模块覆盖范围) + +22 个指标按复杂度 + 优先级分: + +### P1 — Daily 视图已有,后端 service 切换即可(14 项,S 复杂度) + +| # | 指标 | 当前数据源 | 目标 daily 视图 | 重算逻辑 | +|---|------|-----------|----------------|---------| +| 1 | 会员储值卡余额 | dws_member_balance_snapshot(状态) | dws_member_balance_change daily 累计 | SUM(收入-支出) WHERE date<=B | +| 2 | 60 天消费 | dws_member_consumption_summary | dws_member_consumption_daily | SUM 60 day window 终点为 B | +| 3 | 累计消费总额 | 同上 | 同上 | SUM all WHERE date<=B | +| 4 | 距上次到店天数 | 状态字段 | dwd 消费记录 | B - MAX(visit_date WHERE<=B) | +| 5 | 累计服务客户数(助教) | dws_assistant_customer_stats | daily 累计 | COUNT DISTINCT member WHERE date<=B | +| 6 | 助教等级 | dws_assistant_daily_detail.assistant_level_code | 同表 | SELECT WHERE stat_date=B | +| 7 | 月度课时(助教) | dws_assistant_daily_detail.base_hours 等 | 同表月度聚合 | SUM WHERE month_of(B) AND stat_date<=B | +| 8 | 月度计费金额(助教) | dws_assistant_daily_detail.total_ledger_amount | 同上 | SUM WHERE month_of(B) AND stat_date<=B | +| 9 | 门店月度财务 | dws_finance_daily_summary | 同表(已实现 MP-1) | ✓ 已完成 | +| 10 | 月度新增会员 | dws_member_*_summary | dws_member_daily | COUNT WHERE join_date<=B AND month_of(B) | +| 11 | 月度流失会员 | 同上 | 同上 | COUNT WHERE last_visit Decimal: + """根据 RuntimeContext 自动选 live / sandbox 路径。""" + # 实现内会判断: + # - if runtime_ctx.is_sandbox: 调 balance_replay.partial(business_date) + # - else: 调 dws_query.live_balance() + pass +``` + +### 5.3 测试模式 + +每个 replay 模块配套 unit test(BE-3 / T3 模式): +- mock get_runtime_context 返回 live / sandbox 两路 +- 断言 SQL 包含 daily 累计 + business_date 截断 +- 部分 integration test 用真实测试库验证数据一致性 + +### 5.4 性能考虑 + +- daily 累计 SQL 比 monthly snapshot 慢(SUM 多行 vs 单行查) +- 建议:sandbox 模式下接受性能折衷;live 模式仍走原 dws 月度路径 +- 必要时可做 in-memory cache(business_date 不变时缓存命中) + +## 六、阶段 B 前置依赖清单 + +| 依赖 | 来源 | 状态 | +|------|------|------| +| RuntimeContext + business_date | F1-5a | ✓ 已完成 | +| app 视图 daily 上界裁剪 | 2026-05-02 迁移 | ✓ 已完成 | +| Excel 修正表 effective_date 字段 | F1-5b MP-2 prep(本次) | ✓ 本次完成 | +| ETL Excel 上传 UI 支持 effective_date 列 | F1-6 阶段 B 内 | ⏳ 待做 | +| dws_assistant_daily_salary 表/视图 | F1-6 阶段 B 内(MP-2 真正实施) | ⏳ 待做 | +| dws_member_daily 系列视图(部分缺失) | F1-6 阶段 B 内 | ⏳ 待做 | + +## 七、阶段 C 远期目标 + +### sandbox_audit_log(用户行为审计) + +**业务目标**:沙箱模式下用户的所有操作都记录,切回 live 后可追溯,支持"沙箱演练复盘"场景。 + +**表设计草案**: +```sql +CREATE TABLE biz.sandbox_audit_log ( + id BIGSERIAL PRIMARY KEY, + site_id BIGINT NOT NULL, + sandbox_instance_id VARCHAR(100) NOT NULL, + user_id BIGINT NOT NULL, + action_type VARCHAR(50) NOT NULL, -- 'view' / 'trigger_ai' / 'modify' / ... + page_path VARCHAR(200), + payload JSONB, + created_at TIMESTAMPTZ DEFAULT now() +); +``` + +**写入入口**:统一 middleware 或 decorator 拦截 sandbox 模式请求。 + +## 八、关联 + +- F1-5a 主体审计:`docs/audit/changes/2026-05-05__wave1_f1_5a_sandbox_batch_run.md` +- F1-5a 走查报告:`docs/audit/changes/2026-05-05__wave1_f1_5a_backend_walkthrough.md` +- F1-5b 任务清单:`docs/_overview/wave1-findings/F1-5b-tasks.md` +- F1-5b MP-2 prep 审计:`docs/audit/changes/2026-05-05__wave1_f1_5b_mp2_prep.md` +- P20 SPEC:`docs/prd/specs/P20-runtime-context-sandbox.md` +- 业务日上界 ETL 迁移:`db/etl_feiqiu/migrations/20260502__rls_views_business_date_upper_bound.sql` + +## 九、决策路径(Owner Approval) + +- **2026-05-05 Neo 决策**: + > "沙箱'全数据时光机'模块可行性 — 同意,分阶段实现,往主干任务排期中增加。" +- **本 spec 状态**:已 commit,等待 F1-6 启动。F1-6 启动时本 spec 作为阶段 B 实施依据。 diff --git a/docs/_overview/wave1-findings/F1-5b-tasks.md b/docs/_overview/wave1-findings/F1-5b-tasks.md index 9734149..35b347d 100644 --- a/docs/_overview/wave1-findings/F1-5b-tasks.md +++ b/docs/_overview/wave1-findings/F1-5b-tasks.md @@ -230,7 +230,7 @@ D1 ──> D2 ──> D3 ──> D4 (本阶段全部完成后) |---|---|---|---|---|---| | 13 | UI-3 AIDashboard sandbox 提示条 + runtime 分组 | follow-up | M | A1 | C3 | | 14 | UI-5 AITriggerJobs runtime 列 | follow-up | XS | 无 | C3 | -| 15 | MP-2 board-coach 月度面板双口径 | follow-up | L | A2 | C4 | +| 15 | MP-2 board-coach 月度面板双口径 | **延期 F1-6** | L | A2 + dws_assistant_daily_salary 视图(F1-6 内建) | F1-6 | | 16 | MP-4 coach-detail data.coachId 修复 | follow-up | S | 无 | C4 | | 17 | T3 unit dispatcher runtime | 测试 | M | A3 | C5 | | 18 | BE-3 ai_run_logs runtime 写入回归 | follow-up | S | T2 | C5 | @@ -334,7 +334,7 @@ def reset_runtime_context_to_live(): | — | **Wave A mid-wave checkpoint** | — | — | — | — | — | — | Neo 复审 | | UI-3 | AIDashboard sandbox 提示+分组 | 待开始 | — | C3 | — | ▢ | ▢ | 双线分组显示 | | UI-5 | AITriggerJobs runtime 列 | 待开始 | — | C3 | — | ▢ | ▢ | 列正确显示 | -| MP-2 | board-coach 月度面板双口径 | 待开始 | — | C4 | — | ▢ | ▢ | partial vs settled | +| MP-2 | board-coach 月度面板双口径 | **延期 F1-6** | 2026-05-05 | C10(prep) | mp2_prep.md | - | - | **改方向**:走 D 方案需新建 dws_assistant_daily_salary,本次只做 schema prep(3 stg 表加 effective_date)+ spec 文档,完整实施在 F1-6 沙箱时光机阶段 B | | MP-4 | coach-detail data.coachId 修复 | 待开始 | — | C4 | — | ▢ | - | 预存 bug 与 sandbox 无关 | | T3 | dispatcher unit | 待开始 | — | C5 | — | ▢ | - | mock | | BE-3 | ai_run_logs runtime 回归 | 待开始 | — | C5 | — | ▢ | ▢ | runtime 字段写入 | diff --git a/docs/audit/changes/2026-05-05__wave1_f1_5b_mp2_prep.md b/docs/audit/changes/2026-05-05__wave1_f1_5b_mp2_prep.md new file mode 100644 index 0000000..07e85e0 --- /dev/null +++ b/docs/audit/changes/2026-05-05__wave1_f1_5b_mp2_prep.md @@ -0,0 +1,121 @@ +# 2026-05-05 · F1-5b MP-2 准备工作 + 沙箱时光机模块 spec + +> Wave 1 / F1-5b Wave B 第 3 项任务调整(详见 `docs/_overview/wave1-findings/F1-5b-tasks.md` §4.2 顺序 15) +> +> 工作量评估 L / 4-6h(原方案)→ **本次仅 prep 1.5h**,完整实施延期到 F1-6 +> +> **MP-2 决策路径**:经 4 轮调研 + Neo 反馈后,采纳方案 C(推迟到 F1-6 沙箱时光机阶段 B) + +## 决策路径回顾 + +### 第 1 轮:原方案 D(双口径) +推荐 partial(daily 累计) + settled(monthly 全量)双字段并存。 + +### 第 2 轮:Neo 反馈 — 助教薪资业务流程澄清 +- 系统先算工资,Excel 导入再修正(扣款/奖励对应到某一天) +- 任务完成率应按沙箱日截至 +- 支出(房租)应按支出日期 + 沙箱日处理 +- "信任危机"业务侧担心是伪命题(沙箱本就是产品功能) +- → 改方案 D''(单一口径,按 business_date 实时累计;DB 加 effective_date 字段) + +### 第 3 轮:Neo 架构纠正 — 不要直接读 DWD +- DWD 是连接器层(飞球/未来其他),后续会做 DWD 处理 + 更新 Core +- Core 跨连接器保持表/字段一致(标准化) +- DWS / app 视图 / RLS 都基于 Core +- 后端应该读 Core / DWS / app,不读 DWD +- → 改方案 D'''(走 app.v_dws_assistant_daily_detail 已有 daily 视图) + +### 第 4 轮:DWS 视图靠谱性审计 — 发现 daily_detail 不能替代 salary_calc +- `dws_assistant_daily_detail` 是计费明细(ledger_amount = 台费金额) +- 不是助教工资(gross_salary,需助教等级时薪 + 抽成 + 罚分) +- 缺 effective_hours / work_days 字段(monthly 表才有) +- → **MP-2 真正实施需要新建 dws_assistant_daily_salary 表(ETL 改造,工作量 M)** +- → 推迟到 F1-6 沙箱时光机阶段 B,跟其他 14 个 P1 指标一起做(更高效) + +### 第 5 轮:Neo 同意方向 1(推迟 + prep) +本次 Wave B 只做 prep:DB 迁移 + sandbox_replay spec 文档 + tasks.md 状态调整。 + +## 改动清单 + +### 1. DB 迁移(zqyy_app) + +**文件**:`db/zqyy_app/migrations/20260505__add_effective_date_for_excel_adjustments.sql` + +3 张 stg 表(全空)ADD COLUMN effective_date DATE NOT NULL(无 DEFAULT,因 Neo 反馈"截至当前还没有任何 Excel 被上传",强制未来必须带): + +- `biz.salary_adjustments`(助教薪资扣款/奖励) +- `biz.stg_finance_expense`(月度支出) +- `biz.stg_platform_income`(平台结算收入) + +3 个复合索引 `(site_id, effective_date)` 支持 daily 截断查询。 + +`biz.stg_recharge_commission` 已有 `recharge_date DATE` 字段,无需改造。 + +### 2. docs/database/ 同步 + +**文件**:`docs/database/changes/2026-05-05__add_effective_date_for_excel_adjustments.md` + +完整变更说明 + 兼容性 + 回滚 + 5 条校验 SQL + 正式库执行说明。 + +### 3. 沙箱时光机模块 spec + +**文件**:`docs/_overview/sandbox-replay-engine-spec.md` + +完整模块设计: +- 22 个相关指标分 P1/P2/P3 优先级 +- 14 个 P1 指标(daily 视图已有,后端切换即可) +- 5 个 P2 指标(算法重算,含 MP-2 完整 daily salary) +- 3 个 P3 指标(状态算法 + sandbox_audit_log 用户行为) +- 4 阶段实施路径:阶段 0(本次 prep) → 阶段 A(F1-5a/b 已完成) → 阶段 B(F1-6,2-3 周) → 阶段 C(F1-7+,1-2 周) +- sandbox_replay 模块结构 + runtime_aware decorator 接口契约 +- 性能 + 测试模式建议 + +### 4. F1-5b-tasks.md 状态调整 + +§4.3 MP-2 行从 `待开始 / C4` → `延期 F1-6`,理由 + 关联文档已登记。 +§4.2 顺序 15 行类似调整。 + +## Step 4 验证 + +DB 迁移在测试库执行 + 5/5 校验全 PASS: +1. ✓ 3 表 effective_date 字段存在(NOT NULL DATE 无 default) +2. ✓ 3 个复合索引存在 +3. ✓ 索引列序 (site_id, effective_date) +4. ✓ 字段注释含 'F1-5b MP-2 prep' +5. ✓ INSERT 不带 effective_date 触发 NotNullViolation + +走查脚本:`_DEL/walkthrough_f1_5b/step_mp2_prep_apply_migration.py` + +## 影响范围 + +| 端 | 改动 | 影响 | +|----|------|------| +| zqyy_app DB | 3 表 schema + 3 索引 | 当前数据为空,无既有数据迁移风险 | +| ETL Excel 上传 | **未改**(F1-6 同期实施) | 旧模板上传会因 NOT NULL 失败 → 等 F1-6 ETL 改造同步 | +| 后端 service | **未改**(MP-2 主体延期) | board-coach sandbox 行为暂未修复(F1-6 解决) | +| admin-web / 小程序 | 无影响 | — | + +## 风险与未覆盖 + +- **board-coach sandbox 行为遗留**:sandbox=2026-04-20 时,board-coach 仍展示月度全量(含 4-21~30 未发生数据),F1-6 才彻底修复 +- **Excel 上传未来兼容**:旧模板上传将因 NOT NULL 失败,需要 F1-6 同期完成 ETL UI 改造 + 操作员培训 +- **跨页面 audit 已识别**:board-finance / customer-records / coach-service-records / customer-service-records 等已合规(F1-5b A1/A3 + MP-1/3/5 收益),不需要本次额外处理 + +## 回滚策略 + +```sql +BEGIN; +DROP INDEX IF EXISTS biz.idx_salary_adj_site_eff_date; +DROP INDEX IF EXISTS biz.idx_stg_finance_expense_site_eff; +DROP INDEX IF EXISTS biz.idx_stg_platform_income_site_eff; +ALTER TABLE biz.salary_adjustments DROP COLUMN IF EXISTS effective_date; +ALTER TABLE biz.stg_finance_expense DROP COLUMN IF EXISTS effective_date; +ALTER TABLE biz.stg_platform_income DROP COLUMN IF EXISTS effective_date; +COMMIT; +``` + +详见 `docs/database/changes/2026-05-05__add_effective_date_for_excel_adjustments.md`。 + +## Co-Authored-By + +Claude Opus 4.7 (1M context) diff --git a/docs/database/changes/2026-05-05__add_effective_date_for_excel_adjustments.md b/docs/database/changes/2026-05-05__add_effective_date_for_excel_adjustments.md new file mode 100644 index 0000000..a319526 --- /dev/null +++ b/docs/database/changes/2026-05-05__add_effective_date_for_excel_adjustments.md @@ -0,0 +1,173 @@ +# 2026-05-05 · 3 张 Excel 暂存表加 effective_date 字段(F1-5b MP-2 prep) + +> F1-5b Wave B MP-2 准备工作(后端 board_service 改造延期到 F1-6 沙箱时光机阶段 B) +> +> migration: `db/zqyy_app/migrations/20260505__add_effective_date_for_excel_adjustments.sql` + +## 背景 + +F1-5b MP-2 调研发现:sandbox 模式下需要按业务日上界过滤 Excel 修正(扣款/奖励/支出/收入),但当前 schema 只有 YYYY-MM 月度精度,无法 daily 截断。 + +**Neo 决策(2026-05-05)**: +1. 三张目标表当前**全为空**(尚无任何 Excel 上传),直接 ADD COLUMN NOT NULL 不带 DEFAULT,强制未来 Excel 上传必须带 effective_date(操作员业务规范) +2. 后端 board_service 改造**延期到 F1-6 沙箱时光机阶段 B**(详见 `docs/_overview/sandbox-replay-engine-spec.md`) +3. 本次仅做 schema 准备 + 索引,不动 ETL 上传 / 后端业务逻辑 + +## 变更说明 + +### Schema 变更(3 张表) + +| 表 | 用途 | 改动 | +|----|------|------| +| `biz.salary_adjustments` | 助教薪资扣款/奖励 | ADD COLUMN effective_date DATE NOT NULL | +| `biz.stg_finance_expense` | 月度支出(房租/水电/成本) | ADD COLUMN effective_date DATE NOT NULL | +| `biz.stg_platform_income` | 平台结算收入 | ADD COLUMN effective_date DATE NOT NULL | + +`biz.stg_recharge_commission` 已有 `recharge_date DATE` 字段,无需改造。 + +### 字段定义 + +```sql +ALTER TABLE biz. + ADD COLUMN effective_date DATE NOT NULL; + +COMMENT ON COLUMN biz..effective_date IS + '生效日期(F1-5b MP-2 prep): Excel 上传时强制带,' + '用于 sandbox 模式下按业务日上界 (effective_date <= business_date) 过滤'; +``` + +### 索引(3 个复合索引) + +```sql +CREATE INDEX idx_salary_adj_site_eff_date + ON biz.salary_adjustments (site_id, effective_date); +CREATE INDEX idx_stg_finance_expense_site_eff + ON biz.stg_finance_expense (site_id, effective_date); +CREATE INDEX idx_stg_platform_income_site_eff + ON biz.stg_platform_income (site_id, effective_date); +``` + +支持后续 daily 截断查询模式 `WHERE site_id = ? AND effective_date <= ?`。 + +## 兼容性 + +| 端 | 影响 | 处理 | +|----|------|------| +| **现有数据** | 三表当前全为空,ADD COLUMN NOT NULL 不引发既有数据迁移失败 | — | +| **ETL Excel 上传** | 后续(F1-6)需要在导入流程中解析 effective_date 列 | F1-6 内做 | +| **后端 API** | 当前无任何后端 SQL 使用 effective_date,**本次 schema 改动无即时业务影响** | — | +| **管理后台 Excel 上传 UI** | tenant-admin 的 ExcelUpload 模块需要在 Excel 模板新增 effective_date 列 | F1-6 内做(配套) | +| **既有 Excel 文件模板** | 未来上传必须新模板带日期列;旧模板上传会因 NOT NULL 失败 | 需要操作员培训 + 模板分发 | + +## 回滚策略 + +```sql +BEGIN; +DROP INDEX IF EXISTS biz.idx_salary_adj_site_eff_date; +DROP INDEX IF EXISTS biz.idx_stg_finance_expense_site_eff; +DROP INDEX IF EXISTS biz.idx_stg_platform_income_site_eff; +ALTER TABLE biz.salary_adjustments DROP COLUMN IF EXISTS effective_date; +ALTER TABLE biz.stg_finance_expense DROP COLUMN IF EXISTS effective_date; +ALTER TABLE biz.stg_platform_income DROP COLUMN IF EXISTS effective_date; +COMMIT; +``` + +幂等,执行多次安全。 + +## 验证 SQL(测试库已 PASS) + +### 校验 1:三表都有 effective_date 字段(NOT NULL DATE 无 default) + +```sql +SELECT table_name, column_name, data_type, is_nullable, column_default +FROM information_schema.columns +WHERE table_schema = 'biz' + AND table_name IN ('salary_adjustments', 'stg_finance_expense', 'stg_platform_income') + AND column_name = 'effective_date' +ORDER BY table_name; +-- 期望: 3 行,全部 data_type=date / is_nullable='NO' / column_default=NULL +``` + +### 校验 2:三个复合索引都已创建 + +```sql +SELECT tablename, indexname FROM pg_indexes +WHERE schemaname = 'biz' + AND indexname IN ( + 'idx_salary_adj_site_eff_date', + 'idx_stg_finance_expense_site_eff', + 'idx_stg_platform_income_site_eff' + ) +ORDER BY indexname; +-- 期望: 3 行 +``` + +### 校验 3:索引列序正确 (site_id, effective_date) + +```sql +SELECT i.relname AS index_name, + array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) AS columns +FROM pg_class t +JOIN pg_index ix ON t.oid = ix.indrelid +JOIN pg_class i ON i.oid = ix.indexrelid +JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey) +WHERE i.relname IN ( + 'idx_salary_adj_site_eff_date', + 'idx_stg_finance_expense_site_eff', + 'idx_stg_platform_income_site_eff' +) +GROUP BY i.relname +ORDER BY i.relname; +-- 期望: 全部 ['site_id', 'effective_date'] +``` + +### 校验 4:字段注释存在 + +```sql +SELECT c.table_name, + col_description((c.table_schema||'.'||c.table_name)::regclass::oid, c.ordinal_position) AS comment +FROM information_schema.columns c +WHERE c.table_schema = 'biz' + AND c.table_name IN ('salary_adjustments', 'stg_finance_expense', 'stg_platform_income') + AND c.column_name = 'effective_date' +ORDER BY c.table_name; +-- 期望: 3 行,comment 含 'F1-5b MP-2 prep' +``` + +### 校验 5:INSERT 不带 effective_date 应失败(NOT NULL 约束生效) + +```sql +BEGIN; +INSERT INTO biz.salary_adjustments + (site_id, assistant_name, assistant_number, salary_month, + adjustment_type, amount, reason, upload_batch_id, created_by) +VALUES (2790685415443269, 'test', 'T001', '2026-04', 'penalty', + 100.00, 'test', 1, 1); +ROLLBACK; +-- 期望: NotNullViolation +``` + +## 正式库执行说明 + +本次 migration **仅在测试库执行**。生产环境同步时: + +```bash +psql "$APP_DB_DSN" -f db/zqyy_app/migrations/20260505__add_effective_date_for_excel_adjustments.sql +``` + +执行前必须确认三张表为空(若已有数据,需先评估回填策略)。执行后跑 5 条校验 SQL。 + +## 后续依赖任务(F1-6 沙箱时光机阶段 B) + +详见 `docs/_overview/sandbox-replay-engine-spec.md`: + +1. **ETL Excel 上传 UI 改造**:tenant-admin/ExcelUpload 模块支持 effective_date 列解析 +2. **Excel 模板分发**:操作员培训 + 新模板分发 +3. **后端 board_service 改造**:替换 `dws_assistant_salary_calc` 直查为 daily 累计 + adjustments 截断 +4. **MP-2 实施 + 双口径走查** + +## 关联 + +- F1-5b MP-2 调研记录:`docs/audit/changes/2026-05-05__wave1_f1_5b_mp2_prep.md` +- 沙箱时光机模块 spec:`docs/_overview/sandbox-replay-engine-spec.md` +- F1-5b 任务清单:`docs/_overview/wave1-findings/F1-5b-tasks.md`(MP-2 状态已调整为"延期 F1-6")