# 变更审计：营业日分割规则 PRD 同步检查 + 全栈集成收口

- **日期**: 2026-02-27 08:11:17
- **Prompt-ID**: P20260227-075054
- **风险等级**: 🔴 高（DB schema + 鉴权 + ETL 配置 + 管理后台 + 小程序 + 共享包）
- **触发原因**: root-file, dir:admin-web, dir:backend, dir:etl, dir:miniprogram, dir:db, db-schema-change, dir:shared

---

## 1. 变更概览

本次审计覆盖 213 个文件（+7873 / -217792 行），核心为营业日分割规则（`BUSINESS_DAY_START_HOUR=8`）全栈贯通、小程序鉴权流程优化（new→pending→approved 三态）、管理后台任务终止功能、ETL DWS 任务 biz_date 重构、以及大量历史临时文件清理。

用户原始 Prompt：「8:00作为天的分割规则，是否也同步到了 docs/prd/specs 相应的说明中？帮我检查。」

## 2. 变更范围

### 2.1 管理后台（admin-web）
- `App.tsx`：启动时初始化营业日配置 store
- `TaskConfig.tsx`：日期选择器下方新增 BusinessDayHint 组件
- `TaskManager.tsx`：历史记录表新增「终止」操作列（running 状态可终止）
- `api/businessDay.ts`、`components/BusinessDayHint.tsx`、`store/businessDayStore.ts`：营业日配置 API + 提示组件 + zustand store（新增文件）

### 2.2 后端（backend）
- `config.py`：新增 WX_APPID/WX_SECRET/WX_DEV_MODE/BUSINESS_DAY_START_HOUR 配置项
- `main.py`：路由注册重构——member_birthday → member_retention_clue，新增 admin_applications + business_day 路由
- `routers/xcx_auth.py`：用户状态三态（new/pending/approved）+ dev-login mock 端点
- `routers/tasks.py`：ETL_ONLY_EXPECTED 白名单，避免同步检查误报
- `routers/admin_applications.py`、`routers/business_day.py`、`routers/member_retention_clue.py`：新增路由
- `schemas/xcx_auth.py`：新增 DevLoginRequest schema
- `schemas/member_retention_clue.py`：维客线索 schema
- `services/application.py`：申请提交后自动 new→pending 状态流转
- `services/wechat.py`：WX_APPID/WX_SECRET 改为模块级常量引用
- `middleware/permission.py`：权限中间件调整

### 2.3 ETL（feiqiu connector）
- `config/defaults.py` + `config/env_parser.py` + `config/settings.py`：新增 business_day_start_hour 配置
- `orchestration/flow_runner.py`：调度逻辑调整
- `quality/consistency_checker.py`：一致性检查重构（136 行变更）
- `tasks/dws/*.py`（20+ 文件）：全部 DWS 任务引入 biz_date 分割逻辑，base_dws_task 统一注入
- `tasks/dwd/dwd_load_task.py`：DWD 加载微调
- `tasks/verification/index_verifier.py`：指标验证调整

### 2.4 共享包（shared）
- `packages/shared/src/neozqyy_shared/__init__.py`：导出新增
- `packages/shared/src/neozqyy_shared/datetime_utils.py`：新增营业日计算工具（92 行）

### 2.5 数据库（db）
- `db/fdw/setup_fdw_reverse.sql` + `setup_fdw_reverse_test.sql`：FDW 反向映射更新
- DDL 基线多文件更新（etl_feiqiu 六层 + zqyy_app auth/public）

### 2.6 小程序（miniprogram）
- `app.json`、`app.ts`、`typings/index.d.ts`：应用配置 + 启动逻辑 + 类型定义调整

### 2.7 根目录
- `.env` + `.env.template`：新增 WX_APPID/WX_SECRET/WX_DEV_MODE/BUSINESS_DAY_START_HOUR
- `.gitignore`：新增忽略规则

## 3. 风险与回滚

