feat: P1-P3 全栈集成 — 数据库基础 + DWS 扩展 + 小程序鉴权 + 工程化体系

## P1 数据库基础
- zqyy_app: 创建 auth/biz schema、FDW 连接 etl_feiqiu
- etl_feiqiu: 创建 app schema RLS 视图、商品库存预警表
- 清理 assistant_abolish 残留数据

## P2 ETL/DWS 扩展
- 新增 DWS 助教订单贡献度表 (dws.assistant_order_contribution)
- 新增 assistant_order_contribution_task 任务及 RLS 视图
- member_consumption 增加充值字段、assistant_daily 增加处罚字段
- 更新 ODS/DWD/DWS 任务文档及业务规则文档
- 更新 consistency_checker、flow_runner、task_registry 等核心模块

## P3 小程序鉴权系统
- 新增 xcx_auth 路由/schema(微信登录 + JWT)
- 新增 wechat/role/matching/application 服务层
- zqyy_app 鉴权表迁移 + 角色权限种子数据
- auth/dependencies.py 支持小程序 JWT 鉴权

## 文档与审计
- 新增 DOCUMENTATION-MAP 文档导航
- 新增 7 份 BD_Manual 数据库变更文档
- 更新 DDL 基线快照(etl_feiqiu 6 schema + zqyy_app auth)
- 新增全栈集成审计记录、部署检查清单更新
- 新增 BACKLOG 路线图、FDW→Core 迁移计划

## Kiro 工程化
- 新增 5 个 Spec(P1/P2/P3/全栈集成/核心业务)
- 新增审计自动化脚本(agent_on_stop/build_audit_context/compliance_prescan)
- 新增 6 个 Hook(合规检查/会话日志/提交审计等)
- 新增 doc-map steering 文件

## 运维与测试
- 新增 ops 脚本:迁移验证/API 健康检查/ETL 监控/集成报告
- 新增属性测试:test_dws_contribution / test_auth_system
- 清理过期 export 报告文件
- 更新 .gitignore 排除规则
This commit is contained in:
Neo
2026-02-26 08:03:53 +08:00
parent fafc95e64c
commit b25308c3f4
224 changed files with 17660 additions and 32198 deletions

View File

