# 变更审计记录：修复 recall_completion_check 事件链断裂

| 字段 | 值 |
|------|-----|
| 日期 | 2026-03-29 05:33:11 |
| Prompt-ID | P20260329-052721 |
| Session-ID | 9e51396e |
| Session 路径 | docs/audit/session_logs/2026-03/29/10_4d217477_050959 |

## 操作摘要

修复 recall_completion_check 事件链断裂，让回访任务（follow_up_visit）能正常生成。ETL DWD 加载完成后，通过 HTTP 调用后端新增的 `/api/internal/fire-event` 端点，触发 `etl_data_updated` 事件，驱动 `recall_completion_check` → `recall_detector.run()` → 标记召回完成 → 生成回访任务。

## 风险评估

- 后端新增端点：**低**（独立路由，Internal-Token 认证，不影响现有功能）
- ETL 触发调用：**低**（失败仅记录日志，不中断 DWD 加载）
- 认证解耦：**低**（复用同一个 INTERNAL_API_TOKEN 环境变量）

## 数据库变更

无。利用现有的 `biz.trigger_jobs` 表和 `fire_event()` 机制。

## 本次对话文件变更

### 新增文件
- `apps/backend/app/auth/internal_token.py`
- `apps/backend/app/routers/internal_events.py`
- `apps/etl/connectors/feiqiu/utils/biz_trigger.py`

### 删除文件
- `docs/audit/session_logs/2026-03/29/10_4d217477_050959/main_01_55fd330d.md`（Session 日志重建）

## 改动注解

### `apps/backend/app/auth/internal_token.py`
- 变更类型：新增
- 原始原因：将 Internal-Token 认证逻辑从 AIConfig 解耦，使 `/api/internal/*` 端点可独立使用 token 校验，不依赖 AI 模块配置
- 思路分析：从 `Authorization` header 解析 `Internal-Token {token}` 格式，与环境变量 `INTERNAL_API_TOKEN` 比对。三层防御：格式校验 → 空值校验 → 匹配校验。未配置时返回 500 而非静默放行
- 修改结果：提供 `verify_internal_token` FastAPI 依赖函数，供 `internal_events` 路由使用。与 `internal_ai` 路由的认证方式一致

### `apps/backend/app/routers/internal_events.py`
- 变更类型：新增
- 原始原因：ETL 完成 DWD 加载后需要一个 HTTP 入口来触发后端的事件驱动任务（如 recall_completion_check），此前事件链在 ETL→后端 这一环断裂
- 思路分析：设计为通用事件触发端点 `POST /api/internal/fire-event`，接收 `event_name` + 可选 `payload`，调用 `trigger_scheduler.fire_event()` 查找并执行 `biz.trigger_jobs` 中匹配的 event 类型任务。使用 Internal-Token 认证防止外部调用
- 修改结果：ETL 可通过 HTTP 触发任意已注册的 event 类型 job，响应返回实际执行的 job 数量。当前主要消费者是 `etl_data_updated` 事件

### `apps/etl/connectors/feiqiu/utils/biz_trigger.py`
- 变更类型：新增
- 原始原因：DWD 加载任务需要在完成后通知后端触发器系统，但 ETL 侧缺少调用后端 API 的工具函数
- 思路分析：封装 `trigger_biz_event()` 函数，通过 `BACKEND_API_URL` + `INTERNAL_API_TOKEN` 环境变量构造 HTTP 请求。设计为"尽力而为"模式：环境变量缺失时 warning 并返回 False，请求失败时 warning 并返回 False，绝不抛异常中断 ETL 流程。超时设置 (5s, 10s) 防止后端无响应时阻塞
- 修改结果：ETL 任务可安全调用 `trigger_biz_event("etl_data_updated")` 通知后端，失败不影响数据加载

### `apps/backend/app/main.py`
- 变更类型：修改
- 原始原因：需要注册新增的 `internal_events` 路由到 FastAPI 应用
- 思路分析：在 import 列表和 `include_router` 调用中添加 `internal_events`。同时包含了其他近期新增路由的注册（tenant_*, admin_*, trigger_jobs 等，属于累积变更）
- 修改结果：`/api/internal/fire-event` 端点可用。CHANGE 注释记录了路由注册历史

### `apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py`
- 变更类型：修改
- 原始原因：DWD 加载完成后需要触发 `etl_data_updated` 事件，驱动召回完成检测
- 思路分析：在 `load()` 方法末尾、return 之前插入事件触发逻辑。触发条件精确限定为"本批处理了 `dwd_assistant_service_log` 或 `dwd_assistant_service_log_ex`"（召回检测依赖助教服务记录数据）。用 try/except 包裹，异常仅 warning 不中断。同时包含了 2026-03-26 的字段映射扩展（other_pay_money_sum、last_consume_time 等新字段从 payload 提取）
- 修改结果：DWD 加载助教服务记录后自动触发事件链：`etl_data_updated` → `recall_completion_check` → `recall_detector.run()` → 标记召回完成 → 生成回访任务

### `apps/backend/app/routers/xcx_board.py`
- 变更类型：修改（非本次核心变更）
- 简要说明：看板路由的近期调整，与事件链修复无直接关联

### `apps/backend/app/schemas/xcx_board.py`
- 变更类型：修改（非本次核心变更）
- 简要说明：看板 Schema 的近期调整

### `apps/backend/app/services/board_service.py`
- 变更类型：修改（非本次核心变更）
- 简要说明：看板服务层的近期调整

### `apps/backend/app/services/customer_service.py`
- 变更类型：修改（非本次核心变更）
- 简要说明：客户服务层的近期调整

### `apps/miniprogram/miniprogram/pages/board-coach/board-coach.ts`
- 变更类型：修改（非本次核心变更）
- 简要说明：助教看板页面的近期调整

### `apps/miniprogram/miniprogram/services/api.ts`
- 变更类型：修改（非本次核心变更）
- 简要说明：小程序 API 服务层的近期调整

## 回滚策略

1. 删除 3 个新增文件
2. 从 `main.py` 移除 `internal_events` 路由注册
3. 从 `dwd_load_task.py` 移除事件触发代码块（`load()` 末尾的 `_service_log_tables` 相关逻辑）

## 合规检查

- ⚠️ 接口代码已变更但 OpenAPI spec 未同步（`compliance.openapi_spec_stale: true`）
- ⚠️ DDL 基线待确认（`compliance.has_ddl_baseline: false`，但本次无数据库变更）
- 无新增迁移 SQL