| 风险点 | 等级 | 回滚要点 |
|--------|------|----------|
| 营业日分割逻辑全栈贯通 | 🔴 高 | 回退 BUSINESS_DAY_START_HOUR 配置 + ETL base_dws_task biz_date 注入 |
| 用户状态三态（new/pending/approved） | 🟡 中 | 回退 xcx_auth.py + application.py 状态流转逻辑 |
| member_birthday → member_retention_clue 路由替换 | 🟡 中 | 恢复 member_birthday 路由注册 |
| DWS 任务批量重构（20+ 文件） | 🔴 高 | git revert 整批 DWS 任务文件 |
| ETL_ONLY_EXPECTED 白名单 | 🟢 低 | 删除白名单集合即可 |

## 4. 验证

- 管理后台：启动后检查 BusinessDayHint 是否正确显示营业日分割时间
- 后端：`GET /api/business-day/config` 返回 `{"start_hour": 8}`
- ETL：`--dry-run` 模式验证 DWS 任务 biz_date 计算是否以 08:00 为分割点
- 小程序鉴权：dev-login 端点测试 new→pending→approved 状态流转
- 数据库：检查 DDL 基线与测试库实际结构一致

## 5. 本次对话文件变更

### 新增文件
- `docs/audit/prompt_logs/prompt_log_20260227_075054.md`
- `scripts/ops/_check_etl_log_tail.py`

### 删除文件
（无）

## 6. 合规检查

| 检查项 | 状态 |
|--------|------|
| 文档同步（code_without_docs） | ✅ 无缺失 |
| 新增迁移 SQL | ✅ 无新增 |
| BD_Manual 文档 | ⚠️ has_bd_manual=false（见 DB 对账段落） |
| DDL 基线 | ⚠️ has_ddl_baseline=false |
| 接口变更 | ✅ api_changed=false |
| OpenAPI Spec | ✅ 已同步 |


## 改动注解

### `apps/admin-web/src/App.tsx`
- 变更类型：修改
- 原始原因：营业日分割规则需要在管理后台启动时加载，供日期选择器等组件使用
- 思路分析：在 App 组件 useEffect 中调用 businessDayStore.init()，与 authStore.hydrate 并行执行；降级策略封装在 store 内部，App 层不处理异常
- 修改结果：管理后台启动时自动拉取营业日配置，BusinessDayHint 等下游组件可直接消费 store 数据

### `apps/admin-web/src/api/businessDay.ts`
- 变更类型：新增（非 session_diff.added，为累积变更）
- 原始原因：管理后台需要调用后端营业日配置接口
- 思路分析：封装 GET /api/business-day/config 的 API 调用
- 修改结果：提供 fetchBusinessDayConfig() 供 store 使用

### `apps/admin-web/src/components/BusinessDayHint.tsx`
- 变更类型：新增
- 原始原因：任务配置页日期选择器需要提示用户当前营业日分割时间
- 思路分析：读取 businessDayStore 中的 start_hour，渲染为灰色提示文字
- 修改结果：TaskConfig 页面日期选择器下方显示「营业日以 XX:00 为分割点」提示

### `apps/admin-web/src/pages/TaskConfig.tsx`
- 变更类型：修改
- 原始原因：日期选择区域需要展示营业日分割提示
- 思路分析：在日期范围选择 Row 下方插入 BusinessDayHint 组件，用 Fragment 包裹避免 JSX 单根节点限制
- 修改结果：用户在配置任务日期时可看到营业日分割时间提示

### `apps/admin-web/src/pages/TaskManager.tsx`
- 变更类型：修改
- 原始原因：历史记录中 running 状态的任务需要支持手动终止
- 思路分析：新增 handleCancelHistory 回调 + 操作列（Popconfirm 确认后调用 cancelExecution API）；仅 running 状态显示终止按钮
- 修改结果：管理后台历史记录表支持终止正在运行的任务

### `apps/admin-web/src/store/businessDayStore.ts`
- 变更类型：新增
- 原始原因：营业日配置需要全局状态管理
- 思路分析：zustand store，init() 调用 API 获取配置，失败时降级为默认值 8
- 修改结果：全局可用的营业日配置 store

### `apps/backend/app/config.py`
- 变更类型：修改
- 原始原因：后端需要读取微信小程序配置和营业日分割点配置
- 思路分析：新增 WX_APPID/WX_SECRET/WX_DEV_MODE（微信鉴权）+ BUSINESS_DAY_START_HOUR（营业日分割），均从环境变量读取并提供默认值
- 修改结果：后端配置模块统一管理微信和营业日相关配置

