init: 项目初始提交 - NeoZQYY Monorepo 完整代码
This commit is contained in:
591
apps/etl/pipelines/feiqiu/docs/etl_tasks/utility_tasks.md
Normal file
591
apps/etl/pipelines/feiqiu/docs/etl_tasks/utility_tasks.md
Normal file
@@ -0,0 +1,591 @@
|
||||
# 工具类任务详解
|
||||
|
||||
> 本文档说明飞球 ETL 系统中所有工具类(Utility)和校验类(Verification)任务。
|
||||
> 这些任务不属于 ODS/DWD/DWS/INDEX 四层业务管线,而是为系统初始化、
|
||||
> 数据灌入、归档、截止时间检查和完整性校验等运维场景服务。
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
工具类任务共 8 个(含 1 个校验类任务),注册于 `orchestration/task_registry.py`:
|
||||
|
||||
| 任务代码 | Python 类 | 用途 | task_type | requires_db_config |
|
||||
|----------|-----------|------|-----------|-------------------|
|
||||
| `INIT_ODS_SCHEMA` | `InitOdsSchemaTask` | 执行 ODS + etl_admin DDL,创建必要目录 | utility | `False` |
|
||||
| `INIT_DWD_SCHEMA` | `InitDwdSchemaTask` | 执行 DWD DDL | utility | `False` |
|
||||
| `INIT_DWS_SCHEMA` | `InitDwsSchemaTask` | 执行 DWS DDL | utility | `False` |
|
||||
| `MANUAL_INGEST` | `ManualIngestTask` | 从本地 JSON 文件手动入库到 ODS | utility | `False` |
|
||||
| `ODS_JSON_ARCHIVE` | `OdsJsonArchiveTask` | 在线抓取 ODS 接口数据并落盘 JSON | utility | `False` |
|
||||
| `CHECK_CUTOFF` | `CheckCutoffTask` | 检查各任务/表的数据截止时间 | utility | `False` |
|
||||
| `SEED_DWS_CONFIG` | `SeedDwsConfigTask` | 初始化 DWS 配置种子数据 | utility | `True`(默认) |
|
||||
| `DATA_INTEGRITY_CHECK` | `DataIntegrityTask` | API → ODS → DWD 数据完整性校验 | verification | `False` |
|
||||
|
||||
> 典型执行顺序(首次部署):`INIT_ODS_SCHEMA` → `INIT_DWD_SCHEMA` → `INIT_DWS_SCHEMA` → `SEED_DWS_CONFIG`
|
||||
|
||||
---
|
||||
|
||||
## 1. INIT_ODS_SCHEMA — ODS + etl_admin Schema 初始化
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 任务代码 | `INIT_ODS_SCHEMA` |
|
||||
| Python 类 | `tasks.utility.init_schema_task.InitOdsSchemaTask` |
|
||||
| 继承 | `BaseTask` |
|
||||
| 用途 | 创建 ODS 层和 etl_admin 调度元数据的数据库结构,并准备运行时目录 |
|
||||
|
||||
### 执行流程
|
||||
|
||||
```
|
||||
extract()
|
||||
├── 读取 DDL 文件路径(schema_ODS_doc.sql、schema_etl_admin.sql)
|
||||
├── 收集需创建的目录列表
|
||||
└── 返回 SQL 文本 + 目录列表
|
||||
|
||||
load()
|
||||
├── 创建必要目录(log_root、export_root、fetch_root、ingest_dir)
|
||||
├── 执行 etl_admin DDL(schema_etl_admin.sql)
|
||||
└── 执行 ODS DDL(schema_ODS_doc.sql,清洗后)
|
||||
```
|
||||
|
||||
### 执行的 DDL 文件
|
||||
|
||||
| DDL 文件 | 创建的 Schema | 主要内容 |
|
||||
|----------|--------------|----------|
|
||||
| `database/schema_etl_admin.sql` | `etl_admin` | `etl_task`(任务注册表)、`etl_cursor`(游标表)、`etl_run`(运行记录表) |
|
||||
| `database/schema_ODS_doc.sql` | `billiards_ods` | 20+ 张 ODS 原始表(member_profiles、settlement_records、payment_transactions 等) |
|
||||
|
||||
### ODS DDL 清洗逻辑
|
||||
|
||||
ODS DDL 文件可能包含头部说明文本和 `COMMENT ON` 语句(CamelCase 未加引号会导致执行失败),因此 `load()` 阶段会做轻量清洗:
|
||||
|
||||
1. 定位第一个 `DROP SCHEMA` 语句,丢弃之前的非 SQL 文本
|
||||
2. 逐行过滤掉以 `COMMENT ON` 开头的行
|
||||
|
||||
### 创建的目录
|
||||
|
||||
| 配置路径 | 说明 |
|
||||
|----------|------|
|
||||
| `io.log_root` | 日志输出根目录 |
|
||||
| `io.export_root` | 数据导出根目录 |
|
||||
| `pipeline.fetch_root` | API 抓取数据落盘目录 |
|
||||
| `pipeline.ingest_source_dir` | 手动入库数据源目录(默认同 fetch_root) |
|
||||
|
||||
### 配置参数
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `schema.ods_file` | `database/schema_ODS_doc.sql` | ODS DDL 文件路径 |
|
||||
| `schema.etl_admin_file` | `database/schema_etl_admin.sql` | etl_admin DDL 文件路径 |
|
||||
| `io.log_root` | — | 日志目录 |
|
||||
| `io.export_root` | — | 导出目录 |
|
||||
| `pipeline.fetch_root` | — | 抓取数据目录 |
|
||||
| `pipeline.ingest_source_dir` | 同 fetch_root | 入库数据源目录 |
|
||||
|
||||
### CLI 示例
|
||||
|
||||
```bash
|
||||
python -m cli.main --tasks INIT_ODS_SCHEMA --pg-dsn "$PG_DSN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. INIT_DWD_SCHEMA — DWD Schema 初始化
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 任务代码 | `INIT_DWD_SCHEMA` |
|
||||
| Python 类 | `tasks.utility.init_dwd_schema_task.InitDwdSchemaTask` |
|
||||
| 继承 | `BaseTask` |
|
||||
| 用途 | 创建 DWD 明细数据层的数据库结构 |
|
||||
|
||||
### 执行流程
|
||||
|
||||
```
|
||||
extract()
|
||||
├── 读取 DDL 文件路径(schema_dwd_doc.sql)
|
||||
└── 读取 drop_first 配置
|
||||
|
||||
load()
|
||||
├── [可选] DROP SCHEMA billiards_dwd CASCADE
|
||||
└── 执行 DWD DDL(schema_dwd_doc.sql)
|
||||
```
|
||||
|
||||
### 执行的 DDL 文件
|
||||
|
||||
| DDL 文件 | 创建的 Schema | 主要内容 |
|
||||
|----------|--------------|----------|
|
||||
| `database/schema_dwd_doc.sql` | `billiards_dwd` | 维度表(dim_*,含 SCD2 约束)、事实表(dwd_*、fact_*)、扩展表(*_ex) |
|
||||
|
||||
DWD DDL 的特殊处理:
|
||||
- 自动为含 `scd2_start_time` 列的表设置 SCD2 默认值(`scd2_start_time=now()`、`scd2_end_time='9999-12-31'`、`scd2_is_current=1`、`scd2_version=1`)
|
||||
- 自动创建 SCD2 排他约束(`EXCLUDE USING gist`,防止同一业务主键的生效区间重叠)
|
||||
- 自动创建当前版本唯一索引(`WHERE scd2_is_current = 1`)
|
||||
|
||||
### 配置参数
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `schema.dwd_file` | `database/schema_dwd_doc.sql` | DWD DDL 文件路径 |
|
||||
| `dwd.drop_schema_first` | `False` | 是否先 DROP 再重建(危险操作,会丢失所有 DWD 数据) |
|
||||
|
||||
### CLI 示例
|
||||
|
||||
```bash
|
||||
# 常规初始化
|
||||
python -m cli.main --tasks INIT_DWD_SCHEMA --pg-dsn "$PG_DSN"
|
||||
|
||||
# 重建(先删后建,慎用)
|
||||
python -m cli.main --tasks INIT_DWD_SCHEMA --pg-dsn "$PG_DSN" --extra dwd.drop_schema_first=true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. INIT_DWS_SCHEMA — DWS Schema 初始化
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 任务代码 | `INIT_DWS_SCHEMA` |
|
||||
| Python 类 | `tasks.utility.init_dws_schema_task.InitDwsSchemaTask` |
|
||||
| 继承 | `BaseTask` |
|
||||
| 用途 | 创建 DWS 数据服务层的数据库结构 |
|
||||
|
||||
### 执行流程
|
||||
|
||||
```
|
||||
extract()
|
||||
├── 读取 DDL 文件路径(schema_dws.sql)
|
||||
└── 读取 drop_first 配置
|
||||
|
||||
load()
|
||||
├── [可选] DROP SCHEMA billiards_dws CASCADE
|
||||
└── 执行 DWS DDL(schema_dws.sql)
|
||||
```
|
||||
|
||||
### 执行的 DDL 文件
|
||||
|
||||
| DDL 文件 | 创建的 Schema | 主要内容 |
|
||||
|----------|--------------|----------|
|
||||
| `database/schema_dws.sql` | `billiards_dws` | 配置表(5 张 cfg_*)、助教域(5 张)、会员域(2 张)、财务域(7 张)、订单汇总(1 张) |
|
||||
|
||||
DWS Schema 包含的配置表:
|
||||
|
||||
| 配置表 | 说明 |
|
||||
|--------|------|
|
||||
| `cfg_performance_tier` | 绩效档位配置(阈值、抽成比例、假期天数) |
|
||||
| `cfg_assistant_level_price` | 助教等级定价 |
|
||||
| `cfg_bonus_rules` | 奖金规则配置 |
|
||||
| `cfg_area_category` | 台区分类映射 |
|
||||
| `cfg_skill_type` | 技能课程类型映射 |
|
||||
|
||||
### 配置参数
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `schema.dws_file` | `database/schema_dws.sql` | DWS DDL 文件路径 |
|
||||
| `dws.drop_schema_first` | `False` | 是否先 DROP 再重建(危险操作) |
|
||||
|
||||
### CLI 示例
|
||||
|
||||
```bash
|
||||
python -m cli.main --tasks INIT_DWS_SCHEMA --pg-dsn "$PG_DSN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. SEED_DWS_CONFIG — DWS 配置种子数据初始化
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 任务代码 | `SEED_DWS_CONFIG` |
|
||||
| Python 类 | `tasks.utility.seed_dws_config_task.SeedDwsConfigTask` |
|
||||
| 继承 | `BaseTask` |
|
||||
| 用途 | 向 DWS 配置表插入初始数据(绩效档位、等级定价、奖金规则等) |
|
||||
|
||||
### 前置条件
|
||||
|
||||
- `billiards_dws` schema 已创建(需先执行 `INIT_DWS_SCHEMA`)
|
||||
- 配置表(`cfg_*`)已存在
|
||||
|
||||
### 执行流程
|
||||
|
||||
```
|
||||
extract()
|
||||
└── 读取 seed_dws_config.sql 文件内容
|
||||
|
||||
load()
|
||||
└── 执行 SQL(TRUNCATE + INSERT 配置数据)
|
||||
```
|
||||
|
||||
### 执行的种子文件
|
||||
|
||||
| 文件 | 目标表 | 说明 |
|
||||
|------|--------|------|
|
||||
| `database/seed_dws_config.sql` | `cfg_performance_tier` | 绩效档位(含历史口径:旧方案至 2026-02-28,新方案 2026-03-01 起) |
|
||||
| | `cfg_assistant_level_price` | 助教等级定价 |
|
||||
| | `cfg_bonus_rules` | 奖金规则 |
|
||||
| | `cfg_area_category` | 台区分类映射 |
|
||||
| | `cfg_skill_type` | 技能课程类型映射 |
|
||||
|
||||
### 配置参数
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `schema.seed_dws_file` | `database/seed_dws_config.sql` | 种子数据 SQL 文件路径 |
|
||||
|
||||
### CLI 示例
|
||||
|
||||
```bash
|
||||
# 通常与 INIT_DWS_SCHEMA 一起执行
|
||||
python -m cli.main --tasks INIT_DWS_SCHEMA,SEED_DWS_CONFIG --pg-dsn "$PG_DSN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. MANUAL_INGEST — 手动 JSON 入库
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 任务代码 | `MANUAL_INGEST` |
|
||||
| Python 类 | `tasks.utility.manual_ingest_task.ManualIngestTask` |
|
||||
| 继承 | `BaseTask` |
|
||||
| 用途 | 从本地 JSON 文件批量灌入 ODS 表,用于离线回放或示例数据导入 |
|
||||
|
||||
### 执行流程概览
|
||||
|
||||
```
|
||||
execute()
|
||||
├── 确定数据目录(manual.data_dir / pipeline.ingest_source_dir / tests/testdata_json)
|
||||
├── 遍历目录下所有 .json 文件(按文件名排序)
|
||||
│ ├── [可选] 按 include_files 过滤
|
||||
│ ├── 读取并解析 JSON
|
||||
│ ├── 提取记录列表(兼容多层 data/list 包装)
|
||||
│ ├── 按文件名关键字匹配目标 ODS 表
|
||||
│ └── 批量入库(INSERT ON CONFLICT)
|
||||
└── 返回统计计数(fetched/inserted/updated/skipped/errors)
|
||||
```
|
||||
|
||||
### 文件匹配规则
|
||||
|
||||
`MANUAL_INGEST` 通过 `FILE_MAPPING` 将文件名关键字映射到目标 ODS 表。匹配逻辑:**文件名中包含关键字即匹配**(大小写敏感)。
|
||||
|
||||
| 文件名关键字 | 目标 ODS 表 |
|
||||
|-------------|------------|
|
||||
| `member_profiles` | `billiards_ods.member_profiles` |
|
||||
| `member_balance_changes` | `billiards_ods.member_balance_changes` |
|
||||
| `member_stored_value_cards` | `billiards_ods.member_stored_value_cards` |
|
||||
| `recharge_settlements` | `billiards_ods.recharge_settlements` |
|
||||
| `settlement_records` | `billiards_ods.settlement_records` |
|
||||
| `assistant_cancellation_records` | `billiards_ods.assistant_cancellation_records` |
|
||||
| `assistant_accounts_master` | `billiards_ods.assistant_accounts_master` |
|
||||
| `assistant_service_records` | `billiards_ods.assistant_service_records` |
|
||||
| `site_tables_master` | `billiards_ods.site_tables_master` |
|
||||
| `table_fee_discount_records` | `billiards_ods.table_fee_discount_records` |
|
||||
| `table_fee_transactions` | `billiards_ods.table_fee_transactions` |
|
||||
| `goods_stock_movements` | `billiards_ods.goods_stock_movements` |
|
||||
| `stock_goods_category_tree` | `billiards_ods.stock_goods_category_tree` |
|
||||
| `goods_stock_summary` | `billiards_ods.goods_stock_summary` |
|
||||
| `payment_transactions` | `billiards_ods.payment_transactions` |
|
||||
| `refund_transactions` | `billiards_ods.refund_transactions` |
|
||||
| `platform_coupon_redemption_records` | `billiards_ods.platform_coupon_redemption_records` |
|
||||
| `group_buy_redemption_records` | `billiards_ods.group_buy_redemption_records` |
|
||||
| `group_buy_packages` | `billiards_ods.group_buy_packages` |
|
||||
| `settlement_ticket_details` | `billiards_ods.settlement_ticket_details` |
|
||||
| `store_goods_master` | `billiards_ods.store_goods_master` |
|
||||
| `tenant_goods_master` | `billiards_ods.tenant_goods_master` |
|
||||
| `store_goods_sales_records` | `billiards_ods.store_goods_sales_records` |
|
||||
|
||||
### JSON 解析逻辑
|
||||
|
||||
`_extract_records()` 方法兼容多种 JSON 包装格式:
|
||||
|
||||
1. **顶层数组**:`[{...}, {...}]` → 直接作为记录列表
|
||||
2. **data 包装**:`{"data": [...]}` 或 `{"code": 0, "data": [...]}` → 展开 `data` 字段
|
||||
3. **嵌套 list**:`{"data": {"someKey": [{...}]}}` → 自动查找第一个 list 类型的值
|
||||
4. **settleList 特殊处理**:充值/结算记录的 `data.settleList` 结构会被展开,内层 `settleList` 提取为独立记录,并保留外层 `siteProfile` 供字段补充
|
||||
|
||||
### 入库流程
|
||||
|
||||
对每张目标表,入库过程如下:
|
||||
|
||||
1. **查询表结构**:通过 `information_schema.columns` 获取目标表的列名、数据类型
|
||||
2. **构建 SQL**:生成 `INSERT INTO ... VALUES %s ON CONFLICT ...` 语句
|
||||
- 有 `content_hash` 列:`ON CONFLICT (pk, content_hash) DO NOTHING`(内容去重)
|
||||
- 无 `content_hash` 列:`ON CONFLICT (pk) DO UPDATE SET ...`(upsert 覆盖)
|
||||
3. **值映射**:逐列匹配 JSON 字段(忽略大小写),特殊列处理:
|
||||
- `payload`:存储原始 JSON 记录
|
||||
- `source_file`:填入文件名
|
||||
- `fetched_at`:取记录中的值或当前时间
|
||||
- `content_hash`:基于记录内容计算 SHA-256(排除 `fetched_at`、`payload` 等 ETL 元数据字段)
|
||||
- JSON 类型列:自动包装为 `psycopg2.extras.Json`
|
||||
- 整数/浮点/时间戳列:自动类型转换
|
||||
4. **批量执行**:使用 `psycopg2.extras.execute_values` 分批提交(默认 chunk_size=50,最大 500)
|
||||
5. **降级处理**:批量执行失败时,降级为逐行 + `SAVEPOINT` 模式,跳过异常行继续处理
|
||||
6. **事务粒度**:每个文件一次 `commit`,避免长事务
|
||||
|
||||
### 特殊处理
|
||||
|
||||
- **充值/结算记录**(`recharge_settlements`、`settlement_records`):自动从 `siteProfile` 补齐 `tenantid`、`siteid`、`sitename`
|
||||
- **空值规范化**:空字符串 `""`、空 JSON `"{}"` / `"[]"` 统一转为 `None`
|
||||
- **主键校验**:主键值为 `None` 或空字符串的记录直接跳过
|
||||
|
||||
### 配置参数
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `manual.data_dir` | — | JSON 数据文件目录(优先级最高) |
|
||||
| `pipeline.ingest_source_dir` | — | 入库数据源目录(次优先) |
|
||||
| — | `tests/testdata_json` | 兜底默认目录 |
|
||||
| `manual.include_files` | `[]`(全部) | 限定处理的文件名列表(不含扩展名,小写匹配) |
|
||||
| `manual.execute_values_page_size` | `50` | 批量插入每批行数(1-500) |
|
||||
|
||||
### CLI 示例
|
||||
|
||||
```bash
|
||||
# 从默认目录灌入所有 JSON
|
||||
python -m cli.main --tasks MANUAL_INGEST --pg-dsn "$PG_DSN"
|
||||
|
||||
# 指定数据目录
|
||||
python -m cli.main --tasks MANUAL_INGEST --pg-dsn "$PG_DSN" \
|
||||
--extra manual.data_dir=/path/to/json_files
|
||||
|
||||
# 只灌入指定文件
|
||||
python -m cli.main --tasks MANUAL_INGEST --pg-dsn "$PG_DSN" \
|
||||
--extra manual.include_files=member_profiles,settlement_records
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. ODS_JSON_ARCHIVE — ODS 接口数据归档
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 任务代码 | `ODS_JSON_ARCHIVE` |
|
||||
| Python 类 | `tasks.ods.ods_json_archive_task.OdsJsonArchiveTask` |
|
||||
| 继承 | `BaseTask` |
|
||||
| 用途 | 在线抓取所有 ODS 相关 API 接口数据,落盘为简化 JSON 文件,供后续离线回放/入库 |
|
||||
|
||||
> 注意:虽然注册为 `task_type="utility"`,但该任务的源文件位于 `tasks/ods/` 目录下,因为它本质上是 ODS 数据的抓取归档。
|
||||
|
||||
### 归档策略
|
||||
|
||||
- **输出格式**:每页一个 JSON 文件,格式为 `{"code": 0, "data": [...records...]}`,与 `MANUAL_INGEST` 的解析逻辑兼容
|
||||
- **文件命名**:`{endpoint_stem}__p{page_no:04d}.json`(如 `GetAllOrderSettleList__p0001.json`)
|
||||
- **小票文件**:按 `orderSettleId` 分文件写入(`GetOrderSettleTicketNew__{orderSettleId}.json`)
|
||||
- **清单文件**:抓取完成后生成 `manifest.json`,记录窗口、端点、记录数等元信息
|
||||
|
||||
### 抓取的 API 端点
|
||||
|
||||
任务内置 22 个端点配置(`ENDPOINTS`),按窗口参数风格分类:
|
||||
|
||||
| 窗口风格 | 参数 | 端点示例 |
|
||||
|----------|------|----------|
|
||||
| `site` | `siteId` | `/MemberProfile/GetTenantMemberList`、`/Table/GetSiteTables` 等 |
|
||||
| `start_end` | `siteId` + `startTime` / `endTime` | `/MemberProfile/GetMemberCardBalanceChange`、`/TenantGoods/GetGoodsSalesList` 等 |
|
||||
| `range` | `siteId` + `rangeStartTime` / `rangeEndTime` | `/Site/GetAllOrderSettleList`、`/Site/GetRechargeSettleList` |
|
||||
| `pay` | `siteId` + `StartPayTime` / `EndPayTime` | `/PayLog/GetPayLogListPage` |
|
||||
|
||||
此外,还有一个特殊端点 `/Order/GetOrderSettleTicketNew`(小票详情),按支付日志中提取的 `orderSettleId` 逐单抓取。
|
||||
|
||||
### 执行流程
|
||||
|
||||
```
|
||||
extract()
|
||||
├── 验证 API 客户端类型(必须为 APIClient,即在线模式)
|
||||
├── 确定输出目录(api.output_dir / pipeline.fetch_root)
|
||||
├── 遍历 ENDPOINTS,逐端点分页抓取
|
||||
│ ├── 构建请求参数(按 window_style 选择参数格式)
|
||||
│ ├── 调用 iter_paginated() 分页获取
|
||||
│ ├── 每页落盘为独立 JSON 文件
|
||||
│ └── 从支付日志中收集 orderSettleId(用于小票抓取)
|
||||
├── 按 orderSettleId 逐单抓取小票详情
|
||||
└── 生成 manifest.json 清单文件
|
||||
```
|
||||
|
||||
### 配置参数
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `pipeline.fetch_root` | — | JSON 文件输出目录 |
|
||||
| `api.page_size` | `200` | API 分页大小 |
|
||||
| `io.write_pretty_json` | `False` | 是否格式化输出 JSON |
|
||||
|
||||
### CLI 示例
|
||||
|
||||
```bash
|
||||
# 在线抓取并归档
|
||||
python -m cli.main --tasks ODS_JSON_ARCHIVE --pg-dsn "$PG_DSN" \
|
||||
--store-id "$STORE_ID" --api-token "$API_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. CHECK_CUTOFF — 数据截止时间检查
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 任务代码 | `CHECK_CUTOFF` |
|
||||
| Python 类 | `tasks.utility.check_cutoff_task.CheckCutoffTask` |
|
||||
| 继承 | `BaseTask` |
|
||||
| 用途 | 报告各任务的游标截止时间和各层数据表的最新时间戳,用于运维监控 |
|
||||
|
||||
### 执行流程
|
||||
|
||||
该任务不走标准的 extract → transform → load 流程,而是直接在 `execute()` 中完成所有逻辑:
|
||||
|
||||
```
|
||||
execute()
|
||||
├── 1. 查询 etl_admin 游标截止时间
|
||||
│ ├── 关联 etl_task + etl_cursor 表
|
||||
│ ├── 筛选当前门店已启用的任务
|
||||
│ ├── [可选] 按 task_codes 过滤
|
||||
│ └── 计算总体截止时间(排除 INIT_* 任务的最小 last_end)
|
||||
├── 2. 探测 ODS 表抓取时间
|
||||
│ ├── 遍历 DwdLoadTask.TABLE_MAP 中的 ODS 表
|
||||
│ ├── 查询每张表的 MAX(fetched_at) 和 COUNT(*)
|
||||
│ └── 计算 ODS 截止时间(最小 max_fetched_at)
|
||||
└── 3. 探测 DWD/DWS 关键时间列
|
||||
├── DWD: max(pay_time) from dwd_settlement_head / dwd_payment / dwd_refund
|
||||
└── DWS: max(order_date) / max(updated_at) from dws_order_summary
|
||||
```
|
||||
|
||||
### 校验逻辑
|
||||
|
||||
- **游标截止时间**:从 `etl_admin.etl_cursor.last_end` 获取每个任务的最后成功窗口结束时间,排除 `INIT_*` 任务后取最小值作为总体截止时间
|
||||
- **ODS 抓取时间**:查询每张 ODS 表的 `MAX(fetched_at)`,取最小值作为 ODS 层截止时间
|
||||
- **DWD/DWS 业务时间**:探测关键业务时间列(`pay_time`、`order_date`、`updated_at`),反映数据实际覆盖范围
|
||||
|
||||
### 输出
|
||||
|
||||
任务通过日志输出检查结果,同时在返回值的 `report` 字段中包含结构化数据:
|
||||
|
||||
```python
|
||||
{
|
||||
"rows": [...], # 每个任务的游标信息
|
||||
"overall_cutoff": datetime, # 总体截止时间
|
||||
"ods_fetched_at": {...}, # 每张 ODS 表的 max_fetched_at
|
||||
"dw_max_times": {...}, # DWD/DWS 关键时间列
|
||||
}
|
||||
```
|
||||
|
||||
### 配置参数
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `app.store_id` | — | 门店 ID(必填) |
|
||||
| `run.cutoff_task_codes` | `None`(全部) | 逗号分隔的任务代码列表,限定检查范围 |
|
||||
|
||||
### CLI 示例
|
||||
|
||||
```bash
|
||||
# 检查所有任务的截止时间
|
||||
python -m cli.main --tasks CHECK_CUTOFF --pg-dsn "$PG_DSN" --store-id "$STORE_ID"
|
||||
|
||||
# 只检查指定任务
|
||||
python -m cli.main --tasks CHECK_CUTOFF --pg-dsn "$PG_DSN" --store-id "$STORE_ID" \
|
||||
--extra run.cutoff_task_codes=ORDERS,PAYMENTS,MEMBERS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. DATA_INTEGRITY_CHECK — 数据完整性校验
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 任务代码 | `DATA_INTEGRITY_CHECK` |
|
||||
| Python 类 | `tasks.utility.data_integrity_task.DataIntegrityTask` |
|
||||
| 继承 | `BaseTask` |
|
||||
| 注册 task_type | `verification`(非 utility,但在本文档中一并说明) |
|
||||
| 用途 | 检查 API → ODS → DWD 全链路数据完整性,支持自动回填缺失数据 |
|
||||
|
||||
### 两种运行模式
|
||||
|
||||
#### 1. 历史模式(`history`,默认)
|
||||
|
||||
从指定起始日期到结束日期,按月分段检查全量历史数据的完整性。
|
||||
|
||||
```
|
||||
execute() [mode=history]
|
||||
├── 解析 history_start / history_end 时间范围
|
||||
├── 调用 run_history_flow()
|
||||
│ ├── 按月分段执行完整性检查
|
||||
│ ├── 对比 API 记录数 vs ODS 记录数
|
||||
│ ├── [可选] 对比内容一致性(content_hash)
|
||||
│ └── [可选] 自动回填缺失数据
|
||||
└── 生成 JSON 报表
|
||||
```
|
||||
|
||||
#### 2. 窗口模式(`window`)
|
||||
|
||||
检查指定时间窗口内的数据完整性,当提供 CLI 窗口覆盖参数时自动切换到此模式。
|
||||
|
||||
```
|
||||
execute() [mode=window]
|
||||
├── 获取时间窗口(支持 CLI 覆盖)
|
||||
├── 构建窗口分段(build_window_segments)
|
||||
├── 调用 run_window_flow()
|
||||
│ ├── 逐段执行完整性检查
|
||||
│ ├── 汇总缺失/不一致/错误计数
|
||||
│ └── [可选] 自动回填 + 复查
|
||||
└── 生成 JSON 报表
|
||||
```
|
||||
|
||||
### 校验逻辑
|
||||
|
||||
核心校验由 `quality/integrity_service.py` 和 `quality/integrity_checker.py` 实现:
|
||||
|
||||
1. **记录数对比**:API 返回的记录数 vs ODS 表中的记录数
|
||||
2. **内容一致性**(可选):抽样对比 API 记录与 ODS 记录的 `content_hash`
|
||||
3. **缺失检测**:识别 API 中存在但 ODS 中缺失的记录
|
||||
4. **不一致检测**:识别 API 与 ODS 中内容不匹配的记录
|
||||
|
||||
### 自动回填
|
||||
|
||||
当 `auto_backfill=True` 时,检测到缺失或不一致数据后会自动触发回填:
|
||||
|
||||
1. 调用 `scripts/repair/backfill_missing_data.run_backfill()` 重新抓取缺失数据
|
||||
2. 回填完成后可选复查(`recheck_after_backfill`),验证回填效果
|
||||
|
||||
### 配置参数
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `integrity.mode` | `history` | 运行模式:`history`(历史全量)/ `window`(时间窗口) |
|
||||
| `integrity.history_start` | `2025-07-01` | 历史模式起始日期 |
|
||||
| `integrity.history_end` | —(当前时间) | 历史模式结束日期 |
|
||||
| `integrity.include_dimensions` | `False` | 是否包含维度表检查 |
|
||||
| `integrity.ods_task_codes` | —(全部) | 限定检查的 ODS 任务代码 |
|
||||
| `integrity.auto_backfill` | `False` | 是否自动回填缺失数据 |
|
||||
| `integrity.compare_content` | `True` | 是否对比内容一致性 |
|
||||
| `integrity.content_sample_limit` | — | 内容对比抽样上限 |
|
||||
| `integrity.backfill_mismatch` | `True` | 是否回填不一致数据(仅 auto_backfill 时生效) |
|
||||
| `integrity.recheck_after_backfill` | `True` | 回填后是否复查 |
|
||||
| `integrity.force_monthly_split` | `True` | 是否强制按月分段 |
|
||||
| `run.window_override.start` | — | CLI 窗口覆盖起始时间(触发 window 模式) |
|
||||
| `run.window_override.end` | — | CLI 窗口覆盖结束时间 |
|
||||
|
||||
### 输出报表
|
||||
|
||||
检查结果以 JSON 格式写入 `reports/` 目录:
|
||||
|
||||
- 历史模式:`reports/data_integrity_history_{timestamp}.json`
|
||||
- 窗口模式:`reports/data_integrity_window_{timestamp}.json`
|
||||
|
||||
### CLI 示例
|
||||
|
||||
```bash
|
||||
# 历史全量检查(默认从 2025-07-01 至今)
|
||||
python -m cli.main --tasks DATA_INTEGRITY_CHECK --pg-dsn "$PG_DSN" \
|
||||
--store-id "$STORE_ID" --api-token "$API_TOKEN"
|
||||
|
||||
# 指定时间范围
|
||||
python -m cli.main --tasks DATA_INTEGRITY_CHECK --pg-dsn "$PG_DSN" \
|
||||
--store-id "$STORE_ID" --api-token "$API_TOKEN" \
|
||||
--extra integrity.history_start=2026-01-01 --extra integrity.history_end=2026-02-01
|
||||
|
||||
# 窗口模式 + 自动回填
|
||||
python -m cli.main --tasks DATA_INTEGRITY_CHECK --pg-dsn "$PG_DSN" \
|
||||
--store-id "$STORE_ID" --api-token "$API_TOKEN" \
|
||||
--window-start "2026-02-01" --window-end "2026-02-15" \
|
||||
--extra integrity.auto_backfill=true
|
||||
```
|
||||
Reference in New Issue
Block a user