@@ -0,0 +1,393 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""DWS 层扩展验证脚本 — 影子跑数验证。
对照 Requirements 9.19.4 验证三个 DWS 表的结构完整性和数据合理性:
1. dws_assistant_order_contribution — 四项统计一致性
2. dws_member_consumption_summary — 充值窗口字段
3. dws_assistant_daily_detail — 定档折算惩罚字段
4. RLS 视图 + FDW 外部表存在性
用法:
cd apps/etl/connectors/feiqiu
python scripts/verify_dws_extensions.py
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
from dotenv import load_dotenv
# ---------------------------------------------------------------------------
# 1. 加载根 .env遵循 testing-env.md 规范)
# ---------------------------------------------------------------------------
_ROOT = Path(__file__).resolve().parents[5] # scripts/ → feiqiu/ → connectors/ → etl/ → apps/ → 根目录
load_dotenv(_ROOT / ".env")
PG_DSN = os.environ.get("PG_DSN")
if not PG_DSN:
raise RuntimeError("PG_DSN 未设置,请检查 .env 配置")
APP_DB_DSN = os.environ.get("APP_DB_DSN")
if not APP_DB_DSN:
raise RuntimeError("APP_DB_DSN 未设置,请检查 .env 配置")
# ---------------------------------------------------------------------------
# 2. 数据库连接
# ---------------------------------------------------------------------------
try:
import psycopg2
except ImportError:
print("ERROR: psycopg2 未安装,请执行 uv pip install psycopg2-binary")
sys.exit(1)
# ---------------------------------------------------------------------------
# 辅助函数
# ---------------------------------------------------------------------------
class _Result:
"""单条验证结果。"""
def __init__(self, name: str):
self.name = name
self.passed = True
self.details: list[str] = []
def fail(self, msg: str) -> None:
self.passed = False
self.details.append(f" FAIL: {msg}")
def ok(self, msg: str) -> None:
self.details.append(f" OK: {msg}")
def __str__(self) -> str:
status = "PASS" if self.passed else "FAIL"
header = f"[{status}] {self.name}"
if self.details:
return header + "\n" + "\n".join(self.details)
return header
def _query(conn, sql: str, params=None) -> list[tuple]:
with conn.cursor() as cur:
cur.execute(sql, params)
return cur.fetchall()
def _query_one(conn, sql: str, params=None):
rows = _query(conn, sql, params)
return rows[0] if rows else None
def _column_exists(conn, schema: str, table: str, column: str) -> bool:
row = _query_one(
conn,
"""
SELECT 1 FROM information_schema.columns
WHERE table_schema = %s AND table_name = %s AND column_name = %s
""",
(schema, table, column),
)
return row is not None
def _table_exists(conn, schema: str, table: str) -> bool:
row = _query_one(
conn,
"""
SELECT 1 FROM information_schema.tables
WHERE table_schema = %s AND table_name = %s
""",
(schema, table),
)
return row is not None
# ---------------------------------------------------------------------------
# 验证 1dws_assistant_order_contribution 四项统计Req 9.1, 9.2
# ---------------------------------------------------------------------------
def verify_contribution_table(conn) -> _Result:
r = _Result("验证 1dws_assistant_order_contribution 表结构与数据")
# 1a. 表存在
if not _table_exists(conn, "dws", "dws_assistant_order_contribution"):
r.fail("表 dws.dws_assistant_order_contribution 不存在")
return r
r.ok("表存在")
# 1b. 关键字段存在
required_cols = [
"contribution_id", "site_id", "tenant_id", "assistant_id",
"assistant_nickname", "stat_date",
"order_gross_revenue", "order_net_revenue",
"time_weighted_revenue", "time_weighted_net_revenue",
"order_count", "total_service_seconds",
"created_at", "updated_at",
]
missing = [c for c in required_cols
if not _column_exists(conn, "dws", "dws_assistant_order_contribution", c)]
if missing:
r.fail(f"缺少字段: {', '.join(missing)}")
else:
r.ok(f"全部 {len(required_cols)} 个字段存在")
# 1c. 唯一索引存在
idx_row = _query_one(
conn,
"""
SELECT indexname FROM pg_indexes
WHERE schemaname = 'dws'
AND tablename = 'dws_assistant_order_contribution'
AND indexname = 'idx_aoc_site_assistant_date'
""",
)
if idx_row:
r.ok("唯一索引 idx_aoc_site_assistant_date 存在")
else:
r.fail("唯一索引 idx_aoc_site_assistant_date 不存在")
# 1d. 数据行数(信息性,不判 FAIL
row = _query_one(conn, "SELECT COUNT(*) FROM dws.dws_assistant_order_contribution")
count = row[0] if row else 0
r.ok(f"当前数据行数: {count}")
# 1e. 如果有数据,检查四项统计非负
if count > 0:
neg_row = _query_one(
conn,
"""
SELECT COUNT(*) FROM dws.dws_assistant_order_contribution
WHERE order_gross_revenue < 0
OR order_net_revenue < 0
OR time_weighted_revenue < 0
OR time_weighted_net_revenue < 0
""",
)
neg_count = neg_row[0] if neg_row else 0
if neg_count > 0:
r.fail(f"存在 {neg_count} 条四项统计为负值的记录")
else:
r.ok("四项统计数值均非负")
return r
# ---------------------------------------------------------------------------
# 验证 2dws_member_consumption_summary 充值窗口字段Req 9.3
# ---------------------------------------------------------------------------
def verify_consumption_fields(conn) -> _Result:
r = _Result("验证 2dws_member_consumption_summary 充值窗口字段")
if not _table_exists(conn, "dws", "dws_member_consumption_summary"):
r.fail("表 dws.dws_member_consumption_summary 不存在")
return r
r.ok("表存在")
new_cols = [
"recharge_count_30d", "recharge_count_60d", "recharge_count_90d",
"recharge_amount_30d", "recharge_amount_60d", "recharge_amount_90d",
"avg_ticket_amount",
]
missing = [c for c in new_cols
if not _column_exists(conn, "dws", "dws_member_consumption_summary", c)]
if missing:
r.fail(f"缺少新增字段: {', '.join(missing)}")
else:
r.ok(f"全部 {len(new_cols)} 个新增字段存在")
# 如果有数据,检查充值金额和次均消费非负
row = _query_one(conn, "SELECT COUNT(*) FROM dws.dws_member_consumption_summary")
count = row[0] if row else 0
r.ok(f"当前数据行数: {count}")
if count > 0:
neg_row = _query_one(
conn,
"""
SELECT COUNT(*) FROM dws.dws_member_consumption_summary
WHERE recharge_amount_30d < 0
OR recharge_amount_60d < 0
OR recharge_amount_90d < 0
OR avg_ticket_amount < 0
""",
)
neg_count = neg_row[0] if neg_row else 0
if neg_count > 0:
r.fail(f"存在 {neg_count} 条充值金额或次均消费为负值的记录")
else:
r.ok("充值金额和次均消费均非负")
return r
# ---------------------------------------------------------------------------
# 验证 3dws_assistant_daily_detail 惩罚字段Req 9.4
# ---------------------------------------------------------------------------
def verify_penalty_fields(conn) -> _Result:
r = _Result("验证 3dws_assistant_daily_detail 惩罚字段")
if not _table_exists(conn, "dws", "dws_assistant_daily_detail"):
r.fail("表 dws.dws_assistant_daily_detail 不存在")
return r
r.ok("表存在")
new_cols = ["penalty_minutes", "penalty_reason", "is_exempt", "per_hour_contribution"]
missing = [c for c in new_cols
if not _column_exists(conn, "dws", "dws_assistant_daily_detail", c)]
if missing:
r.fail(f"缺少新增字段: {', '.join(missing)}")
else:
r.ok(f"全部 {len(new_cols)} 个惩罚字段存在")
# 检查 is_exempt 字段类型为 boolean
type_row = _query_one(
conn,
"""
SELECT data_type FROM information_schema.columns
WHERE table_schema = 'dws'
AND table_name = 'dws_assistant_daily_detail'
AND column_name = 'is_exempt'
""",
)
if type_row and type_row[0] == "boolean":
r.ok("is_exempt 字段类型为 boolean")
elif type_row:
r.fail(f"is_exempt 字段类型为 {type_row[0]},预期 boolean")
# 如果有数据,检查 penalty_minutes >= 0
row = _query_one(conn, "SELECT COUNT(*) FROM dws.dws_assistant_daily_detail")
count = row[0] if row else 0
r.ok(f"当前数据行数: {count}")
if count > 0:
neg_row = _query_one(
conn,
"""
SELECT COUNT(*) FROM dws.dws_assistant_daily_detail
WHERE penalty_minutes < 0
""",
)
neg_count = neg_row[0] if neg_row else 0
if neg_count > 0:
r.fail(f"存在 {neg_count} 条 penalty_minutes 为负值的记录")
else:
r.ok("penalty_minutes 均非负")
return r
# ---------------------------------------------------------------------------
# 验证 4RLS 视图和 FDW 映射Req 7, 8
# ---------------------------------------------------------------------------
def verify_rls_views(conn_etl) -> _Result:
r = _Result("验证 4aRLS 视图存在性ETL 库 app schema")
views = [
"v_dws_assistant_order_contribution",
"v_dws_member_consumption_summary",
"v_dws_assistant_daily_detail",
]
for v in views:
if _table_exists(conn_etl, "app", v):
r.ok(f"视图 app.{v} 存在")
else:
r.fail(f"视图 app.{v} 不存在")
return r
def verify_fdw_tables(conn_app) -> _Result:
r = _Result("验证 4bFDW 外部表存在性(业务库 fdw_etl schema")
# FDW 外部表名与 RLS 视图名一致,带 v_ 前缀
tables = [
"v_dws_assistant_order_contribution",
"v_dws_member_consumption_summary",
"v_dws_assistant_daily_detail",
]
for t in tables:
row = _query_one(
conn_app,
"""
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'fdw_etl' AND table_name = %s
""",
(t,),
)
if row:
r.ok(f"外部表 fdw_etl.{t} 存在")
else:
r.fail(f"外部表 fdw_etl.{t} 不存在")
return r
# ---------------------------------------------------------------------------
# 主函数
# ---------------------------------------------------------------------------
def main() -> int:
results: list[_Result] = []
# 连接 ETL 测试库
try:
conn_etl = psycopg2.connect(PG_DSN)
conn_etl.autocommit = True
except Exception as e:
print(f"ERROR: 无法连接 ETL 库 ({PG_DSN[:40]}...): {e}")
return 1
# 连接业务测试库
try:
conn_app = psycopg2.connect(APP_DB_DSN)
conn_app.autocommit = True
except Exception as e:
print(f"ERROR: 无法连接业务库 ({APP_DB_DSN[:40]}...): {e}")
conn_etl.close()
return 1
try:
print("=" * 60)
print("DWS 层扩展验证 — 影子跑数验证")
print("=" * 60)
print()
# ETL 库验证
results.append(verify_contribution_table(conn_etl))
results.append(verify_consumption_fields(conn_etl))
results.append(verify_penalty_fields(conn_etl))
results.append(verify_rls_views(conn_etl))
# 业务库验证
results.append(verify_fdw_tables(conn_app))
# 输出结果
for r in results:
print(r)
print()
# 汇总
total = len(results)
passed = sum(1 for r in results if r.passed)
failed = total - passed
print("=" * 60)
print(f"汇总: {passed}/{total} 通过, {failed} 失败")
print("=" * 60)
return 0 if failed == 0 else 1
finally:
conn_etl.close()
conn_app.close()
if __name__ == "__main__":
sys.exit(main())