在准备环境前提交次全部更改。
This commit is contained in:
16
apps/etl/connectors/feiqiu/docs/architecture/README.md
Normal file
16
apps/etl/connectors/feiqiu/docs/architecture/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# 架构设计文档
|
||||
|
||||
本目录存放飞球 ETL 系统的架构设计文档,涵盖系统整体架构、数据流向和模块交互关系。
|
||||
|
||||
## 文档索引
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [system_overview.md](system_overview.md) | 系统整体架构:技术栈、模块交互、执行链路 |
|
||||
| [data_flow.md](data_flow.md) | 数据流向详解:ODS → DWD → DWS 三层架构 |
|
||||
|
||||
## 相关资源
|
||||
|
||||
- [根 README](../../README.md) — 快速开始与命令参考
|
||||
- [数据库文档](../database/README.md) — 表结构与 Schema 说明
|
||||
- [业务规则文档](../business-rules/README.md) — 指数算法、口径定义等
|
||||
126
apps/etl/connectors/feiqiu/docs/architecture/data_flow.md
Normal file
126
apps/etl/connectors/feiqiu/docs/architecture/data_flow.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# 数据流向详解:ODS → DWD → DWS
|
||||
|
||||
## 整体数据流
|
||||
|
||||
```
|
||||
上游 SaaS API / 离线 JSON
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ ODS 层(ods) │
|
||||
│ 操作数据存储 — 原始数据落地 │
|
||||
│ 保留源 payload,便于回溯 │
|
||||
│ 23 张 ODS 表,对应 23 个 API 端点 │
|
||||
└───────────────┬───────────────────────┘
|
||||
│ DWD_LOAD_FROM_ODS
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ DWD 层(dwd) │
|
||||
│ 明细数据 — 清洗、标准化、关联 │
|
||||
│ 维度表走 SCD2(缓慢变化维度) │
|
||||
│ 事实表按时间增量写入 │
|
||||
└───────────────┬───────────────────────┘
|
||||
│ DWS 汇总任务
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ DWS 层(dws) │
|
||||
│ 数据服务 — 汇总、指标 │
|
||||
│ 助教业绩 / 财务日报 / 会员分析 │
|
||||
│ 工资计算 │
|
||||
└───────────────┬───────────────────────┘
|
||||
│ INDEX 指数任务
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ INDEX 层(dws) │
|
||||
│ 自定义指数算法 │
|
||||
│ WBI / NCI / RS / OS / MS / ML │
|
||||
└───────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## ODS 层(操作数据存储)
|
||||
|
||||
- Schema:`ods`
|
||||
- 职责:从上游 SaaS API 抓取原始数据并落地,保留完整源 payload
|
||||
- 数据来源:在线 API 抓取(`APIClient`)或离线 JSON 回放(`LocalJsonClient`)
|
||||
- 任务模式:每个业务实体对应一个 ODS 任务(如 `ODS_PAYMENT`、`ODS_MEMBER`、`ODS_SETTLEMENT_RECORDS` 等),由 `BaseOdsTask` + `OdsTaskSpec` 声明式配置驱动,通过 `_build_task_class()` 工厂函数动态生成任务类
|
||||
- 加载方式:`_insert_records_schema_aware` schema-aware 写入——运行时从 `information_schema` 读取目标表结构,按列名自动匹配,批量 upsert + 冲突处理(`ON CONFLICT` 策略可配置)
|
||||
|
||||
### 核心业务实体(23 个 ODS 任务)
|
||||
|
||||
助教账号档案(assistant_accounts_master)、结账记录(settlement_records)、台费计费流水(table_fee_transactions)、助教服务流水(assistant_service_records)、助教废除记录(assistant_cancellation_records)、门店商品销售流水(store_goods_sales_records)、支付流水(payment_transactions)、退款流水(refund_transactions)、平台/团购券核销(platform_coupon_redemption_records)、会员档案(member_profiles)、会员储值卡(member_stored_value_cards)、会员余额变动(member_balance_changes)、充值结算(recharge_settlements)、团购套餐定义(group_buy_packages)、团购套餐核销(group_buy_redemption_records)、库存汇总(goods_stock_summary)、库存变化记录(goods_stock_movements)、台桌维表(site_tables_master)、库存商品分类树(stock_goods_category_tree)、门店商品档案(store_goods_master)、台费折扣/调账(table_fee_discount_records)、租户商品档案(tenant_goods_master)、结账小票详情(settlement_ticket_details)。
|
||||
|
||||
## DWD 层(明细数据)
|
||||
|
||||
- Schema:`dwd`
|
||||
- 职责:对 ODS 原始数据进行清洗、标准化、关联,生成可分析的明细数据
|
||||
- 核心任务:`DWD_LOAD_FROM_ODS`
|
||||
- 质量检查:`DWD_QUALITY_CHECK`
|
||||
|
||||
### 维度处理(SCD2)
|
||||
|
||||
维度表采用 SCD2(缓慢变化维度 Type 2)策略,由 `scd/` 模块处理:
|
||||
- 会员维度(`dim_member`)
|
||||
- 助教维度(`dim_assistant`)
|
||||
- 商品维度(`dim_product`)
|
||||
- 台桌维度(`dim_table`)
|
||||
- 套餐维度(`dim_package`)
|
||||
|
||||
每条维度记录包含 `valid_from`、`valid_to`、`is_current` 字段,支持历史版本追溯。
|
||||
|
||||
### 事实表处理
|
||||
|
||||
事实表按时间增量写入,由 `loaders/facts/` 中的加载器处理:
|
||||
- 订单事实、支付事实、退款事实、小票明细、充值结算、台费流水等
|
||||
|
||||
## DWS 层(数据服务)
|
||||
|
||||
- Schema:`dws`
|
||||
- 职责:基于 DWD 明细数据进行汇总计算,输出业务指标和分析结果
|
||||
|
||||
### 汇总任务分类
|
||||
|
||||
| 类别 | 任务示例 | 建议频率 |
|
||||
|------|----------|----------|
|
||||
| 助教业绩 | `DWS_ASSISTANT_DAILY`、`DWS_ASSISTANT_MONTHLY` | 每小时 / 每日 |
|
||||
| 财务日报 | `DWS_FINANCE_DAILY`、`DWS_FINANCE_INCOME_STRUCTURE` | 每小时 |
|
||||
| 会员分析 | `DWS_MEMBER_CONSUMPTION`、`DWS_MEMBER_VISIT` | 每日 |
|
||||
| 工资计算 | `DWS_ASSISTANT_SALARY` | 每月(月初) |
|
||||
| 指数算法 | `DWS_WINBACK_INDEX`、`DWS_NEWCONV_INDEX`、`DWS_RELATION_INDEX` | 每 2-4 小时 |
|
||||
|
||||
### 自定义指数算法
|
||||
|
||||
系统实现了六个自定义业务指数,参数存储在 `dws.cfg_index_parameters`:
|
||||
|
||||
| 指数 | 全称 | 说明 |
|
||||
|------|------|------|
|
||||
| WBI | Winback Index | 召回指数 |
|
||||
| NCI | New Conversion Index | 新客转化指数 |
|
||||
| RS | Relation Score | 关系评分 |
|
||||
| OS | Overall Score | 综合评分 |
|
||||
| MS | Member Score | 会员评分 |
|
||||
| ML | Manual Ledger | 人工台账 |
|
||||
|
||||
公共参数:`percentile_lower/upper`(分位截断锚点)、`ewma_alpha`(指数加权移动平均平滑系数)。
|
||||
|
||||
## ETL 管理层
|
||||
|
||||
- Schema:`meta`
|
||||
- 职责:调度元数据管理
|
||||
- 内容:游标(水位)记录、任务运行记录、调度配置
|
||||
- 关键组件:`cursor_manager.py`(水位管理)、`run_tracker.py`(运行记录)
|
||||
|
||||
## 窗口切分与补偿
|
||||
|
||||
大时间范围的数据抓取会按窗口切分执行,避免单次请求数据量过大:
|
||||
|
||||
| 配置项 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `run.window_split.unit` | `day` | 切分单位:day / week / month / none |
|
||||
| `run.window_split.days` | `10` | 切分天数 |
|
||||
| `run.window_split.compensation_hours` | `2` | 补偿小时数(处理跨窗口数据) |
|
||||
|
||||
## 数据质量保障
|
||||
|
||||
- `DWD_QUALITY_CHECK`:DWD 层质量检查
|
||||
- `quality/integrity_service.py`:完整性检查服务(余额一致性等)
|
||||
- `tasks/verification/`:ETL 后置校验(ODS/DWD/DWS/指数校验器)
|
||||
@@ -0,0 +1,126 @@
|
||||
# OdsTaskSpec 参数优化方案
|
||||
|
||||
> 状态:方案 1-4 已完成 ✅ · 方案 5 中长期待办
|
||||
> 完成日期:2026-02-17
|
||||
> 范围:`tasks/ods/ods_tasks.py` 中的 `OdsTaskSpec` 数据类及 `BaseOdsTask.execute()` 相关逻辑
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
当前 `OdsTaskSpec` 有 8 个关键配置参数。经逐一审查,其中 3 个存在设计问题,1 个有轻微隐式耦合。这些问题不影响当前功能正确性,但会增加维护成本、降低可读性、埋下未来改动时的陷阱。
|
||||
|
||||
---
|
||||
|
||||
## 方案 1:清理 OdsTaskSpec 无效/冗余配置 ✅ 已完成(2026-02-17)
|
||||
|
||||
### 实施内容
|
||||
|
||||
在 `ods_tasks.py` 中完成了以下改造:
|
||||
|
||||
- 新增 `SnapshotMode` 枚举(NONE / FULL_TABLE / WINDOW),替代 `snapshot_full_table` + `snapshot_window_columns` 的组合字段
|
||||
- 从 OdsTaskSpec 中删除 7 个无效/冗余字段:
|
||||
- `conflict_columns_override` — 运行时不生效,仅声明性标注
|
||||
- `include_site_column` — 全部 False,硬编码移除
|
||||
- `include_page_no` — 全部 False,硬编码移除
|
||||
- `include_page_size` — 全部 False,硬编码移除
|
||||
- `snapshot_full_table` — 被 `SnapshotMode` 替代
|
||||
- `snapshot_window_columns` — 被 `SnapshotMode` + `snapshot_time_column` 替代
|
||||
- `enable_content_hash_dedup` — 被 `skip_unchanged` 替代
|
||||
- 新增字段:`skip_unchanged: bool = True`、`snapshot_mode: SnapshotMode`、`snapshot_time_column: str | None`
|
||||
- `__post_init__` 校验:WINDOW 必须有 `snapshot_time_column`,FULL_TABLE/NONE 不能有
|
||||
- 23 个 ODS_TASK_SPECS 声明全部迁移到新字段
|
||||
|
||||
### 与原提案的对应关系
|
||||
|
||||
- 原"问题三(`conflict_columns_override` 默认值设反)"→ 直接删除该字段(PK 唯一来源为 DDL)
|
||||
- 原"无问题的参数"中 `include_site_column`、`snapshot_full_table`、`snapshot_window_columns` → 重新评估后删除或替代
|
||||
- 原"问题二方案 A(显式去重开关)"→ 以 `skip_unchanged=True` 实现,默认开启
|
||||
|
||||
---
|
||||
|
||||
## 方案 2:默认开启 skip_unchanged + hash 算法改为 payload + is_delete ✅ 已完成(2026-02-17)
|
||||
|
||||
### 实施内容
|
||||
|
||||
- `skip_unchanged` 默认值为 `True`(原 `enable_content_hash_dedup` 默认 False,22/23 任务关闭)
|
||||
- `_compute_content_hash` 改为基于原始 payload JSON + is_delete 计算 SHA-256
|
||||
- 输入:`json.dumps(payload, sort_keys=True, separators=(',',':'), ensure_ascii=False)` + `|` + `is_delete`
|
||||
- 删除 `_sanitize_record_for_hash` 方法(不再需要字段排除逻辑)
|
||||
- 所有 23 个任务默认开启去重,内容不变则跳过写入
|
||||
|
||||
### 与原提案的对应关系
|
||||
|
||||
- 原"问题二(`include_fetched_at` 副作用隐蔽)"→ 通过改用 payload + is_delete 算 hash,彻底消除了 `include_fetched_at` 对去重的隐式影响
|
||||
- 原"问题二方案 A(显式去重开关)"→ 以 `skip_unchanged` 实现,去重行为透明可控
|
||||
|
||||
---
|
||||
|
||||
## 方案 3:DDL 迁移 — 添加"取最新版本"复合索引 ✅ 已完成(2026-02-17)
|
||||
|
||||
### 实施内容
|
||||
|
||||
- 迁移脚本:`db/etl_feiqiu/migrations/2026-02-17__add_ods_latest_version_indexes.sql`
|
||||
- 为全部 23 张 ODS 表创建 `(业务主键, fetched_at DESC)` 复合索引
|
||||
- 使用 `CREATE INDEX CONCURRENTLY IF NOT EXISTS`,保证幂等且不锁表
|
||||
- 索引命名规范:`idx_ods_{table_name}_latest`
|
||||
- 同步更新 `db/etl_feiqiu/schemas/ods.sql` 中的索引定义
|
||||
|
||||
### 用途
|
||||
|
||||
支持 `DISTINCT ON (id) ORDER BY id, fetched_at DESC` 的高效查询模式,供 DWD 层取最新版本使用。
|
||||
|
||||
---
|
||||
|
||||
## 方案 4:软删除改为"插入删除版本行" ✅ 已完成(2026-02-17)
|
||||
|
||||
### 实施内容
|
||||
|
||||
- `_mark_missing_as_deleted` 从 `UPDATE ... SET is_delete=1` 改为 INSERT 一条 `is_delete=1` 的新版本行
|
||||
- 历史版本行完全不变,保持 ODS 追加写入的语义一致性
|
||||
- 幂等性:若最新版本已是 `is_delete=1`,跳过插入
|
||||
- 接口变更:`window_columns`/`full_table` 参数改为 `snapshot_mode`/`snapshot_time_column`
|
||||
- 软删除双路径覆盖:
|
||||
- 路径 A(API 返回 is_delete):通过 is_delete 参与 hash 计算,自动产生新版本行
|
||||
- 路径 B(快照空缺):`_mark_missing_as_deleted` 读取最新版本 → 构造删除版本 → INSERT
|
||||
|
||||
---
|
||||
|
||||
## 方案 5:冷数据归档 📋 中长期待办
|
||||
|
||||
### 问题描述
|
||||
|
||||
随着 ODS 层版本行持续累积(尤其是方案 2 切换 hash 算法后首次运行会产生全量新版本),历史数据量将持续增长。需要冷数据归档策略来控制 ODS 表体积。
|
||||
|
||||
### 待规划内容
|
||||
|
||||
- 归档策略:按 `fetched_at` 时间窗口,将超过 N 天的历史版本行迁移到归档表或外部存储
|
||||
- 归档粒度:保留每个业务 ID 的最新 K 个版本,其余归档
|
||||
- 归档触发:定时任务 or 手动触发
|
||||
- 回溯支持:归档数据仍可按需查询(冷查询路径)
|
||||
|
||||
### 优先级
|
||||
|
||||
低优先级,待 ODS 数据量达到性能瓶颈时再启动。当前 23 张表的数据量尚在可控范围内。
|
||||
|
||||
---
|
||||
|
||||
## 原提案中未纳入本次改造的问题
|
||||
|
||||
### 问题一:`requires_window` 语义模糊
|
||||
|
||||
原提案建议将 `requires_window` 拆分为 `api_supports_time_filter` + `data_model_type`。本次改造未涉及此项,`requires_window` 保持原样。可作为后续独立优化项。
|
||||
|
||||
### 轻微问题:`include_record_index` 与 `conflict_columns_override` 隐式耦合
|
||||
|
||||
原提案建议在 `__post_init__` 中加校验。由于 `conflict_columns_override` 已在方案 1 中被删除,此耦合问题已自然消解。
|
||||
|
||||
---
|
||||
|
||||
## 回归验证
|
||||
|
||||
本次改造(方案 1-4)配套了完整的属性测试和单元测试:
|
||||
|
||||
- 属性测试文件:`tests/unit/test_ods_dedup_properties.py`(8 个 Property,覆盖 SnapshotMode 校验、hash 确定性/区分性、skip_unchanged、记录数闭合、软删除正确性/幂等性/不修改历史)
|
||||
- 单元测试:适配 `test_ods_tasks.py` 和 `test_debug_ods_properties.py` 到新接口
|
||||
- 测试框架:pytest + hypothesis
|
||||
106
apps/etl/connectors/feiqiu/docs/architecture/system_overview.md
Normal file
106
apps/etl/connectors/feiqiu/docs/architecture/system_overview.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# 系统整体架构
|
||||
|
||||
## 技术栈
|
||||
|
||||
| 类别 | 技术 |
|
||||
|------|------|
|
||||
| 语言 | Python 3.10+ |
|
||||
| 数据库 | PostgreSQL(远程实例) |
|
||||
| DB 驱动 | psycopg2-binary |
|
||||
| HTTP 客户端 | requests |
|
||||
| 日期处理 | python-dateutil / tzdata |
|
||||
| 配置管理 | python-dotenv |
|
||||
| Excel 导入导出 | openpyxl |
|
||||
| 后端 API | FastAPI + uvicorn |
|
||||
| 管理后台 | React + Vite + Ant Design(`apps/admin-web/`) |
|
||||
| 测试 | pytest / hypothesis |
|
||||
|
||||
## 模块交互关系
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 入口层 │
|
||||
│ cli/main.py(CLI) │
|
||||
└──────────┬──────────────────────────────────────────────┘
|
||||
│ AppConfig
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 编排层 │
|
||||
│ orchestration/ │
|
||||
│ ├── flow_runner.py Flow 运行器(执行流程编排) │
|
||||
│ ├── task_executor.py 任务执行器 │
|
||||
│ ├── task_registry.py 任务注册表 │
|
||||
│ ├── topological_sort.py 任务依赖拓扑排序 │
|
||||
│ ├── scheduler.py ETL 调度器(已弃用) │
|
||||
│ ├── cursor_manager.py 游标(水位)管理 │
|
||||
│ └── run_tracker.py 运行记录追踪 │
|
||||
└──────────┬──────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 执行层 │
|
||||
│ tasks/ │
|
||||
│ ├── base_task.py BaseTask 基类 │
|
||||
│ ├── ods/ ODS 抓取任务(23 个业务实体) │
|
||||
│ ├── dwd/ DWD 装载任务(维度/事实/质检) │
|
||||
│ ├── dws/ DWS 汇总与指数任务 │
|
||||
│ │ └── index/ 指数计算(WBI/NCI/RS/OS/MS/ML)│
|
||||
│ ├── utility/ 工具任务(Schema 初始化等) │
|
||||
│ └── verification/ ETL 后置校验 │
|
||||
└──────────┬──────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐ ┌───────────────────────────────┐
|
||||
│ 数据源层 │ │ 数据装载层 │
|
||||
│ api/ │ │ loaders/ │
|
||||
│ ├── APIClient │ │ ├── base_loader.py │
|
||||
│ ├── LocalJsonClient │ │ ├── ods/ ODS 加载器 │
|
||||
│ └── RecordingClient │ │ ├── dimensions/ SCD2 维度 │
|
||||
│ │ │ └── facts/ 事实表 │
|
||||
└───────────────────────┘ └───────────────────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ PostgreSQL │
|
||||
│ ods │ dwd │ dws │ meta │ core │ app │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 执行链路
|
||||
|
||||
系统采用三层架构,执行流程如下:
|
||||
|
||||
1. **CLI 层**(`cli/main.py`):解析命令行参数 → 生成 `AppConfig` → 依赖注入
|
||||
2. **编排层**(`orchestration/flow_runner.py`):Flow 名称或 `--layers` 组合 → 层 → 任务列表解析 → 拓扑排序;`processing_mode` 控制增量/校验流程
|
||||
3. **执行层**(`orchestration/task_executor.py`):`DataSource` 枚举决定 fetch/ingest 路径,含游标管理、运行记录、失败标记
|
||||
|
||||
## 核心架构模式
|
||||
|
||||
### 任务模式
|
||||
|
||||
每个 ETL 任务继承 `BaseTask`,遵循 Extract → Transform → Load 模板方法,在 `orchestration/task_registry.py` 中注册。任务代码采用大写蛇形命名(如 `DWD_LOAD_FROM_ODS`)。
|
||||
|
||||
### 加载器模式
|
||||
|
||||
每张目标表对应一个加载器,继承 `BaseLoader` 并实现 `upsert()` 方法。维度加载器位于 `loaders/dimensions/`(走 SCD2),事实加载器位于 `loaders/facts/`(增量写入)。核心是批量 upsert + 冲突处理策略。
|
||||
|
||||
### 配置分层
|
||||
|
||||
配置按优先级叠加:`config/defaults.py`(默认值)→ `.env` / 环境变量 → CLI 参数覆盖。通过 `AppConfig.get("dotted.path")` 统一访问。
|
||||
|
||||
### API 抽象
|
||||
|
||||
`APIClient`(HTTP 在线抓取)、`LocalJsonClient`(离线 JSON 回放)、`RecordingAPIClient`(抓取 + 落盘)共享相同接口,任务代码无需关心数据来源。
|
||||
|
||||
### Flow(执行流程)模式
|
||||
|
||||
> 术语说明:**Connector**(数据源连接器)指对接的上游 SaaS 平台(如飞球);**Flow**(执行流程)指 ETL 任务的处理链路。CLI 参数 `--flow` 传递 Flow ID(`--pipeline` 作为已弃用别名保留),`--layers` 支持自由组合层级。
|
||||
|
||||
通过 `--data-source` 参数控制:
|
||||
- `hybrid`:在线抓取 + 入库(默认)
|
||||
- `online`:仅在线抓取
|
||||
- `offline`:仅离线入库
|
||||
|
||||
### 调度与水位
|
||||
|
||||
`FlowRunner` 编排任务执行,`cursor_manager` 管理增量水位,`run_tracker` 在 `meta` Schema 中记录运行状态,确保增量正确性和可重复性。`--force-full` 参数可强制全量重跑,忽略游标。
|
||||
Reference in New Issue
Block a user