32 KiB
飞球 ETL 连接器架构文档
生成日期:2026-02-18 | 基于代码实际阅读
目录
1. DWD → DWS/INDEX 数据流
1.1 整体分层
上游 SaaS API
│
▼
┌─────────┐
│ ODS │ 原始数据落地(保留源 payload)
└────┬────┘
│ DWD_LOAD_FROM_ODS
▼
┌─────────┐
│ DWD │ 明细层(维度 SCD2,事实按时间增量)
└────┬────┘
│
├──────────────────────────┐
▼ ▼
┌─────────┐ ┌───────────┐
│ DWS │ 汇总层 │ INDEX │ 指数算法层
└─────────┘ └───────────┘
1.2 类继承体系
BaseTask # 提供 E/T/L 模板方法 + 窗口分段
├── ODS 任务(略)
├── DwdLoadTask # DWD 装载
└── BaseDwsTask # DWS 层基类(提供默认 extract/load 模板方法)
│ # 子类声明 DATE_COL + 实现 _do_extract()/transform()
├── AssistantDailyTask # 助教日度
├── AssistantMonthlyTask # 助教月度
├── ...(其他 DWS 任务)
├── FinanceBaseTask # 财务任务共享基类(共享提取方法)
│ ├── FinanceDailyTask
│ ├── FinanceRechargeTask
│ ├── FinanceIncomeStructureTask
│ └── FinanceDiscountDetailTask
├── DwsMaintenanceTask # 统一维护(MV 刷新 + 数据清理)
└── BaseIndexTask # 指数算法基类
├── MemberIndexBaseTask # 会员指数共享逻辑(模板 execute)
│ ├── WinbackIndexTask # WBI(实现 _calculate_scores/_save_results)
│ └── NewconvIndexTask # NCI
├── RelationIndexTask # RS/OS/MS/ML
└── MlManualImportTask # ML 人工台账导入
1.3 BaseTask 核心机制
BaseTask.execute() 是所有任务的统一入口,流程:
_build_context(cursor_data)→ 构建TaskContext(store_id, window_start/end, cursor)build_window_segments()→ 根据配置将大窗口拆分为多段(day/week/month)- 对每个分段循环执行:
extract() → transform() → load() → commit() - 累加各段统计,返回汇总结果
时间窗口确定优先级:
run.window_override.start/end(CLI--window-start/--window-end)> 游标cursor.last_end> 默认回溯
闲时/忙时自适应窗口:
- 闲时(04:00–16:00):默认 180 分钟
- 忙时:默认 30 分钟
- 游标存在时:从
cursor.last_end - overlap_seconds开始
1.4 BaseDwsTask 通用能力
BaseDwsTask 继承 BaseTask,为 DWS 层提供:
| 能力 | 说明 |
|---|---|
DATE_COL |
类属性,子类声明日期列名(用于默认 extract/load) |
_do_extract(context) |
抽象方法,子类实现数据提取逻辑 |
extract(context) |
默认实现:调用 _do_extract() 并包装为标准字典 |
load(transformed, context) |
默认实现:delete_existing_data(date_col=DATE_COL) + bulk_insert(),返回标准统计字典 |
get_target_table() |
抽象方法,返回目标表名(不含 schema) |
get_primary_keys() |
抽象方法,返回主键列表(用于幂等更新) |
iter_dwd_rows() / query_dwd() |
从 DWD 层读取数据 |
load_config_cache() |
缓存配置表(绩效档位、等级定价、奖金规则、区域分类、技能类型),TTL 300 秒 |
get_assistant_level_asof() |
SCD2 维度 as-of 取值(获取指定日期生效的助教等级) |
get_member_card_balance_asof() |
获取会员储值卡余额 |
delete_existing_data() |
按主键删除已有数据(delete-before-insert 幂等) |
bulk_insert() / upsert() |
批量写入 / UPSERT |
calculate_rolling_stats() |
滚动窗口统计(7/10/15/30/60/90 天) |
calculate_rank_with_ties() |
带并列的排名计算 |
| 时间分层枚举 | TimeLayer(近2天/1月/3月/6月/全量)、TimeWindow(本周/上周/本月/上月/季度等) |
DWS 子类开发模式:大多数 DWS 子类只需声明 DATE_COL 并实现 _do_extract() 和 transform(),即可复用基类的默认 extract() 和 load() 实现。有自定义逻辑的子类(如 AssistantSalaryTask 的月度删除)可覆盖默认实现。
公共辅助模块:tasks/dws/dws_helpers.py 提供 mask_mobile()、calc_days_since()、parse_id_list()、safe_division() 等纯函数,消除子类间的重复代码。
2. DWS 层任务详解
2.1 已注册的 DWS 任务
| 任务代码 | 类名 | 目标表 | 主键 | 说明 |
|---|---|---|---|---|
DWS_BUILD_ORDER_SUMMARY |
DwsBuildOrderSummaryTask | — | — | 订单汇总(工具类) |
DWS_ASSISTANT_DAILY |
AssistantDailyTask | dws_assistant_daily_detail |
site_id, assistant_id, stat_date | 助教日度业绩明细 |
DWS_ASSISTANT_MONTHLY |
AssistantMonthlyTask | — | — | 助教月度汇总 |
DWS_ASSISTANT_CUSTOMER |
AssistantCustomerTask | — | — | 助教-客户关系 |
DWS_ASSISTANT_SALARY |
AssistantSalaryTask | — | — | 助教工资计算 |
DWS_ASSISTANT_FINANCE |
AssistantFinanceTask | — | — | 助教财务汇总 |
DWS_MEMBER_CONSUMPTION |
MemberConsumptionTask | — | — | 会员消费分析 |
DWS_MEMBER_VISIT |
MemberVisitTask | — | — | 会员到访分析 |
DWS_FINANCE_DAILY |
FinanceDailyTask | — | — | 财务日报 |
DWS_FINANCE_RECHARGE |
FinanceRechargeTask | — | — | 充值分析 |
DWS_FINANCE_INCOME_STRUCTURE |
FinanceIncomeStructureTask | — | — | 收入结构分析 |
DWS_FINANCE_DISCOUNT_DETAIL |
FinanceDiscountDetailTask | — | — | 优惠明细 |
DWS_RETENTION_CLEANUP |
— | — | [已合并] 见 DWS_MAINTENANCE | |
DWS_MV_REFRESH_FINANCE_DAILY |
— | — | [已合并] 见 DWS_MAINTENANCE | |
DWS_MV_REFRESH_ASSISTANT_DAILY |
— | — | [已合并] 见 DWS_MAINTENANCE | |
DWS_MAINTENANCE |
DwsMaintenanceTask | — | — | 统一维护(MV 刷新 + 数据清理) |
2.2 DWS 任务典型流程(以 AssistantDailyTask 为例)
extract(context):
├── 从 dwd.dwd_assistant_service_log 提取助教服务记录
├── 从 dwd.dwd_assistant_trash_event 提取废除记录
└── 加载配置缓存(技能类型映射等)
transform(extracted, context):
├── 按 (assistant_id, stat_date) 聚合
├── 排除废除记录
├── 通过 SCD2 as-of 获取统计日当日助教等级
├── 通过 skill_id 映射课程类型(基础课/附加课)
└── 汇总:服务次数、计费时长、计费金额、客户数、台桌数
load(transformed, context):
├── delete_existing_data() # 按日期窗口删除
└── bulk_insert() # 批量写入 dws.dws_assistant_daily_detail
更新策略:每小时增量更新,幂等方式为 delete-before-insert(按日期窗口)。
3. INDEX 层指数算法任务详解
3.1 已注册的 INDEX 任务
| 任务代码 | 类名 | 指数类型 | 目标表 | 主键 |
|---|---|---|---|---|
DWS_WINBACK_INDEX |
WinbackIndexTask | WBI | dws_member_winback_index |
site_id, member_id |
DWS_NEWCONV_INDEX |
NewconvIndexTask | NCI | dws_member_newconv_index |
site_id, member_id |
DWS_RELATION_INDEX |
RelationIndexTask | RS/OS/MS/ML | dws_member_assistant_relation_index |
site_id, member_id, assistant_id |
DWS_ML_MANUAL_IMPORT |
MlManualImportTask | ML | dws_ml_manual_source + dws_member_assistant_relation_index |
— |
3.2 BaseIndexTask 核心能力
BaseIndexTask 继承 BaseDwsTask,提供指数计算通用功能:
| 能力 | 方法 | 说明 |
|---|---|---|
| 半衰期衰减 | decay(days, halflife) |
exp(-ln(2) * d / h),d=h 时权重=0.5 |
| 分位数计算 | calculate_percentiles(scores, lower, upper) |
默认 P5/P95 |
| Winsorize 截断 | winsorize(value, lower, upper) |
将值截断到分位点范围 |
| 归一化映射 | normalize_to_display(value, min, max, compression) |
映射到 0–10 分 |
| 批量归一化 | batch_normalize_to_display(raw_scores, ...) |
计算分位点 → Winsorize → 映射 |
| 参数加载 | load_index_parameters(index_type) |
从 dws.cfg_index_parameters 读取,TTL 300 秒缓存 |
| 分位点历史 | save_percentile_history() / get_last_percentile_history() |
写入/读取 dws.dws_index_percentile_history |
| EWMA 平滑 | _apply_ewma_smoothing() |
对分位点做指数加权移动平均,避免跳变 |
| 压缩函数 | _resolve_compression() |
支持 none / log1p / asinh 三种压缩模式 |
归一化流程:
Raw Score → [可选压缩: ln(1+x) 或 asinh(x)]
→ Winsorize(P5, P95)
→ MinMax 映射: score = 10 * (x - P5) / (P95 - P5)
→ clip(0, 10)
3.3 参数加载机制
所有指数参数存储在 dws.cfg_index_parameters 表中:
SELECT param_name, param_value
FROM dws.cfg_index_parameters
WHERE index_type = %s
AND effective_from <= CURRENT_DATE
AND (effective_to IS NULL OR effective_to >= CURRENT_DATE)
ORDER BY effective_from DESC
加载优先级:数据库参数 > 代码中的 DEFAULT_PARAMS(子类定义)。
缓存策略:按 index_type 隔离,TTL 300 秒。
运行时覆盖:run.index_lookback_days 可覆盖 lookback_days_recency。
3.4 WBI — 老客挽回指数
业务含义:衡量老客流失风险,分数越高越需要挽回干预。
执行流程:
- 获取 site_id / tenant_id
_load_params()→ 合并数据库参数与DEFAULT_PARAMS_build_member_activity()→ 构建会员活动特征(来自 MemberIndexBaseTask)- 对每个会员计算
MemberWinbackData:overdue_old:逾期衰减分(基于理想到访间隔 vs 实际间隔)drop_old:消费下降分recharge_old:充值衰减分value_old:价值分(消费+余额)
- 加权求和:
raw_score = w_over * overdue + w_drop * drop + w_re * recharge + w_value * value batch_normalize_to_display()→ 归一化到 0–10
默认参数(DEFAULT_PARAMS):
| 参数 | 默认值 | 说明 |
|---|---|---|
lookback_days_recency |
60 | 近期活跃度回溯天数 |
visit_lookback_days |
180 | 到访历史回溯天数 |
percentile_lower / upper |
5 / 95 | 分位点截断 |
compression_mode |
0 | 压缩模式(0=none) |
use_smoothing |
1 | 启用 EWMA 平滑 |
ewma_alpha |
0.2 | EWMA 平滑系数 |
new_visit_threshold |
2 | 新客到访次数阈值 |
new_days_threshold |
30 | 新客天数阈值 |
overdue_alpha |
2.0 | 逾期衰减指数 |
overdue_weight_halflife_days |
30 | 逾期权重半衰期 |
h_recharge |
7 | 充值衰减半衰期(天) |
amount_base_M0 |
300 | 消费基准金额 |
balance_base_B0 |
500 | 余额基准金额 |
w_over / w_drop / w_re / w_value |
2.0 / 1.0 / 0.4 / 1.2 | 四维权重 |
enable_stop_high_balance_exception |
0 | STOP 高余额例外(默认关闭) |
high_balance_threshold |
1000 | 高余额阈值 |
3.5 NCI — 新客转化指数
业务含义:衡量新客转化为忠实客户的潜力,分数越高越值得重点跟进。
执行流程与 WBI 类似,但评分维度不同:
_build_member_activity()→ 构建会员活动特征- 对每个新客计算:
welcome:欢迎窗口内的互动分need:需求紧迫度(基于到访间隔)recharge:充值行为分value:价值分
- 加权求和:
raw_score = w_welcome * welcome + w_need * need + w_re * recharge + w_value * value - 归一化到 0–10
默认参数(DEFAULT_PARAMS):
| 参数 | 默认值 | 说明 |
|---|---|---|
no_touch_days_new |
3 | 新客免打扰天数 |
t2_target_days |
7 | 第二次到访目标天数 |
salvage_start / salvage_end |
30 / 60 | 挽救窗口(天) |
welcome_window_days |
3 | 欢迎窗口天数 |
active_new_visit_threshold_14d |
2 | 14 天内活跃新客到访阈值 |
active_new_recency_days |
7 | 活跃新客近期天数 |
active_new_penalty |
0.2 | 活跃新客惩罚系数 |
w_welcome / w_need / w_re / w_value |
1.0 / 1.6 / 0.8 / 1.0 | 四维权重 |
3.6 RelationIndexTask — 关系指数(RS/OS/MS/ML)
业务含义:衡量助教与会员之间的关系强度,一个任务产出四个子指数。
执行流程:
- 提取服务记录
_extract_service_records() - 合并会话
_group_and_merge_sessions()(按session_merge_hours合并相邻服务) - 分别计算四个子指数:
- RS(Relation Strength):关系强度,基于服务频次 + 时长衰减
- OS(Ownership Score):归属度,基于 RS 占比判断主教练/共管/无归属
- MS(Momentum Score):动量分,短期 vs 长期趋势对比
- ML(Money Link):资金关联度,基于人工台账的充值归因
- 各子指数独立归一化到 0–10
RS 默认参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
lookback_days |
60 | 回溯天数 |
session_merge_hours |
4 | 会话合并间隔(小时) |
incentive_weight |
1.5 | 激励权重 |
halflife_session |
14.0 | 会话频次半衰期(天) |
halflife_last |
10.0 | 最近一次服务半衰期(天) |
weight_f / weight_d |
1.0 / 0.7 | 频次/时长权重 |
gate_alpha |
0.6 | 门控系数 |
OS 默认参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
min_rs_raw_for_ownership |
0.05 | 参与归属判定的最低 RS |
min_total_rs_raw |
0.10 | 总 RS 最低阈值 |
ownership_main_threshold |
0.60 | 主教练占比阈值 |
ownership_comanage_threshold |
0.35 | 共管占比阈值 |
ownership_gap_threshold |
0.15 | 归属差距阈值 |
MS 默认参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
halflife_short |
7.0 | 短期半衰期(天) |
halflife_long |
30.0 | 长期半衰期(天) |
ML 默认参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
amount_base |
500.0 | 充值基准金额 |
halflife_recharge |
21.0 | 充值衰减半衰期(天) |
3.7 MlManualImportTask — ML 人工台账导入
业务含义:从 Excel 文件导入人工分配的充值归因数据,作为 ML 指数的数据源。
执行流程:
_resolve_file_path()→ 解析 Excel 文件路径_read_excel_rows()→ 读取 Excel 数据_normalize_row()→ 标准化每行数据_build_source_row()→ 构建源数据行 → 写入dws_ml_manual_source_build_alloc_rows()→ 构建分配行 → UPSERT 到dws_member_assistant_relation_index- 按
resolve_scope()确定导入范围(P30 桶),幂等删除后重新插入
导入范围:按 (site_id, biz_date) 解析为 P30 桶(30 天滚动窗口),同一桶内的数据整体替换。
4. 任务调度与编排系统
4.1 架构概览
CLI (cli/main.py)
│
├── 传统模式 ──→ TaskExecutor.run_tasks(task_codes)
│
└── Flow 模式 ──→ FlowRunner.run(flow, processing_mode, ...)
│
├── _resolve_tasks(layers) → 层→任务映射(拓扑排序)
├── TaskExecutor.run_tasks(auto_tasks) → 增量 ETL
└── _run_verification() → 后置校验(可选)
核心组件:
| 组件 | 文件 | 职责 |
|---|---|---|
TaskRegistry |
orchestration/task_registry.py |
任务注册与工厂,维护 task_code → TaskMeta 映射 |
TaskExecutor |
orchestration/task_executor.py |
单任务执行生命周期(游标、运行记录、抓取/入库) |
FlowRunner |
orchestration/flow_runner.py |
Flow 编排(层→任务映射、拓扑排序、校验编排) |
CursorManager |
orchestration/cursor_manager.py |
游标管理(meta.etl_cursor) |
RunTracker |
orchestration/run_tracker.py |
运行记录(meta.etl_run) |
ETLScheduler |
orchestration/scheduler.py |
薄包装层(已弃用,兼容旧调用方) |
topological_sort |
orchestration/topological_sort.py |
任务依赖拓扑排序(Kahn's algorithm) |
4.2 两种执行模式
4.2.1 传统模式(--tasks)
直接指定任务代码列表,由 TaskExecutor.run_tasks() 顺序执行。
python -m cli.main --tasks ODS_MEMBER,ODS_ORDER,DWD_LOAD_FROM_ODS
流程:
- 解析
--tasks为任务代码列表 TaskExecutor.run_tasks(task_codes, data_source)逐个执行- 每个任务走
run_single_task()完整生命周期
4.2.2 Flow 模式(--flow / --layers)
指定 Flow 类型或层组合,由 FlowRunner 自动编排多层 ETL。
# 使用 --flow 快捷别名
python -m cli.main --flow api_full --processing-mode increment_verify
# 使用 --layers 自由组合
python -m cli.main --layers ODS,DWD,DWS --processing-mode increment_only
--pipeline仍可使用但已弃用,使用时会输出 DeprecationWarning。--layers和--flow/--pipeline互斥。
4.3 Flow 定义(7 种 Pipeline)
| Flow 名称 | 包含层 | 典型用途 |
|---|---|---|
api_ods |
ODS | 仅从 API 抓取到 ODS |
api_ods_dwd |
ODS → DWD | 抓取 + 清洗装载 |
api_full |
ODS → DWD → DWS → INDEX | 全链路 |
ods_dwd |
DWD | 仅 ODS→DWD 装载 |
dwd_dws |
DWS | 仅 DWD→DWS 汇总 |
dwd_dws_index |
DWS → INDEX | DWS 汇总 + 指数计算 |
dwd_index |
INDEX | 仅指数计算 |
4.4 三种处理模式
| 模式 | 说明 |
|---|---|
increment_only |
仅增量处理(默认) |
verify_only |
跳过增量,直接校验数据一致性并自动补齐 |
increment_verify |
先增量处理,再校验补齐 |
verify_only 模式可选 --fetch-before-verify:校验前先从 API 获取最新数据。
4.5 三种数据源模式(DataSource)
| 模式 | 说明 | 旧参数映射 |
|---|---|---|
online |
仅在线抓取(API → JSON 落盘) | FETCH_ONLY |
offline |
仅本地入库(JSON 回放 → DB) | INGEST_ONLY |
hybrid |
抓取 + 入库(默认) | FULL |
4.6 TaskExecutor 单任务执行生命周期
TaskExecutor.run_single_task() 是单任务执行的核心方法:
run_single_task(task_code, run_uuid, store_id, data_source)
│
├── 工具类任务? → _run_utility_task() → 直接执行,跳过游标和运行记录
│
├── _load_task_config() → 从 meta.etl_task_config 加载任务配置
│ └── 未启用/不存在 → 返回 SKIP
│
├── cursor_mgr.get_or_create() → 获取/创建游标
│
├── run_tracker.create_run() → 创建运行记录(状态=RUNNING)
│
├── ODS 任务?
│ ├── data_source 包含 fetch?
│ │ └── _execute_ods_record_and_load() → RecordingAPIClient 抓取 + 直接入库
│ └── 否则
│ └── _execute_ingest() → LocalJsonClient 回放入库
│
├── 非 ODS 任务(DWD/DWS/INDEX)
│ ├── data_source 包含 fetch?
│ │ └── _execute_fetch() → RecordingAPIClient 抓取落盘
│ │ └── data_source == online? → 仅抓取,不入库,直接返回
│ ├── data_source 包含 ingest?
│ │ └── _execute_ingest() → LocalJsonClient 回放 → task.execute()
│ └── 对于 DWS/INDEX 任务:直接走 task.execute(cursor_data)
│
├── run_tracker.update_run() → 更新运行记录(状态/计数/窗口)
│
└── cursor_mgr.advance() → 成功后推进游标水位
关键设计:
- ODS 任务在
execute()内完成 DB upsert,走特殊路径 - DWS/INDEX 任务的
requires_db_config=False,被标记为工具类任务,跳过游标和运行记录管理 RecordingAPIClient包装原始 API 客户端,抓取时同时落盘 JSONLocalJsonClient从本地 JSON 文件回放数据,用于离线模式
4.7 FlowRunner 层→任务解析
_resolve_tasks(layers) 将层列表解析为具体任务代码,并执行拓扑排序确保依赖顺序:
| 层 | 解析优先级 |
|---|---|
| ODS | 配置 run.ods_tasks > TaskRegistry.get_tasks_by_layer("ODS") |
| DWD | 固定 DWD_LOAD_FROM_ODS |
| DWS | 配置 run.dws_tasks > TaskRegistry.get_tasks_by_layer("DWS") |
| INDEX | 配置 run.index_tasks > TaskRegistry.get_tasks_by_layer("INDEX") |
已移除所有硬编码回退列表,统一走
TaskRegistry.get_tasks_by_layer()获取任务。空 Registry + 无配置时记录警告并返回空列表。 DWS/INDEX 层支持轻量级校验(跳过完整性校验或仅执行行数校验,返回 SKIPPED 状态)。
4.8 TaskRegistry 任务元数据
每个任务注册时携带 TaskMeta:
@dataclass
class TaskMeta:
task_class: type
requires_db_config: bool = True # False → 工具类任务,跳过游标/运行记录
layer: str | None = None # "ODS" / "DWD" / "DWS" / "INDEX" / None
task_type: str = "etl" # "etl" / "utility" / "verification"
depends_on: list[str] = field(default_factory=list) # 依赖的任务代码列表
任务依赖声明:通过 depends_on 字段声明任务间的依赖关系,_resolve_tasks() 会对任务列表执行拓扑排序(Kahn's algorithm),确保被依赖任务先于依赖方执行。循环依赖会抛出 ValueError,缺失依赖(不在当前执行列表中)会记录警告但继续执行。
已知依赖关系:
DWS_ASSISTANT_FINANCE→DWS_ASSISTANT_SALARYDWS_ASSISTANT_MONTHLY→DWS_ASSISTANT_DAILYDWS_MAINTENANCE→ 所有其他 DWS 任务DWS_WINBACK_INDEX/DWS_NEWCONV_INDEX→DWS_MEMBER_VISIT,DWS_MEMBER_CONSUMPTIONDWS_RELATION_INDEX→DWS_ASSISTANT_DAILY
当前注册统计:
- ODS 层:动态注册(由
ODS_TASK_CLASSES字典生成) - DWD 层:2 个(DWD_LOAD_FROM_ODS, DWD_QUALITY_CHECK)
- DWS 层:13 个(含统一维护任务 DWS_MAINTENANCE,原 3 个 MV 刷新/清理任务已合并)
- INDEX 层:4 个(WBI, NCI, Relation, ML 导入)
- 工具类:7 个(手动入库、Schema 初始化、JSON 归档、截止检查、种子配置)
- 校验类:1 个(DATA_INTEGRITY_CHECK)
4.9 游标管理(CursorManager)
存储在 meta.etl_cursor 表中,每个 (task_id, store_id) 一条记录:
| 字段 | 说明 |
|---|---|
last_start |
上次窗口开始时间 |
last_end |
上次窗口结束时间 |
last_id |
上次处理的最大 ID(可选) |
last_run_id |
关联的运行记录 ID |
updated_at |
更新时间 |
推进逻辑:任务成功后,advance() 将 last_start/last_end 更新为本次窗口范围,last_id 取 GREATEST(旧值, 新值)。
4.10 运行记录(RunTracker)
存储在 meta.etl_run 表中,每次任务执行创建一条记录:
| 字段 | 说明 |
|---|---|
run_uuid |
本次运行唯一标识 |
task_id / store_id |
任务和门店 |
status |
SUCC / FAIL / PARTIAL |
window_start / window_end / window_minutes |
时间窗口 |
fetched_count / loaded_count / updated_count / skipped_count / error_count |
计数统计 |
export_dir / log_path |
输出目录和日志路径 |
request_params |
API 请求参数(JSONB) |
manifest / extra |
扩展信息(JSONB) |
5. CLI 入口与参数
5.1 入口文件
apps/etl/connectors/feiqiu/cli/main.py
5.2 完整参数列表
基本参数
| 参数 | 类型 | 说明 |
|---|---|---|
--store-id |
int | 门店 ID |
--tasks |
str | 任务列表,逗号分隔(传统模式) |
--dry-run |
flag | 试运行(不提交) |
Flow 参数
| 参数 | 可选值 | 默认值 | 说明 |
|---|---|---|---|
--flow |
api_ods, api_ods_dwd, api_full, ods_dwd, dwd_dws, dwd_dws_index, dwd_index | — | Flow 类型(替代 --pipeline) |
--pipeline |
同上 | — | [已弃用] --flow 的别名,使用时输出 DeprecationWarning |
--layers |
ODS,DWD,DWS,INDEX 的任意组合 | — | ETL 层自由组合(与 --flow/--pipeline 互斥) |
--processing-mode |
increment_only, verify_only, increment_verify | increment_only | 处理模式 |
--fetch-before-verify |
flag | — | 校验前先从 API 获取数据 |
--verify-tables |
str | — | 仅校验指定表(逗号分隔) |
--window-split |
none, day, week, month | none | 时间窗口切分 |
--lookback-hours |
int | 24 | 回溯小时数 |
--overlap-seconds |
int | 3600 | 冗余秒数 |
数据源参数
| 参数 | 可选值 | 默认值 | 说明 |
|---|---|---|---|
--data-source |
online, offline, hybrid | hybrid | 数据源模式 |
--pipeline-flow |
FULL, FETCH_ONLY, INGEST_ONLY | — | [已弃用] 请使用 --data-source |
--fetch-root |
str | — | 抓取 JSON 输出根目录 |
--ingest-source |
str | — | 本地清洗入库源目录 |
--write-pretty-json |
flag | — | 抓取 JSON 美化输出 |
时间窗口参数
| 参数 | 类型 | 说明 |
|---|---|---|
--window-start |
str | 固定时间窗口开始(优先级高于游标) |
--window-end |
str | 固定时间窗口结束 |
--force-window-override |
flag | 强制使用 window_start/end,不走 MAX(fetched_at) 兜底 |
--window-split-unit |
str | 窗口切分单位(day/week/month/none) |
--window-split-days |
1/10/30 | 按天切分的天数 |
--window-compensation-hours |
int | 窗口前后补偿小时数 |
数据库 / API / 目录参数
| 参数 | 说明 |
|---|---|
--pg-dsn / --pg-host / --pg-port / --pg-name / --pg-user / --pg-password |
PostgreSQL 连接参数 |
--api-base / --api-token / --api-timeout / --api-page-size / --api-retry-max |
API 连接参数 |
--export-root / --log-root |
输出目录 |
--idle-start / --idle-end |
闲时窗口(HH:MM) |
--allow-empty-advance |
允许空结果推进窗口 |
5.3 典型使用示例
# 传统模式:指定任务
python -m cli.main --tasks ODS_MEMBER,ODS_ORDER --store-id 1
# Flow 全链路增量
python -m cli.main --flow api_full --processing-mode increment_only
# Flow 仅指数计算
python -m cli.main --flow dwd_index
# Flow DWS + INDEX
python -m cli.main --flow dwd_dws_index
# 使用 --layers 自由组合
python -m cli.main --layers ODS,DWD --store-id 1
# 仅执行 DWS 层
python -m cli.main --layers DWS
# 指定时间窗口
python -m cli.main --flow api_ods_dwd --window-start "2026-02-01" --window-end "2026-02-02"
# 校验并修复(先获取 API 数据)
python -m cli.main --flow api_full --processing-mode verify_only --fetch-before-verify
# 增量 + 校验
python -m cli.main --flow api_full --processing-mode increment_verify
# 离线模式(从本地 JSON 入库)
python -m cli.main --tasks DWD_LOAD_FROM_ODS --data-source offline --ingest-source ./data/json
5.4 配置优先级
代码默认值 < 根 .env < 应用 .env.local < 环境变量 < CLI 参数
时间窗口优先级:
--window-start/--window-end(CLI 显式指定)
> run.window_override.start/end(配置文件)
> cursor.last_end - overlap_seconds(游标推进)
> now - window_minutes(默认回溯)
附录 A:ODS 层软删除与 Early-Cutoff 保护
A.1 快照软删除机制
ODS 层通过快照对比实现软删除:将本次 API 返回的业务 ID 集合与数据库中已有 ID 对比,缺失的 ID 插入一条 is_delete=1 的新版本行(INSERT 而非 UPDATE,保留历史版本)。
三种快照模式(SnapshotMode):
| 模式 | 行为 | 适用场景 |
|---|---|---|
NONE |
不做快照对比 | 流水类数据(支付、退款等) |
FULL_TABLE |
对比全表所有记录 | 维度类数据(助教档案、会员卡等) |
WINDOW |
仅对比时间窗口内的记录 | 按时间分布的流水(台费、服务记录等) |
运行时配置参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
run.snapshot_missing_delete |
true |
总开关,设为 false 可紧急关闭所有软删除 |
run.snapshot_allow_empty_delete |
false |
API 返回空结果时是否仍触发软删除 |
run.snapshot_protect_early_cutoff |
true |
Early-cutoff 保护开关(仅 WINDOW 模式) |
A.2 Early-Cutoff 保护(2026-02-18 新增)
问题场景:请求时间窗口 2026-01-01 ~ 2026-02-18,但 API 实际只返回 2026-02-01 起的数据(上游截断了更早的数据)。若直接用请求窗口做快照对比,01-01 ~ 01-31 的数据会被误标为软删除。
保护机制:在 SnapshotMode.WINDOW 模式下,收集每个 segment 内 API 返回记录中 snapshot_time_column 的实际最小值,将软删除的窗口起点从 seg_start 收窄至 max(seg_start, actual_earliest)。
请求窗口: [--- 01-01 -------- 02-18 ---]
API 实际返回: [-- 02-01 -- 02-18 --]
↑ actual_earliest
软删除范围: [-- 02-01 -- 02-18 --] ← 收窄后
↑ 01-01~01-31 不受影响
触发条件:
run.snapshot_protect_early_cutoff = true(默认)- 任务的
snapshot_mode = WINDOW - API 返回数据中
snapshot_time_column的最小值晚于请求的seg_start
关闭方式:设置 run.snapshot_protect_early_cutoff = false。
附录 B:窗口分段机制
utils/windowing.py 提供窗口拆分能力:
| 切分单位 | 行为 |
|---|---|
none |
不切分,整个窗口作为一段 |
day |
按天切分(可配置 split_days=1/10/30) |
week |
按 7 天切分 |
month |
按自然月切分 |
补偿机制:compensation_hours 在窗口前后各扩展指定小时数,避免边界数据遗漏。
BaseTask.execute() 中的窗口分段仅在 run.window_override 存在时生效(override_only=True),否则整个窗口作为一段执行。
附录 C:跨层强制全量更新(2026-02-18 新增)
C.1 参数
| 参数 | 默认值 | 说明 |
|---|---|---|
run.force_full_update |
false |
跳过所有变更检测,无条件写入 |
C.2 适用场景
表结构发生变化(新增/修改字段)后,历史数据需要按新结构全量刷新。正常流程中 content_hash 去重和 IS DISTINCT FROM 对比会跳过"看起来没变"的行,导致新字段无法回填。
C.3 各层行为
| 层 | 正常模式 | force_full_update = true |
|---|---|---|
| ODS | content_hash 相同则跳过;ON CONFLICT 带 WHERE IS DISTINCT FROM | 跳过 hash 去重;ON CONFLICT 无条件 DO UPDATE SET |
| DWD 维度表 | _is_row_changed() 逐列对比,无变更则跳过 |
跳过变更检测,所有已存在行强制关闭旧版 + 插入新版 |
| DWD 事实表 | ON CONFLICT 带 WHERE IS DISTINCT FROM | ON CONFLICT 无条件 DO UPDATE SET |
C.4 注意事项
- 该参数会显著增加数据库写入量,仅在结构变更后的一次性刷新中使用
- 刷新完成后应立即关闭(恢复为
false) - 可通过 CLI 临时覆盖:
--set run.force_full_update=true