feat(db): app2a DWS 新列 + ai_cache CHECK 约束放开

1. db/etl_feiqiu/migrations/20260423__app2a_add_member_order_count.sql:
   - dws.dws_finance_area_daily 增加 member_order_count 列 (integer NOT NULL DEFAULT 0)
   - 重建 app.v_dws_finance_area_daily RLS 视图暴露新列
   - 同步重建 dws.v_dws_finance_area_daily(遵守双 schema 规则)
   - 列顺序因 PostgreSQL CREATE OR REPLACE VIEW 限制必须加在末尾

2. db/zqyy_app/migrations/20260423__ai_cache_allow_app2a.sql:
   - biz.ai_cache.chk_ai_cache_type CHECK 约束放开 app2a_finance_area 新值
   - DROP 旧 7 项 CHECK + CREATE 含 8 项的新 CHECK(新增 app2a_finance_area)

3. docs/database/changes/ 两份变更文档:
   - 变更说明 + 兼容性 + 回滚策略 + 3-4 条验证 SQL

测试库已执行 + 验证通过。生产库待上线窗口按 checklist 跑。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Neo
2026-04-22 21:55:01 +08:00
parent cd511d0670
commit 76a23639ee
4 changed files with 584 additions and 0 deletions

View File

@@ -0,0 +1,180 @@
-- =============================================================
-- Migration: 20260423__app2a_add_member_order_count.sql
-- Purpose: 为 app2a 区域财务洞察增加 dws 层区域级会员订单数列
-- Related: docs/ai/app2_finance_multi_app_design.md v2 · Phase B
-- Author: Claude + Neo
-- =============================================================
--
-- 改动说明:
-- 1. dws.dws_finance_area_daily 增加 member_order_count 列(默认 0允许新历史数据为 0
-- 2. 重建 RLS 视图 app.v_dws_finance_area_daily暴露新列
-- 3. 若存在 dws.v_dws_finance_area_daily 同名镜像视图,同步重建
--
-- 后置依赖:
-- - ETL loader apps/etl/connectors/feiqiu/loaders/dws_finance_area_daily.py
-- 需要同步改造:按 (area_code, stat_date) 聚合 DWD 订单时,
-- 将 is_member_order = true 的订单数合入 member_order_count
-- - 改造前历史数据 member_order_count = 0改造后新增数据生效
-- - 如需历史回填scripts/ops/backfill_area_member_order.py本迁移不含
--
-- 回滚策略:
-- 见文件末 ROLLBACK 段
--
-- 验证 SQL
-- 见文件末 VERIFICATION 段
-- =============================================================
BEGIN;
-- ------------------------------------------------------------
-- Step 1: dws 层表加列
-- ------------------------------------------------------------
ALTER TABLE dws.dws_finance_area_daily
ADD COLUMN IF NOT EXISTS member_order_count integer NOT NULL DEFAULT 0;
COMMENT ON COLUMN dws.dws_finance_area_daily.member_order_count IS
'会员订单数 (区域粒度, 从 DWD 聚合 is_member_order=true 的订单计数, 默认 0)';
-- ------------------------------------------------------------
-- Step 2: 重建 app schema 的 RLS 视图
-- - 保留原 RLS 过滤条件 (current_setting('app.current_site_id'))
-- - PostgreSQL 限制CREATE OR REPLACE VIEW 仅允许末尾加列,不能中间插入
-- - 故 member_order_count 加到末尾(逻辑归属仍为单位经济,顺序不影响语义)
-- ------------------------------------------------------------
CREATE OR REPLACE VIEW app.v_dws_finance_area_daily AS
SELECT
id,
site_id,
tenant_id,
stat_date,
area_code,
table_fee_amount,
goods_amount,
assistant_pd_amount,
assistant_cx_amount,
gross_amount,
discount_groupbuy,
discount_vip,
discount_manual,
discount_gift_card,
discount_rounding,
discount_other,
discount_total,
confirmed_income,
cash_pay_amount,
cash_paper_amount,
scan_pay_amount,
groupbuy_pay_amount,
recharge_cash_inflow,
cash_inflow_total,
cash_outflow_total,
cash_balance_change,
card_consume_total,
recharge_card_consume,
gift_card_consume,
recharge_cash,
first_recharge_cash,
renewal_cash,
order_count,
created_at,
updated_at,
member_order_count -- 新增列PostgreSQL CREATE OR REPLACE VIEW 限制:只能末尾加)
FROM dws.dws_finance_area_daily
WHERE site_id = (current_setting('app.current_site_id'::text))::bigint;
COMMENT ON VIEW app.v_dws_finance_area_daily IS
'区域级财务日粒度 RLS 视图 | app2a 区域财务洞察数据源 | 2026-04-23 新增 member_order_count';
-- ------------------------------------------------------------
-- Step 3: 重建 dws schema 镜像 RLS 视图
-- (按 CLAUDE.md "RLS 视图双 Schema 规则" 要求dws 和 app 两个 schema 的 RLS 视图必须同步)
-- ------------------------------------------------------------
CREATE OR REPLACE VIEW dws.v_dws_finance_area_daily AS
SELECT
id,
site_id,
tenant_id,
stat_date,
area_code,
table_fee_amount,
goods_amount,
assistant_pd_amount,
assistant_cx_amount,
gross_amount,
discount_groupbuy,
discount_vip,
discount_manual,
discount_gift_card,
discount_rounding,
discount_other,
discount_total,
confirmed_income,
cash_pay_amount,
cash_paper_amount,
scan_pay_amount,
groupbuy_pay_amount,
recharge_cash_inflow,
cash_inflow_total,
cash_outflow_total,
cash_balance_change,
card_consume_total,
recharge_card_consume,
gift_card_consume,
recharge_cash,
first_recharge_cash,
renewal_cash,
order_count,
created_at,
updated_at,
member_order_count -- 新增列PostgreSQL CREATE OR REPLACE VIEW 限制:只能末尾加)
FROM dws.dws_finance_area_daily
WHERE site_id = (current_setting('app.current_site_id'::text))::bigint;
COMMENT ON VIEW dws.v_dws_finance_area_daily IS
'区域级财务日粒度 RLS 视图 (dws schema 镜像) | 与 app.v_dws_finance_area_daily 双 schema 规则要求下同步';
COMMIT;
-- =============================================================
-- VERIFICATION (事后手工执行)
-- =============================================================
-- 1. 列存在性
-- SELECT column_name, data_type, column_default, is_nullable
-- FROM information_schema.columns
-- WHERE table_schema = 'dws'
-- AND table_name = 'dws_finance_area_daily'
-- AND column_name = 'member_order_count';
-- -- 期望: 1 行, integer, 0, NO
--
-- 2. 视图暴露新列
-- SELECT column_name
-- FROM information_schema.columns
-- WHERE table_schema = 'app'
-- AND table_name = 'v_dws_finance_area_daily'
-- AND column_name = 'member_order_count';
-- -- 期望: 1 行
--
-- 3. 数据校验 (ETL loader 改造并跑一次后执行)
-- SET app.current_site_id = <测试门店 ID>;
-- SELECT s.site_id,
-- s.stat_date,
-- s.member_order_count AS full_store_member_orders,
-- COALESCE(SUM(a.member_order_count), 0) AS sum_area_member_orders,
-- s.member_order_count - COALESCE(SUM(a.member_order_count), 0) AS diff
-- FROM app.v_dws_finance_daily_summary s
-- LEFT JOIN app.v_dws_finance_area_daily a
-- ON s.site_id = a.site_id AND s.stat_date = a.stat_date
-- WHERE s.stat_date >= CURRENT_DATE - INTERVAL '7 days'
-- GROUP BY s.site_id, s.stat_date, s.member_order_count
-- HAVING s.member_order_count <> COALESCE(SUM(a.member_order_count), 0);
-- -- 期望: 0 行 (全店会员订单数 = 区域之和)
-- =============================================================
-- ROLLBACK
-- =============================================================
-- BEGIN;
-- DROP VIEW IF EXISTS app.v_dws_finance_area_daily CASCADE;
-- DROP VIEW IF EXISTS dws.v_dws_finance_area_daily CASCADE;
-- ALTER TABLE dws.dws_finance_area_daily DROP COLUMN IF EXISTS member_order_count;
-- -- 重新 CREATE VIEW app.v_dws_finance_area_daily (不含新列) - 从 db/etl_feiqiu/schemas/app.sql:714 复制
-- COMMIT;

