# -*- coding: utf-8 -*-
"""导出完整 BUG 修复报告（BUG 1~11）。"""
from pathlib import Path
from dotenv import load_dotenv

load_dotenv(Path(__file__).resolve().parents[2] / ".env")
from _env_paths import get_output_path

out = get_output_path("SYSTEM_LOG_ROOT")

report = r"""# ETL 前后端联调 — BUG 修复全记录

> 日期: 2026-02-21
> 执行轮次: v1 ~ v8（共 8 次）
> 任务配置: api_full, full_window, 2025-11-01 ~ 2026-02-20, 30天窗口切分, force_full, 19个任务

---

## 总览

| 指标 | v1 (首次) | v6 (中期最佳) | v8 (最终) |
|------|-----------|--------------|-----------|
| 耗时 | 590.7s | 29m26s | 1m24s |
| 成功任务 | 10/41 | 11/19 | 14/19 |
| 失败任务 | 31/41 | 8/19 | 5/19 |
| 累计修复 BUG | 0 | 7 | 11 |

最终 5 个失败均为 `InFailedSqlTransaction` 级联（根因是上游数据质量问题，非代码 BUG）。

---

## BUG 详情

### BUG 1 — DWS_ASSISTANT_DAILY SQL 字段引用错误

| 项目 | 内容 |
|------|------|
| 发现版本 | v1 |
| 验证版本 | v2 |
| 文件 | `apps/etl/connectors/feiqiu/tasks/dws/assistant_daily_task.py` |
| 错误现象 | `UndefinedColumn: column "xxx" does not exist`，DWS_ASSISTANT_DAILY 及其下游 31 个任务全部失败 |
| 根因 | SQL 中引用了 DWD 表中不存在的列名（4 处字段名与实际 DDL 不匹配） |
| 修复方式 | 修正 4 处列名引用，对齐 `dwd.dwd_table_fee_log` / `dwd.dwd_assistant_service_log` 的实际 DDL |
| 修复结果 | ✅ v2 中 DWS_ASSISTANT_DAILY 执行成功 |

### BUG 2 — DWS_ASSISTANT_MONTHLY GROUP BY 聚合错误

| 项目 | 内容 |
|------|------|
| 发现版本 | v2 |
| 验证版本 | v3 |
| 文件 | `apps/etl/connectors/feiqiu/tasks/dws/assistant_monthly_task.py` |
| 错误现象 | `UniqueViolation: duplicate key value violates unique constraint` |
| 根因 | GROUP BY 子句缺少必要的聚合列，导致同一主键产生多行，INSERT 时违反唯一约束 |
| 修复方式 | 将非 GROUP BY 列改用 `MAX()` 聚合函数包裹 |
| 修复结果 | ✅ v3 中 DWS_ASSISTANT_MONTHLY 执行成功（删除 9 行，插入 9 行） |

### BUG 3 — DWS_ASSISTANT_CUSTOMER 引用不存在的 site_id 列

| 项目 | 内容 |
|------|------|
| 发现版本 | v2 |
| 验证版本 | v3 |
| 文件 | `apps/etl/connectors/feiqiu/tasks/dws/assistant_customer_task.py` |
| 错误现象 | `UndefinedColumn: column dm.site_id does not exist` |
| 根因 | `dwd.dim_member` 表没有 `site_id` 列，实际字段为 `register_site_id` |
| 修复方式 | `dm.site_id` → `dm.register_site_id` |
| 修复结果 | ✅ v3 中 DWS_ASSISTANT_CUSTOMER 执行成功（285 行） |

### BUG 4 — 多个 DWS 任务引用 dim_member/dim_member_card_account 的 site_id

| 项目 | 内容 |
|------|------|
| 发现版本 | v3 |
| 验证版本 | v4 |
| 文件 | `assistant_customer_task.py`、`member_consumption_task.py`、`finance_recharge_task.py`（共 4 处） |
| 错误现象 | 多个 DWS 任务因 `UndefinedColumn: site_id` 失败 |
| 根因 | 与 BUG 3 同源 — `dim_member` 和 `dim_member_card_account` 均无 `site_id`，需用 `register_site_id` |
| 修复方式 | 4 处 `site_id` → `register_site_id` |
| 修复结果 | ✅ v4 中相关任务执行成功 |

### BUG 5 — DWS_MEMBER_VISIT 引用不存在的 birthday 字段

| 项目 | 内容 |
|------|------|
| 发现版本 | v4 |
| 验证版本 | v6 |
| 文件 | `apps/etl/connectors/feiqiu/tasks/dws/member_visit_task.py` |
| 错误现象 | `UndefinedColumn: column dm.birthday does not exist` |
| 根因 | `dwd.dim_member` 表没有 `birthday` 字段（上游 API 不提供） |
| 修复方式 | 移除 `birthday` 相关的 SELECT/INSERT/GROUP BY 引用 |
| 修复结果 | ✅ v6 中 DWS_MEMBER_VISIT 执行成功（v5 被 BUG 6 遮蔽） |

### BUG 6 — DWS_MEMBER_VISIT _extract_table_info() 字段名不匹配

| 项目 | 内容 |
|------|------|
| 发现版本 | v5 |
| 验证版本 | v6 |
| 文件 | `apps/etl/connectors/feiqiu/tasks/dws/member_visit_task.py` |
| 错误现象 | `UndefinedColumn` — `_extract_table_info()` 方法中引用了 DWD 表中不存在的列名 |
| 根因 | `_extract_table_info()` 中的字段名与 `dwd.dwd_table_fee_log` 实际 DDL 不一致 |
| 修复方式 | 修正 `_extract_table_info()` 中的列名映射 |
| 修复结果 | ✅ v6 中 DWS_MEMBER_VISIT 执行成功 |

### BUG 7 — DWS_FINANCE_INCOME_STRUCTURE JOIN 条件列名错误

| 项目 | 内容 |
|------|------|
| 发现版本 | 预防性修复（v5 代码审查发现） |
| 验证版本 | v6 |
| 文件 | `apps/etl/connectors/feiqiu/tasks/dws/finance_income_task.py` |
| 错误现象 | JOIN 条件中 `dt.site_table_id` 不存在 |
| 根因 | `dwd.dwd_table_fee_log` 的台桌 ID 列名是 `table_id`，不是 `site_table_id` |
| 修复方式 | `dt.site_table_id` → `dt.table_id` |
| 修复结果 | ✅ v6 中未出现该错误（但被 BUG 8 级联遮蔽） |

### BUG 8 — DWS_FINANCE_DAILY / DWS_FINANCE_RECHARGE 字段名错误

| 项目 | 内容 |
|------|------|
| 发现版本 | v6 |
| 验证版本 | v8 |
| 文件 | `finance_base_task.py`、`finance_recharge_task.py` |
| 错误现象 | `UndefinedColumn: column "pay_money" does not exist`，DWS_FINANCE_DAILY 失败并级联导致 7 个下游任务失败 |
| 根因 | `dwd.dwd_recharge_order` 的实际字段是 `pay_amount` / `point_amount`，代码中写的是 `pay_money` / `gift_money` |
| 修复方式 | `pay_money` → `pay_amount`，`gift_money` → `point_amount`（2 个文件） |
| 修复结果 | ✅ v8 中 DWS_FINANCE_DAILY 和 DWS_FINANCE_RECHARGE 均执行成功（v7 被 BUG 9 遮蔽） |

### BUG 9 — DWD_LOAD_FROM_ODS 缺少 _pick_snapshot_order_column 方法

| 项目 | 内容 |
|------|------|
| 发现版本 | v7 |
| 验证版本 | v8 |
| 文件 | `apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py` |
| 错误现象 | `AttributeError: 'DwdLoadTask' object has no attribute '_pick_snapshot_order_column'`，所有 dim 表 SCD2 装载全部失败 |
| 根因 | `_merge_dim_scd2` 方法调用了 `self._pick_snapshot_order_column()`，但该方法只存在于 `integrity_checker.py` 中作为模块级函数，`DwdLoadTask` 类中没有定义 |
| 修复方式 | 在 `DwdLoadTask` 类中添加 `_pick_snapshot_order_column` 静态方法（逻辑与 `integrity_checker.py` 中的同名函数一致） |
| 修复结果 | ✅ v8 中所有 15 个 dim 表 SCD2 装载成功（dim_site, dim_table, dim_assistant, dim_member, dim_member_card_account, dim_tenant_goods, dim_store_goods, dim_goods_category, dim_groupbuy_package 及其 _ex 表） |

### BUG 10 — goods_stock 表 FACT_MAPPINGS 驼峰字段名导致 SQL 错误

| 项目 | 内容 |
|------|------|
| 发现版本 | v7 |
| 验证版本 | v8 |
| 文件 | `apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py` |
| 错误现象 | `UndefinedColumn: column "siteGoodsId" does not exist, perhaps you mean "sitegoodsid"` |
| 根因 | `FACT_MAPPINGS` 中 `dwd_goods_stock_summary` 和 `dwd_goods_stock_movement` 的源列使用了带引号的驼峰名（如 `"siteGoodsId"`），但 ODS 表中 PostgreSQL 存储的列名是全小写的 `sitegoodsid`（ODS 入库时 `_int_col("sitegoodsid", "siteGoodsId")` 已将 JSON 驼峰键转为小写列名） |
| 修复方式 | 将 FACT_MAPPINGS 中 2 个表共 30+ 个字段的驼峰引用全部改为小写（如 `"siteGoodsId"` → `"sitegoodsid"`） |
| 修复结果 | ✅ v8 中 `dwd_goods_stock_summary`（716 条 INSERT）和 `dwd_goods_stock_movement`（14306 条 INSERT）装载成功 |

### BUG 11 — flow_runner.py sum() 类型不安全

| 项目 | 内容 |
|------|------|
| 发现版本 | v7 |
| 验证版本 | v8 |
| 文件 | `apps/etl/connectors/feiqiu/orchestration/flow_runner.py` |
| 错误现象 | `TypeError: unsupported operand type(s) for +: 'int' and 'list'` |
| 根因 | 某些任务的 `counts.errors` 返回了 `list`（错误详情列表）而非 `int`（错误计数），`sum()` 无法将 `int` 与 `list` 相加 |
| 修复方式 | 添加 `_safe_int()` 辅助函数，将 `int`/`list`/`None` 统一转为 `int` 计数（`list` 取 `len()`） |
| 修复结果 | ✅ v8 中不再出现 TypeError，Flow 汇总正常完成 |

---

## 未修复的遗留问题

### 数据质量问题 — dim_assistant_ex / dim_member_card_account_ex 非法日期

| 项目 | 内容 |
|------|------|
| 发现版本 | v8 |
| 性质 | 上游数据质量问题，非代码 BUG |
| 错误现象 | `ValueError: year -1 is out of range` |
| 根因 | ODS 中某些记录的日期字段包含非法值（year=-1），Python `datetime` 无法解析 |
| 影响 | `dim_assistant_ex` 和 `dim_member_card_account_ex` 装载失败 → 事务进入 `InFailedSqlTransaction` → 级联导致 5 个 DWS 任务失败（DWS_FINANCE_INCOME_STRUCTURE, DWS_FINANCE_DISCOUNT_DETAIL, DWS_WINBACK_INDEX, DWS_NEWCONV_INDEX, DWS_RELATION_INDEX） |
| 建议 | 在 DWD 装载的日期类型转换中添加容错处理（捕获 ValueError，将非法日期置为 NULL 或哨兵值） |

---

## 修复文件清单

| 文件 | 修复的 BUG |
|------|-----------|
| `apps/etl/connectors/feiqiu/tasks/dws/assistant_daily_task.py` | BUG 1 |
| `apps/etl/connectors/feiqiu/tasks/dws/assistant_monthly_task.py` | BUG 2 |
| `apps/etl/connectors/feiqiu/tasks/dws/assistant_customer_task.py` | BUG 3, 4 |
| `apps/etl/connectors/feiqiu/tasks/dws/member_consumption_task.py` | BUG 4 |
| `apps/etl/connectors/feiqiu/tasks/dws/member_visit_task.py` | BUG 5, 6 |
| `apps/etl/connectors/feiqiu/tasks/dws/finance_income_task.py` | BUG 7 |
| `apps/etl/connectors/feiqiu/tasks/dws/finance_base_task.py` | BUG 8 |
| `apps/etl/connectors/feiqiu/tasks/dws/finance_recharge_task.py` | BUG 4, 8 |
| `apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py` | BUG 9, 10 |
| `apps/etl/connectors/feiqiu/orchestration/flow_runner.py` | BUG 11 |

---

## 执行历史

| 版本 | execution_id | 耗时 | 成功 | 失败 | 修复验证 |
|------|-------------|------|------|------|---------|
| v1 | `dbf0c29a-...` | 590.7s | 10 | 31 | — |
| v2 | `e21e1935-...` | 150.4s | — | — | BUG 1 ✅ |
| v3 | `abc94b2d-...` | 681.2s | 9 | 22 | BUG 2,3 ✅ |
| v4 | `efd4f421-...` | 11m55s | 10 | 21 | BUG 4 ✅ |
| v5 | `fe87144a-...` | 11m37s | 10 | 21 | BUG 5 部署（被 BUG 6 遮蔽） |
| v6 | `d9443781-...` | 29m26s | 11 | 8 | BUG 5,6,7 ✅ |
| v7 | `0929ab3a-...` | 89.3s | — | 全部 | BUG 8 部署（被 BUG 9 遮蔽） |
| v8 | `f943bac6-...` | 1m24s | 14 | 5 | BUG 8,9,10,11 ✅ |
"""

(out / "2026-02-21__etl_full_bug_report.md").write_text(report, encoding="utf-8")
print(f"报告已导出: {out / '2026-02-21__etl_full_bug_report.md'}")