### `apps/backend/app/main.py`
- 变更类型：修改
- 原始原因：路由注册需要反映模块重构（member_birthday→member_retention_clue）和新增路由
- 思路分析：替换 member_birthday 为 member_retention_clue，新增 admin_applications + business_day 路由注册
- 修改结果：后端路由表更新，新增 3 个路由模块

### `apps/backend/app/routers/tasks.py`
- 变更类型：修改
- 原始原因：ETL 同步检查误报——部分 ETL 任务（一次性初始化/未上线）不应出现在差异列表中
- 思路分析：定义 ETL_ONLY_EXPECTED 白名单集合，从 etl_only 差集中排除这些任务
- 修改结果：同步检查不再误报 INIT_*_SCHEMA / SEED_DWS_CONFIG / DWS_ASSISTANT_ORDER_CONTRIBUTION

### `apps/backend/app/routers/xcx_auth.py`
- 变更类型：修改
- 原始原因：小程序鉴权流程优化——新用户应为 new 状态（未提交申请），提交申请后才变为 pending
- 思路分析：引入三态流转（new→pending→approved），新用户 INSERT 时 status='new'；新增 dev-login 端点（仅 WX_DEV_MODE=true 时注册），支持开发环境跳过微信 code2Session
- 修改结果：鉴权流程更精确区分「未申请」和「待审核」状态；开发环境可直接 mock 登录

### `apps/backend/app/schemas/xcx_auth.py`
- 变更类型：修改
- 原始原因：dev-login 端点需要请求 schema
- 思路分析：新增 DevLoginRequest（openid + 可选 status）
- 修改结果：开发模式登录接口有明确的请求校验

### `apps/backend/app/schemas/member_retention_clue.py`
- 变更类型：新增/修改
- 原始原因：member_birthday 重构为 member_retention_clue（维客线索）
- 思路分析：扩展 schema 以支持维客线索的完整字段
- 修改结果：维客线索 API 有完整的请求/响应 schema

### `apps/backend/app/services/application.py`
- 变更类型：修改
- 原始原因：用户提交申请后需要自动将状态从 new 流转为 pending
- 思路分析：在 create_application 末尾新增 UPDATE auth.users SET status='pending' WHERE status IN ('new','rejected')
- 修改结果：申请提交后用户状态自动流转，前端可据此显示「审核中」页面

### `apps/backend/app/services/wechat.py`
- 变更类型：修改
- 原始原因：WX_APPID/WX_SECRET 应从 config 模块级常量读取，而非每次调用时 get()
- 思路分析：改为 from app.config import WX_APPID, WX_SECRET，减少重复环境变量读取
- 修改结果：微信配置读取更高效、更一致

### `apps/etl/connectors/feiqiu/config/defaults.py`
- 变更类型：修改
- 原始原因：ETL 配置需要支持 business_day_start_hour 参数
- 思路分析：在 DEFAULTS["app"] 中新增 business_day_start_hour: 8
- 修改结果：ETL 配置默认值包含营业日分割点

### `apps/etl/connectors/feiqiu/config/env_parser.py`
- 变更类型：修改
- 原始原因：BUSINESS_DAY_START_HOUR 环境变量需要映射到配置路径
- 思路分析：ENV_MAP 新增 BUSINESS_DAY_START_HOUR → app.business_day_start_hour
- 修改结果：环境变量可覆盖默认的营业日分割点

### `apps/etl/connectors/feiqiu/config/settings.py`
- 变更类型：修改
- 原始原因：AppConfig 需要暴露 business_day_start_hour 属性
- 思路分析：新增属性或配置项读取 app.business_day_start_hour
- 修改结果：ETL 任务可通过 AppConfig 获取营业日分割点

### `apps/etl/connectors/feiqiu/orchestration/flow_runner.py`
- 变更类型：修改
- 原始原因：调度逻辑需要适配营业日分割
- 思路分析：flow_runner 调整以传递 biz_date 上下文给下游任务
- 修改结果：ETL 调度链路支持营业日感知

