Files
Neo-ZQYY/apps/etl/pipelines/feiqiu/docs/etl_tasks/utility_tasks.md

23 KiB
Raw Blame History

工具类任务详解

本文档说明飞球 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_SCHEMAINIT_DWD_SCHEMAINIT_DWS_SCHEMASEED_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 DDLschema_etl_admin.sql
  └── 执行 ODS DDLschema_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 示例

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 DDLschema_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=1scd2_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 示例

# 常规初始化
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 DDLschema_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 示例

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()
  └── 执行 SQLTRUNCATE + 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 示例

# 通常与 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_atpayload 等 ETL 元数据字段)
    • JSON 类型列:自动包装为 psycopg2.extras.Json
    • 整数/浮点/时间戳列:自动类型转换
  4. 批量执行:使用 psycopg2.extras.execute_values 分批提交(默认 chunk_size=50最大 500
  5. 降级处理:批量执行失败时,降级为逐行 + SAVEPOINT 模式,跳过异常行继续处理
  6. 事务粒度:每个文件一次 commit,避免长事务

特殊处理

  • 充值/结算记录recharge_settlementssettlement_records):自动从 siteProfile 补齐 tenantidsiteidsitename
  • 空值规范化:空字符串 ""、空 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 示例

# 从默认目录灌入所有 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 示例

# 在线抓取并归档
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_timeorder_dateupdated_at),反映数据实际覆盖范围

输出

任务通过日志输出检查结果,同时在返回值的 report 字段中包含结构化数据:

{
    "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 示例

# 检查所有任务的截止时间
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.pyquality/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 示例

# 历史全量检查(默认从 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