在前后端开发联调前 的提交20260223

This commit is contained in:
Neo
2026-02-23 23:02:20 +08:00
parent 254ccb1e77
commit fafc95e64c
1142 changed files with 10366960 additions and 36957 deletions

View File

@@ -0,0 +1,207 @@
# -*- coding: utf-8 -*-
"""
导出 DWS_ASSISTANT_DAILY BUG 修复报告到 SYSTEM_LOG_ROOT。
用法python scripts/ops/export_bug_report.py
"""
from __future__ import annotations
import sys
from datetime import datetime
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from _env_paths import get_output_path
REPORT = r"""# DWS_ASSISTANT_DAILY BUG 修复报告
> 生成时间:{now}
> 执行 run_uuid4ba9d2d365ee4a858f1c4104b1942dc2
> 执行开始2026-02-21 15:29:20
---
## 1. BUG 概述
ETL 执行过程中 `DWS_ASSISTANT_DAILY` 任务失败,根因是 `assistant_daily_task.py` 中
`_extract_trash_records` 方法的 SQL 引用了 `dwd.dwd_assistant_trash_event` 表中不存在的字段。
### 错误信息
```
psycopg2.errors.UndefinedColumn: 错误: 字段 "assistant_service_id" 不存在
LINE 3: assistant_service_id,
^
```
### 级联影响
`DWS_ASSISTANT_DAILY` 失败后psycopg2 连接进入 `InFailedSqlTransaction` 状态,
级联导致以下 8 个任务全部失败:
| # | 任务代码 | 失败原因 |
|---|---------|---------|
| 1 | DWS_ASSISTANT_DAILY | 根因UndefinedColumn |
| 2 | DWS_ASSISTANT_MONTHLY | InFailedSqlTransaction级联 |
| 3 | DWS_ASSISTANT_CUSTOMER | InFailedSqlTransaction级联 |
| 4 | DWS_ASSISTANT_SALARY | InFailedSqlTransaction级联 |
| 5 | DWS_ASSISTANT_FINANCE | InFailedSqlTransaction级联 |
| 6 | ODS_SETTLEMENT_RECORDS | InFailedSqlTransaction级联 |
| 7 | ODS_PAYMENT | InFailedSqlTransaction级联 |
| 8 | ODS_REFUND | InFailedSqlTransaction级联 |
| 9 | DWS_BUILD_ORDER_SUMMARY | InFailedSqlTransaction级联 |
从 `ODS_TABLE_USE` 开始task_executor 的连接恢复机制生效,后续任务恢复正常执行。
---
## 2. 根因分析
### 2.1 错误 SQL修复前
```sql
SELECT assistant_service_id, trash_seconds, trash_reason, trash_time
FROM dwd.dwd_assistant_trash_event
WHERE site_id = %s AND DATE(trash_time) >= %s AND DATE(trash_time) <= %s
```
### 2.2 `dwd_assistant_trash_event` 实际表结构
| 字段名 | 类型 | 说明 |
|--------|------|------|
| assistant_trash_event_id | BIGINT (PK) | 废除事件 ID |
| site_id | BIGINT | 门店 ID |
| table_id | BIGINT | 台桌 ID |
| table_area_id | BIGINT | 区域 ID |
| assistant_no | VARCHAR(32) | 助教编号 |
| assistant_name | VARCHAR(64) | 助教姓名 |
| charge_minutes_raw | INTEGER | 废除时长(分钟) |
| abolish_amount | NUMERIC(18,2) | 废除金额 |
| trash_reason | VARCHAR(255) | 废除原因 |
| create_time | TIMESTAMPTZ | 废除时间 |
| tenant_id | BIGINT | 租户 ID |
### 2.3 字段映射错误
| 错误引用 | 实际字段 | 说明 |
|----------|---------|------|
| `assistant_service_id` | `assistant_trash_event_id` | PK 名称不同 |
| `trash_seconds` | `charge_minutes_raw` | 单位不同(分钟 vs 秒) |
| `trash_time` | `create_time` | 字段名不同 |
### 2.4 深层设计缺陷
废除表 `dwd_assistant_trash_event` 没有 `assistant_service_id` 外键,
无法与服务记录表 `dwd_assistant_service_log` 做 1:1 关联。
原代码的 `_build_trash_index` 用 `assistant_service_id` 做 key 构建索引,
`_aggregate_by_assistant_date` 用 `service_id in trash_index` 判断服务是否被废除。
即使 SQL 字段名修正后,这个匹配逻辑在设计上也是无效的——两个 ID 不同源。
---
## 3. 修复方案
### 3.1 文件
`apps/etl/connectors/feiqiu/tasks/dws/assistant_daily_task.py`
### 3.2 修改点(共 4 处)
#### (1) `_extract_trash_records` — SQL 字段名修正
```sql
-- 修复后
SELECT
assistant_trash_event_id,
charge_minutes_raw * 60 AS trash_seconds,
trash_reason,
create_time AS trash_time,
table_id,
assistant_name
FROM dwd.dwd_assistant_trash_event
WHERE site_id = %s
AND DATE(create_time) >= %s
AND DATE(create_time) <= %s
```
#### (2) `_extract_service_records` — JOIN _ex 表取 is_trash
```sql
-- 新增 LEFT JOIN 和 is_trash 字段
SELECT
asl.assistant_service_id,
...
DATE(asl.start_use_time) AS service_date,
COALESCE(ex.is_trash, 0) AS is_trash
FROM dwd.dwd_assistant_service_log asl
LEFT JOIN dwd.dwd_assistant_service_log_ex ex
ON asl.assistant_service_id = ex.assistant_service_id
WHERE asl.site_id = %s
AND DATE(asl.start_use_time) >= %s
AND DATE(asl.start_use_time) <= %s
AND asl.is_delete = 0
```
#### (3) `_build_trash_index` — key 改为 assistant_trash_event_id
```python
# 修复前
service_id = record.get('assistant_service_id')
# 修复后
event_id = record.get('assistant_trash_event_id')
```
#### (4) `_aggregate_by_assistant_date` — 废除判断改用 is_trash
```python
# 修复前
is_trashed = service_id in trash_index
# 修复后
is_trashed = bool(record.get('is_trash', 0))
```
废除时长也从 `trash_index[service_id]` 改为直接用 `income_seconds`。
### 3.3 设计决策说明
`dwd_assistant_service_log_ex` 表的 `is_trash` 字段来自上游 SaaS 系统的
`assistant_service_records` API是服务记录级别的废除标记比跨表匹配更可靠。
废除时长统计改用服务记录自身的 `income_seconds`(即该服务的计费时长),
而非从废除表取 `charge_minutes_raw`(废除事件的计费分钟数),
因为两者无法 1:1 关联。
---
## 4. 验证计划
修复将在下次 ETL 执行时生效。验证步骤:
1. 重新提交包含 `DWS_ASSISTANT_DAILY` 的执行
2. 确认无 SQL 错误
3. 检查 `dws.dws_assistant_daily` 表中 `trashed_count` / `trashed_seconds` 是否合理
4. 对比 `dwd_assistant_service_log_ex.is_trash = 1` 的记录数与 DWS 汇总的 `trashed_count`
---
## 5. 回滚方案
如需回滚,恢复 `assistant_daily_task.py` 到修改前版本即可。
DWS 表数据可通过重新执行 `DWS_ASSISTANT_DAILY` 任务覆盖。
"""
def main():
out_dir = get_output_path("SYSTEM_LOG_ROOT")
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
content = REPORT.replace("{now}", now)
out_file = out_dir / "2026-02-21__dws_assistant_daily_bug_fix.md"
out_file.write_text(content, encoding="utf-8")
print(f"BUG 修复报告已导出: {out_file}")
if __name__ == "__main__":
main()