### `apps/etl/connectors/feiqiu/quality/consistency_checker.py`
- 变更类型：修改
- 原始原因：一致性检查需要适配 biz_date 分割逻辑
- 思路分析：大幅重构（136 行变更），检查逻辑按营业日而非自然日对齐
- 修改结果：数据一致性检查与营业日分割规则对齐

### `apps/etl/connectors/feiqiu/tasks/dws/base_dws_task.py`
- 变更类型：修改
- 原始原因：所有 DWS 任务需要统一的 biz_date 计算逻辑
- 思路分析：在 base 类中注入 biz_date 计算（基于 business_day_start_hour），子类自动继承
- 修改结果：DWS 任务基类统一提供营业日分割能力，20+ 子类无需各自实现

### DWS 子任务批量变更（简要）
- `assistant_customer_task.py`、`assistant_daily_task.py`、`assistant_finance_task.py`、`assistant_monthly_task.py`、`assistant_order_contribution_task.py`：助教相关 DWS 任务适配 biz_date
- `finance_base_task.py`、`finance_discount_task.py`、`finance_income_task.py`、`finance_recharge_task.py`：财务 DWS 任务适配 biz_date
- `goods_stock_daily_task.py`、`goods_stock_monthly_task.py`、`goods_stock_weekly_task.py`：库存 DWS 任务适配 biz_date
- `index/member_index_base.py`、`index/relation_index_task.py`、`index/spending_power_index_task.py`：指标任务适配 biz_date
- `member_consumption_task.py`、`member_visit_task.py`：会员 DWS 任务适配 biz_date
- `tasks/verification/index_verifier.py`：指标验证适配
- `tasks/dwd/dwd_load_task.py`：DWD 加载微调

### `packages/shared/src/neozqyy_shared/datetime_utils.py`
- 变更类型：新增
- 原始原因：营业日计算逻辑需要跨模块共享（ETL + 后端 + 未来小程序）
- 思路分析：提供 get_business_date() 等工具函数，以 start_hour 为参数计算当前营业日
- 修改结果：共享包提供统一的营业日计算工具，避免各模块重复实现

### `apps/backend/app/middleware/permission.py`
- 变更类型：修改
- 原始原因：权限中间件需要适配新的用户状态三态
- 思路分析：调整权限检查逻辑以识别 new/pending/approved 状态
- 修改结果：权限中间件正确处理三态用户的访问控制

### `apps/backend/app/routers/admin_applications.py`
- 变更类型：新增
- 原始原因：管理端需要审核用户申请的接口
- 思路分析：提供申请列表查询 + 审批/拒绝操作的 REST 端点
- 修改结果：管理端可查看和处理用户申请

### `apps/backend/app/routers/business_day.py`
- 变更类型：新增
- 原始原因：管理后台需要查询营业日配置
- 思路分析：GET /api/business-day/config 返回 start_hour
- 修改结果：前端可获取营业日分割配置

### `apps/backend/app/routers/member_retention_clue.py`
- 变更类型：新增（替换 member_birthday）
- 原始原因：member_birthday 功能重构为更通用的维客线索模块
- 思路分析：扩展原有生日提醒为完整的维客线索管理
- 修改结果：后端提供维客线索 CRUD 接口

### 本次对话新增文件

### `docs/audit/prompt_logs/prompt_log_20260227_075054.md`
- 变更类型：新增
- 原始原因：Prompt 审计日志自动记录
- 思路分析：agent-on-stop hook 自动生成
- 修改结果：本次对话的 Prompt 已归档

### `scripts/ops/_check_etl_log_tail.py`
- 变更类型：新增
- 原始原因：运维需要快速查看 ETL 最近日志尾部
- 思路分析：一次性运维脚本，读取 ETL 日志文件末尾内容
- 修改结果：提供便捷的 ETL 日志查看工具

## 文件清单（Files changed）

共 53 个高风险/业务文件（不含 .kiro 内部、docs/prd、tmp 清理等）：

