P2 逐域切换:analytics 改读 public 干净层 + C2 CI 门禁落地(#319 路线图) #322
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
背景
#319 数据分层路线图 P2(逐域切换)。P0(#319)已完成 21 张信源表移入
odsschema;P1(#320)已完成套餐维度干净层样板。本单为 P2:令services/analytics/**按 refund → complaint → reception 逐域改读 public 干净层,并把契约 C2(analytics 禁止直读 ods 源表)落为 CI 门禁。现状侦察(2026-10-14 代码实查)
ods.字面引用;实际靠 #319 落地的连接级search_path=public,ods兜底,以非限定名裸 SQL + ORM 模型直读 ods 源表。orm_model64(import schema=ods 的模型类,集中在 today/* 与 _core/cash/category/complaint 等)、sql_table16(裸 SQL FROM/JOIN 直读 refunds/wallet_withdrawals/cs_records/device_combos/manual_refunds/shanmao_refunds/refund_record_reports/external_complaints/card_complaints)。service_tickets等仍留 public,不属违规面。C2 门禁已落地(本单交付之一,随本单首次提交)
scripts/check_analytics_ods_isolation.py:棘轮(ratchet) 模式,扫描三类违规(ods_prefix / sql_table / orm_model),与基线scripts/analytics_ods_isolation_baseline.txt(80 条存量)比对——新增违规阻断 CI,减少则提示收敛。analytics-ods-isolation(.gitea/workflows/test.yml):纯 python3 静态扫描,不依赖 docker。执行方案(逐域切换)
按 refund → complaint → reception 顺序,每个域:
--update-baseline);待对齐(关键设计问题,须人工拍板)
验收标准 / 关闭条件
analytics_ods_isolation_baseline.txt清空(CI C2 门禁完全生效)关联
P2 refund 域切换设计草案 v2(修订:口径不变原则)
〇、口径不变原则(硬约束,本次新增)
ods源换成 public 干净层,过滤条件原样保留。refund_wide里的线下已退款 / 状态未填等与当前分析无关的记录,不得进入。一、改造清单(refund 域,基线 18 条)
refund.pyrefunds×5refund_wide(加等价过滤)refund_chain.pyrefunds/wallet_withdrawalsrefund.pywallet_withdrawalsrefund.pymanual_refunds(D)refund.pyshanmao_refunds(C)refund.pycs_records×5refund.pyrefund_record_reportsrefund.pyBmsOrderrefund.pydevice_combosdim_device_combo)category.pydevice_comboscash.py/category.py/rr_views.py二、承载方案(口径不变)
refunds(现口径=线上+已退款)refund_widerefund_method='线上' AND status='已退款' AND device_sn IS NOT NULL AND created_at_raw IS NOT NULL→ 行集与现状一致device_combosdim_device_combo其余全部不引���(无干净对象,且非本轮明确有用)。
三、口径不变的验证方法(切换前置)
对同窗口做双源 diff,证明零差异:
count(*)与sum(refund_amount):ods.refunds(现过滤) vsrefund_wide(等价过滤)必须相等;refund_no对拍。refund_wide不是保口径超集,停止切换并回报。四、关键决策(收紧后)
口径扩大是否接受→ 已撤销:口径不变(原则〇)。wallet_withdrawals/refund_chain的 B路提现:分析真正需要的字段是否在refund_wide.wallet_*(wallet_id/op_time/op_amount)中?只取明确有用的列;若提现失败明细确实需要 → 才考虑承载,否则不引入。cs_records:本轮不引入(无干净对象、非本轮明确有用);留待各自域,确认"明确有用"时再评估。五、分期(收敛后)
device_combos→dim_device_comborefunds→refund_wide(等价过滤,零差异验证)六、C2 棘轮门禁
CI job
analytics-ods-isolation(#322,提交95e9ff6)已阻止新增违规。本域切换仅收敛refunds条目;其余条目允许保留(因不引入无关对象)。基线不清空也可接受——C2 与"口径不变"冲突时,后者优先。P2 refund 域切换设计草案 v3(实测校准:列不足,阶段1 不成立)
〇、口径不变原则(硬约束,沿 v2)
一、列级实测(决定性,本次新增)
refund_wide现有列 = refund 冗余 9 列(refund_no/method/status/reason/operator/auditor/device_sn/amount/created_at_raw)refund.py事件流从refunds实际读取的列,有 10 列refund_wide没有:actual_amountCOALESCE(actual_amount, refund_amount, 0)(多处)actual_refund_timequality_statusstats_quality_excluded_sql质检排除package_namemerchant_nameremarkaudit_remarkplatform_order_no/third_order_noupdated_at_raw→ 用
refund_wide承载 = 必须新增 10 列。这与"精简 / 不加不相干字段"冲突;且这 10 列对 refund.py 是必要的,一旦加上,refund_wide就退化成refunds + joins,失去精简意义。二、行级实测(沿 v2,补充说明)
refund_wide行范围(A-20260908-001 LOCKED)= 线上+已退款 ∪ 带钱包关联 ∪ SN 有外部投诉 → 是并集/超集,需额外收窄过滤才等于现有口径。csctalon快照:A 路口径 diff=931,但该快照matched_at仅到 08-13、缺行落在 08-11~08-17 → 属物化滞后,非结构缺口,不能据此判定;生产曾观测 2 条非零差异,仍待生产实测定性。三、结论
P2 refund 阶段1(refund.py → refund_wide)不成立,原因:列不足(10 列)+ 行非保口径超集。强行切换 = 扩表 + 加收窄过滤,三条原则(精简/口径不变/不加不相干)全违反。
四、承载方案(供决策,二选一)
public.refund_clean:CREATE VIEW ... AS SELECT <analytics 实际消费的列> FROM ods.refundsrefunds条目refunds→ods),C2 基线保留该条目,待"明确有用"时再评估五、其余域(沿 v2)
device_combos→dim_device_combo:✅ 已完成(#320)。wallet_withdrawals/refund_chain/manual_refunds/shanmao_refunds/cs_records/refund_record_reports/BmsOrder:本轮不引入(无干净对象,非本轮明确有用)。六、C2 棘轮门禁
CI job
analytics-ods-isolation(#322,95e9ff6)已阻止新增违规;baseline 存量条目允许保留(C2 与"口径不变/精简"冲突时,后者优先)。发布核验 · refund 域方案A(refund_clean 视图)(2026-10-14)
不闭单:本条仅为 refund 域的落地+发布凭证;P2 其余域(wallet_withdrawals / refund_chain / manual_refunds / shanmao_refunds / cs_records / refund_record_reports / BmsOrder)后续另行推进。
变更与发布
cc37a68#322 方案A: refund 域 analytics 改读 public.refund_clean 精简视图(19 files, +287/-163)a9b0c1d2e3f4(down_revisionf8a9b0c1d2e3),生产启动日志实测:Running upgrade f8a9b0c1d2e3 -> a9b0c1d2e3f4deploy/upgrade.sh --git(main),自动备份后 git-20261014101804 → git-20261014125424,backend healthy生产实测凭证(可复现)
relkind='v',恰 21 列(information_schema 实测列清单与迁移逐字一致)。SELECT count(*) FROM public.refund_clean=FROM ods.refunds= 201,348 = 201,348(投影视图口径零差异)。/app/app/models/refund.py含refund_clean标记、迁移文件含a9b0c1d2e3f4(排除 COPY 层缓存命中旧镜像)。analytics.refund.refund_decision_overview(db)(覆盖含补列的 GROUP BY 路径),返回 9 个块完整、summary 22 键,无异常。Refund(ods.refunds)写入模型零改动,upsert 入库 / actual_refund_time 回填路径不受视图影响。发布前门禁
当前违规 64 · 基线 64,无新增违规(基线 80→64,收敛 16 条)。All checks passed!。TestRrViewsDbSmokeCompoundSelect.c、test_ops_overview_device_type)已用git stash在干净 HEAD 复现,确属预存失败、与本次无关。遗留(非本次范围)
governance_detector._SKIP_DATE_TABLES是否登记refund_clean(派生视图)待评估。include_object钩子。关闭凭证核验 (2026-10-14)
交付物:commit
89d1057(10 张新增 public 干净视图 + 既有 refund_clean 共 11 张;analytics 14 文件改读;C2 基线 80→0),已 pushmain。python3 scripts/check_analytics_ods_isolation.py→ 「当前违规 0 · 基线 0,门禁通过」;refund/refund_chain/rr_views/cash/category 已改读*_cleancard_complaint_clean/external_complaint_clean/cs_record_cleancs_record_cleanscripts/analytics_ods_isolation_baseline.txt仅余表头(80→0)All checks passed;pytest:见下「存量失败」→ 降级接受生���核验(2026-10-14,节点 VM400;标准入口
deploy/upgrade.sh --git:csctalon-src → git-20261014094251,健康检查通过)alembic current=c3d4e5f6a7b8 (head)(迁移c3d4e5f6a7b8_issue322_clean_views_batch已应用)public下 11 张*_clean视图存在pg_views中 11/11 为纯列投影(FROM ods.<t>,无 WHERE/JOIN/GROUP/CASE/DISTINCT)→ 口径逐字不变的结构保证存量 pytest 失败(非本单引入,降级接受依据)
test_analytics_core_smoke.py::...preset_complaint_subtype_rootcause_heatmap(CompoundSelect.c)——回 HEAD 复跑复现test_category_ops_overview.py::test_ops_overview_device_type(随机 type_code 断言)——回 HEAD 复跑复现test_b2_2_ai_data_supply.py::test_supply_device_behavior_profile(ai_data_supply,未涉 analytics/_clean/ods)test_llm_tiers.py×2(LLM 层级 provider 名,与 #322 无关)审计样本(供业务口径逐项复核;落本地
workdata/,含 PII 不入 git)workdata/view_samples/*.csv:11 张干净视图各 20 行样本workdata/interface_samples/*.json:46 个 analytics 接口默认视图结果 +_index.json遗留评估闭环:governance_detector 是否需跳过 refund_clean 等 *_clean 视图
结论:不需要,实证如下(2026-10-14)
information_schema.tables(public+ods),*_clean视图理论上会进入枚举面;governance_findings共 71 条,其中涉及_clean视图的为 0 条 —— 现有检查类型(外键/索引/日期列/PII 等)对纯列投影视图不产生发现;CLEAN_VIEWS键集加入跳过名单(单一来源app/models/ods_clean.py)。即:不增加代码,留档实证,后续新检查类型接入时参照本条。