在准备环境前提交次全部更改。
This commit is contained in:
1
.kiro/specs/etl-dws-flow-refactor/.config.kiro
Normal file
1
.kiro/specs/etl-dws-flow-refactor/.config.kiro
Normal file
@@ -0,0 +1 @@
|
||||
{"generationMode": "requirements-first"}
|
||||
497
.kiro/specs/etl-dws-flow-refactor/design.md
Normal file
497
.kiro/specs/etl-dws-flow-refactor/design.md
Normal file
@@ -0,0 +1,497 @@
|
||||
# 设计文档:ETL DWS/Flow 重构
|
||||
|
||||
## 概述
|
||||
|
||||
本设计覆盖飞球 ETL 连接器的四阶段重构:
|
||||
|
||||
1. **BaseDwsTask 模板方法重构**:在基类中提供默认 extract()/load(),子类通过声明 `DATE_COL` + 实现 `_do_extract()` 即可完成大部分工作;提取公共辅助方法到 `dws_helpers.py`;财务任务共享提取层;合并 MV 刷新 + 数据清理任务;MemberIndexBaseTask 模板方法。
|
||||
2. **--layers CLI 参数**:新增 `--layers ODS,DWD,DWS,INDEX` 自由组合参数,保留 `--pipeline` 作为快捷别名;去掉硬编码回退,统一走 `TaskRegistry.get_tasks_by_layer()`。
|
||||
3. **任务依赖声明**:在 TaskMeta 中增加 `depends_on` 字段,`_resolve_tasks()` 执行拓扑排序。
|
||||
4. **关键词重命名**:`pipeline → flow`(类名、变量名、CLI 参数、日志);`pipelines → connectors`(目录路径)。
|
||||
|
||||
执行顺序严格按 1→2→3→4→收尾,每阶段完成后运行回归测试。
|
||||
|
||||
## 架构
|
||||
|
||||
### 当前架构
|
||||
|
||||
```
|
||||
BaseTask (tasks/base_task.py)
|
||||
└── BaseDwsTask (tasks/dws/base_dws_task.py)
|
||||
├── 15 个 DWS 子类(各自实现 extract/transform/load)
|
||||
└── BaseIndexTask (tasks/dws/index/base_index_task.py)
|
||||
└── MemberIndexBaseTask (tasks/dws/index/member_index_base.py)
|
||||
├── WinbackIndexTask
|
||||
└── NewconvIndexTask
|
||||
|
||||
PipelineRunner (orchestration/pipeline_runner.py)
|
||||
├── PIPELINE_LAYERS: 7 种固定 Flow 定义
|
||||
└── _resolve_tasks(): 层→任务映射(含硬编码回退)
|
||||
|
||||
TaskRegistry (orchestration/task_registry.py)
|
||||
└── TaskMeta: task_class, requires_db_config, layer, task_type
|
||||
```
|
||||
|
||||
### 目标架构
|
||||
|
||||
```
|
||||
BaseTask (tasks/base_task.py) [不变]
|
||||
└── BaseDwsTask (tasks/dws/base_dws_task.py) [新增默认 extract/load]
|
||||
├── DWS 子类(仅声明 DATE_COL + 实现 _do_extract/transform)
|
||||
├── FinanceBaseTask (tasks/dws/finance_base_task.py) [新增]
|
||||
│ ├── FinanceDailyTask
|
||||
│ ├── FinanceRechargeTask
|
||||
│ ├── FinanceIncomeStructureTask
|
||||
│ └── FinanceDiscountDetailTask
|
||||
├── DwsMaintenanceTask (tasks/dws/maintenance_task.py) [合并]
|
||||
└── BaseIndexTask
|
||||
└── MemberIndexBaseTask [新增模板 execute]
|
||||
├── WinbackIndexTask(仅实现 _calculate_scores/_save_results)
|
||||
└── NewconvIndexTask
|
||||
|
||||
FlowRunner (orchestration/flow_runner.py) [重命名]
|
||||
├── FLOW_LAYERS: 保留快捷别名
|
||||
└── _resolve_tasks(): 拓扑排序 + 纯 Registry 解析
|
||||
|
||||
TaskRegistry (orchestration/task_registry.py)
|
||||
└── TaskMeta: + depends_on: list[str]
|
||||
|
||||
dws_helpers.py (tasks/dws/dws_helpers.py) [新增]
|
||||
└── mask_mobile(), calc_days_since(), parse_id_list() 等
|
||||
|
||||
目录: apps/etl/connectors/feiqiu/ [重命名]
|
||||
```
|
||||
|
||||
### 变更影响范围
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[BaseDwsTask 重构] --> B[15 个 DWS 子类简化]
|
||||
A --> C[dws_helpers.py 提取]
|
||||
A --> D[FinanceBaseTask 提取]
|
||||
A --> E[DwsMaintenanceTask 合并]
|
||||
A --> F[MemberIndexBaseTask 模板]
|
||||
G[--layers 参数] --> H[CLI main.py]
|
||||
G --> I[PipelineRunner._resolve_tasks]
|
||||
G --> J[去掉硬编码回退]
|
||||
K[任务依赖] --> L[TaskMeta.depends_on]
|
||||
K --> M[拓扑排序]
|
||||
N[关键词重命名] --> O[PipelineRunner → FlowRunner]
|
||||
N --> P[pipelines → connectors 路径]
|
||||
N --> Q[所有文档更新]
|
||||
```
|
||||
|
||||
## 组件与接口
|
||||
|
||||
### 组件 1:BaseDwsTask 默认模板方法
|
||||
|
||||
```python
|
||||
class BaseDwsTask(BaseTask):
|
||||
DATE_COL: str | None = None # 子类声明日期列名
|
||||
|
||||
def _do_extract(self, context: TaskContext) -> list[dict]:
|
||||
"""子类实现:返回从 DWD 提取的原始行列表。"""
|
||||
raise NotImplementedError
|
||||
|
||||
def extract(self, context: TaskContext) -> dict:
|
||||
"""默认实现:调用 _do_extract 并包装为标准字典。
|
||||
子类可覆盖以自定义提取逻辑。
|
||||
"""
|
||||
rows = self._do_extract(context)
|
||||
return {
|
||||
"rows": rows,
|
||||
"start_date": context.window_start.date(),
|
||||
"end_date": context.window_end.date(),
|
||||
"site_id": context.store_id,
|
||||
}
|
||||
|
||||
def load(self, transformed, context: TaskContext) -> dict:
|
||||
"""默认实现:delete-before-insert 幂等写入。
|
||||
子类可覆盖以自定义加载逻辑。
|
||||
"""
|
||||
if not transformed:
|
||||
return {"counts": {"fetched": 0, "inserted": 0, "updated": 0, "skipped": 0, "errors": 0}}
|
||||
date_col = self.DATE_COL or "stat_date"
|
||||
deleted = self.delete_existing_data(context, date_col=date_col)
|
||||
inserted = self.bulk_insert(transformed)
|
||||
return {
|
||||
"counts": {"fetched": len(transformed), "inserted": inserted, "updated": 0, "skipped": 0, "errors": 0},
|
||||
"extra": {"deleted": deleted},
|
||||
}
|
||||
```
|
||||
|
||||
**设计决策**:
|
||||
- `_do_extract()` 而非直接修改 `extract()` 签名,是为了保持向后兼容——已覆盖 `extract()` 的子类无需改动。
|
||||
- `DATE_COL = None` 作为哨兵值,未声明时 load() 回退到 `"stat_date"` 默认值。
|
||||
- 子类迁移是渐进式的:先在基类添加默认实现,再逐个子类迁移。
|
||||
|
||||
### 组件 2:dws_helpers.py 公共辅助模块
|
||||
|
||||
```python
|
||||
# tasks/dws/dws_helpers.py
|
||||
|
||||
def mask_mobile(phone: str | None) -> str | None:
|
||||
"""手机号脱敏:138****1234"""
|
||||
|
||||
def calc_days_since(target_date: date | None, base_date: date | None = None) -> int | None:
|
||||
"""计算距今天数"""
|
||||
|
||||
def parse_id_list(value: Any) -> set[int]:
|
||||
"""解析逗号分隔的 ID 列表字符串为 int 集合"""
|
||||
|
||||
def safe_division(numerator, denominator, default=Decimal("0")) -> Decimal:
|
||||
"""安全除法,分母为零时返回默认值"""
|
||||
```
|
||||
|
||||
**设计决策**:使用独立模块而非 Mixin,因为这些是纯函数,不依赖实例状态。
|
||||
|
||||
### 组件 3:FinanceBaseTask 共享提取层
|
||||
|
||||
```python
|
||||
class FinanceBaseTask(BaseDwsTask):
|
||||
"""财务任务共享基类,提供公共数据提取方法。"""
|
||||
|
||||
def _extract_settlement_summary(self, site_id, start_date, end_date) -> list[dict]:
|
||||
"""结算汇总提取(共享 SQL)"""
|
||||
|
||||
def _extract_recharge_summary(self, site_id, start_date, end_date) -> list[dict]:
|
||||
"""充值汇总提取"""
|
||||
|
||||
def _extract_groupbuy_summary(self, site_id, start_date, end_date) -> list[dict]:
|
||||
"""团购汇总提取"""
|
||||
|
||||
def _extract_platform_summary(self, site_id, start_date, end_date) -> list[dict]:
|
||||
"""平台结算提取"""
|
||||
```
|
||||
|
||||
**设计决策**:使用继承(FinanceBaseTask)而非 Mixin,因为财务任务的提取方法需要访问 `self.db` 和 `self.config`,且财务任务形成清晰的子类族。
|
||||
|
||||
### 组件 4:DwsMaintenanceTask 合并任务
|
||||
|
||||
```python
|
||||
class DwsMaintenanceTask(BaseDwsTask):
|
||||
"""合并 MV 刷新 + 数据清理为单一维护任务。"""
|
||||
|
||||
def get_task_code(self) -> str:
|
||||
return "DWS_MAINTENANCE"
|
||||
|
||||
def extract(self, context): ...
|
||||
def transform(self, extracted, context): ...
|
||||
|
||||
def load(self, transformed, context) -> dict:
|
||||
stats = {"refreshed": 0, "cleaned": 0}
|
||||
if self._is_mv_enabled():
|
||||
stats["refreshed"] = self._refresh_all_views()
|
||||
if self._is_retention_enabled():
|
||||
stats["cleaned"] = self._cleanup_all_tables(context)
|
||||
return {"counts": stats}
|
||||
```
|
||||
|
||||
**设计决策**:合并后的任务内部复用原 BaseMvRefreshTask 和 DwsRetentionCleanupTask 的核心逻辑,但作为单一任务注册和调度。
|
||||
|
||||
### 组件 5:MemberIndexBaseTask 模板方法
|
||||
|
||||
```python
|
||||
class MemberIndexBaseTask(BaseIndexTask):
|
||||
def execute(self, cursor_data=None) -> dict:
|
||||
context = self._build_context(cursor_data)
|
||||
site_id = self._get_site_id(context)
|
||||
tenant_id = self._get_tenant_id()
|
||||
params = self._load_params()
|
||||
activities = self._build_member_activity(site_id, tenant_id, params)
|
||||
raw_scores = self._calculate_scores(activities, params, site_id, tenant_id)
|
||||
normalized = self.batch_normalize_to_display(raw_scores, ...)
|
||||
result = self._save_results(normalized, site_id, tenant_id, context)
|
||||
return result
|
||||
|
||||
def _calculate_scores(self, activities, params, site_id, tenant_id) -> dict:
|
||||
raise NotImplementedError
|
||||
|
||||
def _save_results(self, normalized, site_id, tenant_id, context) -> dict:
|
||||
raise NotImplementedError
|
||||
```
|
||||
|
||||
### 组件 6:TaskMeta 依赖声明与拓扑排序
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class TaskMeta:
|
||||
task_class: type
|
||||
requires_db_config: bool = True
|
||||
layer: str | None = None
|
||||
task_type: str = "etl"
|
||||
depends_on: list[str] = field(default_factory=list) # 新增
|
||||
```
|
||||
|
||||
拓扑排序算法(Kahn's algorithm):
|
||||
|
||||
```python
|
||||
def topological_sort(task_codes: list[str], registry: TaskRegistry) -> list[str]:
|
||||
"""对任务列表执行拓扑排序。
|
||||
|
||||
- 仅对当前执行列表内的任务排序
|
||||
- depends_on 中引用的任务不在列表内时记录警告
|
||||
- 检测循环依赖并抛出 ValueError
|
||||
"""
|
||||
in_degree = {code: 0 for code in task_codes}
|
||||
graph = {code: [] for code in task_codes}
|
||||
task_set = set(task_codes)
|
||||
|
||||
for code in task_codes:
|
||||
meta = registry.get_metadata(code)
|
||||
if meta and meta.depends_on:
|
||||
for dep in meta.depends_on:
|
||||
if dep in task_set:
|
||||
graph[dep].append(code)
|
||||
in_degree[code] += 1
|
||||
else:
|
||||
logger.warning("任务 %s 依赖 %s,但后者不在当前执行列表中", code, dep)
|
||||
|
||||
queue = deque(code for code in task_codes if in_degree[code] == 0)
|
||||
result = []
|
||||
while queue:
|
||||
node = queue.popleft()
|
||||
result.append(node)
|
||||
for neighbor in graph[node]:
|
||||
in_degree[neighbor] -= 1
|
||||
if in_degree[neighbor] == 0:
|
||||
queue.append(neighbor)
|
||||
|
||||
if len(result) != len(task_codes):
|
||||
cycle_tasks = [c for c in task_codes if c not in result]
|
||||
raise ValueError(f"检测到循环依赖: {cycle_tasks}")
|
||||
|
||||
return result
|
||||
```
|
||||
|
||||
### 组件 7:--layers CLI 参数
|
||||
|
||||
```python
|
||||
# cli/main.py 新增参数
|
||||
parser.add_argument(
|
||||
"--layers",
|
||||
help="ETL 层自由组合,逗号分隔(ODS,DWD,DWS,INDEX)",
|
||||
)
|
||||
|
||||
# 互斥校验
|
||||
if args.layers and args.pipeline:
|
||||
parser.error("--layers 和 --pipeline/--flow 互斥,请只指定其中一个")
|
||||
|
||||
# 层解析
|
||||
VALID_LAYERS = {"ODS", "DWD", "DWS", "INDEX"}
|
||||
def parse_layers(raw: str) -> list[str]:
|
||||
layers = [l.strip().upper() for l in raw.split(",")]
|
||||
invalid = set(layers) - VALID_LAYERS
|
||||
if invalid:
|
||||
raise ValueError(f"无效的层名: {invalid}")
|
||||
return layers
|
||||
```
|
||||
|
||||
### 组件 8:FlowRunner 重命名
|
||||
|
||||
重命名映射:
|
||||
|
||||
| 原名 | 新名 | 文件 |
|
||||
|------|------|------|
|
||||
| `PipelineRunner` | `FlowRunner` | `orchestration/flow_runner.py` |
|
||||
| `PIPELINE_LAYERS` | `FLOW_LAYERS` | 同上 |
|
||||
| `pipeline_runner.py` | `flow_runner.py` | 文件名 |
|
||||
| `--pipeline` | `--flow`(保留 `--pipeline` 弃用别名) | `cli/main.py` |
|
||||
| 日志中 "Pipeline" / "Flow" | 统一为 "Flow" | 全局 |
|
||||
|
||||
### 组件 9:路径重命名 pipelines → connectors
|
||||
|
||||
```
|
||||
apps/etl/pipelines/feiqiu/ → apps/etl/connectors/feiqiu/
|
||||
```
|
||||
|
||||
影响范围:
|
||||
- `pyproject.toml` workspace 成员声明
|
||||
- 所有 `from pipelines.feiqiu...` 导入(ETL 内部使用相对导入,影响较小)
|
||||
- 脚本中的路径引用(`scripts/`、`run_etl.bat`、`run_etl.sh`)
|
||||
- 文档中的路径引用
|
||||
- `.env` 中的路径配置
|
||||
- CI/CD 配置(如有)
|
||||
|
||||
## 数据模型
|
||||
|
||||
### TaskMeta 扩展
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class TaskMeta:
|
||||
task_class: type
|
||||
requires_db_config: bool = True
|
||||
layer: str | None = None
|
||||
task_type: str = "etl"
|
||||
depends_on: list[str] = field(default_factory=list) # 新增:依赖的任务代码列表
|
||||
```
|
||||
|
||||
### 已知任务依赖关系
|
||||
|
||||
| 任务 | 依赖 | 说明 |
|
||||
|------|------|------|
|
||||
| `DWS_ASSISTANT_FINANCE` | `DWS_ASSISTANT_SALARY` | 财务分析需要工资计算结果 |
|
||||
| `DWS_ASSISTANT_MONTHLY` | `DWS_ASSISTANT_DAILY` | 月度汇总基于日度明细 |
|
||||
| `DWS_MAINTENANCE` | 所有其他 DWS 任务 | MV 刷新和清理应在数据写入后执行 |
|
||||
| `DWS_WINBACK_INDEX` | `DWS_MEMBER_VISIT`, `DWS_MEMBER_CONSUMPTION` | 指数计算依赖会员行为数据 |
|
||||
| `DWS_NEWCONV_INDEX` | `DWS_MEMBER_VISIT`, `DWS_MEMBER_CONSUMPTION` | 同上 |
|
||||
| `DWS_RELATION_INDEX` | `DWS_ASSISTANT_DAILY` | 关系指数依赖助教服务记录 |
|
||||
|
||||
### DWS 子类 DATE_COL 映射
|
||||
|
||||
| 任务类 | DATE_COL | 目标表 |
|
||||
|--------|----------|--------|
|
||||
| AssistantDailyTask | `stat_date` | `dws_assistant_daily_detail` |
|
||||
| AssistantMonthlyTask | `stat_month` | `dws_assistant_monthly_summary` |
|
||||
| AssistantCustomerTask | `stat_date` | `dws_assistant_customer_stats` |
|
||||
| AssistantSalaryTask | `salary_month` | `dws_assistant_salary_calc` |
|
||||
| AssistantFinanceTask | `stat_date` | `dws_assistant_finance_analysis` |
|
||||
| MemberConsumptionTask | `stat_date` | `dws_member_consumption_summary` |
|
||||
| MemberVisitTask | `visit_date` | `dws_member_visit_detail` |
|
||||
| FinanceDailyTask | `stat_date` | `dws_finance_daily_summary` |
|
||||
| FinanceRechargeTask | `stat_date` | `dws_finance_recharge_summary` |
|
||||
| FinanceIncomeStructureTask | `stat_date` | `dws_finance_income_structure` |
|
||||
| FinanceDiscountDetailTask | `stat_date` | `dws_finance_discount_detail` |
|
||||
|
||||
|
||||
## 正确性属性
|
||||
|
||||
*正确性属性是系统在所有合法执行中都应保持为真的特征或行为——本质上是关于系统应该做什么的形式化陈述。属性是人类可读规格说明与机器可验证正确性保证之间的桥梁。*
|
||||
|
||||
### Property 1:默认 extract() 返回标准结构
|
||||
|
||||
*对于任意* 声明了 DATE_COL 且未覆盖 extract() 的 BaseDwsTask 子类,以及任意合法的 TaskContext,调用 extract(context) 应返回包含 "rows"、"start_date"、"end_date"、"site_id" 键的字典,且 "rows" 的值等于 _do_extract(context) 的返回值。
|
||||
|
||||
**Validates: Requirements 1.1, 1.4**
|
||||
|
||||
### Property 2:默认 load() 幂等写入与标准统计
|
||||
|
||||
*对于任意* 非空的 transformed 列表和任意合法的 TaskContext,BaseDwsTask 默认 load() 应返回包含 "counts" 键的字典,其中 "counts" 包含 "fetched"、"inserted"、"updated"、"skipped"、"errors" 五个整数键,且 "fetched" 等于 len(transformed)。对于空的 transformed 列表,所有计数应为 0。
|
||||
|
||||
**Validates: Requirements 1.2, 1.5**
|
||||
|
||||
### Property 3:dws_helpers 函数等价性
|
||||
|
||||
*对于任意* 合法输入,dws_helpers 模块中的 mask_mobile()、calc_days_since()、parse_id_list() 函数应产生与原子类内联实现完全相同的输出。具体地:
|
||||
- *对于任意* 11 位数字字符串,mask_mobile() 应返回中间 4 位被 `****` 替换的字符串
|
||||
- *对于任意* 两个 date 对象(target_date, base_date),calc_days_since() 应返回 (base_date - target_date).days
|
||||
- *对于任意* 包含逗号分隔整数的字符串,parse_id_list() 应返回对应的 int 集合
|
||||
|
||||
**Validates: Requirements 2.3**
|
||||
|
||||
### Property 4:DwsMaintenanceTask 配置控制
|
||||
|
||||
*对于任意* mv_enabled 和 retention_enabled 的布尔组合,DwsMaintenanceTask.load() 应:
|
||||
- 当 mv_enabled=True 时执行物化视图刷新,否则跳过
|
||||
- 当 retention_enabled=True 时执行数据清理,否则跳过
|
||||
- 返回的统计字典始终包含 "refreshed" 和 "cleaned" 键
|
||||
|
||||
**Validates: Requirements 4.3, 4.4**
|
||||
|
||||
### Property 5:--layers 解析正确性
|
||||
|
||||
*对于任意* {ODS, DWD, DWS, INDEX} 的非空子集,以逗号分隔拼接为字符串后,parse_layers() 应返回包含且仅包含该子集元素的列表,且元素均为大写。对于包含无效层名的字符串,parse_layers() 应抛出 ValueError。
|
||||
|
||||
**Validates: Requirements 6.1, 6.2**
|
||||
|
||||
### Property 6:配置优先级——配置值优先于 Registry
|
||||
|
||||
*对于任意* 层名和任意非空的配置任务列表,_resolve_tasks() 应返回配置中指定的任务列表,而非 TaskRegistry.get_tasks_by_layer() 的结果。当配置为空时,应返回 Registry 的结果。
|
||||
|
||||
**Validates: Requirements 7.2**
|
||||
|
||||
### Property 7:拓扑排序正确性
|
||||
|
||||
*对于任意* 有向无环图(DAG)表示的任务依赖关系,topological_sort() 返回的列表中,每个任务的所有依赖(在当前列表内的)都应排在该任务之前。
|
||||
|
||||
**Validates: Requirements 8.3**
|
||||
|
||||
### Property 8:循环依赖检测
|
||||
|
||||
*对于任意* 包含至少一个环的有向图表示的任务依赖关系,topological_sort() 应抛出 ValueError,且错误信息中包含环中涉及的任务代码。
|
||||
|
||||
**Validates: Requirements 8.4**
|
||||
|
||||
## 错误处理
|
||||
|
||||
### BaseDwsTask 模板方法
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|----------|
|
||||
| 子类未实现 _do_extract() 且未覆盖 extract() | 抛出 NotImplementedError |
|
||||
| 子类未声明 DATE_COL | load() 回退到 "stat_date" 默认值 |
|
||||
| _do_extract() 返回 None | extract() 将 rows 设为空列表 |
|
||||
| bulk_insert() 失败 | 异常向上传播,由 BaseTask.execute() 的 try/except 捕获并 rollback |
|
||||
|
||||
### 拓扑排序
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|----------|
|
||||
| 循环依赖 | 抛出 ValueError,包含循环涉及的任务列表 |
|
||||
| 依赖任务不在执行列表中 | 记录 WARNING 日志,继续执行 |
|
||||
| 空任务列表 | 返回空列表 |
|
||||
|
||||
### CLI 参数
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|----------|
|
||||
| --layers 和 --pipeline/--flow 同时指定 | argparse 报错退出 |
|
||||
| --layers 包含无效层名 | 抛出 ValueError,提示有效层名 |
|
||||
| --pipeline 使用已弃用参数 | 输出 DeprecationWarning,正常执行 |
|
||||
|
||||
### 路径重命名
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|----------|
|
||||
| 导入路径未更新 | ImportError,需在重命名脚本中全量扫描 |
|
||||
| 配置文件中的旧路径 | 启动时检查并输出警告 |
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 测试框架
|
||||
|
||||
- 单元测试:`pytest`
|
||||
- 属性测试:`hypothesis`(Python 的属性测试库)
|
||||
- 测试工具:`apps/etl/pipelines/feiqiu/tests/unit/task_test_utils.py` 提供 FakeDB/FakeAPI
|
||||
|
||||
### 属性测试配置
|
||||
|
||||
- 每个属性测试最少运行 100 次迭代
|
||||
- 使用 `@settings(max_examples=100)` 配置
|
||||
- 每个属性测试用注释标注对应的设计属性编号
|
||||
- 标注格式:`# Feature: etl-dws-flow-refactor, Property N: <属性标题>`
|
||||
|
||||
### 双轨测试方法
|
||||
|
||||
**属性测试**(验证普遍性质):
|
||||
- Property 1-2:BaseDwsTask 默认模板方法的返回值结构和行为
|
||||
- Property 3:dws_helpers 函数等价性
|
||||
- Property 4:DwsMaintenanceTask 配置控制
|
||||
- Property 5:--layers 解析正确性
|
||||
- Property 6:配置优先级
|
||||
- Property 7:拓扑排序正确性
|
||||
- Property 8:循环依赖检测
|
||||
|
||||
**单元测试**(验证具体示例和边界条件):
|
||||
- DwsMaintenanceTask 执行顺序(先刷新后清理)
|
||||
- TaskRegistry 注册项替换(旧任务移除、新任务添加)
|
||||
- --pipeline 快捷别名映射
|
||||
- --layers 和 --pipeline 互斥报错
|
||||
- --pipeline 弃用警告
|
||||
- 空 Registry 返回空列表(边界条件)
|
||||
- 依赖任务不在执行列表中的警告(边界条件)
|
||||
- 路径重命名后的导入正确性
|
||||
|
||||
### 测试文件组织
|
||||
|
||||
| 测试文件 | 内容 |
|
||||
|----------|------|
|
||||
| `tests/unit/test_base_dws_template.py` | Property 1-2 + BaseDwsTask 模板方法单元测试 |
|
||||
| `tests/unit/test_dws_helpers.py` | Property 3 + dws_helpers 函数单元测试 |
|
||||
| `tests/unit/test_maintenance_task.py` | Property 4 + DwsMaintenanceTask 单元测试 |
|
||||
| `tests/unit/test_layers_cli.py` | Property 5 + --layers CLI 参数单元测试 |
|
||||
| `tests/unit/test_resolve_tasks.py` | Property 6 + _resolve_tasks 配置优先级单元测试 |
|
||||
| `tests/unit/test_topological_sort.py` | Property 7-8 + 拓扑排序单元测试 |
|
||||
| `tests/unit/test_flow_rename.py` | FlowRunner 重命名相关单元测试 |
|
||||
| `tests/test_etl_refactor_properties.py` | Monorepo 级属性测试(根目录) |
|
||||
167
.kiro/specs/etl-dws-flow-refactor/requirements.md
Normal file
167
.kiro/specs/etl-dws-flow-refactor/requirements.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# 需求文档:ETL DWS/Flow 重构
|
||||
|
||||
## 简介
|
||||
|
||||
对 NeoZQYY Monorepo 的飞球 ETL 连接器进行大型重构,涵盖四个主要方向:
|
||||
1. BaseDwsTask 模板方法重构——消除 DWS 子类中的样板代码
|
||||
2. `--layers` CLI 参数替代固定 pipeline 名称——提升用户体验
|
||||
3. 任务依赖声明与拓扑排序——消除隐式依赖风险
|
||||
4. 关键词重命名(pipeline → flow、pipelines → connectors)——统一术语与路径
|
||||
|
||||
执行顺序严格按 1→2→3→4→收尾,每一步完成后进行回归测试。
|
||||
|
||||
## 术语表
|
||||
|
||||
- **BaseDwsTask**:DWS 层任务基类,位于 `tasks/dws/base_dws_task.py`,提供 DWD 数据读取、幂等写入、配置缓存等通用能力
|
||||
- **BaseIndexTask**:INDEX 层指数算法基类,继承 BaseDwsTask,位于 `tasks/dws/index/base_index_task.py`
|
||||
- **MemberIndexBaseTask**:会员指数共享基类,继承 BaseIndexTask,位于 `tasks/dws/index/member_index_base.py`
|
||||
- **TaskRegistry**:任务注册表,维护 task_code → TaskMeta 映射,位于 `orchestration/task_registry.py`
|
||||
- **TaskMeta**:任务元数据数据类,包含 task_class、requires_db_config、layer、task_type 字段
|
||||
- **PipelineRunner**:Flow 编排器,根据 Flow 定义执行多层 ETL 任务,位于 `orchestration/pipeline_runner.py`
|
||||
- **TaskExecutor**:单任务执行器,管理游标、运行记录和任务生命周期,位于 `orchestration/task_executor.py`
|
||||
- **Flow**:ETL 编排单元,定义一组按层顺序执行的任务集合(原名 pipeline)
|
||||
- **Layer**:ETL 数据处理层级,包括 ODS、DWD、DWS、INDEX
|
||||
- **Connector**:ETL 连接器,对接特定上游 SaaS 的数据抽取模块(原名 pipeline 目录)
|
||||
- **DATE_COL**:DWS 子类声明的日期列名,用于 extract 和 delete_existing_data 的时间过滤
|
||||
- **TaskContext**:运行期上下文数据类,包含 store_id、window_start/end、window_minutes、cursor
|
||||
- **拓扑排序**:根据任务间依赖关系确定执行顺序的算法,确保被依赖任务先于依赖方执行
|
||||
- **幂等**:同一操作执行多次与执行一次效果相同,本系统通过 delete-before-insert 实现
|
||||
|
||||
## 需求
|
||||
|
||||
### 需求 1:BaseDwsTask 默认 extract/load 模板方法
|
||||
|
||||
**用户故事:** 作为 ETL 开发者,我希望 BaseDwsTask 提供默认的 extract() 和 load() 实现,以便 DWS 子类只需声明 DATE_COL 并实现 _do_extract() 和 transform(),从而减少每个子类 20-30 行样板代码。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 一个 DWS 子类声明了 DATE_COL 类属性且未覆盖 extract(),THE BaseDwsTask SHALL 使用 DATE_COL 从 DWD 层按时间窗口提取数据并传递给 transform()
|
||||
2. WHEN 一个 DWS 子类声明了 DATE_COL 类属性且未覆盖 load(),THE BaseDwsTask SHALL 执行 delete_existing_data(date_col=DATE_COL) 后调用 bulk_insert(),并返回标准统计字典
|
||||
3. WHEN 一个 DWS 子类覆盖了 extract() 或 load(),THE BaseDwsTask SHALL 使用子类的覆盖实现而非默认实现
|
||||
4. WHEN 默认 extract() 执行时,THE BaseDwsTask SHALL 调用子类实现的 _do_extract(context) 方法获取原始数据
|
||||
5. THE BaseDwsTask 默认 load() SHALL 返回包含 fetched、inserted、updated、skipped、errors 键的统计字典
|
||||
|
||||
### 需求 2:DWS 公共辅助方法提取
|
||||
|
||||
**用户故事:** 作为 ETL 开发者,我希望将散落在多个 DWS 子类中的重复辅助方法提取到公共位置,以便消除代码重复并统一行为。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE dws_helpers 模块 SHALL 提供 _mask_mobile()、_calc_days_since()、_parse_id_list() 等公共辅助函数
|
||||
2. WHEN 多个 DWS 子类使用相同的辅助逻辑时,THE 子类 SHALL 调用 dws_helpers 中的公共实现而非各自维护副本
|
||||
3. WHEN dws_helpers 中的辅助函数被调用时,THE 函数 SHALL 产生与原子类内联实现完全相同的输出结果
|
||||
|
||||
### 需求 3:财务任务共享提取层
|
||||
|
||||
**用户故事:** 作为 ETL 开发者,我希望财务类 DWS 任务(FinanceDailyTask、FinanceRechargeTask、FinanceIncomeStructureTask、FinanceDiscountDetailTask)共享数据提取逻辑,以便减少重复的 SQL 查询和数据获取代码。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE FinanceExtractMixin 或 FinanceBaseTask SHALL 提供财务任务共用的数据提取方法(结算汇总、充值汇总、团购汇总等)
|
||||
2. WHEN 财务类 DWS 子类执行 extract() 时,THE 子类 SHALL 通过共享提取层获取公共数据,仅补充各自特有的提取逻辑
|
||||
3. WHEN 共享提取层返回数据时,THE 数据 SHALL 与原各子类独立提取的结果在数值精度和字段结构上完全一致
|
||||
|
||||
### 需求 4:MV 刷新与数据清理任务合并
|
||||
|
||||
**用户故事:** 作为 ETL 运维人员,我希望将 DWS_MV_REFRESH_FINANCE_DAILY、DWS_MV_REFRESH_ASSISTANT_DAILY 和 DWS_RETENTION_CLEANUP 三个任务合并为一个统一的维护任务,以便简化调度配置和减少任务数量。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE 合并后的 DWS_MAINTENANCE 任务 SHALL 在单次执行中完成物化视图刷新和历史数据清理
|
||||
2. WHEN DWS_MAINTENANCE 任务执行时,THE 任务 SHALL 先执行物化视图刷新,再执行数据清理
|
||||
3. WHEN 物化视图刷新或数据清理功能被配置为禁用时,THE DWS_MAINTENANCE 任务 SHALL 跳过对应步骤并记录日志
|
||||
4. WHEN DWS_MAINTENANCE 任务完成时,THE 任务 SHALL 返回包含刷新视图数和清理行数的统计信息
|
||||
5. THE TaskRegistry SHALL 移除原 DWS_MV_REFRESH_FINANCE_DAILY、DWS_MV_REFRESH_ASSISTANT_DAILY、DWS_RETENTION_CLEANUP 三个注册项,替换为 DWS_MAINTENANCE
|
||||
|
||||
### 需求 5:MemberIndexBaseTask 模板方法
|
||||
|
||||
**用户故事:** 作为 ETL 开发者,我希望 MemberIndexBaseTask 提供模板方法 execute(),以便子类(WinbackIndexTask、NewconvIndexTask)只需实现 _calculate_scores() 和 _save_results(),减少重复的编排代码。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE MemberIndexBaseTask SHALL 提供 execute() 模板方法,按顺序执行:获取站点信息 → 加载参数 → 构建会员活动数据 → 调用 _calculate_scores() → 归一化 → 调用 _save_results()
|
||||
2. WHEN 子类实现 _calculate_scores(member_activities, params) 时,THE 方法 SHALL 接收会员活动数据和参数字典,返回原始评分字典
|
||||
3. WHEN 子类实现 _save_results(normalized_scores, context) 时,THE 方法 SHALL 接收归一化后的评分和上下文,完成数据持久化
|
||||
4. WHEN MemberIndexBaseTask 的 execute() 执行完成时,THE 方法 SHALL 返回与原子类 execute() 相同结构的结果字典
|
||||
|
||||
### 需求 6:--layers CLI 参数
|
||||
|
||||
**用户故事:** 作为 ETL 运维人员,我希望使用 `--layers ODS,DWD,DWS,INDEX` 的自由组合方式替代固定的 pipeline 名称,以便更灵活地控制 ETL 执行范围。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 用户指定 `--layers ODS,DWD` 时,THE CLI SHALL 解析为 ["ODS", "DWD"] 层列表并按顺序执行对应任务
|
||||
2. WHEN 用户指定 `--layers` 参数时,THE CLI SHALL 接受 ODS、DWD、DWS、INDEX 四个层的任意组合
|
||||
3. THE CLI SHALL 保留 `--pipeline` 参数作为快捷别名(如 `--pipeline api_full` 等价于 `--layers ODS,DWD,DWS,INDEX`)
|
||||
4. WHEN 用户同时指定 `--layers` 和 `--pipeline` 时,THE CLI SHALL 报错并提示两者互斥
|
||||
5. WHEN `--layers` 包含 DWS 或 INDEX 层时,THE PipelineRunner SHALL 跳过完整性校验或仅执行轻量级行数校验
|
||||
|
||||
### 需求 7:统一层→任务解析
|
||||
|
||||
**用户故事:** 作为 ETL 开发者,我希望去掉 _resolve_tasks() 中的硬编码回退列表,统一走 TaskRegistry.get_tasks_by_layer() 获取任务,以便新增任务时只需在 TaskRegistry 注册即可自动纳入 Flow。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE PipelineRunner._resolve_tasks() SHALL 仅通过 TaskRegistry.get_tasks_by_layer() 获取各层任务列表,移除所有硬编码回退列表
|
||||
2. WHEN 配置中指定了 run.ods_tasks / run.dws_tasks / run.index_tasks 时,THE _resolve_tasks() SHALL 优先使用配置值
|
||||
3. WHEN TaskRegistry.get_tasks_by_layer() 返回空列表且无配置覆盖时,THE _resolve_tasks() SHALL 记录警告日志并返回空列表
|
||||
|
||||
### 需求 8:任务依赖声明
|
||||
|
||||
**用户故事:** 作为 ETL 开发者,我希望在 TaskMeta 中声明任务间的依赖关系,以便系统自动进行拓扑排序,消除隐式依赖导致的执行顺序错误。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE TaskMeta 数据类 SHALL 包含 depends_on: list[str] 字段,默认为空列表
|
||||
2. WHEN 注册任务时指定 depends_on 时,THE TaskRegistry SHALL 存储依赖关系
|
||||
3. WHEN _resolve_tasks() 生成任务列表时,THE PipelineRunner SHALL 对任务列表执行拓扑排序,确保被依赖任务排在依赖方之前
|
||||
4. WHEN 任务依赖关系中存在循环依赖时,THE 拓扑排序 SHALL 抛出明确的错误信息,指出循环涉及的任务
|
||||
5. WHEN 任务 A 声明 depends_on 包含任务 B,且任务 B 不在当前执行列表中时,THE 拓扑排序 SHALL 记录警告日志但继续执行
|
||||
|
||||
### 需求 9:关键词重命名 pipeline → flow
|
||||
|
||||
**用户故事:** 作为 ETL 开发者,我希望将代码中所有 "pipeline" 相关术语统一为 "flow",以便术语一致性和代码可读性。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE PipelineRunner 类 SHALL 重命名为 FlowRunner
|
||||
2. THE PIPELINE_LAYERS 常量 SHALL 重命名为 FLOW_LAYERS
|
||||
3. THE CLI 参数 `--pipeline` SHALL 重命名为 `--flow`,同时保留 `--pipeline` 作为已弃用别名
|
||||
4. WHEN 用户使用已弃用的 `--pipeline` 参数时,THE CLI SHALL 输出弃用警告并正常执行
|
||||
5. THE 代码中所有 pipeline_runner 模块名 SHALL 重命名为 flow_runner
|
||||
6. THE 所有日志消息、注释和文档中的 "pipeline" 术语 SHALL 替换为 "flow"
|
||||
|
||||
### 需求 10:路径重命名 pipelines → connectors
|
||||
|
||||
**用户故事:** 作为 ETL 开发者,我希望将 `apps/etl/pipelines` 目录重命名为 `apps/etl/connectors`,以便目录名准确反映其"连接器"语义。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE 目录 `apps/etl/pipelines/` SHALL 重命名为 `apps/etl/connectors/`
|
||||
2. WHEN 路径重命名完成后,THE 所有 Python 导入路径 SHALL 更新为使用新路径
|
||||
3. WHEN 路径重命名完成后,THE 所有配置文件、脚本和文档中的旧路径引用 SHALL 更新为新路径
|
||||
4. WHEN 路径重命名完成后,THE pyproject.toml 中的 workspace 成员声明 SHALL 更新为新路径
|
||||
5. WHEN 路径重命名完成后,THE 所有测试 SHALL 通过且无导入错误
|
||||
|
||||
### 需求 11:回归测试与数据验证
|
||||
|
||||
**用户故事:** 作为 ETL 运维人员,我希望重构后的系统通过完整的回归测试,以便确保数据处理无错误和偏移。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN BaseDwsTask 模板方法重构完成后,THE 所有现有 DWS 单元测试 SHALL 通过且无失败
|
||||
2. WHEN --layers 参数实现完成后,THE CLI 参数解析测试 SHALL 覆盖所有合法和非法的层组合
|
||||
3. WHEN 任务依赖声明实现完成后,THE 拓扑排序测试 SHALL 覆盖正常依赖、循环依赖和缺失依赖场景
|
||||
4. WHEN 关键词和路径重命名完成后,THE 所有现有测试 SHALL 通过且无导入错误
|
||||
5. WHEN 整体重构完成后,THE 系统 SHALL 通过端到端 dry-run 测试,验证 ODS→DWD→DWS→INDEX 全链路无异常
|
||||
|
||||
### 需求 12:文档同步更新
|
||||
|
||||
**用户故事:** 作为 ETL 开发者,我希望所有相关文档在重构后同步更新,以便文档与代码保持一致。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 重构完成后,THE `docs/etl-feiqiu-architecture.md` SHALL 反映所有类名、方法名和术语变更
|
||||
2. WHEN 重构完成后,THE `apps/etl/pipelines/feiqiu/docs/` 下的所有文档 SHALL 更新路径引用和术语
|
||||
3. WHEN 重构完成后,THE CLI 帮助文本和示例 SHALL 反映新的 `--layers` 和 `--flow` 参数
|
||||
4. WHEN 重构完成后,THE `tasks/README.md` SHALL 更新任务列表和继承关系说明
|
||||
190
.kiro/specs/etl-dws-flow-refactor/tasks.md
Normal file
190
.kiro/specs/etl-dws-flow-refactor/tasks.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# 实施计划:ETL DWS/Flow 重构
|
||||
|
||||
## 概述
|
||||
|
||||
按 4 个阶段顺序实施:BaseDwsTask 模板方法重构 → --layers CLI 参数 → 任务依赖声明 → 关键词/路径重命名。每个阶段完成后运行回归测试。
|
||||
|
||||
## 任务
|
||||
|
||||
- [x] 1. BaseDwsTask 默认模板方法
|
||||
- [x] 1.1 在 BaseDwsTask 中添加 DATE_COL 类属性和默认 extract()/load() 实现
|
||||
- 添加 `DATE_COL: str | None = None` 类属性
|
||||
- 添加 `_do_extract(self, context) -> list[dict]` 抽象方法(raise NotImplementedError)
|
||||
- 实现默认 `extract()`:调用 `_do_extract()` 并包装为标准字典
|
||||
- 实现默认 `load()`:delete_existing_data + bulk_insert,返回标准统计字典
|
||||
- _Requirements: 1.1, 1.2, 1.4, 1.5_
|
||||
|
||||
- [x] 1.2 编写 BaseDwsTask 默认模板方法的属性测试
|
||||
- **Property 1: 默认 extract() 返回标准结构**
|
||||
- **Validates: Requirements 1.1, 1.4**
|
||||
- **Property 2: 默认 load() 幂等写入与标准统计**
|
||||
- **Validates: Requirements 1.2, 1.5**
|
||||
|
||||
- [x] 1.3 迁移 DWS 子类使用默认模板方法
|
||||
- 为每个 DWS 子类声明 DATE_COL
|
||||
- 将各子类的 extract() 逻辑迁移到 _do_extract()
|
||||
- 移除与默认 load() 行为一致的子类 load() 覆盖
|
||||
- 保留有自定义逻辑的子类覆盖(如 AssistantSalaryTask 的月度删除)
|
||||
- 涉及文件:assistant_daily_task.py, assistant_monthly_task.py, assistant_customer_task.py, assistant_finance_task.py, member_consumption_task.py, member_visit_task.py, finance_daily_task.py, finance_recharge_task.py, finance_income_task.py, finance_discount_task.py
|
||||
- _Requirements: 1.1, 1.2, 1.3_
|
||||
|
||||
- [x] 1.4 运行现有 DWS 单元测试确认无回归
|
||||
- `cd apps/etl/pipelines/feiqiu && pytest tests/unit/test_dws_tasks.py -v`
|
||||
- _Requirements: 11.1_
|
||||
|
||||
- [x] 2. 公共辅助方法提取与财务基类
|
||||
- [x] 2.1 创建 dws_helpers.py 公共辅助模块
|
||||
- 创建 `tasks/dws/dws_helpers.py`
|
||||
- 提取 mask_mobile()、calc_days_since()、parse_id_list()、safe_division() 等函数
|
||||
- 更新各 DWS 子类的导入,替换内联实现为 dws_helpers 调用
|
||||
- _Requirements: 2.1, 2.2_
|
||||
|
||||
- [x] 2.2 编写 dws_helpers 函数等价性属性测试
|
||||
- **Property 3: dws_helpers 函数等价性**
|
||||
- **Validates: Requirements 2.3**
|
||||
|
||||
- [x] 2.3 创建 FinanceBaseTask 共享提取层
|
||||
- 创建 `tasks/dws/finance_base_task.py`
|
||||
- 从 FinanceDailyTask 提取共享方法:_extract_settlement_summary, _extract_recharge_summary, _extract_groupbuy_summary, _extract_platform_summary
|
||||
- 迁移 FinanceDailyTask, FinanceRechargeTask, FinanceIncomeStructureTask, FinanceDiscountDetailTask 继承 FinanceBaseTask
|
||||
- _Requirements: 3.1, 3.2, 3.3_
|
||||
|
||||
- [x] 3. MV 刷新与数据清理合并 + MemberIndexBaseTask 模板
|
||||
- [x] 3.1 创建 DwsMaintenanceTask 合并任务
|
||||
- 创建 `tasks/dws/maintenance_task.py`
|
||||
- 合并 BaseMvRefreshTask 和 DwsRetentionCleanupTask 的核心逻辑
|
||||
- 在 TaskRegistry 中注册 DWS_MAINTENANCE,移除原三个任务注册
|
||||
- 更新 `tasks/dws/__init__.py` 导出
|
||||
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_
|
||||
|
||||
- [x] 3.2 编写 DwsMaintenanceTask 属性测试和单元测试
|
||||
- **Property 4: DwsMaintenanceTask 配置控制**
|
||||
- **Validates: Requirements 4.3, 4.4**
|
||||
- 单元测试:执行顺序(先刷新后清理)、注册项替换
|
||||
|
||||
- [x] 3.3 重构 MemberIndexBaseTask 模板方法
|
||||
- 在 MemberIndexBaseTask 中实现 execute() 模板方法
|
||||
- 添加 _calculate_scores() 和 _save_results() 抽象方法
|
||||
- 迁移 WinbackIndexTask 和 NewconvIndexTask 使用新模板
|
||||
- _Requirements: 5.1, 5.2, 5.3, 5.4_
|
||||
|
||||
- [x] 4. 检查点 - 阶段 1 回归测试
|
||||
- 运行 `cd apps/etl/pipelines/feiqiu && pytest tests/unit -v` 确保所有测试通过
|
||||
- 确保所有测试通过,如有问题请询问用户
|
||||
|
||||
- [x] 5. --layers CLI 参数与统一层解析
|
||||
- [x] 5.1 实现 --layers CLI 参数
|
||||
- 在 cli/main.py 中添加 `--layers` 参数
|
||||
- 实现 parse_layers() 函数:解析逗号分隔的层名,校验合法性
|
||||
- 添加 --layers 和 --pipeline 互斥校验
|
||||
- 更新 main() 函数:当指定 --layers 时,构造层列表传递给 PipelineRunner
|
||||
- _Requirements: 6.1, 6.2, 6.3, 6.4_
|
||||
|
||||
- [x] 5.2 编写 --layers 解析属性测试
|
||||
- **Property 5: --layers 解析正确性**
|
||||
- **Validates: Requirements 6.1, 6.2**
|
||||
|
||||
- [x] 5.3 统一 _resolve_tasks() 去掉硬编码回退
|
||||
- 移除 _resolve_tasks() 中所有硬编码回退列表
|
||||
- 统一走 TaskRegistry.get_tasks_by_layer() 获取任务
|
||||
- 保留配置优先级:run.ods_tasks / run.dws_tasks / run.index_tasks > Registry
|
||||
- 空 Registry + 无配置时记录警告并返回空列表
|
||||
- _Requirements: 7.1, 7.2, 7.3_
|
||||
|
||||
- [x] 5.4 编写配置优先级属性测试
|
||||
- **Property 6: 配置优先级——配置值优先于 Registry**
|
||||
- **Validates: Requirements 7.2**
|
||||
|
||||
- [x] 5.5 实现 DWS/INDEX 层轻量级校验
|
||||
- 当 --layers 包含 DWS 或 INDEX 时,跳过完整性校验或仅执行行数校验
|
||||
- _Requirements: 6.5_
|
||||
|
||||
- [x] 6. 任务依赖声明与拓扑排序
|
||||
- [x] 6.1 扩展 TaskMeta 添加 depends_on 字段
|
||||
- 在 TaskMeta 数据类中添加 `depends_on: list[str] = field(default_factory=list)`
|
||||
- 更新 TaskRegistry.register() 接受 depends_on 参数
|
||||
- 为已知依赖关系添加声明(DWS_ASSISTANT_FINANCE → DWS_ASSISTANT_SALARY 等)
|
||||
- _Requirements: 8.1, 8.2_
|
||||
|
||||
- [x] 6.2 实现拓扑排序函数
|
||||
- 创建 `orchestration/topological_sort.py`
|
||||
- 实现 Kahn's algorithm 拓扑排序
|
||||
- 处理循环依赖检测(抛出 ValueError)
|
||||
- 处理缺失依赖警告(记录日志继续执行)
|
||||
- 在 PipelineRunner._resolve_tasks() 中集成拓扑排序
|
||||
- _Requirements: 8.3, 8.4, 8.5_
|
||||
|
||||
- [x] 6.3 编写拓扑排序属性测试
|
||||
- **Property 7: 拓扑排序正确性**
|
||||
- **Validates: Requirements 8.3**
|
||||
- **Property 8: 循环依赖检测**
|
||||
- **Validates: Requirements 8.4**
|
||||
|
||||
- [x] 7. 检查点 - 阶段 2+3 回归测试
|
||||
- 运行 `cd apps/etl/pipelines/feiqiu && pytest tests/unit -v` 确保所有测试通过
|
||||
- 运行 `cd C:\NeoZQYY && pytest tests/ -v` 确保 Monorepo 属性测试通过
|
||||
- 确保所有测试通过,如有问题请询问用户
|
||||
|
||||
- [x] 8. 关键词重命名 pipeline → flow
|
||||
- [x] 8.1 重命名 PipelineRunner → FlowRunner
|
||||
- 将 `orchestration/pipeline_runner.py` 重命名为 `orchestration/flow_runner.py`
|
||||
- 类名 PipelineRunner → FlowRunner
|
||||
- 常量 PIPELINE_LAYERS → FLOW_LAYERS
|
||||
- 更新所有导入引用(task_executor.py, cli/main.py, scheduler.py 等)
|
||||
- _Requirements: 9.1, 9.2, 9.5_
|
||||
|
||||
- [x] 8.2 更新 CLI 参数 --pipeline → --flow
|
||||
- 将 `--pipeline` 重命名为 `--flow`
|
||||
- 保留 `--pipeline` 作为已弃用别名(使用 argparse dest 映射)
|
||||
- 使用已弃用参数时输出 DeprecationWarning
|
||||
- 更新 --layers 互斥校验同时检查 --flow 和 --pipeline
|
||||
- _Requirements: 9.3, 9.4_
|
||||
|
||||
- [x] 8.3 更新所有日志消息和注释中的 pipeline 术语
|
||||
- 全局搜索替换日志中的 "Pipeline" / "pipeline" → "Flow" / "flow"
|
||||
- 更新代码注释中的术语
|
||||
- _Requirements: 9.6_
|
||||
|
||||
- [x] 9. 路径重命名 pipelines → connectors
|
||||
- [x] 9.1 重命名目录 apps/etl/pipelines → apps/etl/connectors
|
||||
- 执行目录重命名
|
||||
- 更新 pyproject.toml workspace 成员声明
|
||||
- 更新所有 Python 导入路径
|
||||
- _Requirements: 10.1, 10.2, 10.4_
|
||||
|
||||
- [x] 9.2 更新所有配置文件、脚本和文档中的路径引用
|
||||
- 更新 .env / .env.template 中的路径
|
||||
- 更新 run_etl.bat / run_etl.sh 中的路径
|
||||
- 更新 scripts/ 目录下引用旧路径的脚本
|
||||
- _Requirements: 10.3_
|
||||
|
||||
- [x] 9.3 运行全量测试确认路径重命名无回归
|
||||
- `cd apps/etl/connectors/feiqiu && pytest tests/unit -v`
|
||||
- `cd C:\NeoZQYY && pytest tests/ -v`
|
||||
- _Requirements: 10.5_
|
||||
|
||||
- [x] 10. 文档同步更新
|
||||
- [x] 10.1 更新架构文档和模块文档
|
||||
- 更新 `docs/etl-feiqiu-architecture.md`:类名、方法名、术语、路径
|
||||
- 更新 `apps/etl/connectors/feiqiu/docs/` 下所有文档
|
||||
- 更新 `tasks/README.md`:任务列表和继承关系
|
||||
- _Requirements: 12.1, 12.2, 12.4_
|
||||
|
||||
- [x] 10.2 更新 CLI 帮助文本和示例
|
||||
- 更新 cli/main.py 中的 epilog 示例
|
||||
- 更新 argparse 帮助文本反映 --layers 和 --flow 参数
|
||||
- _Requirements: 12.3_
|
||||
|
||||
- [x] 11. 最终检查点 - 全量回归测试
|
||||
- 运行 `cd apps/etl/connectors/feiqiu && pytest tests/unit -v`
|
||||
- 运行 `cd C:\NeoZQYY && pytest tests/ -v`
|
||||
- 确保所有测试通过,如有问题请询问用户
|
||||
- _Requirements: 11.1, 11.2, 11.3, 11.4, 11.5_
|
||||
|
||||
## 备注
|
||||
|
||||
- 标记 `*` 的子任务为可选,可跳过以加速 MVP
|
||||
- 每个任务引用具体需求编号以确保可追溯性
|
||||
- 检查点确保增量验证
|
||||
- 属性测试验证普遍正确性属性,单元测试验证具体示例和边界条件
|
||||
- 阶段 4(关键词/路径重命名)风险最高,建议在独立 Git 分支上执行
|
||||
Reference in New Issue
Block a user