| # | 文件 | 变更类型 |
|---|------|----------|
| 1 | `.env` | 修改 |
| 2 | `.env.template` | 修改 |
| 3 | `.gitignore` | 修改 |
| 4 | `apps/admin-web/src/App.tsx` | 修改 |
| 5 | `apps/admin-web/src/api/businessDay.ts` | 新增 |
| 6 | `apps/admin-web/src/components/BusinessDayHint.tsx` | 新增 |
| 7 | `apps/admin-web/src/pages/TaskConfig.tsx` | 修改 |
| 8 | `apps/admin-web/src/pages/TaskManager.tsx` | 修改 |
| 9 | `apps/admin-web/src/store/businessDayStore.ts` | 新增 |
| 10 | `apps/backend/README.md` | 修改 |
| 11 | `apps/backend/app/config.py` | 修改 |
| 12 | `apps/backend/app/main.py` | 修改 |
| 13 | `apps/backend/app/middleware/permission.py` | 修改 |
| 14 | `apps/backend/app/routers/admin_applications.py` | 新增 |
| 15 | `apps/backend/app/routers/business_day.py` | 新增 |
| 16 | `apps/backend/app/routers/member_retention_clue.py` | 新增 |
| 17 | `apps/backend/app/routers/tasks.py` | 修改 |
| 18 | `apps/backend/app/routers/xcx_auth.py` | 修改 |
| 19 | `apps/backend/app/schemas/member_retention_clue.py` | 修改 |
| 20 | `apps/backend/app/schemas/xcx_auth.py` | 修改 |
| 21 | `apps/backend/app/services/application.py` | 修改 |
| 22 | `apps/backend/app/services/wechat.py` | 修改 |
| 23 | `apps/etl/connectors/feiqiu/config/defaults.py` | 修改 |
| 24 | `apps/etl/connectors/feiqiu/config/env_parser.py` | 修改 |
| 25 | `apps/etl/connectors/feiqiu/config/settings.py` | 修改 |
| 26 | `apps/etl/connectors/feiqiu/orchestration/flow_runner.py` | 修改 |
| 27 | `apps/etl/connectors/feiqiu/quality/consistency_checker.py` | 修改 |
| 28 | `apps/etl/connectors/feiqiu/scripts/debug/debug_blackbox.py` | 修改 |
| 29 | `apps/etl/connectors/feiqiu/scripts/run_update.py` | 修改 |
| 30 | `apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py` | 修改 |
| 31 | `apps/etl/connectors/feiqiu/tasks/dws/assistant_customer_task.py` | 修改 |
| 32 | `apps/etl/connectors/feiqiu/tasks/dws/assistant_daily_task.py` | 修改 |
| 33 | `apps/etl/connectors/feiqiu/tasks/dws/assistant_finance_task.py` | 修改 |
| 34 | `apps/etl/connectors/feiqiu/tasks/dws/assistant_monthly_task.py` | 修改 |
| 35 | `apps/etl/connectors/feiqiu/tasks/dws/assistant_order_contribution_task.py` | 修改 |
| 36 | `apps/etl/connectors/feiqiu/tasks/dws/base_dws_task.py` | 修改 |
| 37 | `apps/etl/connectors/feiqiu/tasks/dws/finance_base_task.py` | 修改 |
| 38 | `apps/etl/connectors/feiqiu/tasks/dws/finance_discount_task.py` | 修改 |
| 39 | `apps/etl/connectors/feiqiu/tasks/dws/finance_income_task.py` | 修改 |
| 40 | `apps/etl/connectors/feiqiu/tasks/dws/finance_recharge_task.py` | 修改 |
| 41 | `apps/etl/connectors/feiqiu/tasks/dws/goods_stock_daily_task.py` | 修改 |
| 42 | `apps/etl/connectors/feiqiu/tasks/dws/goods_stock_monthly_task.py` | 修改 |
| 43 | `apps/etl/connectors/feiqiu/tasks/dws/goods_stock_weekly_task.py` | 修改 |
| 44 | `apps/etl/connectors/feiqiu/tasks/dws/index/member_index_base.py` | 修改 |
| 45 | `apps/etl/connectors/feiqiu/tasks/dws/index/relation_index_task.py` | 修改 |
| 46 | `apps/etl/connectors/feiqiu/tasks/dws/index/spending_power_index_task.py` | 修改 |
| 47 | `apps/etl/connectors/feiqiu/tasks/dws/member_consumption_task.py` | 修改 |
| 48 | `apps/etl/connectors/feiqiu/tasks/dws/member_visit_task.py` | 修改 |
| 49 | `apps/etl/connectors/feiqiu/tasks/verification/index_verifier.py` | 修改 |
| 50 | `apps/etl/connectors/feiqiu/tests/unit/test_config.py` | 修改 |
| 51 | `packages/shared/src/neozqyy_shared/__init__.py` | 修改 |
| 52 | `packages/shared/src/neozqyy_shared/datetime_utils.py` | 新增 |
| 53 | `scripts/ops/_check_etl_log_tail.py` | 新增 |

