在准备环境前提交次全部更改。

This commit is contained in:
Neo
2026-02-19 08:35:13 +08:00
parent ded6dfb9d8
commit 4eac07da47
1387 changed files with 6107191 additions and 33002 deletions

View File

@@ -0,0 +1 @@
{"generationMode": "requirements-first"}

View File

@@ -0,0 +1,245 @@
# 设计文档DWD 第一阶段重构
## 概述
本次重构针对 `DwdLoadTask``tasks/dwd/dwd_load_task.py`),目标是:
1. 统一事实表增量窗口模式,消除水位线与窗口两套并行的范围过滤
2. 删除回补机制(`_insert_missing_by_pk`),简化事实表写入路径
3. 清理已确认的死代码、未使用常量和方法
4. 修复 `_build_column_mapping()` 的参数 bug
所有改动集中在 `dwd_load_task.py` 一个文件(加上删除 `base_dwd_task.py`、更新两个外部引用属于低风险重构。重构后代码路径更清晰为第二阶段架构重构E/T/L → `process_segment` 钩子)做好准备。
## 架构
### 当前架构(重构前)
```mermaid
graph TD
A[DwdLoadTask.load] --> B{事实表?}
B -->|是| C{use_window?}
C -->|是| D["_merge_fact_increment(window_start, window_end)"]
C -->|否| E["_get_fact_watermark() → watermark"]
E --> F["_merge_fact_increment(watermark)"]
D --> G["_insert_missing_by_pk回补"]
F --> G
B -->|否| H{scd_cols_present?}
H -->|是| I[_merge_dim_scd2]
H -->|否| J[_merge_dim_type1_upsert]
```
### 目标架构(重构后)
```mermaid
graph TD
A[DwdLoadTask.load] --> B{事实表?}
B -->|是| D["_merge_fact_increment(window_start, window_end)"]
D --> R[返回计数]
B -->|否| I[_merge_dim_scd2]
```
关键变化:
- 事实表路径:消除 `use_window` 分支判断,始终传 `context.window_start/window_end`;删除水位线获取和回补步骤
- 维度表路径:消除 `scd_cols_present` 条件判断,直接调用 `_merge_dim_scd2`(所有 17 张维度表都有 SCD2 列)
## 组件与接口
### 变更组件清单
| 组件 | 变更类型 | 说明 |
|------|----------|------|
| `DwdLoadTask.load()` | 修改 | 删除 `use_window` 判断,始终传 `context.window_start/window_end` |
| `DwdLoadTask._merge_fact_increment()` | 修改 | 删除 `watermark` 分支和回补调用;`window_start`/`window_end` 改为必填参数 |
| `DwdLoadTask._merge_dim()` | 修改 | 删除 Type1 分支,直接调用 `_merge_dim_scd2` |
| `DwdLoadTask._build_column_mapping()` | 修改 | 将 `ods_table``cur` 加入方法签名 |
| `DwdLoadTask._get_fact_watermark()` | 删除 | 水位线机制不再需要 |
| `DwdLoadTask._insert_missing_by_pk()` | 删除 | 回补机制不再需要 |
| `DwdLoadTask._pick_order_column()` | 删除 | 从未被调用的死代码 |
| `DwdLoadTask._merge_dim_type1_upsert()` | 删除 | Type1 分支永远不触发 |
| `DwdLoadTask._upsert_scd2_row()` | 删除 | 已被批量方法替代 |
| `DwdLoadTask._close_current_dim()` | 删除 | 已被 `_close_current_dim_bulk` 替代 |
| `DwdLoadTask._insert_dim_row()` | 删除 | 已被 `_insert_dim_rows_bulk` 替代 |
| `FACT_ORDER_CANDIDATES` 常量 | 删除 | 配合 `_pick_order_column` 删除 |
| `FACT_MISSING_FILL_TABLES` 常量 | 删除 | 配合回补机制删除 |
| `base_dwd_task.py` | 删除文件 | 死代码,从未被使用 |
| `debug_dwd.py` | 修改 | 内联时间列候选列表,替代对 `FACT_ORDER_CANDIDATES` 的引用 |
| `integrity_checker.py` | 修改 | 内联时间列候选列表,替代对 `FACT_ORDER_CANDIDATES` 的引用 |
### 接口变更详情
#### `_merge_fact_increment()` 签名变更
```python
# 重构前
def _merge_fact_increment(
self, cur, dwd_table, ods_table, dwd_cols, ods_cols,
dwd_types, ods_types,
window_start: datetime | None = None, # 可选
window_end: datetime | None = None, # 可选
) -> Dict[str, int]:
# 重构后
def _merge_fact_increment(
self, cur, dwd_table, ods_table, dwd_cols, ods_cols,
dwd_types, ods_types,
window_start: datetime, # 必填
window_end: datetime, # 必填
) -> Dict[str, int]:
```
#### `_build_column_mapping()` 签名变更
```python
# 重构前bug引用了外部作用域的 ods_table 和 cur
def _build_column_mapping(
self, dwd_table: str, pk_cols: Sequence[str], ods_cols: Sequence[str]
) -> Dict[str, tuple[str, str | None]]:
# 重构后
def _build_column_mapping(
self, cur, dwd_table: str, ods_table: str,
pk_cols: Sequence[str], ods_cols: Sequence[str]
) -> Dict[str, tuple[str, str | None]]:
```
#### `_merge_dim()` 简化
```python
# 重构前
def _merge_dim(self, cur, dwd_table, ods_table, dwd_cols, ods_cols, now):
pk_cols = self._get_primary_keys(cur, dwd_table)
scd_cols_present = any(c.lower() in self.SCD_COLS for c in dwd_cols)
if scd_cols_present:
return self._merge_dim_scd2(...)
return self._merge_dim_type1_upsert(...)
# 重构后
def _merge_dim(self, cur, dwd_table, ods_table, dwd_cols, ods_cols, now):
return self._merge_dim_scd2(cur, dwd_table, ods_table, dwd_cols, ods_cols, now)
```
#### `load()` 事实表分支简化
```python
# 重构前
use_window = bool(
self.config.get("run.window_override.start")
and self.config.get("run.window_override.end")
)
fact_counts = self._merge_fact_increment(
...,
window_start=context.window_start if use_window else None,
window_end=context.window_end if use_window else None,
)
# 重构后
fact_counts = self._merge_fact_increment(
...,
window_start=context.window_start,
window_end=context.window_end,
)
```
### 外部引用处理
`FACT_ORDER_CANDIDATES` 被两个外部模块引用,删除后需要同步处理:
1. **`scripts/debug/debug_dwd.py`**:将候选列表内联为模块级常量 `_TIME_COLUMN_CANDIDATES`
2. **`quality/integrity_checker.py`**:将候选列表内联为模块级常量 `_TIME_COLUMN_CANDIDATES`
两处内联的列表内容与原 `FACT_ORDER_CANDIDATES` 相同:`["pay_time", "create_time", "update_time", "occur_time", "settle_time", "start_use_time", "fetched_at"]`。这些模块的用途是调试和完整性检查,与 DWD 装载的核心逻辑无关,内联不会引入耦合问题。
## 数据模型
本次重构不涉及数据库 schema 变更。所有 DWD 表结构、ODS 表结构、`FACT_MAPPINGS``TABLE_MAP` 保持不变。
变更仅影响运行时行为:
- 事实表增量 SQL 的 WHERE 条件从"水位线或窗口"统一为"窗口"
- 回补 LEFT JOIN SQL 不再执行
- 维度表合并路径从"SCD2 或 Type1"统一为"SCD2"
## 正确性属性
*属性Property是一种在系统所有有效执行中都应成立的特征或行为——本质上是对系统应做什么的形式化陈述。属性是人类可读规格说明与机器可验证正确性保证之间的桥梁。*
基于验收标准的前置分析,以下属性覆盖了本次重构的核心正确性要求。静态代码结构检查(方法/常量/文件是否存在)通过单元测试覆盖,不作为属性列出。
### Property 1: 事实表增量 SQL 始终使用窗口范围条件
*For any* 有效的事实表映射(`TABLE_MAP``dwd_` 前缀的表)和任意的 `window_start`/`window_end` 时间对(`window_start < window_end``_merge_fact_increment()` 生成的主增量 SQL 的 WHERE 子句 SHALL 包含 `fetched_at >= window_start AND fetched_at < window_end` 条件,且不包含单边水位线条件(`fetched_at > watermark`)。
**Validates: Requirements 1.1, 1.2**
### Property 2: 事实表增量不执行回补 SQL
*For any* 有效的事实表映射和任意的窗口参数,`_merge_fact_increment()` 执行的 SQL 语句列表中 SHALL 不包含 LEFT JOIN 回补查询(即不包含 `LEFT JOIN ... WHERE ... IS NULL` 模式的 SQL
**Validates: Requirements 2.3**
### Property 3: 事实表主增量 SQL 结构等价性
*For any* 有效的事实表映射、列集合和窗口参数,重构后 `_merge_fact_increment()` 生成的主增量 `INSERT INTO ... SELECT ... ON CONFLICT` SQL 的结构列列表、SELECT 表达式、ON CONFLICT 子句SHALL 与重构前窗口模式(`use_window=True`)生成的 SQL 结构相同。
**Validates: Requirements 5.2, 5.5**
### Property 4: 维度表始终走 SCD2 路径
*For any* 有效的维度表映射(`TABLE_MAP``dim_` 前缀的表),调用 `_merge_dim()` SHALL 始终委托给 `_merge_dim_scd2()`,不经过任何条件分支判断。
**Validates: Requirements 3.8, 5.1**
## 错误处理
本次重构不引入新的错误处理逻辑,保留现有机制:
| 场景 | 处理方式 | 变更 |
|------|----------|------|
| 单表事实增量 SQL 执行失败(含类型转换错误) | `load()``try/except` 捕获,回滚该表事务,记录错误日志,继续处理下一张表 | 无变更(需求 2.4:异常自然传播到 load() 层面) |
| 维度表 SCD2 合并失败 | 同上 | 无变更 |
| ODS 表缺少 `fetched_at` 列 | `_merge_fact_increment` 记录 error 日志并返回零计数 | 无变更 |
| DWD 表无法获取列信息 | `load()` 记录 warning 并跳过该表 | 无变更 |
删除回补机制后,原本由回补兜底的"部分行丢失"场景不再存在——类型转换失败会导致整条 SQL 报错、整张表事务回滚,这是正确的行为(需求 2.4)。
## 测试策略
### 测试框架
- 单元测试:`pytest`,使用 `FakeDB`/`FakeCursor``tests/unit/task_test_utils.py`
- 属性测试:`hypothesis`,最少 100 次迭代
- 测试位置:
- 单元测试:`apps/etl/pipelines/feiqiu/tests/unit/test_dwd_phase1_refactor.py`
- 属性测试:`tests/test_dwd_phase1_properties.py`monorepo 根目录)
### 双轨测试方法
**单元测试**覆盖:
- 静态代码结构验证(死代码/常量/方法已删除)
- `_build_column_mapping()` bug 修复后的调用正确性
- `_merge_dim()` 直接调用 SCD2 的行为
- 外部模块(`debug_dwd.py``integrity_checker.py`)导入不报错
- 表过滤功能(`dwd.only_tables`)回归
**属性测试**覆盖:
- Property 1: 事实表 SQL 窗口条件(通过 FakeCursor 捕获 SQL验证 WHERE 子句)
- Property 2: 无回补 SQL通过 FakeCursor 捕获所有执行的 SQL验证无 LEFT JOIN
- Property 3: SQL 结构等价性(对比重构前后生成的 SQL
- Property 4: 维度表 SCD2 路径(通过 mock 验证调用链)
### 属性测试配置
- 库:`hypothesis`
- 每个属性最少 100 次迭代
- 每个属性测试标注对应的设计属性编号
- 标注格式:`# Feature: dwd-phase1-refactor, Property N: {property_text}`
### 测试数据生成策略
使用 `hypothesis``@st.composite` 策略生成:
- 随机的 `window_start`/`window_end` 时间对(确保 `start < end`
- 随机的列名集合(确保包含 `fetched_at` 和至少一个主键列)
- 随机的 `FACT_MAPPINGS` 条目(列名 → 源列 + 可选类型转换)
使用 `FakeCursor` 捕获执行的 SQL 语句,而非实际连接数据库。

View File

@@ -0,0 +1,81 @@
# 需求文档DWD 第一阶段重构
## 简介
`DwdLoadTask``tasks/dwd/dwd_load_task.py`)进行低风险重构,统一事实表增量窗口模式、删除回补机制、清理死代码和未使用常量、修复已知 bug。改动集中在单个文件及删除一个死代码文件目标是简化代码路径、消除冗余分支为后续架构重构第二阶段打好基础。
## 术语表
- **DwdLoadTask**DWD 层数据装载任务类,负责从 ODS 表读取数据并合并到 DWD 维度表和事实表
- **ODS**Operational Data Store原始数据存储层
- **DWD**Data Warehouse Detail明细数据层
- **SCD2**Slowly Changing Dimension Type 2缓慢变化维度第二类通过版本号和时间戳追踪历史变更
- **水位线Watermark**:基于 DWD 表中已有数据的最大时间戳来确定增量起点的机制
- **窗口模式Window Mode**:通过 `context.window_start` / `context.window_end` 明确指定时间范围的增量机制
- **回补机制Backfill**`_insert_missing_by_pk()` 方法,在主增量写入后通过 LEFT JOIN 补齐缺失主键记录
- **BaseDwdTask**`base_dwd_task.py` 中定义的死代码基类,从未被任何子类使用
- **FACT_MAPPINGS**:事实表和维度表的列映射字典,定义 ODS 列到 DWD 列的转换规则
- **TaskContext**:运行期上下文数据类,包含 `store_id``window_start``window_end``window_minutes` 等字段
- **overlap_seconds**:自动模式下窗口起点的回退秒数,用于覆盖可能的数据延迟
## 需求
### 需求 1统一事实表增量窗口模式
**用户故事:** 作为 ETL 开发者,我希望事实表增量加载统一使用窗口模式(`window_start` / `window_end`),以消除水位线与窗口两套并行的范围过滤逻辑,降低代码复杂度。
#### 验收标准
1. WHEN `DwdLoadTask.load()` 处理事实表时THE DwdLoadTask SHALL 始终将 `context.window_start``context.window_end` 传递给 `_merge_fact_increment()`,不再根据 `run.window_override` 配置判断是否传递
2. WHEN `_merge_fact_increment()` 构建增量 SQL 时THE DwdLoadTask SHALL 统一使用 `WHERE fetched_at >= %s AND fetched_at < %s` 条件过滤,参数为 `window_start``window_end`
3. WHEN 重构完成后THE DwdLoadTask SHALL 不再包含 `_get_fact_watermark()` 方法
4. WHEN `_merge_fact_increment()` 被调用时THE DwdLoadTask SHALL 不再接受 `watermark` 参数,方法签名中 `window_start``window_end` 为必填参数
5. WHEN 自动模式(无 `window_override`运行时THE DwdLoadTask SHALL 通过 `BaseTask._get_time_window()` 计算的 `context.window_start`(含 `overlap_seconds` 回退)和 `context.window_end` 作为窗口范围
### 需求 2删除回补机制
**用户故事:** 作为 ETL 开发者,我希望删除事实表的回补机制(`_insert_missing_by_pk`),因为统一窗口后回补的 LEFT JOIN 结果集几乎一定为空,且类型转换失败应直接报错而非静默丢失数据。
#### 验收标准
1. WHEN 重构完成后THE DwdLoadTask SHALL 不再包含 `_insert_missing_by_pk()` 方法
2. WHEN 重构完成后THE DwdLoadTask SHALL 不再包含 `FACT_MISSING_FILL_TABLES` 常量
3. WHEN `_merge_fact_increment()` 执行完主增量 INSERT 后THE DwdLoadTask SHALL 直接返回插入和更新计数,不再调用回补逻辑
4. WHEN 事实表增量 SQL 执行过程中发生类型转换错误时THE DwdLoadTask SHALL 让异常自然传播,触发该表的事务回滚和错误日志记录
### 需求 3清理死代码和未使用常量
**用户故事:** 作为 ETL 开发者,我希望清理所有已确认的死代码和未使用常量,以减少代码体积、消除维护负担和潜在的误导。
#### 验收标准
1. WHEN 重构完成后THE 代码库 SHALL 不再包含 `tasks/dwd/base_dwd_task.py` 文件
2. WHEN 重构完成后THE DwdLoadTask SHALL 不再包含 `_pick_order_column()` 方法
3. WHEN 重构完成后THE DwdLoadTask SHALL 不再包含 `FACT_ORDER_CANDIDATES` 常量
4. WHEN 重构完成后THE DwdLoadTask SHALL 不再包含 `_upsert_scd2_row()` 方法
5. WHEN 重构完成后THE DwdLoadTask SHALL 不再包含 `_close_current_dim()` 方法
6. WHEN 重构完成后THE DwdLoadTask SHALL 不再包含 `_insert_dim_row()` 方法
7. WHEN 重构完成后THE DwdLoadTask SHALL 不再包含 `_merge_dim_type1_upsert()` 方法
8. WHEN `_merge_dim()` 被调用时THE DwdLoadTask SHALL 直接调用 `_merge_dim_scd2()`,不再检查 `scd_cols_present` 条件分支
9. WHEN 外部模块(`debug_dwd.py``integrity_checker.py`)引用 `FACT_ORDER_CANDIDATES`THE 重构 SHALL 同步更新这些引用,将候选列列表内联到各自模块中或提取为共享常量
### 需求 4修复 `_build_column_mapping()` 参数 bug
**用户故事:** 作为 ETL 开发者,我希望修复 `_build_column_mapping()` 中引用未定义变量 `ods_table``cur` 的 bug以防止未来条件变化时触发 `NameError`
#### 验收标准
1. WHEN `_build_column_mapping()` 被调用时THE DwdLoadTask SHALL 通过方法参数接收 `ods_table``cur`,而非依赖外部作用域的未定义变量
2. WHEN `_build_column_mapping()` 的方法签名变更后THE DwdLoadTask SHALL 同步更新所有调用点,传入正确的 `ods_table``cur` 参数
### 需求 5保持现有功能行为不变
**用户故事:** 作为 ETL 开发者,我希望重构后的代码在功能行为上与重构前保持一致(除了移除水位线和回补),以确保生产环境的数据处理不受影响。
#### 验收标准
1. WHEN 维度表执行 SCD2 合并时THE DwdLoadTask SHALL 保持与重构前相同的关闭旧版本和插入新版本行为
2. WHEN 事实表执行增量插入时THE DwdLoadTask SHALL 保持与重构前相同的 UPSERT 逻辑(含 `ON CONFLICT``IS DISTINCT FROM` 变更检测)
3. WHEN `overlap_seconds` 配置存在时THE DwdLoadTask SHALL 保留自动模式下窗口起点的回退机制
4. WHEN `dwd.only_tables``DWD_ONLY_TABLES` 配置存在时THE DwdLoadTask SHALL 保留表过滤功能
5. FOR ALL 有效的 `FACT_MAPPINGS` 配置和 ODS 源数据,重构后的事实表增量插入 SHALL 产生与重构前窗口模式相同的 DWD 写入结果

View File

@@ -0,0 +1,96 @@
# 实施计划DWD 第一阶段重构
## 概述
按照 confirmed_changes.md 中 2.1-2.4 的顺序,对 `DwdLoadTask` 进行低风险重构。改动集中在 `dwd_load_task.py`,辅以删除 `base_dwd_task.py` 和更新两个外部引用。每个步骤递增构建,确保无悬空代码。
## 任务
- [x] 1. 统一窗口模式,去掉水位线(需求 1
- [x] 1.1 修改 `_merge_fact_increment()` 签名:`window_start``window_end` 改为必填参数(去掉 `| None` 和默认值 `None`);删除方法体内的 watermark 分支(`elif order_col:` 块及 `watermark = None` 赋值),统一使用 `WHERE fetched_at >= %s AND fetched_at < %s`
- 文件:`tasks/dwd/dwd_load_task.py`,方法 `_merge_fact_increment`
- _Requirements: 1.2, 1.4_
- [x] 1.2 修改 `load()` 中事实表分支:删除 `use_window` 变量及条件判断,始终传 `window_start=context.window_start, window_end=context.window_end`
- 文件:`tasks/dwd/dwd_load_task.py`,方法 `load`
- _Requirements: 1.1_
- [x] 1.3 删除 `_get_fact_watermark()` 方法(约 30 行)
- 文件:`tasks/dwd/dwd_load_task.py`
- _Requirements: 1.3_
- [x] 1.4 编写属性测试:事实表增量 SQL 始终使用窗口范围条件
- **Property 1: 事实表增量 SQL 始终使用窗口范围条件**
- **Validates: Requirements 1.1, 1.2**
- 使用 `hypothesis` 生成随机 `window_start`/`window_end`,通过 `FakeCursor` 捕获 SQL验证 WHERE 子句包含 `>= %s AND < %s`,不包含单边水位线条件
- 文件:`tests/test_dwd_phase1_properties.py`
- [x] 2. 删除回补机制(需求 2
- [x] 2.1 删除 `_merge_fact_increment()` 末尾的回补调用块:移除对 `_insert_missing_by_pk()` 的调用及 `missing_inserted` 相关代码
- 文件:`tasks/dwd/dwd_load_task.py`,方法 `_merge_fact_increment`
- _Requirements: 2.3_
- [x] 2.2 删除 `_insert_missing_by_pk()` 方法(约 100 行)
- 文件:`tasks/dwd/dwd_load_task.py`
- _Requirements: 2.1_
- [x] 2.3 删除 `FACT_MISSING_FILL_TABLES` 常量
- 文件:`tasks/dwd/dwd_load_task.py`
- _Requirements: 2.2_
- [x] 2.4 编写属性测试:事实表增量不执行回补 SQL
- **Property 2: 事实表增量不执行回补 SQL**
- **Validates: Requirements 2.3**
- 通过 `FakeCursor` 捕获所有执行的 SQL验证无 `LEFT JOIN ... IS NULL` 模式
- 文件:`tests/test_dwd_phase1_properties.py`
- [x] 3. 检查点 - 确保窗口统一和回补删除后测试通过
- 运行 `cd apps/etl/pipelines/feiqiu && pytest tests/unit``cd C:\NeoZQYY && pytest tests/ -v`,确保所有测试通过,如有问题请询问用户。
- [x] 4. 清理死代码和未使用常量(需求 3
- [x] 4.1 删除 `_pick_order_column()` 方法和 `FACT_ORDER_CANDIDATES` 常量
- 文件:`tasks/dwd/dwd_load_task.py`
- _Requirements: 3.2, 3.3_
- [x] 4.2 更新外部引用:在 `debug_dwd.py``integrity_checker.py` 中将 `DwdLoadTask.FACT_ORDER_CANDIDATES` 替换为模块级常量 `_TIME_COLUMN_CANDIDATES`(内联相同列表)
- 文件:`scripts/debug/debug_dwd.py``quality/integrity_checker.py`
- _Requirements: 3.9_
- [x] 4.3 删除逐行 SCD2 方法:`_upsert_scd2_row()``_close_current_dim()``_insert_dim_row()`
- 文件:`tasks/dwd/dwd_load_task.py`
- _Requirements: 3.4, 3.5, 3.6_
- [x] 4.4 删除 `_merge_dim_type1_upsert()` 方法;简化 `_merge_dim()` 直接调用 `_merge_dim_scd2()`
- 文件:`tasks/dwd/dwd_load_task.py`
- _Requirements: 3.7, 3.8_
- [x] 4.5 删除 `base_dwd_task.py` 文件
- 文件:`tasks/dwd/base_dwd_task.py`
- _Requirements: 3.1_
- [x] 4.6 编写属性测试:维度表始终走 SCD2 路径
- **Property 4: 维度表始终走 SCD2 路径**
- **Validates: Requirements 3.8, 5.1**
- 通过 mock `_merge_dim_scd2` 验证 `_merge_dim()` 始终委托给它
- 文件:`tests/test_dwd_phase1_properties.py`
- [x] 5. 修复 `_build_column_mapping()` 参数 bug需求 4
- [x] 5.1 修改 `_build_column_mapping()` 签名:添加 `cur``ods_table` 参数;更新所有调用点(`_merge_dim_type1_upsert` 已删除,剩余调用点为 `_merge_dim_scd2`)传入正确参数
- 文件:`tasks/dwd/dwd_load_task.py`
- _Requirements: 4.1, 4.2_
- [x] 5.2 编写单元测试:验证 `_build_column_mapping()``fetched_at` 缺失时正确使用 `ods_table``cur` 参数记录日志
- 文件:`apps/etl/pipelines/feiqiu/tests/unit/test_dwd_phase1_refactor.py`
- _Requirements: 4.1_
- [x] 6. 编写回归单元测试(需求 5
- [x] 6.1 编写单元测试:验证死代码已清理(`hasattr` 检查所有已删除的方法和常量)
- 文件:`apps/etl/pipelines/feiqiu/tests/unit/test_dwd_phase1_refactor.py`
- _Requirements: 1.3, 2.1, 2.2, 3.1-3.7_
- [x] 6.2 编写单元测试:验证外部模块导入正常(`debug_dwd.py``integrity_checker.py` 无 ImportError
- 文件:`apps/etl/pipelines/feiqiu/tests/unit/test_dwd_phase1_refactor.py`
- _Requirements: 3.9_
- [x] 6.3 编写属性测试:事实表主增量 SQL 结构等价性
- **Property 3: 事实表主增量 SQL 结构等价性**
- **Validates: Requirements 5.2, 5.5**
- 使用 `hypothesis` 生成随机列集合和窗口参数,通过 `FakeCursor` 捕获 SQL验证 INSERT INTO ... ON CONFLICT 结构与预期一致
- 文件:`tests/test_dwd_phase1_properties.py`
- [~] 7. 最终检查点 - 确保所有测试通过
- 运行 `cd apps/etl/pipelines/feiqiu && pytest tests/unit``cd C:\NeoZQYY && pytest tests/ -v`,确保所有测试通过,如有问题请询问用户。
## 备注
- 标记 `*` 的子任务为可选测试任务,可跳过以加速 MVP
- 每个任务引用了具体的需求编号,确保可追溯
- 检查点确保增量验证
- 属性测试验证通用正确性属性,单元测试验证具体示例和边界情况
- 本次重构涉及 `tasks/` 高风险路径,完成后需运行 `/audit`