View File

@@ -0,0 +1,70 @@
-- =============================================================
-- Migration: 20260423__ai_cache_allow_app2a.sql
-- Purpose: 放开 biz.ai_cache.chk_ai_cache_type CHECK 约束,
-- 允许新 cache_type 'app2a_finance_area'app2a 区域财务洞察)
-- Related: docs/ai/app2_finance_multi_app_design.md v2 · Phase F
-- Author: Claude + Neo
-- =============================================================
--
-- 改动说明:
-- 1. DROP 旧 CHECK 约束 chk_ai_cache_type
-- 2. CREATE 新 CHECK 约束,加入 'app2a_finance_area' 值
--
-- 兼容性:
-- - 旧 cache_type 值全部保留,仅新增一个允许值
-- - 无需数据迁移
--
-- 回滚策略:
-- 见文件末 ROLLBACK 段
--
-- 验证 SQL
-- 见文件末 VERIFICATION 段
-- =============================================================
BEGIN;
-- 1. DROP 旧约束
ALTER TABLE biz.ai_cache DROP CONSTRAINT IF EXISTS chk_ai_cache_type;
-- 2. CREATE 新约束(加入 app2a_finance_area
ALTER TABLE biz.ai_cache ADD CONSTRAINT chk_ai_cache_type
CHECK (cache_type IN (
'app2_finance',
'app2a_finance_area', -- 2026-04-23 新增(区域财务洞察 · 64 组合)
'app3_clue',
'app4_analysis',
'app5_tactics',
'app6_note_analysis',
'app7_customer_analysis',
'app8_clue_consolidated'
));
COMMIT;
-- =============================================================
-- VERIFICATION事后手工执行
-- =============================================================
-- 1. 约束允许值检查
-- SELECT pg_get_constraintdef(oid)
-- FROM pg_constraint
-- WHERE conname = 'chk_ai_cache_type';
-- -- 期望:返回的 CHECK 里包含 'app2a_finance_area'
--
-- 2. 插入新 cache_type 应成功
-- INSERT INTO biz.ai_cache (cache_type, site_id, target_id, result_json, status, expires_at)
-- VALUES ('app2a_finance_area', 1, 'test__all', '{}', 'valid', NOW() + INTERVAL '1 day');
-- -- 期望:成功插入
-- DELETE FROM biz.ai_cache WHERE cache_type = 'app2a_finance_area' AND site_id = 1 AND target_id = 'test__all';
-- =============================================================
-- ROLLBACK
-- =============================================================
-- BEGIN;
-- ALTER TABLE biz.ai_cache DROP CONSTRAINT IF EXISTS chk_ai_cache_type;
-- ALTER TABLE biz.ai_cache ADD CONSTRAINT chk_ai_cache_type
-- CHECK (cache_type IN (
-- 'app2_finance', 'app3_clue', 'app4_analysis', 'app5_tactics',
-- 'app6_note_analysis', 'app7_customer_analysis', 'app8_clue_consolidated'
-- ));
-- COMMIT;
-- 注:回滚前必须先 DELETE 所有 cache_type = 'app2a_finance_area' 的记录,否则 CHECK 会失败