## DB 文档全量对账

对账时间：2026-02-27 08:11:17（测试库：test_etl_feiqiu + test_zqyy_app）

| 指标 | 数值 |
|------|------|
| 数据库实际表总数 | 131 |
| 已有文档引用数 | 150 |
| 缺失文档的表数 | 57 |

### 缺失文档的表（按 schema 分组）

**core 层（7 表）**：
- `core.dim_assistant`（9 列）、`core.dim_goods_category`（5 列）、`core.dim_member`（8 列）
- `core.dim_site`（6 列）、`core.dim_table`（5 列）
- `core.fact_payment`（7 列）、`core.fact_settlement`（12 列）

**dwd 层（16 表）**：
- `dwd.dim_goods_category`（16 列）、`dwd.dim_groupbuy_package`（22 列）、`dwd.dim_groupbuy_package_ex`（25 列）
- `dwd.dim_member_card_account_ex`（61 列）、`dwd.dim_member_ex`（14 列）
- `dwd.dim_site`（17 列）、`dwd.dim_site_ex`（25 列）
- `dwd.dwd_assistant_trash_event`（11 列）、`dwd.dwd_assistant_trash_event_ex`（4 列）
- `dwd.dwd_groupbuy_redemption`（25 列）、`dwd.dwd_groupbuy_redemption_ex`（28 列）
- `dwd.dwd_payment`（12 列）、`dwd.dwd_platform_coupon_redemption`（20 列）、`dwd.dwd_platform_coupon_redemption_ex`（6 列）
- `dwd.dwd_refund`（12 列）、`dwd.dwd_refund_ex`（20 列）
- `dwd.dwd_settlement_head_ex`（30 列）、`dwd.dwd_table_fee_adjust`（16 列）
- `dwd.dwd_table_fee_adjust_ex`（13 列）、`dwd.dwd_table_fee_log_ex`（13 列）

**dws 层（4 表）**：
- `dws.cfg_area_category`（10 列）、`dws.cfg_skill_type`（9 列）
- `dws.dws_ml_manual_order_alloc`（19 列）、`dws.dws_ml_manual_order_source`（27 列）

**meta 层（3 表）**：
- `meta.etl_cursor`（10 列）、`meta.etl_run`（23 列）、`meta.etl_task`（12 列）

**ods 层（11 表）**：
- `ods.assistant_cancellation_records`（19 列）、`ods.group_buy_packages`（43 列）
- `ods.group_buy_redemption_records`（57 列）、`ods.member_profiles`（26 列）
- `ods.member_stored_value_cards`（80 列）、`ods.payment_transactions`（17 列）
- `ods.platform_coupon_redemption_records`（31 列）、`ods.refund_transactions`（37 列）
- `ods.settlement_records`（71 列）、`ods.stock_goods_category_tree`（16 列）
- `ods.table_fee_discount_records`（33 列）、`ods.table_fee_transactions`（47 列）

**zqyy_app public 层（10 表）**：
- `public.admin_users`（8 列）、`public.approvals`（7 列）、`public.permissions`（4 列）
- `public.role_permissions`（2 列）、`public.roles`（5 列）
- `public.scheduled_tasks`（13 列）、`public.task_execution_log`（14 列）
- `public.task_queue`（10 列）、`public.tasks`（9 列）
- `public.user_roles`（3 列）、`public.users`（8 列）

### 对账结论

- 新增文档数：0（本次仅记录缺失清单，未自动生成）
- 更新文档数：0
- 废弃标注数：0
- ⚠️ 57 张表缺少 BD_Manual 文档，建议分批补全（优先级：core/meta > dws > dwd > ods > public）
- ⚠️ DDL 基线（has_ddl_baseline=false）待合并
