From f87998e9b81c17d35d1a73b3ab439ca209ca9b2d Mon Sep 17 00:00:00 2001
From: tanzk <15814458408@qq.com>
Date: Thu, 15 Oct 2026 22:56:27 +0800
Subject: [PATCH] =?UTF-8?q?chore(repo):=20#330=20=E5=B7=A5=E5=85=B7?=
=?UTF-8?q?=E7=9B=AE=E5=BD=95=20.trae/.dsh/.workbuddy=20=E5=8E=BB=E8=B7=9F?=
=?UTF-8?q?=E8=B8=AA=20+=20.gitignore?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
依据《仓库入库规范》C7(工具私有状态不入库):
- git rm -r --cached .trae .dsh .workbuddy (15 文件, 仅去跟踪, 磁盘保留)
- .gitignore 增补 .trae/ .dsh/
---
.../SKILL.md | 56 -
.dsh/skills/csctalon-ui-organization/SKILL.md | 73 --
.dsh/skills/csctalon-workflow-design/SKILL.md | 71 --
.gitignore | 2 +
.trae/documents/327-mcp-readonly-export.md | 81 --
.trae/documents/README.md | 9 -
.../ai-assistant-performance-optimization.md | 169 ---
...eed-refactor-dual-layer-and-daily-grain.md | 502 --------
.../documents/ai-report-optimization-plan.md | 151 ---
.../complaint-and-specialty-improvements.md | 174 ---
.../metric-alerting-notification-design.md | 379 ------
.trae/documents/refund-dashboard-redesign.md | 1096 -----------------
.../time-aware-specialty-workload.md | 191 ---
.trae/documents/收尾计划-AI治理大任务前.md | 105 --
.trae/specs/191-299-closeout/spec.md | 86 --
.trae/specs/191-299-closeout/tasks.md | 45 -
.workbuddy/memory/2026-08-20.md | 18 -
17 files changed, 2 insertions(+), 3206 deletions(-)
delete mode 100644 .dsh/skills/csctalon-management-effectiveness/SKILL.md
delete mode 100644 .dsh/skills/csctalon-ui-organization/SKILL.md
delete mode 100644 .dsh/skills/csctalon-workflow-design/SKILL.md
delete mode 100644 .trae/documents/327-mcp-readonly-export.md
delete mode 100644 .trae/documents/README.md
delete mode 100644 .trae/documents/ai-assistant-performance-optimization.md
delete mode 100644 .trae/documents/ai-feed-refactor-dual-layer-and-daily-grain.md
delete mode 100644 .trae/documents/ai-report-optimization-plan.md
delete mode 100644 .trae/documents/complaint-and-specialty-improvements.md
delete mode 100644 .trae/documents/metric-alerting-notification-design.md
delete mode 100644 .trae/documents/refund-dashboard-redesign.md
delete mode 100644 .trae/documents/time-aware-specialty-workload.md
delete mode 100644 .trae/documents/收尾计划-AI治理大任务前.md
delete mode 100644 .trae/specs/191-299-closeout/spec.md
delete mode 100644 .trae/specs/191-299-closeout/tasks.md
delete mode 100644 .workbuddy/memory/2026-08-20.md
diff --git a/.dsh/skills/csctalon-management-effectiveness/SKILL.md b/.dsh/skills/csctalon-management-effectiveness/SKILL.md
deleted file mode 100644
index 244c4fd..0000000
--- a/.dsh/skills/csctalon-management-effectiveness/SKILL.md
+++ /dev/null
@@ -1,56 +0,0 @@
----
-name: csctalon-management-effectiveness
-description: CSCTalon 客服管理系统管理效果/管理闭环设计规范。定义例外管理、归因不归责、管理成本核算、周报自动化、预警驱动、绩效只汇总不扣分等模式,用于提升"符合管理效果"层面。发现"报表没人看/管理动作重/例外被淹没/绩效扣分争议/周报手工汇总"时使用。
-whenToUse: 设计管理看板、评审报表价值、规划绩效与预警、优化周报/复盘流程、梳理管理层体验时加载。
----
-
-# CSCTalon 管理效果/管理闭环设计规范
-
-> 定位: 系统是解决问题的, 不是制造麻烦的。管理层体验 = 看结果不翻过程、管例外不管全量、催闭环不催人。
-
-## 1. 例外管理(主管视角核心)
-
-- 主管首页只突出「需要动作」的项: 异常/超期/待回看/待审/积压, 正常数据折叠不打扰。
-- 看板默认口径: 红色=要处理, 黄色=快到期, 绿色=正常不显示。
-- 每个「例外」可一键跳转到处理页(详情/跟进/审核), 不要求主管跨页面拼信息。
-
-## 2. 归因不归责(数据真实的地基)
-
-- 投诉审计/退款挽回/绩效数据**只做分析汇总, 不做扣分依据**。
-- 审计结论用于改进流程, 不挂钩个人绩效扣罚。
-- 系统内任何"审计/评估"文案都强调改进导向, 避免逼人"优化数据"。
-
-## 3. 管理成本核算(砍掉没人看的报表)
-
-- 每张报表/看板必须回答: **谁看? 看完做什么决策?** 答不上就不上线。
-- 管理动作(登记/审计/跟进/报表)字段能简则简, 抓核心。
-- 已有报表定期审计: 使用频率低的合并/下线, 不累积僵尸页面。
-
-## 4. 周报/复盘自动化
-
-- 周报 10 项框架数据自动填充(接待/退款/投诉/措施/异常), 人只补结论。
-- 周例会只审到期措施 + 例外项, 不每周重复全量审。
-- 周报确认后固化快照, 可持续批注(活文档), 不下钻不强制派生报告。
-
-## 5. 预警驱动(让异常自己浮现)
-
-- 预警规则可配置(退款激增/工单积压/异常激增/投诉激增), 调度器定时扫描。
-- 预警进待办中心 + 通知中心, 人从"盯数据"变"确认预警"。
-- 预警幂等(同日同规则只一条), 避免刷屏。
-
-## 6. 绩效只汇总不扣分
-
-- 绩效看板呈现工作量/效率/成功率**汇总与趋势**(纵向比进步), 不排名惩罚。
-- 人效算法口径(专项难度系数/本职转接)文档化, 数据变化可追溯解释。
-- 不把投诉/异常数据直接折算进个人扣分。
-
-## 7. 管理效果评审清单(梳理管理层体验时逐项过)
-
-- [ ] 主管第一屏是否只突出例外(异常/超期/待审)?
-- [ ] 是否每个例外一键可达处理页?
-- [ ] 报表是否都能回答"谁看/看完做什么决策"?
-- [ ] 是否有没人看的僵尸报表可下线?
-- [ ] 周报是否自动填充、只补结论?
-- [ ] 预警是否驱动处理而非人工盯数据?
-- [ ] 绩效是否只汇总不扣分、口径可追溯?
-- [ ] 管理动作字段是否最简?
diff --git a/.dsh/skills/csctalon-ui-organization/SKILL.md b/.dsh/skills/csctalon-ui-organization/SKILL.md
deleted file mode 100644
index aa402d0..0000000
--- a/.dsh/skills/csctalon-ui-organization/SKILL.md
+++ /dev/null
@@ -1,73 +0,0 @@
----
-name: csctalon-ui-organization
-description: CSCTalon 客服管理系统前端界面组织规范(Vue3+ElementPlus)。定义页面分层、四范式结构、菜单导航组织、状态色板、组件复用规则,用于新增页面与整体界面梳理。发现"界面组织混乱/菜单无序/风格不统一/组件重复"时使用。
-whenToUse: 新增前端页面、重构菜单导航、统一页面风格、评审界面组织合理性时加载。
----
-
-# CSCTalon 前端界面组织规范
-
-> 适用技术栈: Vue 3 + Vite + Element Plus + ECharts + Pinia。目标是"不培训就会用、新页面照模板拼装"。
-
-## 1. 页面分层(内容组织四层)
-
-任何页面按此四层组织,禁止跳层:
-
-1. **页头**: 页面标题(左) + 一句话副标题说明用途 + 主操作按钮(右, 右上角)。
-2. **筛选区**: 顶部卡片内放 FilterBar/筛选控件(状态/日期/关键词), 变更即刷新。
-3. **主体区**: 表格(列表页) 或 卡片栅格(看板页); 看板优先复用 dashboard 组件(KpiCard/ChartCard/SectionHeader)。
-4. **页脚区**: 分页 + 总数统计(列表页); 看板页可不设。
-
-## 2. 四大交互范式(页面模板)
-
-| 范式 | 结构 | 适用 |
-|------|------|------|
-| 列表页 | 页头 + 筛选 + 表格 + 分页 + 导出 + 右上主按钮 | 异常/标签/发票/政策/排班/知识库 |
-| 详情页 | 面包屑 + 元信息头(el-descriptions) + 主体 + 侧栏/底部时间线 | 异常详情/投诉审计/报告详情 |
-| 表单页 | 必填 ≤3 项置顶 + 其余折叠"更多" + 保存草稿/提交 | 所有登记 |
-| 导入页 | 模板下载 + 上传 + 预览复核 + 确认(回执成功/失败) | 标签批量/排班/发票 |
-
-## 3. 菜单与导航组织(侧边栏)
-
-- 一级菜单按角色可见性分层: 全员可见(运营看板/工作台/我的待办/数据中心/发票/知识库) → 管理层(决策看板/管理闭环) → 管理员(系统管理)。
-- 同一业务域的页面放同一子菜单(如 管理闭环: 报告/周报/措施/异常/排班/预警/跨部门), 不散落。
-- 菜单名与页面 title 一致; 命名避免歧义(如"设备专项标签"区别于"专项任务")。
-- 隐藏路由(hidden: true)用于详情页/跳转页, 不进菜单。
-
-## 4. 全局状态色板(统一语义)
-
-| 状态族 | 颜色 | 语义 |
-|--------|------|------|
-| 待处理/待审/待回看 | warning(黄) | 需要动作 |
-| 进行中/执行中 | primary(蓝) | 已启动 |
-| 已完成/生效/闭环 | success(绿) | 结束 |
-| 异常/失效/超期 | danger(红) | 需关注 |
-| 归档/废止 | info(灰) | 只读留存 |
-
-统一封装在 `web/src/utils/statusColor.js`(已存在), 页面禁止私写配色。
-
-## 5. 组件复用(禁止重复造轮子)
-
-- KPI 卡 → `KpiCard`; 图表卡 → `ChartCard` + `BaseChart`; 区块标题 → `SectionHeader`; 筛选条 → `FilterBar`; 三列队列 → `QueuePanel`。
-- 新页面先引入公共组件拼装, 禁止内联 KPI 卡/图表卡样式。
-- 需要新 props 时向后兼容扩展公共组件, 不在使用页私写覆盖类名。
-
-## 6. 角色化工作台(第一屏差异化)
-
-| 角色 | 第一屏 |
-|------|--------|
-| 系统管理员 | 全局 KPI + 系统告警 + 待审队列 |
-| 主管 | 全局 KPI + 例外清单(异常/超期/待回看) + 待审 |
-| 专项组长 | 本专项 KPI + 本专项待办 + 本组排班 |
-| 专员 | 本人待办 + 本人数据 + 被指派专项 |
-| 普通客服 | 待读政策 + 本人接待 + 提交发票入口 |
-
-## 7. 审核清单(梳理界面时逐项过)
-
-- [ ] 每个页面是否四层结构完整?
-- [ ] 是否落在四大范式之一?
-- [ ] 菜单是否按角色分层、业务归组、命名无歧义?
-- [ ] 状态色是否用统一色板?
-- [ ] 是否复用了公共组件而非内联样式?
-- [ ] 必填项是否 ≤3、其余折叠?
-- [ ] 空数据是否有 el-empty 提示?
-- [ ] 加载是否有 v-loading?
diff --git a/.dsh/skills/csctalon-workflow-design/SKILL.md b/.dsh/skills/csctalon-workflow-design/SKILL.md
deleted file mode 100644
index 8c57dbb..0000000
--- a/.dsh/skills/csctalon-workflow-design/SKILL.md
+++ /dev/null
@@ -1,71 +0,0 @@
----
-name: csctalon-workflow-design
-description: CSCTalon 客服管理系统办公流程/工作流设计规范。定义状态机、统一待办中心、通知提醒、闭环分级、跨模块联动(异常→措施→回看)等设计模式,用于业务流程规划与评审。发现"流程断点/状态混乱/该闭环未闭环/待办分散/无提醒"时使用。
-whenToUse: 设计新业务流程、评审现有流程是否闭环、规划状态机与待办通知、梳理跨模块联动时加载。
----
-
-# CSCTalon 办公流程/工作流设计规范
-
-> 核心原则: 系统第一屏应「推着人走」而非「人到处找事」; 流程要闭环但分级, 不搞一刀切。
-
-## 1. 流程设计五问(任何新流程先回答)
-
-1. **谁发起?** 录入入口是否 ≤30 秒、能自动则自动?
-2. **谁处理?** 是否有明确负责人/接收人(缺省=进统一待办中心)?
-3. **什么状态?** 状态机是否线性可预期(待处理→处理中→已完成/闭环)?
-4. **何时提醒?** 到期/超期/新增是否有通知(站内信+角标)?
-5. **如何闭环?** 结束态是否明确(关闭/归档/回看), 结果是否可追溯?
-
-答不上的流程=不完整, 不上线。
-
-## 2. 状态机规范
-
-- 状态用 String 枚举(避免 SA Enum 迁移麻烦), Pydantic 层校验。
-- 状态流转线性的: `待处理 → 处理中 → 已完成` 为主轴; `挂起/驳回/退回` 为分支。
-- 每个状态变更落时间线记录(谁/何时/从哪到哪/备注), 复用跟进记录表。
-- 状态色统一用 `statusColor.js` 色板(黄=需动作/蓝=进行中/绿=结束/红=异常/灰=归档)。
-
-## 3. 统一待办中心(必做)
-
-- 汇聚所有「该我处理的事」: 待办事项 / 待跟进异常 / 待回看措施 / 待审发票报销 / 待读政策 / 被指派工单。
-- 每条带: 来源 + 优先级 + 截止 + 一键跳转; 按角色过滤(管理层看全部, 一线看本人)。
-- 新模块的"待处理"数据必须接入统一待办中心, 禁止散落各页面。
-
-## 4. 通知提醒(轻量站内信)
-
-- 触发点: 到期(措施回看/异常跟进)、新增(异常登记/政策发布/审计指派)、状态变化(审核通过/被驳回)。
-- 落 `notifications` 表(recipient_id/type/title/content/ref_type/ref_id/is_read), 前端角标 + 列表。
-- 局域网内站内信即可, 不做外部推送。
-
-## 5. 闭环分级(不过度管理)
-
-| 事项重要度 | 闭环要求 |
-|-----------|---------|
-| 小事/日常 | 一句跟进记录即闭环(记录>不记录) |
-| 中事/影响有限 | 跟进 + 状态流转 + 到期回看 |
-| 大事/影响大 | 完整流程: 登记 → 复盘 → 输出措施 → 到期回看 → 结论归档 |
-
-禁止对小事强制走完整流程(会逼人少登记, 数据失真)。
-
-## 6. 跨模块联动(推荐链路)
-
-```
-异常事故(登记+跟进)
- ├─→ 转待办(todo_items) 当需要人执行
- ├─→ 转管理措施(measures) 当需要持续改进/到期回看
- └─→ 转跨部门派发(upstream) 当根因在上游
-投诉审计 → 审计结论 → 转管理措施
-政策发布 → 通知相关角色 + 关联知识库话术同步
-发票提交 → 主管审核 → 报销 → 导出
-```
-
-## 7. 流程评审清单(梳理流程时逐项过)
-
-- [ ] 五问是否全部可答?
-- [ ] 状态机是否线性可预期、流转落时间线?
-- [ ] 待处理项是否进统一待办中心?
-- [ ] 到期/新增是否有通知?
-- [ ] 结束态是否明确、可追溯?
-- [ ] 是否按重要度分级闭环而非一刀切?
-- [ ] 是否与异常/措施/待办/上游正确联动?
-- [ ] 录入是否 ≤30 秒、能自动则自动?
diff --git a/.gitignore b/.gitignore
index ec8d9a3..e8b6d08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -152,4 +152,6 @@ work-data/
certs/
# C7 工具私有状态: AI 工具记忆/会话状态, 写给工具自己用的不入库
.workbuddy/
+.trae/
+.dsh/
diff --git a/.trae/documents/327-mcp-readonly-export.md b/.trae/documents/327-mcp-readonly-export.md
deleted file mode 100644
index f7185ce..0000000
--- a/.trae/documents/327-mcp-readonly-export.md
+++ /dev/null
@@ -1,81 +0,0 @@
-# #327 MCP 只读数据出口 — 实现方案
-
-## 背景与目标
-
-#319 P3 最后一步:对外(第三方/AI Agent)提供受控只读数据出口,替代各处临时拼接的不可审计接口。
-
-**已确认边界**:SSE 传输 + 首批 2 个工具(device_behavior / recharge_traffic)+ 会话级只读兜底。排班考勤延后。
-
-## 技术选型
-
-- **不引新依赖**(无 mcp/fastmcp):用 FastAPI 原生 `StreamingResponse` 实现 SSE 端点,自实现 JSON-RPC 2.0 消息循环(MCP over SSE 协议)。
-- **认证**:复用 `external_service.authenticate` + `check_rate_limit` + `touch_last_used`。
-- **只读兜底**:每个工具调用前 `SET TRANSACTION READ ONLY`(PG 会话级硬约束),连接池归还时自动 reset。
-- **审计**:复用 `S.record_external_access`,每次 MCP 工具调用写 `system_events`。
-
-## 文件变更
-
-### 新增
-
-1. **`backend/app/mcp/__init__.py`** — MCP 协议层(空,标记包)
-2. **`backend/app/mcp/server.py`** — MCP SSE Server 核心
- - `McpSseServer` 类:管理 SSE 连接、JSON-RPC 消息路由、工具分发
- - `handle_message()`:JSON-RPC 2.0 入口(initialize / tools/list / tools/call)
- - `tool_registry`:工具注册表(名称 → handler → schema → scope)
- - `set_readonly(db)`:会话级只读兜底
-3. **`backend/app/mcp/tools.py`** — 首批 2 个工具实现
- - `device_behavior(sn)` → `ADS.supply_device_behavior()`
- - `recharge_traffic(sn, start, end)` → `ADS.supply_recharge()`
- - 每个工具声明:name / description / inputSchema / scope / handler
-4. **`backend/app/api/v1/mcp.py`** — FastAPI 路由
- - `GET /mcp/sse` — SSE 端点(客户端连接,返回 `text/event-stream`)
- - `POST /mcp/messages` — 客户端→服务器 JSON-RPC 消息(MCP over SSE 标准)
- - 认证:从 query param `?token=` 或 `Authorization: Bearer` 取 token(SSE 连接无法带 Header,兼容两种)
- - 限流/审计/scope 校验复用 `_auth` 模式
-
-### 修改
-
-5. **`backend/app/api/v1/router.py`** — `include_router(mcp_router, prefix="/mcp")`
-6. **`backend/app/services/external_service.py`** — `SCOPES` 新增 `"mcp"`(MCP 专用 scope,与既有 device/recharge 区分,便于独立授权)
-
-### 测试
-
-7. **`backend/tests/test_mcp_server.py`** — 单元测试
- - MCP initialize 握手
- - tools/list 返回白名单
- - tools/call 无 token → 401
- - tools/call 无效 scope → 403
- - tools/call device_behavior 正常返回
- - tools/call recharge_traffic 正常返回
- - 只读兜底验证(尝试写操作 → 报错)
-
-## 关键设计决策
-
-| 决策 | 理由 |
-|---|---|
-| 不引 mcp/fastmcp 依赖 | 项目零 MCP 先例,最小实现降低风险;后续若需完整 MCP 生态可再引入 |
-| 自实现 JSON-RPC 2.0 | MCP over SSE 协议简单(initialize/list/call 三类消息),200 行内可覆盖 |
-| scope 新增 `"mcp"` | 与既有 device/recharge 区分,允许管理员单独授权 MCP 通道(可只给 MCP 不给 REST) |
-| 只读兜底用 PG 会话级 | `SET TRANSACTION READ ONLY` 是数据库层硬约束,比应用层判断更可靠;连接池归还自动 reset 无副作用 |
-| SSE 端点支持 query token | SSE 连接(EventSource)无法自定义 Header,兼容 `?token=` 与 `Authorization: Bearer` 两种 |
-
-## 安全约束
-
-- 所有工具调用前强制 `SET TRANSACTION READ ONLY`
-- 工具白名单硬编码在 `tools.py`,新增工具需显式注册(代码评审把关)
-- 审计日志记录 tool_name + 参数摘要 + 调用方 token prefix
-- 限流复用 Redis 固定窗口(每 token 独立计数)
-
-## 验证方案
-
-1. **单元测试**:pytest 覆盖握手/鉴权/白名单/只读/正常调用
-2. **本地冒烟**:启动 uvicorn,用 `curl -N` 连 SSE 端点,验证 initialize → tools/list → tools/call 全流程
-3. **生产冒烟**:发布后用一个测试 token 调用 device_behavior,确认返回结构与 REST 端点一致
-4. **回归**:pytest 全量 0 失败 + ruff 通过 + C2 门禁 0 违规
-
-## 验收对应
-
-- [x] MCP 工具清单与白名单登记文档(`tools.py` 内注册表即文档,头部注释说明)
-- [x] external_token 鉴权 + 只读兜底 + 审计日志落地
-- [x] 端到端:外部客户端经 MCP 查询 device_behavior + recharge_traffic 成功
-- [x] pytest 全量 0 失败 + ruff 通过 + 标准发布
diff --git a/.trae/documents/README.md b/.trae/documents/README.md
deleted file mode 100644
index 745f1fc..0000000
--- a/.trae/documents/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# 说明
-
-本目录下的设计文档已迁移至仓库根目录 `docs/arch/_pending/`(保留 git 历史)。
-
-迁移目的:统一"AI 协作治理规范" 4.2 的架构文档资产化管理,设计文档进 `docs/arch/` 并接受架构锁定流程。
-
-- 迁移日期:2026-09-01
-- 迁移去向:`docs/arch/_pending/`(状态 PENDING,待人工审核锁定)
-- 原 20 份文档经 `git mv` 迁移,历史完整保留
diff --git a/.trae/documents/ai-assistant-performance-optimization.md b/.trae/documents/ai-assistant-performance-optimization.md
deleted file mode 100644
index ed66329..0000000
--- a/.trae/documents/ai-assistant-performance-optimization.md
+++ /dev/null
@@ -1,169 +0,0 @@
-# AI 分析助手性能优化方案(规范流程版)
-
-## Context(为什么改)
-
-生产环境(192.168.10.21,DeepSeek `deepseek-v4-flash`)AI 助手用户反馈"分析耗时较长",但实际诉求是"按既定逻辑 + 系统数据做简单判断"。根因是**架构代差**:全量喂模型 + 让模型自由组织中文报告,简单问句也要走完整 LLM 链路。
-
-### 生产实测(2026-09-12 ~ 09-15,16 条 `ai task done` 日志)
-
-| 指标 | 实测范围 | 平均 | 占比 |
-|---|---|---|---|
-| `build_ms`(数据底座组装) | 360-3042ms | ~700ms(热)/ 2s(冷) | ~15% |
-| `llm_ms`(模型生成) | 1479-6893ms | ~4000ms | **~75%** |
-| `total_ms` | 2188-9935ms | ~5000ms | 100% |
-
-**关键发现**(推翻前置假设):
-1. `LLM_THINKING` 未显式配置 → 走默认 `disabled`(**非思考模式误配**,先前担心的 low/high 不存在)
-2. **`llm_ms` 是绝对大头**(75%),`build_ms` 是次要矛盾
-3. `out_chars` 与 `llm_ms` 强正相关(2308 字符→6484ms;171 字符→1479ms)→ **输出长度直接决定耗时**
-4. `AI_SYNC_WAIT_S=3.0` + `total_ms` 平均 5s → 几乎全走轮询 → 前端体感更长
-
-**优化优先级**(数据驱动):层 1 降参数 > 层 3 规则层秒回 > 层 2 精确取数。
-
----
-
-## 规范流程(按 AGENTS.md 工单/界面/文档纪律)
-
-| 环节 | 动作 | 凭证 |
-|---|---|---|
-| ① 工单 | 在 GitHub `tanzk/csctalon` 建 issue,标题"AI 助手性能优化:参数调优+规则层+精确取数",正文含本方案摘要 + 验收标准 | issue 号 `#XX`(实施时填) |
-| ② 方案审核 | 本计划经人工终审(用户确认)后开工 | 计划文件 + 用户确认 |
-| ③ 实现 | 每层独立 commit,提交信息带 `#XX` | commit sha |
-| ④ 测试 | pytest + npm test + 生产灰度对比 | 测试输出 |
-| ⑤ 关闭凭证 | 工单关闭前写 `## 关闭凭证核验 (YYYY-MM-DD)` 评论,逐条对应验收标准 + commit sha + 实测命令 | 评论 |
-
-**文档性质**:本方案属 **REQ**(有功能边界、验收标准、归属工单),非 META。
-**界面纪律**:层 3 规则层不改前端 UI(返回结构兼容),不触发界面设计说明三层落地;若后续加"规则层秒回"角标则需补设计说明。
-
----
-
-## 层 1:参数调优(立竿见影,优先级最高)
-
-生产数据证明 `llm_ms` 是大头,降输入/输出 token + 提同步等待时间见效最快。
-
-| 配置项 | 当前默认 | 改为 | 依据 |
-|---|---|---|---|
-| `AI_MAX_TOKENS` | 1500 | **800** | out_chars 实测多在 1200-2000,800 token 足够;省一半生成时间 |
-| `AI_ANALYZE_MAX_CHARS` | 12000 | **6000** | 喂 4000 输入 token 降首 token 延迟 |
-| `AI_SYNC_WAIT_S` | 3.0 | **8.0** | total_ms 平均 5s,提到 8s 让多数请求一次返回不走轮询 |
-| `LLM_TIMEOUT_S` | 120.0 | 保持 | 云端生成长尾兜底 |
-
-**改文件**:
-- [backend/app/core/config.py](file:///Users/tanzk/dev/csctalon/backend/app/core/config.py#L132-L180)(3 个默认值)
-- [deploy/env.production.example](file:///Users/tanzk/dev/csctalon/deploy/env.production.example#L117-L127)(补 3 个示例值,避免生产 .env 与代码默认脱节——本次生产就是 .env 没显式配走默认值)
-
-**同步改生产**:SSH 192.168.10.21 `/opt/csctalon/.env` 追加 3 行 + `docker compose restart backend`。
-
-**测试影响**:`test_ai_assistant.py` L77-L120 只断言结构/字段不断言长度,**不回归**。
-
-**验收**:改后同问句 `total_ms` 应从 ~5s 降到 ~2.5s,多数请求走 `success` 不走 `running`。
-
----
-
-## 层 2:问题路由 + 精确取数(次优先,build_ms 边际收益小但降输入 token)
-
-**复用 [ai_focus_service._KEYWORD_TO_SCOPE](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_focus_service.py#L31-L36)**(scope 值 `refund/complaint/reception` 与 feed 顶层段名**逐字符对齐**,见 [external.py:436-438](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/external.py#L436-L438)),**不复用 `GLOSSARY_ROUTE`**(粒度错位)。
-
-**改 [backend/app/services/ai_feed.py](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_feed.py)**:
-1. 新增 `_keyword_to_scope(question) -> str | None`:复用 `_KEYWORD_TO_SCOPE` 映射表(导出复用,勿复制防漂移)
-2. 新增 `_select_feed_sections(feed, scope) -> dict`:恒量段(period/date/date_start/date_end/generated_at/_spec/range)始终保留 + 命中 scope 对应业务段;scope=None 回退全量(兼容)
-3. `build_messages` 内:`_build_feed` → `_select_feed_sections` → `_trim_feed`
-
-**缓存策略**:brief 仍走 `_build_brief`(命中 `today:kpi`/`refund:do`/`complaint:*` 既有 key,**铁律 #11**),段选取只在内存 dict 切片零成本。**不**按段单独调聚合函数(today_kpi 一次出 reception+refund,分调反多一次 miss)。
-
-**测试**:`test_build_messages_structure`(L78)走全量兜底不断言段,不回归;新增 `test_build_messages_scope_refund_only` 断言问"今日退款"→ feed 不含 complaint 顶层段。
-
-**风险**:问"今日退款"保留整段 refund(含 `a_route.litigation_*`)不漏涉诉率。
-
----
-
-## 层 3:简单判断走规则层(最贴合诉求,纯取数问句秒回)
-
-**入口**:[ai.py ai_analyze](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/ai.py#L174-L270) 内 `_require_enabled` 之后、`ai_task_manager.submit` 之前**短路**。命中规则 → 直接返回 `R(data={status:success, result_md, **window_info})`,**不入队** task_manager(毫秒级)。
-
-**新增 `backend/app/services/ai_rule_engine.py`**,导出:
-```python
-def try_rule_answer(db: Session, question: str, eff: dict) -> str | None
-```
-逻辑:关键词正则匹配 → 调 `ai_feed._build_feed`(命中缓存秒取,含坏缓存自愈)→ 取字段 → 模板拼 Markdown。未命中/取数为空 → 返回 None 回退 LLM 路径。
-
-**模板放代码常量**(非 `ai_focus_configs`——那是 admin 可改的"主观偏好",规则模板是"客观取数",混放会让运维误改取数口径,违反 System 铁律精神 [ai_feed.py:22-34](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_feed.py#L22-L34))。
-
-**首批规则**(关键词正则 → 模板):
-- `今日|昨日|本周|本月.*(退款|提现).*(多少笔|笔数|情况|金额)` → `refund.total_count/total_amount/a_route.litigation_ratio`
-- `...投诉.*(积压|登记|处理|多少)` → `complaint.{source}.registered/handled/backlog`
-- `...接待.*(量|多少|情况|会话)` → `reception.total/by_specialty`
-
-**前端兼容**:返回结构与现有 `success` 一致,[AssistantDrawer.vue:328](file:///Users/tanzk/dev/csctalon/web/src/components/AssistantDrawer.vue#L312-L345) `if (d.status === 'success')` 天然命中,**零改**。
-
-**测试**:
-- `test_rule_engine_refund_count`(问"今日退款多少笔"→ 命中、返回含 total_count)
-- `test_rule_engine_miss_falls_back_to_llm`(问"分析下经营策略"→ 返回 None)
-- `test_rule_engine_respects_window`(问"昨日投诉积压"→ _AiWindow 解析昨日窗口,规则层取昨日数据)
-
-**风险**:模板数字必须来自 feed(禁编造);取数 None 时模板写"暂无"不写 0;规则层在 `_build_feed` 坏缓存自愈([ai_feed.py:70-81](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_feed.py#L70-L81))之后取数(同函数天然安全)。
-
----
-
-## 实施顺序
-
-1. **建工单** `#XX`(实施时填实际 issue 号)
-2. **层 1**(1 commit):改 config.py + env.production.example + 同步生产 .env 重启 → 看日志 llm_ms 是否降
-3. **层 2**(1 commit):改 ai_feed.py + 新增测试 → pytest
-4. **层 3**(1 commit):新增 ai_rule_engine.py + 改 ai.py 短路 + 新增测试 → pytest
-5. **前端测试**:`npm test -- AssistantDrawer.spec.js` 不回归
-6. **生产灰度**:层 1 上线后对比同问句 total_ms;层 3 上线后"今日退款多少笔"应 <500ms
-7. **关闭凭证核验**:工单评论逐条对应验收标准 + commit sha + 实测命令
-
----
-
-## 验证方案
-
-**后端**:
-```bash
-cd backend && pytest tests/test_ai_assistant.py -v
-# 期望:现有用例全过 + 新增 3 个规则层/段选取用例过
-```
-
-**前端**:
-```bash
-cd web && npm test -- AssistantDrawer.spec.js
-# 期望:L105-L166 轮询/失败重试用例不回归
-```
-
-**生产灰度**(层 1 后):
-```bash
-ssh tanzk@192.168.10.21 'cd /opt/csctalon && tail -f logs/backend/app.log | grep "ai task .* done"'
-# 期望:llm_ms 从 ~4000 降到 ~2000,total_ms 从 ~5000 降到 ~2500
-```
-
-**端到端**(层 3 后):
-```bash
-curl -X POST http://192.168.10.21:8000/api/v1/ai/analyze \
- -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
- -d '{"question":"今日退款多少笔","period":"daily"}'
-# 期望:<500ms 返回 success,不走 LLM
-```
-
----
-
-## 风险汇总
-
-| 风险 | 缓解 |
-|---|---|
-| 规则层模板数字编造 | 模板取数必须来自 feed,None→"暂无" |
-| 精确取数漏段 | refund 整段保留,`a_route.litigation_*` 天然在内 |
-| 缓存 key 漂移 | 全部复用 `today:kpi`/`refund:do`/`complaint:*`,勿新建(铁律 #11) |
-| 800 token 截尾 | 层 2 输入变小后输出同步收缩;复杂问句仍走 LLM |
-| 生产 .env 改动风险 | 改前备份,改后 restart backend,失败可回滚 |
-
-## Critical Files
-
-- [backend/app/api/v1/ai.py](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/ai.py) — 层 3 规则层短路入口
-- [backend/app/services/ai_feed.py](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_feed.py) — 层 2 段选取
-- [backend/app/services/ai_focus_service.py](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_focus_service.py) — 复用 `_KEYWORD_TO_SCOPE`(L31)
-- [backend/app/core/config.py](file:///Users/tanzk/dev/csctalon/backend/app/core/config.py) — 层 1 参数默认值(L132-L180)
-- [deploy/env.production.example](file:///Users/tanzk/dev/csctalon/deploy/env.production.example) — 层 1 生产示例
-- `backend/app/services/ai_rule_engine.py` — 层 3 新增
-- [backend/tests/test_ai_assistant.py](file:///Users/tanzk/dev/csctalon/backend/tests/test_ai_assistant.py) — 测试基线 + 新增用例
-- `/opt/csctalon/.env`(生产)— 层 1 同步
diff --git a/.trae/documents/ai-feed-refactor-dual-layer-and-daily-grain.md b/.trae/documents/ai-feed-refactor-dual-layer-and-daily-grain.md
deleted file mode 100644
index 757cccf..0000000
--- a/.trae/documents/ai-feed-refactor-dual-layer-and-daily-grain.md
+++ /dev/null
@@ -1,502 +0,0 @@
-# AI 助手数据底座重构:双层结构 + 日颗粒度 + DB 化 Prompt Seed
-
-## Context(为什么改)
-
-工单 #98 性能优化已上线(参数调优 + 段选取 + 规则层),但用户实测"昨日整体经营分析"报告暴露**数据源层 + Prompt 配置化**问题:
-
-1. **专项分布脏数据**:报告显示"内部投诉/外部投诉 暂无(lead 为空)0 会话/0 设备",根因是 [external.py:624-709](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/external.py#L624) `_reception_specialty` 只过滤 `is_active=True`,但 specialties 表里这两条历史专项(已合并到"投诉专项")的 `is_active` 仍为 True。
-2. **数据不同源**:`ai_feed._build_feed` ([ai_feed.py:46-88](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_feed.py#L46)) 走 `external._build_brief`,自拼 `_reception_specialty`/`_complaint_enhanced`,与今日值班看板前端用的 `A.today_kpi`/`A.today_reception_breakdown`/`A.today_staff_efficiency` 不同源 → "看板一个数、AI 报告另一个数"。
-3. **缓存 TTL bug**:[external.py:419](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/external.py#L419) `cached_call(..., 60, ...)` 固定 60s,历史日 brief 也 60s 过期重算。
-4. **AI 主观推断**:"处理量 238 > 登记量 190 → 在消化存量"是无证据归因。现有 `focus-complaint` seed 里甚至写了"处理滞后于登记说明积压恶化"——这条规则本身就是错的,需要 DB 层修正。
-5. **硬编码**:`_BUCKET` 映射 (external.py:633-640) 把专项名硬编码到 today_staff_efficiency 字段名;`LEGACY_COMPLAINT_SPECIALTIES` 常量靠名字硬编码过滤。
-6. **业务判定基准缺失**:AI 只列数字不下好坏判断;缺工作时间说明(08:00~23:59,非工作时间 AI 协助);这些本应是运营可改的 prompt seed,但当前写死在代码里就改不了。
-
-用户确认方案:① 日报双层结构(顶层=今日看板原始数据,底层=AI 增量分析);② 周/月/区间按日颗粒度(汇总+日走势);③ 历史日懒加载快照(首次访问重算+24h 缓存,后续命中);④ **业务判定基准/工作时间/防主观推断铁律/AI 增量分析段全部 DB 化(运营可改可排序可启停),不写死代码**;⑤ **数据层去硬编码**(specialty is_active 治理 + efficiency_field 字段);⑥ 一次性完整改造 P0+P1。
-
-**分层原则**(对齐既有设计文档 `docs/design/20260907-ai-assistant-focus-configurable-0.md`):
-- **代码层(不可改)**:`_SYSTEM_PROMPT` 防幻觉底线 + `ai_glossary.GLOSSARY` 技术口径定义(三源分类/四路/登记处理口径/时区/金额单位/专项 vs 行政组长)
-- **DB 层(可改)**:`ai_focus_configs` 表的 Focus seed(运营关注点/业务判定/工作时间/防主观推断指导/AI 增量分析段)
-
-## 关键文件
-
-- [backend/app/services/ai_feed.py](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_feed.py) — `_build_feed` 改造 + `_select_feed_sections` 重写 + `_SYSTEM_PROMPT` 兜底铁律 + 周/月 `_range_feed` 新增
-- [backend/app/services/ai_glossary.py](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_glossary.py) — 不改动(已是技术口径代码常量,按设计分层保留)
-- [backend/app/api/v1/external.py](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/external.py) — `_reception_specialty` 移除 `_BUCKET` 硬编码改为读 `s.efficiency_field` + `_build_brief` TTL bug (line 419)
-- [backend/app/services/cache_warmer.py](file:///Users/tanzk/dev/csctalon/backend/app/services/cache_warmer.py) — 新增 AI 路径 `today:staff:...:200:...` 预热条目
-- [backend/app/models/specialty.py](file:///Users/tanzk/dev/csctalon/backend/app/models/specialty.py) — Specialty 模型加 `efficiency_field` 字段
-- [backend/app/api/v1/ai.py](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/ai.py#L173) — `/ai/analyze` 入口(不改动,仅对齐参数)
-- [backend/app/services/ai_focus_service.py](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_focus_service.py) — 不改动(现有 `_load_all`/`focus_for_question`/CRUD 已支持 DB 化 prompt seed 注入,新增 seed 自动生效)
-
-复用既有函数:`A.today_kpi` (analytics.py:3480) / `A.today_reception_breakdown` (analytics.py:4305) / `A.today_refund_breakdown` (analytics.py:5457) / `A.today_staff_efficiency` (analytics.py:5825) / `complaints.complaint_summary` / `complaints.complaint_backlog` / `cache_ttl_for` (redis_client.py) / `focus_for_question` (ai_focus_service.py:83)
-
----
-
-## A. 数据层去硬编码(P0 必做)
-
-### A.1 DB 治理:specialty is_active 标记
-
-通过 alembic migration 把 specialties 表"内部投诉"/"外部投诉"两条记录的 `is_active` 置 False(DB 治理),代码层 `_reception_specialty` 只过滤 `is_active=True` 即可,不再依赖 `LEGACY_COMPLAINT_SPECIALTIES` 常量名硬编码。
-
-**保留 `LEGACY_COMPLAINT_SPECIALTIES` / `COMPLAINT_SPECIALTIES_ALL` 常量**([analytics.py:1457-1461](file:///Users/tanzk/dev/csctalon/backend/app/services/analytics.py#L1457)):用于 SCD 历史归因查询(analytics.py:3087 / 3110),识别"曾经属于投诉类"的专项(包括已合并的),这是合理的口径定义不是硬编码。
-
-### A.2 specialties 表加 `efficiency_field` 字段
-
-[external.py:633-640](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/external.py#L633) `_BUCKET` dict 把专项名硬编码到 today_staff_efficiency 的 sp_*/pp_* 字段名。改为 DB 字段:
-
-- Specialty 模型加 `efficiency_field: Mapped[str | None]` (String(64), nullable, comment="对应 today_staff_efficiency 的字段名, 格式 'sp_xxx,pp_xxx'")
-- Alembic migration 给 6 个正式专项填值:
- - 咨询接待 → `sp_zixun,pp_zixun`
- - 套餐专项 → `sp_taocan,pp_taocan`
- - 退款挽回 → `sp_recovery,pp_recovery`
- - 退款处理 → `sp_processing,pp_processing`
- - 投诉专项 → `sp_internal,pp_internal`(投诉类合并到此桶)
- - 内部投诉/外部投诉 → NULL(is_active=False 不参与)
-- `_reception_specialty` 改为从 `s.efficiency_field` 读字段名,移除 `_BUCKET` dict
-- 未配置 `efficiency_field` 的专项兜底跳过 sessions/devices 统计(不再硬兜底到"咨询接待"桶,避免错误归并)
-
-### A.3 修复 `_reception_specialty` 兜底逻辑
-
-原 [external.py:645-646](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/external.py#L645) 注释里说"临时专项兜底到咨询接待桶"——这是错误归并。改为:`efficiency_field` 为空的专项(如临时专项、未配置字段)sessions/devices 显示 0 并在 `_spec` 注释说明"未配置 efficiency_field, 不计入会话/设备统计",不再硬兜底。
-
----
-
-## B. 数据源切换(P0 必做:日报双层结构)
-
-### B.1 `ai_feed._build_feed` 改造
-
-废弃走 `_build_brief` 路径,直接调今日看板服务函数,与前端今日看板页逐字段对齐。
-
-```python
-# ai_feed.py 新 _build_feed(db, period, date, start, end) -> dict
-win = _window(period, date, start=start, end=end) # 复用 external._window
-s, e = win["start"], win["end"]
-today_data = {
- "period": win["period"], "date": win.get("date"),
- "date_start": win["date_start"], "date_end": win["date_end"],
- "generated_at": _now_cn().isoformat(),
- "kpi": cached_call(f"today:kpi:None:{s}:{e}", cache_ttl_for(s,e,60), A.today_kpi, db, None, s, e),
- "reception": cached_call(f"today:reception:None:{s}:{e}", cache_ttl_for(s,e,60), A.today_reception_breakdown, db, None, s, e),
- "refund": cached_call(f"today:refund:None:{s}:{e}", cache_ttl_for(s,e,60), A.today_refund_breakdown, db, None, s, e),
- "complaint": _complaint_block(db, s, e), # 调 complaints.summary + backlog 路由本体
- "staff": _compact_specialty_staff(db, s, e), # today_staff_efficiency + _compact_specialty 压缩
- "_spec": _spec_brief(),
-}
-```
-
-关键点:
-- **同源同缓存**:复用今日看板路由层 key(`today:kpi:None:{s}:{e}` 等),与前端共享 Redis,"与看板完全一致"。
-- **TTL 自动感知**:`cache_ttl_for(s, e, 60)` 已实现历史日 24h、今天 60s。
-- **`_complaint_block`**:调 `complaints.complaint_summary` + `complaints.complaint_backlog`(已 60s 缓存),与今日看板投诉接口同源。
-- **`_compact_specialty_staff`**:调 `A.today_staff_efficiency(topn=200, aggregate_by="person")`(前端默认 topn=30,AI 用 200 拿更多人),再用现有 `_compact_specialty` 压缩 by_specialty 段(去逐人名单)。
-- **`today_queue` 不进 daily feed**:实时队列历史日为空,AI 不需要;在 `_spec.today_queue` 注释"实时队列, 历史日为空, AI 应跳过"。
-
-### B.2 顶层 `today_data` 结构示意
-
-```json
-{
- "period": "daily", "date": "2026-09-14",
- "date_start": "2026-09-14T00:00:00+08:00", "date_end": "2026-09-14T23:59:59+08:00",
- "generated_at": "2026-09-15T10:00:00+08:00",
- "kpi": {
- "reception": {"value": 5850, "yoy_value": 6368, "yoy_pct": -8.1, "device_count": 2913, "breakdown": {...}, "ai_takeover": {...}},
- "refund_count": {"value": 256, "yoy_value": 230, "yoy_pct": 11.3},
- "refund_amount": {"value": 34016.20, "yoy_value": 27372.76, "yoy_pct": 24.3},
- "actual_cashflow": {"value": ..., "yoy_value": ...},
- "cash_routes_today": {"route_a": {...}, "route_b": {...}, "route_c": {...}, "route_d": {...}, "operational": ..., "diff": ...},
- "complaint": {"value": 190, "yoy_value": ...},
- "staff_on_duty": {"value": 8, "yoy_value": 9},
- "ticket_done": {"value": 200, "real_done_today": 50, "real_done_yoy": 45},
- "metric_levels": {"refund_amount": {"level": "warning", "target": ..., "reason": ...}}
- },
- "reception": {"by_specialty": [{"specialty": "投诉专项", "lead": "洪佳", "lead_role": "专项负责人", "member_count": 5, "sessions": 9, "devices": 5}]},
- "refund": {"total_amount": {...}, "routes": {"route_a": {...}, ...}, "a_route": {"litigation_ratio": {...}, "litigation_amount": ...}},
- "complaint": {"total": {"registered": 190, "handled": 238, "backlog": 77}, "by_source": [{...}, {...}, {...}]},
- "staff": {"by_specialty": [...]},
- "_spec": {"money_unit": "元", "complaint": "registered=窗口内新登记...", "today_queue": "历史日为空"}
-}
-```
-
-### B.3 `_select_feed_sections` 重写
-
-新顶层结构变为 `kpi`/`reception`/`refund`/`complaint`/`staff`。`kpi` 内已含所有指标汇总+环比,单独切 `kpi` 就够答大多数问句。
-
-新映射:
-- `scope="reception"` → 保留 `kpi`(只含 reception 相关子字段)+ `reception`
-- `scope="refund"` → `kpi`(refund_count/refund_amount/cash_routes_today/actual_cashflow)+ `refund`
-- `scope="complaint"` → `kpi`(complaint)+ `complaint`
-- `scope=None` → 全量
-
-实现:`_select_feed_sections` 内对 `kpi` 做子字段过滤(按 scope 选 key),其余顶层段按 scope 直接切片。`_CONSTANT_FEED_KEYS` 扩展加入 `kpi`(裁剪后)。
-
-### B.4 `_build_brief` 不删
-
-[external.py:312-325](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/external.py#L312) `external_brief` 路由对外开放(鉴权+scope 校验+access log),依赖 `_build_brief`。**保留 `_build_brief` 但做最小修复**:修 TTL bug (line 419);`_reception_specialty` 的修复已在 A 节完成(移除 `_BUCKET` + DB is_active 治理自动生效)。`ai_feed` 不再调它即可,二者解耦。
-
----
-
-## C. 历史日懒加载快照
-
-### C.1 复用 `today:*` 命名空间
-
-不新建 `ai:snapshot:{date}`。理由:
-1. `today:*` 已被今日看板前端、`cache_warmer` 预热,历史日首次访问大概率已命中。
-2. 设计目标"与看板完全一致"——共享 key 是强约束,隔离反而可能漂移。
-3. `cache_ttl_for` 已实现历史日 24h、今天 60s 自动切换。
-
-### C.2 流程
-
-```
-1. 用户问"昨天的情况" → ai.py /ai/analyze 解析出 date=昨天
-2. _build_feed 调 cached_call("today:kpi:None:2026-09-14T00:00:00:2026-09-14T23:59:59", 24h, A.today_kpi, ...)
-3. Redis 未命中 → A.today_kpi 重算(_resolve_windows 内部 date=历史日 → is_realtime=False, 整天窗口)
-4. cached_call 写回 Redis TTL=24h
-5. 后续访问直接命中缓存, 毫秒级返回
-```
-
-无需额外代码,复用 `cache_ttl_for` 即可。
-
-### C.3 `today_queue` 等实时接口
-
-历史日天然返回空(实时队列只看当前未结)。在 `_spec.today_queue` 注释明示"历史日为空,AI 应跳过该段不陈述"。`today_staff_efficiency` 历史日有值(按人聚合窗口内数据),正常用。
-
----
-
-## D. 周/月/区间按日颗粒度
-
-### D.1 双层结构
-
-```python
-range_data = {
- "period": "weekly|monthly|range",
- "date_start": win.start, "date_end": win.end,
- "summary": _range_summary(db, s, e), # 窗口总量(复用 today_kpi 多日模式)
- "daily_trend": _daily_trend(db, s, e), # 按日时序数组
- "_spec": _spec_brief(),
-}
-```
-
-### D.2 汇总:复用 `today_kpi` 多日模式
-
-`today_kpi(db, start=s, end=e)` 已支持多日窗口([analytics.py:3491](file:///Users/tanzk/dev/csctalon/backend/app/services/analytics.py#L3491) 调 `_resolve_windows` 走 start+end 分支),返回的 `value` 即窗口总量、`yoy_value` 即上一等长周期总量。**直接复用,不重新做 sum 各日**(避免双计/口径漂移)。
-
-```python
-def _range_summary(db, s, e):
- return cached_call(f"today:kpi:None:{s}:{e}", cache_ttl_for(s,e,60), A.today_kpi, db, None, s, e)
-```
-
-### D.3 日走势数组
-
-```python
-def _daily_trend(db, s, e):
- days = _days_range(s, e) # 北京时区日历日列表
- out = []
- for d in days:
- ds, de = f"{d}T00:00:00", f"{d}T23:59:59"
- k = cached_call(f"today:kpi:None:{ds}:{de}", cache_ttl_for(ds,de,60), A.today_kpi, db, None, ds, de)
- c = _complaint_block(db, ds, de) # 复用 complaint 段
- out.append({
- "date": d,
- "reception": k["reception"]["value"],
- "devices": k["reception"]["device_count"],
- "ai_takeover_pct": k["reception"]["ai_takeover"]["session_pct"],
- "refund_count": k["refund_count"]["value"],
- "refund_amount": k["refund_amount"]["value"],
- "actual_cashflow": k["actual_cashflow"]["value"],
- "complaint_registered": c["total"]["registered"],
- "complaint_handled": c["total"]["handled"],
- "complaint_backlog": c["total"]["backlog"],
- })
- return out
-```
-
-JSON 样例:
-
-```json
-"daily_trend": [
- {"date":"2026-09-08","reception":1200,"devices":800,"ai_takeover_pct":15.0,"refund_count":40,"refund_amount":9500.00,"actual_cashflow":9000.00,"complaint_registered":12,"complaint_handled":15,"complaint_backlog":28},
- {"date":"2026-09-09","reception":1180,"devices":790,"ai_takeover_pct":16.2,"refund_count":38,"refund_amount":8800.00,"actual_cashflow":8600.00,"complaint_registered":10,"complaint_handled":14,"complaint_backlog":24}
-]
-```
-
-### D.4 月报 30 天 token 控制
-
-30 天 × 9 字段 ≈ 270 数字 + JSON 结构字符 ≈ 2-3k token,可控。**但前提是不带成员名单/专项明细**——日走势只放关键指标,明细走 `summary` 段(一份)。
-
-降级策略(超过 `AI_ANALYZE_MAX_CHARS`):
-- 30 天 → 前 14 天逐日 + 后 16 天按周聚合(4 周),结构不变,date 标签换 `W37(09-08~09-14)`。
-- 仍超 → 只保留 6 个核心指标(reception / refund_amount / complaint_registered / complaint_handled / complaint_backlog / ai_takeover_pct),砍掉 devices / refund_count / actual_cashflow。
-
-### D.5 与 `today_kpi` weekly/monthly 关系
-
-复用 `today_kpi` 自身的多日模式拿窗口总量(start+end 一把调);日走势是额外的按日循环,不冲突。`today_kpi` 不需要改。
-
----
-
-## E. Prompt 层 DB 化(核心改动:去硬编码 → ai_focus_configs seed)
-
-### E.1 分层原则(对齐既有设计文档)
-
-- **代码层(不可改)**:`_SYSTEM_PROMPT` 防幻觉底线 + `ai_glossary.GLOSSARY` 技术口径定义
-- **DB 层(可改)**:`ai_focus_configs` 表的 Focus seed(运营可改可排序可启停)
-
-### E.2 新增 4 条 ai_focus_configs seed(通过 alembic migration 插入)
-
-**Seed 1: business-benchmark**(业务判定基准)
-```python
-{
- "code": "business-benchmark", "scope_key": "global", "sort_order": 2,
- "title": "业务判定基准",
- "content": (
- "业务判定基准(方向性判据, 用于判定'好/坏'与'风险/机会'): "
- "退款越少越好(金额/笔数); 均单价越低越好; 退款单数越少越好; "
- "接待量越多越好; 接待会话数越多越好; "
- "投诉越少越好; 投诉严重性 外部投诉 > 商户号投诉 > 内部投诉(即外部投诉 1 件 > 商户号 10 件); "
- "投诉处理越及时越好(handle - register 间隔越短越好); "
- "退款挽回率越高越好(挽回成功/分母); "
- "积压投诉越低越好(窗口内登记未闭环). "
- "陈述'环比涨跌'后必须按此基准标注'利好/利空', 不得只报数字不评判."
- ),
-}
-```
-
-**Seed 2: work-time-ai-assist**(工作时间与 AI 协助)
-```python
-{
- "code": "work-time-ai-assist", "scope_key": "global", "sort_order": 3,
- "title": "工作时间与 AI 协助",
- "content": (
- "客服工作时间 08:00~23:59; 非工作时间(00:00~07:59)由 AI 协助处理. "
- "解读'接待量为 0 / 完工单为 0'时, 必须先核对窗口是否落在非工作时间, "
- "不得直接判定'无人值班'或'系统故障'. "
- "AI 接管率(ai_takeover)是可控指标前置: 接待量可升可控, AI 接管率上升=利好."
- ),
-}
-```
-
-**Seed 3: inference-discipline**(防主观推断指导)
-```python
-{
- "code": "inference-discipline", "scope_key": "global", "sort_order": 4,
- "title": "防主观推断指导",
- "content": (
- "防主观推断指导: "
- "① 不同口径的数字(如 registered=窗口内新登记 vs handled=窗口内完成含历史单)禁止直接相减或比较大小; "
- "必须显式标注口径差异后再下结论. "
- "② 禁止做'处理量 > 登记量 → 消化存量'这类无证据因果推断; "
- "如需描述关系, 只能说'本期 handled 高于 registered, 差额源于窗口外登记、窗口内办结的历史单'等可验证陈述. "
- "③ 数据未提供的归因(如'退款上升因 XX 活动')禁止编造, 一律写'原因需结合运营侧确认'. "
- "④ yoy_value/yoy_pct 是上一等长周期汇总, 不是另一天的独立明细, 不得当独立日数据展开陈述."
- ),
-}
-```
-
-**Seed 4: ai-incremental-analysis**(AI 增量分析任务段,开放题触发)
-```python
-{
- "code": "ai-incremental-analysis", "scope_key": "global", "sort_order": 5,
- "title": "AI 增量分析任务",
- "content": (
- "当用户问'分析/总览/怎么看/风险/异常'类开放题时(纯取数问句跳过本段), 增量分析任务: "
- "- 口径差异提醒: 当数据里出现 registered 与 handled 这类不同口径数字(见口径卡), 必须显式标注口径, 禁止直接相减或推断因果; "
- "- 环比异常预警: yoy_pct 超 ±30% 的指标, 必须点名并给出可能原因方向(数据/运营/外部, 不下结论); "
- "- 风险与机会: 按 business_benchmark 卡的方向判定(如'退款金额上升=风险'), 每类最多 1 条; "
- "- 下钻建议: 给出'看哪个看板卡片/下钻到哪一层'的可执行建议(不重复数据)."
- ),
-}
-```
-
-### E.3 更新现有 seed(修正错误口径)
-
-**修正 focus-complaint seed**(现有内容里有"处理滞后于登记说明积压恶化"是错误推断,且专项列表过时):
-- 原文:"②登记(窗口新增)与处理(真实办结)要分开看, 处理滞后于登记说明积压恶化"
-- 改为:"②登记(窗口新增)与处理(真实办结)是不同口径, 禁止直接相减或推断因果; 处理>登记属正常(含历史单办结), 不必然意味'消化存量'"
-
-**修正 focus-reception seed**(专项列表过时,内部/外部投诉已合并):
-- 原文:"②六个专项(咨询/套餐/退款处理/退款挽回/内部投诉/外部投诉)的会话与设备规模"
-- 改为:"②五个正式专项(咨询接待/套餐专项/退款处理/退款挽回/投诉专项)的会话与设备规模, 历史专项(内部投诉/外部投诉)已合并到投诉专项"
-
-### E.4 `_SYSTEM_PROMPT` 兜底铁律(不可改,防幻觉底线)
-
-在 [ai_feed.py:23-35](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_feed.py#L23) `_SYSTEM_PROMPT` 末尾追加(一句话兜底,详细在 Focus seed):
-
-```
-8. 防主观推断底线: 不同口径数字(如投诉 registered/handled)禁止直接相减或做因果推断; 禁止"处理量高→消化存量"这类无证据归因.
-9. 业务判定方向: 退款↓利好 / 接待↑利好 / 投诉↓利好 / 挽回↑利好 / 积压↓利好; 比较后必须标注利好/利空.
-10. 工作时间 08:00~23:59; 非工作时间由 AI 协助; 解读空值前先核对窗口时段.
-```
-
-### E.5 不改动 `ai_glossary.GLOSSARY`
-
-现有 8 个 key(complaint_sources/complaint_reg_handled/refund_routes/refund_litigation/reception_metric/timezone/money_unit/lead_systems)属于技术口径定义,按设计分层保留代码常量。`glossary_for_question` 的"基础卡强制注入"逻辑也保留(路由逻辑不是 seed 内容)。
-
-### E.6 现有 `focus_for_question` 自动加载(无需改服务层)
-
-`ai_focus_service._load_all(db)` ([ai_focus_service.py:43-60](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_focus_service.py#L43)) 已实现 60s 进程缓存 + 按 scope_key 路由 + global 兜底全带。新 seed 写进 DB 后自动被加载,无需改服务层代码。运营通过 `/ai/focus-configs` REST 路由([ai.py:306-329](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/ai.py#L306))增删改启停。
-
----
-
-## F. 前序修复(P0 必做)
-
-### F.1 修 `today_kpi` 缓存 TTL bug
-
-[external.py:419](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/external.py#L419):
-
-```python
-# 改前
-kpi = cached_call(f"today:kpi:None:{start_s}:{end_s}", 60, A.today_kpi, db, None, start_s, end_s)
-# 改后
-kpi = cached_call(f"today:kpi:None:{start_s}:{end_s}", cache_ttl_for(start_s, end_s, 60), A.today_kpi, db, None, start_s, end_s)
-```
-
-第 424 行 `refund_decision_overview` 已正确用了 `cache_ttl_for`,对照即可。修后历史日 brief 不再 60s 过期重算。
-
-### F.2 数据源切换是否自动解决 LEGACY 问题
-
-部分解决:`ai_feed` 改走 `A.today_reception_breakdown` / `A.today_staff_efficiency`(这俩内部按 specialty_id 聚合,不依赖 LEGACY 常量),AI 报告里的专项分布自动干净。但 `external /brief` 仍依赖 `_reception_specialty`,必须靠 A 节的 DB 治理(is_active=False)+ efficiency_field 字段移除 `_BUCKET` 硬编码。
-
----
-
-## G. 风险与回归
-
-### G.1 `_build_brief` 依赖方
-
-- `external_brief` 路由(external.py:312-325):对外开放,鉴权+access log,**不能删**。
-- 修复策略:保留 `_build_brief` 函数体,只修 TTL bug (F.1);`_reception_specialty` 的修复靠 A 节 DB 治理自动生效;`ai_feed` 不再调它即可,二者解耦。
-- 回归:`curl -H "Authorization: ..." /api/v1/external/brief?period=daily` 验证专项分布不再出"内部投诉/外部投诉"。
-
-### G.2 LLM token 上限
-
-- 单日 feed:`today_kpi` 全量约 1-2k 字符 + reception/refund/complaint/staff 各几百字符,压缩后约 4-5k 字符(< `AI_ANALYZE_MAX_CHARS=6000` 默认),可控。
-- 月报 30 天日走势:约 2-3k token,加 summary 段共约 5-6k token,仍可控。
-- 风险点:`today_staff_efficiency` 的 `data` 数组(200 人 × 多字段)会爆。**日走势里不带 staff.data**,只带 `summary.staff_on_duty`;staff 明细只在 daily 模式且 scope=reception 时带(且经 `_compact_specialty` 压缩)。
-
-### G.3 缓存 key 冲突
-
-`ai_feed` 改造后**共享**今日看板的 `today:*` key(设计目标"与看板完全一致"),不冲突。但要注意:
-- `today:staff:None:{s}:{e}:200:0:person:None:None:None` 这个 key 与前端今日看板 staff 接口默认参数(topn=30)对齐不一致——AI 用 topn=200 会生成不同 key,**这是设计上的隔离**(AI 要更多人),需在 `cache_warmer.py` 加预热条目(否则 AI 路径冷加载)。
-- 不需要在 `ai_feed` 里再做"_compact_specialty 自愈删坏缓存"逻辑([ai_feed.py:62-82](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_feed.py#L62))——`today_kpi` 直接调,无 `_build_brief` 的 routes 拼装环节,坏缓存场景消失。但保留自愈无害,可留作安全网。
-
-### G.4 DB 治理的回滚风险
-
-- 把"内部投诉"/"外部投诉"两条记录的 `is_active=False` 是**数据修复**,对应 alembic migration 的 downgrade 应该把 is_active 恢复为 True。
-- specialty 表加 `efficiency_field` 字段是 nullable,未配置的专项不影响现有逻辑(_reception_specialty 兜底跳过 sessions/devices 统计)。
-- 现有 `COMPLAINT_SPECIALTIES_ALL` 常量仍包含 LEGACY 名字,用于 SCD 历史归因查询——保持不变,不影响。
-
-### G.5 Focus seed 注入顺序
-
-新增 4 条 seed 的 sort_order:focus-global=1 / business-benchmark=2 / work-time-ai-assist=3 / inference-discipline=4 / ai-incremental-analysis=5。`focus_for_question` 按 sort_order 排序注入,确保基准/工作时间/防推断/增量段顺序合理。
-
----
-
-## H. 实施步骤(一次性完整改造 P0+P1)
-
-### 步骤 1: DB 治理 alembic migration(数据层去硬编码)
-
-新建 alembic migration:
-- upgrade:① specialties 表加 `efficiency_field` 字段(String(64), nullable);② 给 6 个正式专项填 efficiency_field 值;③ 把"内部投诉"/"外部投诉"两条记录的 is_active 置 False;④ ai_focus_configs 表插入 4 条新 seed(business-benchmark / work-time-ai-assist / inference-discipline / ai-incremental-analysis);⑤ UPDATE focus-complaint / focus-reception 两条现有 seed 修正口径。
-- downgrade:反向操作。
-
-验证:`docker exec csctalon-db psql -U postgres -d csctalon -c "SELECT name, is_active, efficiency_field FROM specialties ORDER BY display_order;"` — 应见 5 条 is_active=True + 2 条 is_active=False;`SELECT code, scope_key, sort_order, enabled FROM ai_focus_configs ORDER BY sort_order;` — 应见 8 条 seed。
-
-### 步骤 2: Specialty 模型加 efficiency_field 字段
-
-改 [backend/app/models/specialty.py](file:///Users/tanzk/dev/csctalon/backend/app/models/specialty.py) Specialty 模型,加 `efficiency_field: Mapped[str | None]` (String(64), nullable, comment="对应 today_staff_efficiency 的字段名, 格式 'sp_xxx,pp_xxx'")。
-
-### 步骤 3: 修 `_reception_specialty` 移除 `_BUCKET` 硬编码
-
-改 [external.py:624-709](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/external.py#L624) `_reception_specialty`:
-- 移除 `_BUCKET` dict (line 633-640)
-- 改为从 `s.efficiency_field` 读字段名(如 `sp_zixun,pp_zixun` 分割得到 sp_field/pp_field)
-- `efficiency_field` 为空的专项 sessions/devices 显示 0,不再硬兜底到"咨询接待"桶
-- 由于 DB 已把"内部投诉"/"外部投诉" is_active=False,`select(Specialty).where(Specialty.is_active.is_(True))` 自动排除,不再需要 `~Specialty.name.in_(LEGACY_COMPLAINT_SPECIALTIES)` 过滤
-
-### 步骤 4: 修 `today_kpi` 缓存 TTL bug
-
-改 [external.py:419](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/external.py#L419):`60` → `cache_ttl_for(start_s, end_s, 60)`。
-
-### 步骤 5: `ai_feed._build_feed` 改造为直接调今日看板服务
-
-改 [ai_feed.py:46-88](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_feed.py#L46):
-- 替换 `_build_brief` 调用为直接调 `A.today_kpi` / `A.today_reception_breakdown` / `A.today_refund_breakdown` / `complaints` summary/backlog
-- 新增 `_complaint_block(db, s, e)` helper(调 complaints 路由本体)
-- 新增 `_compact_specialty_staff(db, s, e)` helper(调 today_staff_efficiency topn=200 + _compact_specialty)
-- 保留 `_compact_specialty`(仍需压缩 staff by_specialty)
-- 重写 `_select_feed_sections` 按 B.3 新映射
-
-### 步骤 6: `_SYSTEM_PROMPT` 追加兜底铁律
-
-改 [ai_feed.py:23-35](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_feed.py#L23) `_SYSTEM_PROMPT` 末尾追加铁律 8/9/10(E.4)。
-
-### 步骤 7: 周/月/区间按日颗粒度
-
-改 [ai_feed.py](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_feed.py) 新增:
-- `_range_feed(db, period, start, end)` → 组装 summary + daily_trend + _spec
-- `_daily_trend(db, s, e)` → 按日循环调 today_kpi + complaint_block
-- `_days_range(s, e)` → 北京时区日历日列表 helper
-- `build_messages` 按 period 分流:daily → `_build_feed`;weekly/monthly/range → `_range_feed`
-- 月报 token 降级策略(D.4)
-
-### 步骤 8: cache_warmer 预热 AI 路径的 staff topn=200 key
-
-改 [cache_warmer.py](file:///Users/tanzk/dev/csctalon/backend/app/services/cache_warmer.py) `_warm_analytics` 加 `today:staff:None:{s}:{e}:200:0:person:None:None:None` 预热条目。
-
-### 步骤 9: 部署 + 验证
-
-```bash
-# 部署到生产
-ssh tanzk@192.168.10.21 "cd /opt/csctalon && sudo ./upgrade.sh -git"
-
-# 验证 1: 专项分布修复
-ssh tanzk@192.168.10.21 "sudo docker exec csctalon-backend python -c \"
-from app.api.v1.external import _reception_specialty
-from app.core.database import SessionLocal
-db = SessionLocal()
-r = _reception_specialty(db, '2026-09-14T00:00:00', '2026-09-14T23:59:59')
-print([(s['specialty'], s['sessions']['value'], s['devices']['value']) for s in r])
-\""
-# 期望:5 条正式专项(咨询接待/套餐专项/退款处理/退款挽回/投诉专项),不再含'内部投诉'/'外部投诉'
-
-# 验证 2: TTL 修复
-ssh tanzk@192.168.10.21 "sudo docker exec csctalon-db redis-cli TTL 'today:kpi:None:2026-09-14T00:00:00:2026-09-14T23:59:59'"
-# 期望:86400 附近
-
-# 验证 3: Focus seed 注入
-ssh tanzk@192.168.10.21 "sudo docker exec csctalon-db psql -U postgres -d csctalon -c \"SELECT code, scope_key, sort_order, enabled FROM ai_focus_configs ORDER BY sort_order;\""
-# 期望:8 条 seed(4 条原有 + 4 条新增),全 enabled=true
-
-# 验证 4: AI 报告不再有"消化存量"推断 + 带利好/利空标注
-# 前端发"昨日整体经营分析"问句,检查 AI 输出:
-# - 含"registered 与 handled 不同口径"显式说明
-# - 含利好/利空标注(如"退款金额+24.3% 利空")
-# - 不再出现"处理量高→消化存量"推断
-# - 含工作时间核对(如"08:00~23:59 工作时间")
-
-# 验证 5: AI 数据与今日看板一致
-# 前端今日看板同窗口对比 AI 报告数字
-
-# 验证 6: 周/月报按日颗粒度
-# 前端发"本周接待量走势",检查 AI 输出 7 天日走势 + 窗口总量
-# 前端发"本月经营总览",检查 AI 输出 30 天日走势或降级为前 14 天 + 后周聚合
-
-# 验证 7: cache_warmer 预热 AI 路径
-ssh tanzk@192.168.10.21 "sudo docker exec csctalon-db redis-cli KEYS 'today:staff:None:*:200:0:person:None:None:None'"
-# 期望:返回 1 个 key(今日)+ 1 个 key(昨日,历史日 24h 缓存)
-```
-
----
-
-## 关键文件清单
-
-### 必须修改
-- /Users/tanzk/dev/csctalon/backend/app/services/ai_feed.py — `_build_feed` 改造 + `_select_feed_sections` 重写 + `_SYSTEM_PROMPT` 兜底铁律 + 周/月 `_range_feed`/`_daily_trend`/`_days_range` 新增
-- /Users/tanzk/dev/csctalon/backend/app/api/v1/external.py — `_reception_specialty` 移除 `_BUCKET` 改读 `s.efficiency_field` (line 624-709) + `_build_brief` TTL bug (line 419)
-- /Users/tanzk/dev/csctalon/backend/app/models/specialty.py — Specialty 模型加 `efficiency_field` 字段
-- /Users/tanzk/dev/csctalon/backend/app/services/cache_warmer.py — 新增 AI 路径 staff topn=200 预热条目
-
-### 新建
-- /Users/tanzk/dev/csctalon/backend/alembic/versions/xxx_ai_feed_refactor.py — DB 治理 migration(specialty 字段+数据修复 + ai_focus_configs 新 seed + 现有 seed 修正)
-
-### 不修改(已对齐分层)
-- /Users/tanzk/dev/csctalon/backend/app/services/ai_glossary.py — 技术口径代码常量(按设计分层保留)
-- /Users/tanzk/dev/csctalon/backend/app/services/ai_focus_service.py — DB 化 Focus 服务(已支持,无需改)
-- /Users/tanzk/dev/csctalon/backend/app/api/v1/ai.py — `/ai/analyze` 入口(参数已对齐)
diff --git a/.trae/documents/ai-report-optimization-plan.md b/.trae/documents/ai-report-optimization-plan.md
deleted file mode 100644
index 1ccafdf..0000000
--- a/.trae/documents/ai-report-optimization-plan.md
+++ /dev/null
@@ -1,151 +0,0 @@
-# AI 报告系统优化方案
-
-## Context
-
-当前 AI 报告系统已完成多 provider 框架(#127-#131):122b 专供预生成、27b 供实时问答。但存在三个核心问题:
-1. **对比类报告含当天不完整数据**——周报/月报用"本周一到此刻"做窗口,当天才过了几小时就拿来和昨天全天比,口径不对等
-2. **命中=返回全文,未命中=实时跑27b**——用户只想看退款部分却拿到整份报告,或未命中走27b实时分析(质量不如122b预生成草稿)
-3. **缺少统一 AI 管理入口**——模板、模型、报告分散在不同页面
-
-用户确认分三批实施,本方案覆盖全部三批。
-
----
-
-## 批次 A:对比报告截止前一天 + trigger_at 调半夜
-
-### A1. 新增窗口计算模块
-
-**新建** `backend/app/services/ai_report_window.py`:
-- `compute_pregen_window(period, ref_date=None) -> dict`:纯计算,无 IO
- - weekly: 本周周一 ~ 昨天;若今天是周一→退化为上周一~上周日
- - monthly: 本月1日 ~ 昨天;若今天是1号→退化为上月1日~上月末
- - daily: 返回 None(日报保持现行 date=None 实时数据)
- - 返回 `{start, end, snapshot_date, label}`
-- `compute_compare_window(start, end) -> tuple[str, str]`:等长前对比窗口(紧贴 start 之前取同长度天数)
-
-### A2. 改 generate_for_template
-
-**改** [ai_report_worker.py](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_report_worker.py) L196-211:
-
-weekly/monthly 模板不再走 `build_messages(period=template.period, date=None)`,改为:
-- 调 `compute_pregen_window(template.period)` 算出 start/end
-- 调 `build_messages(period="range", date=None, start=start, end=end)`
- - `_window` 在 start/end 模式下 end < today → `date_end = f"{e}T23:59:59+08:00"` 整天,`is_realtime=False`([external.py](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/external.py) L360-366)
-- 落库 `save_report(start=start, end=end, snapshot_date=今日)`,snapshot_date 保持今日让 matcher 命中
-- daily 模板保持现行 `date=None`(随同步刷新,当天实时数据不变)
-
-### A3. trigger_at 调半夜 + warm_idle 时段放宽
-
-**改** [ai_report_worker.py](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_report_worker.py):
-- `warm_idle()` L438:移除 `_DAYTIME_HOURS` 限制(当前8-23才扫描,凌晨不触发 trigger_at)
-- `warm_on_startup()` L405:保持白天限制(避免重启时大量并发)
-- **Alembic 迁移**:`UPDATE ai_report_templates SET trigger_at='1-02:00' WHERE code IN ('weekly_compare_last','monthly_overview')`
-- daily_general_morning 保持 `09:00`(refresh_on_sync 覆盖白天实时刷新)
-
-### A4. feed 增对比段(可选,A 批先不接入)
-
-`_build_feed` 已支持 `start/end` 透传([ai_feed.py](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_feed.py) L92),`_enrich_with_daily_trend` 在 start/end 模式下自动走日走势路径(L124-130)。等长对比窗口在批次 B 随草稿结构化一起接入。
-
----
-
-## 批次 B:草稿结构化 + 摘取层 + 三级语义命中
-
-### B1. 草稿结构化标记
-
-**段落锚点(全树统一)**:
-- `## 接待分析` / `## 退款分析` / `## 投诉分析` / `## 趋势对比` / `## 结论建议`
-
-**改** [ai_feed.py](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_feed.py) `_SYSTEM_PROMPT` L35-58 第3条:增加"周报/月报必须按 H2 锚点结构化输出"约束。
-
-**Alembic 迁移**:
-- `UPDATE weekly_compare_last SET max_tokens=4000`(当前2000不够完整草稿)
-- `UPDATE monthly_overview SET max_tokens=5000`
-- `UPDATE daily_general_morning SET max_tokens=2500`
-
-### B2. 新增摘取服务
-
-**新建** `backend/app/services/ai_extract_service.py`:
-- `split_sections(answer_md) -> dict[str, str]`:按 `## ` H2 锚点切分,兜底按段落分
-- `extract_section(db, question, report, template) -> dict`:
- 1. split_sections 切分草稿
- 2. 关键词路由:`keywords_to_scopes(question)` → scope → 段落(复用 [ai_feed.py](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_feed.py) L497 `_select_feed_sections` 同款逻辑)
- 3. 调 27b(`provider_code=None` 走 default 链,`timeout_s=30`,`max_tokens=1200`):从命中段落摘取+按问句排版
- 4. 输出 JSON `{result_md, confidence, need_clarify, clarify_options}`
-
-### B3. 新增语义匹配服务
-
-**新建** `backend/app/services/ai_semantic_matcher.py`:
-- `semantic_match(db, question, candidates) -> dict`:27b 判定问句与候选报告的语义相关性,输出 `{hit_report, confidence}`
-
-### B4. 改命中链路
-
-**改** [ai.py](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/ai.py) L272-299:
-
-```
-L1 关键词匹配(现行 match_template_report 不变)
- → 命中 → 走摘取层(不返回全文)
- → 未命中 ↓
-L2 27b 语义匹配(semantic_match, confidence≥0.6 命中)
- → 命中 → 走摘取层
- → 未命中 ↓
-L3 兜底: 返回 clarify_options(当日报告 H2 标题列表)
- 或走原实时 LLM 路径(保留兼容)
-```
-
-### B5. 前端改造
-
-**改** [AssistantDrawer.vue](file:///Users/tanzk/dev/csctalon/web/src/components/AssistantDrawer.vue):
-- 新增 `status === "clarify"` 状态:渲染 `clarify_options` 为可点按钮,点击后重新发 /ai/analyze
-
----
-
-## 批次 C:月报逐天小时粒度 + AI 管理页面
-
-### C1. feed 逐天小时粒度
-
-**改** [ai_feed.py](file:///Users/tanzk/dev/csctalon/backend/app/services/ai_feed.py) `_enrich_with_daily_trend`:
-- 在 `span > 14`(月报路径)下,对 daily_trend 每条额外调 `cached_call(f"today:hourly:None:{d}:{d}", ..., A.today_hourly_trend, ...)` 取当日 hourly
-- 新增 `_compact_daily_hourly_summary(day_hourly) -> dict`:每天只取工作时段合计 + 峰谷小时(~6标量/天 × 30天 = 180字段,~6KB)
-
-### C2. AI 管理页面
-
-**新建** `web/src/views/system/AiAdmin.vue`(5 Tab):
-- Tab 1 报告列表:复用 `/admin/ai/reports` API([admin_ai.py](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/admin_ai.py) L160)
-- Tab 2 模板管理:内嵌 [AiTemplates.vue](file:///Users/tanzk/dev/csctalon/web/src/views/system/AiTemplates.vue)
-- Tab 3 模型/算力节点:内嵌 LlmProviders 管理组件([llm_providers.py](file:///Users/tanzk/dev/csctalon/backend/app/api/v1/llm_providers.py) API)
-- Tab 4 提示词管理:内嵌 AiFocusConfigs + 模板 question_template 编辑
-- **设计说明三层落地**(AGENTS.md 铁律 7):代码注释 + 页面 ⓘ + review-checklist
-
-**改** `web/src/router/index.js`:新增 `system/ai-admin` 路由
-
-### C3. 维度扩充
-
-先与用户确认扩充清单再实施(AGENTS.md:不确定停下来提问)。
-
----
-
-## 工单规划
-
-| 工单 | 批次 | 标题 | 关键文件 |
-|---|---|---|---|
-| #132 | A | 周报/月报预生成窗口截止前一天 | ai_report_worker.py, ai_report_window.py(新) |
-| #133 | A | trigger_at 调半夜 + warm_idle 凌晨可触发 | ai_report_worker.py, 迁移 f8g9h0i1j2k3 |
-| #134 | B | 草稿结构化标记 + 模板 max_tokens 放宽 | ai_feed.py(_SYSTEM_PROMPT), 迁移 g3h4i5j6k7l8 |
-| #135 | B | 摘取层 + 三级语义命中链路 | ai_extract_service.py(新), ai_semantic_matcher.py(新), ai.py, AssistantDrawer.vue |
-| #136 | C | 月报逐天小时粒度 | ai_feed.py(_compact_daily_hourly_summary) |
-| #137 | C | AI 管理页面(5 Tab) | AiAdmin.vue(新), router/index.js |
-| #138 | C | 数据维度扩充(先确认清单) | ai_feed.py |
-
-**依赖**:#132→#133(批次A) | #134→#135(批次B) | #136/#137/#138 独立(批次C)
-
-## 验证方式
-
-每批完成后:
-1. **ruff + pytest**:`ruff check backend/ && pytest -k ai`
-2. **本地 docker cp 到 csctalon-backend 容器验证**
-3. **部署生产**:`ssh tanzk@192.168.10.21 "sudo /opt/csctalon/current/deploy/upgrade.sh --git"`
-4. **端到端实测**:
- - A:手动触发周报预生成,查 `ai_reports.start/end` 截止昨天、trigger_at=02:00 凌晨自动触发
- - B:用户提问"退款情况"→命中预生成→只返回退款段落(非全文);提问模糊→返回 clarify 选项
- - C:月报 feed 含 `hourly_summary`;AI 管理页面 5 Tab 可用
-5. **凭证评论关闭工单**(逐条可复现核验方式)
diff --git a/.trae/documents/complaint-and-specialty-improvements.md b/.trae/documents/complaint-and-specialty-improvements.md
deleted file mode 100644
index 396a0cb..0000000
--- a/.trae/documents/complaint-and-specialty-improvements.md
+++ /dev/null
@@ -1,174 +0,0 @@
-# 投诉与专项优化实施方案
-
-## Context
-
-用户反馈运营看板和人员管理的四个问题:
-1. 人员编辑的专项可选择项不全(只有3个硬编码选项),需做成可配置字典
-2. 投诉子类分布将内部/商户号/外部平台混在一起,需分开展示
-3. 投诉处理人排行只显示内部投诉,需单列外部投诉表
-4. 未闭环投诉显示"200条"但列表为空,数据不一致
-
----
-
-## 问题1: 人员编辑专项选项不全 → 改为动态字典
-
-### 现状
-- 后端 `STANDARD_SPECIALTIES` (specialty.py:25-32) 定义6个专项,API `/specialties` 已返回完整列表
-- 前端 `Persons.vue` 已加载 `specialtiesList` 用于筛选下拉框(第95行)
-- 但任职分配表单(第470-472行)和批量编辑表单(第533-534行)硬编码了3个选项
-
-### 修改
-**文件: `web/src/views/system/Persons.vue`**
-
-1. 第470-472行,将硬编码替换为动态选项:
-```html
-
-
-
-```
-
-2. 第533-534行,同样替换:
-```html
-
-
-
-```
-
-### 验证
-- 打开人员管理 → 点击编辑 → 专项下拉框应显示6个选项(咨询接待/套餐专项/退款处理/退款挽回/内部投诉/外部投诉)
-- 批量编辑对话框同样包含完整选项
-
----
-
-## 问题2: 投诉子类分布 → 按三大类分开三张饼图
-
-### 现状
-- 后端 `complaint_subs` 数据已包含 `source_type` 字段(内部投诉/商户号投诉/外部投诉)
-- 前端 `complaintSubPieOption` (Today.vue:1437-1486) 已按 source_type 分组着色,但所有子类混在一个饼图中
-
-### 修改
-**文件: `web/src/views/dashboards/Today.vue`**
-
-1. 模板中(第227-252行),将单个饼图替换为三列并排:
- - 内部投诉子类(商品/服务)→ 蓝色系
- - 外部投诉-商户号(微信投诉)→ 紫色系
- - 外部投诉-平台(12315/工信部/消协等)→ 红色系
-
-2. 将原 `complaintSubPieOption` 替换为三个 computed:
- - `complaintSubInternalPieOption` — 过滤 `source_type === '内部投诉'`
- - `complaintSubMerchantPieOption` — 过滤 `source_type === '商户号投诉'`
- - `complaintSubExternalPieOption` — 过滤 `source_type === '外部投诉'`
-
-3. 提取公共饼图构建函数 `buildSubPieOption(rows, colorPalette, sourceLabel)` 避免重复
-
-4. 投诉根因柱图移至投诉处理人排行上方独占一行
-
-### 验证
-- 运营看板选择有投诉数据的日期,确认显示三个独立饼图
-- 内部投诉饼图:商品投诉、服务投诉
-- 商户号投诉饼图:微信投诉
-- 外部平台投诉饼图:全国12315平台、工信部投诉、公共服务平台、消费者协会
-
----
-
-## 问题3: 投诉处理人排行 → 拆分为内部/外部两表
-
-### 现状
-- 后端 SQL(analytics.py:3249-3319)UNION ALL 三张表,返回 `source_type` 区分来源
-- 前端显示单个表格,包含"按来源"列(内部/外部/卡商)
-- 用户要求因数量级不同分开展示
-
-### 修改
-
-**后端: `backend/app/services/analytics.py`**
-
-1. 在现有 handler 聚合逻辑(第3323-3366行)之后,新增按 source_type 拆分:
- - `complaint_internal_handlers` — 仅 `source_type == '内部投诉'`
- - `complaint_external_handlers` — `source_type == '外部投诉'` + `'卡商群投诉'`
-
-2. 在返回字典中(第3654-3665行)新增两个字段:
- - `complaint_internal_handlers`
- - `complaint_external_handlers`
- - 保留原 `complaint_handlers` 向后兼容
-
-**前端: `web/src/views/dashboards/Today.vue`**
-
-3. complaintData 初始化(第2121行)新增字段:
- - `complaint_internal_handlers`
- - `complaint_external_handlers`
-
-4. 模板中(第265-332行)替换为两个并列表格:
- - 左:内部投诉处理人排行(Top 15)
- - 右:外部投诉处理人排行(Top 15)
-
-### 验证
-- 运营看板确认显示两个独立处理人排行表
-- 内部表只含 service_tickets 内部投诉处理人
-- 外部表含 external_complaints + card_complaints 处理人
-
----
-
-## 问题4: 未闭环投诉列表为空
-
-### 现状
-- 用户看到"未闭环 200条"标签但列表无数据
-- 后端 `complaint_pending_total` 和 `complaint_pending_list` 来自同一变量 `complaint_pending`,理论上应一致
-- `_INCLUDE_COMPLAINT_FILES = False`(analytics.py:17)影响 complaint_sub/roots 聚合,但不影响 complaint_pending 的三表直接查询
-
-### 修改
-
-**后端: `backend/app/services/analytics.py`**
-
-1. 在 complaint_pending 构建完成后(第3644行),增加数据一致性校验日志:
-```python
-import logging
-_log = logging.getLogger(__name__)
-if complaint_pending_total > 0 and len(complaint_pending) == 0:
- _log.warning(f"complaint_pending 数据不一致: total={complaint_pending_total}, list空")
- complaint_pending_total = 0
-```
-
-2. 在 complaint_pending 查询的 external_complaints/card_complaints 部分(第3495行前)增加注释,说明这些查询不受 `_INCLUDE_COMPLAINT_FILES` 影响
-
-**前端: `web/src/views/dashboards/Today.vue`**
-
-3. complaintData 赋值(第2129-2130行)增加防御性校验:
-```javascript
-complaint_pending_total: rdata.complaint_pending_total ?? null,
-complaint_pending_list: (() => {
- const list = rdata.complaint_pending_list || []
- const total = rdata.complaint_pending_total
- if (total > 0 && list.length === 0) {
- console.warn('[complaint_pending] 数据不一致: total=' + total + ', list为空')
- }
- return list
-})(),
-```
-
-4. 模板中(第369-376行之后)增加边界情况提示:当 `complaint_pending_total > 0` 但 `complaint_pending_list` 为空时,显示警告提示并提供刷新按钮
-
-### 验证
-- 清除浏览器缓存后重新加载运营看板
-- 选择今日视图,确认"未闭环 N 条"标签与下方列表数据一致
-- 若不一致,应看到新增的警告提示(而非空白表格)
-- 检查后端日志,确认无 `complaint_pending 数据不一致` 警告
-
----
-
-## 变更文件汇总
-
-| 文件 | 问题 | 变更类型 |
-|------|------|----------|
-| `web/src/views/system/Persons.vue` | 问题1 | 修改第470-472行、第533-534行,硬编码→动态选项 |
-| `web/src/views/dashboards/Today.vue` | 问题2 | 重构第227-252行模板,新增3个computed替代原pie |
-| `web/src/views/dashboards/Today.vue` | 问题3 | 拆分第265-332行处理人表,新增complaintData字段 |
-| `web/src/views/dashboards/Today.vue` | 问题4 | 第2129-2130行防御性校验,新增边界提示模板 |
-| `backend/app/services/analytics.py` | 问题3 | 新增按source_type拆分的handler聚合+返回字段 |
-| `backend/app/services/analytics.py` | 问题4 | 数据一致性校验日志+注释 |
-
-## 实施顺序
-
-1. **问题1** — 最简单,仅改前端两处模板,无后端依赖
-2. **问题4** — 防御性修改,后端+前端增加校验,不影响现有功能
-3. **问题3** — 后端拆分handler聚合 + 前端新增表格,前后端协调
-4. **问题2** — 前端重构饼图区域,UI布局调整较大,在问题3基础上叠加
\ No newline at end of file
diff --git a/.trae/documents/metric-alerting-notification-design.md b/.trae/documents/metric-alerting-notification-design.md
deleted file mode 100644
index d5ee686..0000000
--- a/.trae/documents/metric-alerting-notification-design.md
+++ /dev/null
@@ -1,379 +0,0 @@
-# 运营指标阈值化与预警通知机制 设计方案
-
-> 版本: v0.3 · 2026-08-21
-> 状态: M1~M3 已实施(最小闭环), M4 通道架构已就绪(站内信已接, 群机器人/邮件可配置接入), M5 扩展待定
-> 范围: 指标注册表 + 多级阈值(优秀/达标/预警/告警) + 预警引擎 + 通知通道抽象
-
-## 0. 实施状态(2026-08-21)
-
-| 里程碑 | 状态 | 说明 |
-|---|---|---|
-| M1 指标与阈值基础 | ✅ 已实施 | 5 个指标注册(recovery_rate/refund_count/ticket_backlog/external_complaint_7d/internal_complaint_daily) + 四级阈值(metric_thresholds 留痕) |
-| M2 预警引擎 | ✅ 已实施 | scan_alerts 注册表驱动: 四级分级/区间幂等/升级/恢复自愈/数据健康检查; 定时每小时自动扫描 |
-| M3 站内通知 | ✅ 已实施 | 告警触发/恢复 → 站内信(notify) + 投递记录(alert_deliveries) |
-| M4 推送通道 | 🟡 架构已就绪 | notification_channels 表 + Channel 抽象(站内信已接; webhook 群机器人/邮件通道类已实现, 配置即用); 前端通道管理+测试按钮 |
-| M5 扩展联动 | ⏳ 待定 | 指标扩充/聚合/与 Incident/Measure 联动 |
-
-**实现与设计差异(§8.1)**: 告警记录复用现有 `alerts` 表(扩展 metric_code/metric_value/threshold_value/period_start/period_end/data_ok/recovered_at 字段), 未新建 alert_events 表。
-
-**新增后端文件**: `app/models/metric.py` / `app/models/notification_channel.py` / `app/services/metric_registry_service.py` / `app/services/notification_channel.py` / `app/api/v1/metrics.py` / `app/api/v1/channels.py`; 迁移 `n9a8b7c6d5e4f_add_metric_alerting.py`(含指标/阈值/站内信种子数据)。
-
-**前端**: `web/src/views/management/Alerts.vue` 升级为四 Tab(指标水位/预警规则/预警记录/通知通道); 新增 `web/src/api/metric.js`、`web/src/api/channel.js`。
-
----
-
-## 1. 背景与目标
-
-当前系统的看板(运营看板/今日看板/投诉分析)已实时计算大量运营指标(退款挽回率、三源投诉、积压工单、首响时长等),但**指标的"好坏"完全靠人肉看数字判断**——没有"低于多少要盯、低于多少要报警"的系统化机制。
-
-目标:
-
-1. **指标化监测**: 运营指标有明确的"正常/预警/告警"水位线,系统按周期自动评估
-2. **阈值参数化**: 优秀/达标/预警/告警四级阈值全部由系统参数配置(可改、可留痕),不硬编码
-3. **预警自动生成**: 指标越线自动生成预警记录,走完整生命周期(待确认→已确认→已恢复)
-4. **通知多渠道**: 预警触发后站内信通知(已有),并预留企业微信/钉钉/飞书群机器人、邮件等推送通道(可扩充)
-
-核心原则:**预警监测的指标口径必须与看板同源同口径**,先有指标注册表,再有阈值与规则,避免"看板一个数、预警另一个数"。
-
----
-
-## 2. 现状盘点
-
-### 2.1 已有基础(可复用)
-
-| 模块 | 位置 | 现状 |
-|---|---|---|
-| 预警规则/记录 | `app/models/alert.py` AlertRule/Alert | 已有: name/type/condition(JSON)/threshold/enabled; 记录含 level/status(pending/confirmed/ignored) |
-| 预警扫描 | `app/api/v1/alerts.py` scan_alerts | 4 种硬编码规则(退款激增/工单积压/事件激增/投诉激增), 单一 threshold, level 由 threshold×2 推断, **当日幂等** |
-| 定时调度 | `app/services/bms_scheduler.py` | 已有每小时预警扫描钩子(当日幂等), 可复用 |
-| 站内信 | `app/services/notifier.py` notify() | 统一入口, 支持按用户/角色, Notification 表, 前端有角标 |
-| 前端预警中心 | `web/src/views/management/Alerts.vue` | 规则 CRUD + 预警记录 + 手动扫描 + 确认/忽略 |
-| 指标计算 | `app/services/analytics.py` | 各看板指标已实时计算(如 refund recovery), 口径已沉淀 |
-
-### 2.2 缺口
-
-1. **阈值单一数值** → 无法表达"达标(绿)/预警(黄)/告警(红)"三级水位
-2. **规则类型硬编码** → 加一个新指标预警必须改 scan_alerts 代码, 不可配置
-3. **只支持"≥阈值"的激增类判断** → 无法表达"挽回率 < 60%"这类"越低越差"指标
-4. **指标无注册表** → 口径散落, 规则无法引用统一指标定义
-5. **预警只入库不通知** → 不调 notify(), 无任何推送通道
-6. **无告警生命周期闭环** → 无"恢复/解除"概念, 无效告警会堆积
-7. **无数据健康保护** → SCRM 同步失败导致指标=0 会误报"恶化告警"
-
----
-
-## 3. 总体架构
-
-```
-┌──────────────────────────────────────────────────────┐
-│ 指标注册表 (metric_registry) │
-│ 每个指标: code / 口径函数 / 周期 / 方向 / 依赖数据源 │
-└───────────────┬──────────────────────────────────────┘
- │ 引用
-┌───────────────▼──────────────────────────────────────┐
-│ 阈值参数 (metric_thresholds) │
-│ metric_code + 优秀线/达标线/预警线/告警线 + 生效区间 │
-└───────────────┬──────────────────────────────────────┘
- │ 计算
-┌───────────────▼──────────────────────────────────────┐
-│ 预警引擎 (alert_engine) │
-│ 周期扫描 → 数据健康检查 → 指标求值 → 与阈值比较 │
-│ → 幂等/升级/静默 → 生成告警记录 │
-└───────────────┬──────────────────────────────────────┘
- │ 触发
-┌───────────────▼──────────────────────────────────────┐
-│ 通知分发 (notification_channel) │
-│ 站内信(先) → 企业微信/钉钉/飞书群机器人 → 邮件(后) │
-└──────────────────────────────────────────────────────┘
-```
-
-分层职责:
-
-- **指标注册表**: 定义"算什么"(口径), 是唯一事实源, 看板与预警共用
-- **阈值参数**: 定义"多好算好"(水位线), 可配置可留痕
-- **预警引擎**: 定义"何时喊"(触发), 与指标/阈值解耦, 新增指标无需改引擎
-- **通知通道**: 定义"喊给谁、怎么喊", 通道可插拔
-
----
-
-## 4. 指标注册表(核心)
-
-### 4.1 设计要点
-
-- 每个指标注册一条元数据:**计算函数** + **周期** + **方向** + **依赖数据源**
-- 指标值由统一求值器调用注册的计算函数得到, **与看板共用同一函数**(同源同口径)
-- 注册方式: 代码注册(函数式) + 数据库元数据(阈值引用), 避免重复实现
-
-### 4.2 首批指标清单(试点范围)
-
-| code | 指标名 | 方向 | 周期 | 依赖数据源 | 口径来源 |
-|---|---|---|---|---|---|
-| recovery_rate | 退款挽回率 | 越高越好 | 日 | SCRM cs_record | analytics 退款挽回计算 |
-| refund_count | 当日退款笔数 | 越低越好 | 日 | refund | 看板 refund kpi |
-| ticket_backlog | 待处理投诉工单积压 | 越低越好 | 日 | service_tickets | 投诉 backlog.total |
-| external_complaint_7d | 近7天外部投诉量 | 越低越好 | 日 | external_complaints | 投诉 supplementary |
-| internal_complaint_daily | 当日内部投诉量 | 越低越好 | 日 | service_tickets | 投诉 internal.total |
-| first_response_sec | 首次响应时长(均值) | 越低越好 | 日 | scrm_session | SCRM 绩效 |
-| transfer_rate | 会话转接率 | 双向 | 日 | scrm_session_transfer | SCRM 绩效 |
-| sync_health | 数据同步成功率 | 越高越好 | 日 | scrm_sync_log/bms_sync_log | 同步日志 |
-
-> 指标清单按需扩充: 值班在线率、工作效率值、四路退款占比、套餐充值量等。
-> 重要:**先有指标, 后有阈值**; 未注册的指标不参与预警。
-
----
-
-## 5. 三级阈值与规则模型
-
-### 5.1 阈值语义(四级: 优秀/达标/预警/告警)
-
-| 级别 | 含义 | 运营动作 | 颜色 |
-|---|---|---|---|
-| 优秀(excellent) | 指标超出预期, 明显优于正常水位 | 通报表扬, 提炼可复制的经验 | 蓝 |
-| 达标(green) | 指标处于正常区间 | 无需处理 | 绿 |
-| 预警(yellow) | 指标偏离正常,需关注 | 盯一盯,排查原因 | 黄 |
-| 告警(red) | 指标严重偏离,需干预 | 立即处理,升级 | 红 |
-
-- **越高越好型**(如挽回率): 阈值 = 下限线。`value ≥ 优秀线` → 优秀; `value < 预警线` → 预警; `value < 告警线` → 告警; 其余 → 达标
-- **越低越好型**(如退款笔数/积压): 阈值 = 上限线。`value ≤ 优秀线` → 优秀; `value > 预警线` → 预警; `value > 告警线` → 告警; 其余 → 达标
-- **优秀线可选**: 不配置优秀线则退化为"达标/预警/告警"三级, 不产生"优秀"状态
-- **仅 预警/告警 触发告警记录与通知**; 优秀/达标仅展示水位状态(优秀可另行配置"通报表扬"通知, 默认不推)
-- 方向由指标注册表声明, 阈值表只存数值, 引擎按方向解释
-
-### 5.2 规则 = 指标 × 阈值 × 调度
-
-规则配置项:
-
-```
-metric_code 引用指标注册表
-warning_threshold 预警线
-critical_threshold 告警线
-period 日/周/月
-enabled 是否启用
-channels 通知通道列表(默认站内信)
-recipient_roles 通知角色(admin/supervisor)或指定用户
-silence_window 静默时段(如 00:00-08:00 不推送,可选)
-```
-
-> 兼容现有 AlertRule: 规则仍存 alert_rules 表, 字段扩展(见 §8), 现有 4 类硬编码规则迁移为注册表指标+阈值。
-
----
-
-## 6. 预警引擎
-
-### 6.1 扫描流程
-
-```
-1. 数据健康检查(防假告警)
- - 指标依赖的数据源今日同步是否成功(sync_health)
- - 数据量是否合理(如 SCRM 会话量低于前3日均值 20% → 视为数据异常)
- - 数据异常 → 跳过该指标, 生成"数据异常"提示(不产生告警)
-2. 指标求值(调用注册表计算函数, 与看板同口径)
-3. 与多级阈值比较 → 确定级别(优秀/达标/预警/告警; 无越线时若之前有告警则生成"已恢复"记录)
-4. 幂等去重: 同规则同周期同级别最多一条 pending
-5. 升级: 连续 N 个周期处于预警且未处理 → 升级为告警
-6. 生成 Alert 记录 + 通知分发(§7)
-```
-
-### 6.2 关键规则
-
-| 规则 | 说明 |
-|---|---|
-| 当日幂等 | 同规则同周期同级别不重复生成(复用现有机制) |
-| 连续恶化升级 | 预警持续 N 天未处理且指标继续恶化 → 自动升级告警 |
-| 恢复自愈 | 指标回到达标线 → 自动生成/更新"已恢复"状态并通知 |
-| 静默窗口 | 配置的时段内不推送(仅入库), 避免夜间打扰 |
-| 数据保护 | 数据不完整/异常时不告警, 单独标记(防假告警, 见 6.3) |
-| 批量聚合 | 同批多条同类预警合并为一条摘要(可选, 后期) |
-
-### 6.3 数据健康保护(重要)
-
-- 预警求值前先跑 `sync_health` 检查: 今日 SCRM/BMS 是否同步成功
-- 指标值出现"断崖式"变化(如为 0 或骤降 80%)时, 先判定为数据异常而非业务恶化
-- 数据异常与业务告警分开展示, 避免"假告警"干扰决策
-
----
-
-## 7. 通知通道抽象
-
-### 7.1 通道接口
-
-```python
-class NotificationChannel(Protocol):
- def send(self, ctx: AlertContext, recipient: list) -> ChannelResult: ...
-```
-
-- `站内信(InAppChannel)` — 复用现有 `notifier.notify()`, **首发实现**
-- `企业微信群机器人 / 钉钉 / 飞书群机器人(WebhookChannel)` — 二期
-- `邮件(EmailChannel)` — 三期
-
-### 7.2 配置模型
-
-- 通道注册表: `notification_channels` 表, 每个通道有 type + config(webhook URL/密钥, 加密存储)
-- 规则级配置: 每条规则选择通知通道 + 接收对象(角色/指定用户)
-- 测试能力: 每个通道提供"发送测试"按钮(前端), 验证配置可用
-- 发送失败降级: 群机器人失败 → 自动降级为站内信, 并记发送日志
-
-### 7.3 通知去重
-
-- 同一告警不重复推送: 以 Alert.id 为幂等键, 推送状态记录在 alert_deliveries 表
-- 恢复通知与触发通知各推送一次
-
----
-
-## 8. 数据模型(表结构草案)
-
-### 8.1 新建
-
-```sql
--- 指标注册表(元数据; 计算函数在代码层注册)
-CREATE TABLE metric_registry (
- id BIGSERIAL PRIMARY KEY,
- code VARCHAR(64) UNIQUE NOT NULL, -- 指标code, 与代码注册对应
- name_cn VARCHAR(128) NOT NULL,
- direction VARCHAR(8) NOT NULL, -- higher_better / lower_better
- period VARCHAR(8) NOT NULL DEFAULT 'day', -- day/week/month
- data_sources JSONB DEFAULT '[]', -- 依赖数据源, 供健康检查
- description TEXT,
- created_at TIMESTAMPTZ DEFAULT now()
-);
-
--- 阈值参数(四级水位: 优秀/达标/预警/告警, 可留痕)
-CREATE TABLE metric_thresholds (
- id BIGSERIAL PRIMARY KEY,
- metric_code VARCHAR(64) REFERENCES metric_registry(code),
- excellent_value DOUBLE PRECISION, -- 优秀线(可选, 不配则无优秀状态)
- warning_value DOUBLE PRECISION NOT NULL, -- 预警线
- critical_value DOUBLE PRECISION NOT NULL, -- 告警线
- unit VARCHAR(16), -- % / 笔 / 秒 ...
- valid_from TIMESTAMPTZ, -- 生效区间(支持历史调参留痕)
- valid_to TIMESTAMPTZ,
- updated_by INTEGER REFERENCES users(id),
- updated_at TIMESTAMPTZ DEFAULT now()
-);
-
--- 告警记录(扩展现有 alert_alerts 或新建 alert_events)
-CREATE TABLE alert_events (
- id BIGSERIAL PRIMARY KEY,
- metric_code VARCHAR(64),
- rule_id BIGINT, -- 关联 alert_rules
- level VARCHAR(16), -- warning / critical / recovered
- metric_value DOUBLE PRECISION,
- threshold_value DOUBLE PRECISION,
- period_start TIMESTAMPTZ,
- period_end TIMESTAMPTZ,
- status VARCHAR(16) DEFAULT 'pending', -- pending/confirmed/ignored/recovered
- data_ok BOOLEAN DEFAULT TRUE, -- 数据健康标记(防假告警)
- detail TEXT,
- created_at TIMESTAMPTZ DEFAULT now()
-);
-
--- 通知通道配置
-CREATE TABLE notification_channels (
- id BIGSERIAL PRIMARY KEY,
- code VARCHAR(32) UNIQUE NOT NULL, -- inapp / wecom_robot / dingtalk_robot / feishu_robot / email
- name_cn VARCHAR(64),
- channel_type VARCHAR(16),
- config JSONB, -- webhook等, 敏感字段加密
- enabled BOOLEAN DEFAULT TRUE,
- created_at TIMESTAMPTZ DEFAULT now()
-);
-
--- 通知投递记录(幂等)
-CREATE TABLE alert_deliveries (
- id BIGSERIAL PRIMARY KEY,
- alert_id BIGINT,
- channel VARCHAR(32),
- status VARCHAR(16), -- success / failed / degraded
- detail TEXT,
- created_at TIMESTAMPTZ DEFAULT now()
-);
-
--- 指标历史值(回溯"为什么触发/持续多久")
-CREATE TABLE metric_daily_snapshots (
- id BIGSERIAL PRIMARY KEY,
- metric_code VARCHAR(64),
- value DOUBLE PRECISION,
- period_date DATE,
- level VARCHAR(16), -- excellent/green/warning/critical
- UNIQUE (metric_code, period_date)
-);
-```
-
-### 8.2 复用与兼容
-
-- `alert_rules` 扩展字段: metric_code / warning_value / critical_value / channels / recipient_roles / silence_window(JSONB), 现有 4 类规则迁移
-- `Alert` 记录增加 metric_code/level 对齐(或迁移到 alert_events)
-- 前端 Alerts.vue 扩展: 规则管理升级为"指标+阈值"配置, 预警记录增加级别/恢复状态
-
----
-
-## 9. API 与前端规划
-
-### 9.1 API(后端)
-
-```
-GET /api/v1/metrics 指标注册表列表
-GET /api/v1/metrics/{code}/current 指标当前值+当前级别(前端展示水位)
-PUT /api/v1/metrics/{code}/thresholds 配置/修改三级阈值(留痕)
-POST /api/v1/alerts/scan 手动触发扫描(已有, 扩展)
-POST /api/v1/alerts/{id}/recover 手动标记恢复
-GET /api/v1/alerts/events 告警事件列表(含数据异常)
-GET /api/v1/channels 通道列表
-POST /api/v1/channels/{code}/test 发送测试通知
-```
-
-### 9.2 前端(Alerts.vue 升级)
-
-1. **指标水位视图**: 指标列表 + 当前值 + 优秀/达标/预警/告警状态徽标 + 趋势
-2. **阈值配置**: 多级阈值可视化编辑, 修改留痕
-3. **告警事件流**: 级别/状态/恢复时间/数据异常标记
-4. **通道管理**: 启停 + 测试发送
-5. **通知中心**: 已有站内信角标复用; 预警类通知可点击跳转预警详情
-
----
-
-## 10. 与现有模块联动
-
-| 联动 | 说明 |
-|---|---|
-| 影响事件(Incident) | 高严重度告警(告警级)可一键/自动生成影响事件, 今日看板横幅已支持 |
-| 管理措施(Measure) | 告警可转"管理措施"或"待办", 形成"发现→措施→回看"闭环 |
-| 数据同步(sync) | 预警依赖数据同步; 同步失败本身也是监测指标(sync_health) |
-| 工作台 | 待处理告警进入工作台待办, 管理端可见 |
-
----
-
-## 11. 实施里程碑
-
-| 阶段 | 内容 | 交付物 |
-|---|---|---|
-| M1 指标与阈值基础 | metric_registry + metric_thresholds 表; 指标求值器(复用 analytics 函数); 首批 3~5 个指标注册(含退款挽回率) | 指标列表 API + 当前值/级别 |
-| M2 预警引擎 | 扩展 scan_alerts 为注册表驱动; 三级阈值比较; 当日幂等 + 恢复自愈; 数据健康检查; 定时扫描接入 | 告警事件 + 恢复通知 |
-| M3 站内通知 | 告警生成/恢复 → notify() 站内信; 前端 Alerts.vue 升级(水位视图+阈值配置+事件流) | 站内信闭环 |
-| M4 推送通道 | notification_channels 表 + 通道抽象 + 企业微信/钉钉/飞书群机器人 + 邮件; 测试发送; 失败降级 | 多通道推送 |
-| M5 扩展 | 指标清单扩充; 连续升级/聚合; 与 Incident/Measure 联动 | 运营指标全覆盖 |
-
-> M1~M3 是"最小闭环"(用户先只做站内信, 推送通道架构先抽象、后续接入)。
-
----
-
-## 12. 风险与权衡
-
-| 风险 | 应对 |
-|---|---|
-| 指标口径与看板不一致 | 指标注册表强制复用看板计算函数, 不另写 |
-| 假告警(数据未同步) | 数据健康检查前置, 数据异常与业务告警分离 |
-| 告警风暴/打扰 | 当日幂等 + 静默窗口 + 恢复自愈 + (后期)聚合 |
-| 硬编码规则迁移成本 | 现有 4 类规则映射到注册表指标, 阈值表迁移, 保留 condition 字段兼容 |
-| 阈值配置失误 | 阈值修改留痕(valid_from/valid_to + updated_by), 可回看可回滚 |
-| 推送通道失败 | 发送失败降级站内信, 投递记录可查 |
-| 性能 | 指标求值缓存(复用 Redis), 日终批量计算 + 快照表 |
-
----
-
-## 待评审问题
-
-1. 首批指标清单(§4.2)是否合理? 是否有遗漏的高优先级指标?
-2. ~~三级阈值是否够用~~ → **已确认改为四级(优秀/达标/预警/告警)**; 是否再引入"红线"级(合规/安全等硬性底线)待定, 运营指标暂不引入
-3. 告警是否需要在运营看板/今日看板顶部展示(类似影响事件横幅)?
-4. M1~M3 最小闭环是否按此顺序推进?
diff --git a/.trae/documents/refund-dashboard-redesign.md b/.trae/documents/refund-dashboard-redesign.md
deleted file mode 100644
index f117aa0..0000000
--- a/.trae/documents/refund-dashboard-redesign.md
+++ /dev/null
@@ -1,1096 +0,0 @@
-# 退款看板重设计 Spec
-
-> 版本:v1.0 · 2026-08-12
-> 依据:运营看板已验证的 6 专项分类 / T+7 判定 / staff_type 三分类 / reason 归一化 7 大类口径
-> 目标:把退款看板从"汇总监控"升级为"人员×设备×类型 深度分析 + 下钻审计"
-
----
-
-## 一、背景与目标
-
-### 1.1 现状问题
-
-当前退款看板(RefundDashboard.vue)只有两个 Tab:
-- **今日视图**:11 张 KPI 卡 + 小时/日趋势 + 失败原因 7 类 + 专员接待排名
-- **综合看板**:催单风险 5 视图 + 挽回专项 6 视图 + 现金流 7 视图
-
-**核心缺失**:
-1. 看到汇总数后无法下钻到具体设备/人员
-2. 没有人员成功率趋势(只有全员趋势)
-3. 没有类型维度的趋势对比
-4. 设备维度完全缺失(反向视角)
-5. AuditView 审计组件是孤岛,未与退款分析打通
-6. staff_detail 已有 rich 字段(department/position/staff_type/refund_op_amount)但前端只展示了 6 列
-
-### 1.2 设计目标
-
-把退款看板打造成"退款深度分析中枢":
-- **人员维度**:查看每个人员周期内接待的所有设备 + 挽回成功率 + 具体挽回/流失的设备清单
-- **设备维度**:反向视角,识别高风险设备(复退款/跨人员/高金额)
-- **类型维度**:退款原因 7 大类 + problem_type 细分的接待量/成功率/趋势
-- **下钻审计**:从汇总数 → 设备清单 → AuditView 时序,形成完整审计链路
-
-### 1.3 设计原则
-
-1. **复用已验证口径**:所有分母/分子/失败判定/T+7 窗口/staff_type 分类的逻辑,与 `refund_recovery_analysis` 保持完全一致,不重新发明
-2. **汇总→下钻模式**:退款看板做"批量对比+定位",AuditView 做"单点深挖",两者通过跳转打通
-3. **数据已有优先**:优先使用 refund_wide / cs_records / refunds / service_tickets 现有数据;SCRM 质量数据本期不接入
-4. **不过度设计**:每个图表都要能回答一个具体业务问题
-
----
-
-## 二、整体架构
-
-### 2.1 Tab 结构(5 Tab)
-
-```
-退款看板 (RefundDashboard.vue)
-├── Tab1: 概览 ← 原今日视图强化,小幅优化
-├── Tab2: 人员分析 ← 新增(P0 核心)
-├── Tab3: 设备分析 ← 新增(P0 核心)
-├── Tab4: 类型×趋势 ← 新增(P0 核心)
-└── Tab5: 综合看板 ← 原 overview,保留微调
-```
-
-### 2.2 数据流
-
-```
-后端 前端
-─────────────────────────────────────────────────────
-refund_recovery_analysis (已有,复用) ──→ Tab1 概览
-refund_personnel_analysis (新增) ──→ Tab2 人员分析
-refund_device_analysis (新增) ──→ Tab3 设备分析
-refund_type_trend (新增) ──→ Tab4 类型×趋势
-executeDashboard (已有,复用) ──→ Tab5 综合看板
-audit_timeline (已有,AuditView) ←── Tab2/Tab3 下钻跳转
-```
-
-### 2.3 路由与权限
-
-- 路由不变:`/dashboards/refund`(code=preset_dashboard_refund)
-- 权限不变:仅 isManager=true 可见(决策看板下)
-- Tab 切换更新 URL query:`?tab=overview|today|personnel|device|type`
-
----
-
-## 三、Tab1:概览(今日视图强化)
-
-### 3.1 保留内容(不变)
-
-- 11 张 KPI 卡(total_sn/success_sn/failed_sn/success_rate/abandon/fail/success/recharge/target_total/actual_refund/fee_ratio)
-- 今日按小时挽回趋势(双 Y 轴:柱=SN 数,线=成功率)
-- 近 7 天按日挽回趋势(双 Y 轴)
-- 失败原因 7 大类分布(柱=单量,线=占比)
-- 退款专员接待排名表(现有 8 列)
-
-### 3.2 新增内容
-
-#### 3.2.1 Top5 高风险设备快照(当日失败金额倒序)
-
-**位置**:KPI 卡下方,左侧半屏(12 col)
-
-**数据来源**:新增后端字段 `top_risk_devices`(在 refund_recovery_analysis 返回中追加)
-
-**展示**:
-```
-┌─ 今日高风险设备 Top5 (失败金额倒序) ──────────┐
-│ SN | 失败原因 | 退款金额 | 接待人 │
-│ ABC123 | 不想用了 | ¥1,280 | 叶珍凤 │
-│ DEF456 | 设备故障 | ¥980 | 朱海洋 │
-│ ... │
-│ [查看全部] → 跳转 Tab3 设备分析 │
-└──────────────────────────────────────────────────┘
-```
-
-**交互**:点击 SN 行 → 跳转 Tab3 并自动选中该 SN → 展开设备详情抽屉
-
-#### 3.2.2 金额分桶饼图
-
-**位置**:KPI 卡下方,右侧半屏(12 col)
-
-**数据来源**:新增后端字段 `amount_buckets`
-
-**展示**:失败 SN 按退款金额分桶
-- 小额(<100 元)
-- 中额(100-500 元)
-- 大额(500-2000 元)
-- 超大额(>2000 元)
-
-每桶显示:SN 数 + 占比% + 金额合计
-
-### 3.3 后端改动
-
-在 `refund_recovery_analysis` 返回中追加 2 个字段:
-
-```python
-# analytics.py refund_recovery_analysis 返回追加:
-{
- ...原有字段...,
- "top_risk_devices": [
- {
- "device_sn": "ABC123",
- "reason": "不想用了", # 归一化原因
- "amount": 1280.00,
- "operator": "企微-叶珍凤", # 接待人(首次接待)
- "operator_name": "叶珍凤",
- "refund_time": "2026-08-12T14:30:00",
- "status": "已退款"
- },
- ... # Top5
- ],
- "amount_buckets": [
- {"bucket": "小额(<100)", "sn_cnt": 12, "amount": 960.00, "ratio": 40.0},
- {"bucket": "中额(100-500)", "sn_cnt": 10, "amount": 2800.00, "ratio": 33.3},
- {"bucket": "大额(500-2000)", "sn_cnt": 6, "amount": 5400.00, "ratio": 20.0},
- {"bucket": "超大额(>2000)", "sn_cnt": 2, "amount": 5600.00, "ratio": 6.7},
- ]
-}
-```
-
----
-
-## 四、Tab2:人员分析(P0 核心)
-
-### 4.1 布局
-
-```
-┌─ 筛选栏 ────────────────────────────────────────────────────┐
-│ [部门:全部▼] [分组:全部▼] [专项:全部▼] [人员类型:全部▼] │
-│ [时间范围:近30天▼] [自定义日期] [刷新] │
-└──────────────────────────────────────────────────────────────┘
-
-┌─ ① 人员对比表(强化版) ────────────────────────────────────┐
-│ 见 4.2 字段清单 │
-└──────────────────────────────────────────────────────────────┘
-
-┌─ ② 人员成功率趋势(多线) ──────┐ ┌─ ③ 人员接待量趋势(堆叠)─┐
-│ 12 col │ │ 12 col │
-└─────────────────────────────────┘ └─────────────────────────────┘
-
-┌─ ④ 人员×原因 热力图矩阵 ────────────────────────────────────┐
-│ 24 col │
-└──────────────────────────────────────────────────────────────┘
-
-┌─ ⑤ 人员详情抽屉(点击某行触发) ────────────────────────────┐
-│ 见 4.3 │
-└──────────────────────────────────────────────────────────────┘
-```
-
-### 4.2 ① 人员对比表(强化版 staff_detail)
-
-**数据来源**:新增后端接口 `GET /api/v1/analytics/refund/personnel-analysis`
-
-**字段清单**(在现有 staff_detail 基础上扩展):
-
-| 列名 | 字段 | 类型 | 说明 |
-|---|---|---|---|
-| 姓名 | name | string | Person.display_name |
-| 部门 | department | string | 企微/店铺/研发 |
-| 分组 | group | string | 5 组之一 |
-| 人员类型 | staff_type | tag | 专职挽回(绿)/退款操作(蓝)/其他人员挽回(灰) |
-| 接待SN | total_sn | int | CS 接待去重 SN 数 |
-| 成功 | success_sn | int | 挽留成功 SN 数(绿色) |
-| 失败 | failed_sn | int | 挽留失败 SN 数(红色) |
-| 成功率 | success_rate | tag% | ≥38%绿/30-38%黄/<30%红 |
-| 退款操作SN | refund_op_cnt | int | 作为 refunds.operator 处理的 SN 数 |
-| 退款操作金额 | refund_op_amount | money | 退款操作金额合计 |
-| 挽回估算金额 | estimated_save_amount | money | success_sn × 同类失败平均金额(新增) |
-| 首次接待 | first_reception | datetime | 周期内首次接待时间(新增) |
-| 末次接待 | last_reception | datetime | 周期内末次接待时间(新增) |
-| 操作 | - | button | [详情] [时序] |
-
-**交互**:
-- 表头支持排序(接待SN/成功率/退款操作金额/挽回估算金额)
-- 点击 [详情] → 展开右侧抽屉(见 4.3)
-- 点击 [时序] → 新窗口打开 AuditView,person_id 已带入
-- 行可多选(checkbox),选中后联动 ②③ 趋势图
-
-### 4.3 ⑤ 人员详情抽屉(核心下钻)
-
-**触发**:点击人员对比表的 [详情] 按钮
-
-**布局**:
-
-```
-┌─ 抽屉(右侧滑出,宽度 60vw) ────────────────────────────────┐
-│ │
-│ ┌─ 人员概要 ──────────────────────────────────────────────┐│
-│ │ 叶珍凤 | 企微 | 朱海洋组 | 专职挽回 ││
-│ │ 接待SN: 45个 | 成功: 28个 | 失败: 17个 | 成功率: 62.2% ││
-│ │ 退款操作: 12个 SN | 退款金额: ¥8,400 | 挽回估算: ¥6,720 ││
-│ │ [查看完整时序] → 跳转 AuditView ││
-│ └──────────────────────────────────────────────────────────┘│
-│ │
-│ ┌─ 接待设备清单 ──────────────────────────────────────────┐│
-│ │ [成功 Tab(28)] [失败 Tab(17)] [全部 Tab(45)] ││
-│ │ ││
-│ │ 筛选: [失败原因▼] [搜索SN] ││
-│ │ ││
-│ │ ┌─ 表格 ─────────────────────────────────────────────┐ ││
-│ │ │ SN | 首次接待 | 末次接待 | 接待次数 | 失败原因 │ ││
-│ │ │ | 退款金额 | 退款时间 | 最终状态 | [时序] │ ││
-│ │ └────────────────────────────────────────────────────┘ ││
-│ │ ││
-│ │ 每行点击 [时序] → AuditView(person+device_sn) ││
-│ └──────────────────────────────────────────────────────────┘│
-│ │
-│ ┌─ 该人员的成功率趋势(单线,日粒度) ────────────────────┐│
-│ │ 双 Y 轴:柱=接待SN,线=成功率 ││
-│ └──────────────────────────────────────────────────────────┘│
-│ │
-│ ┌─ 该人员×原因 分布(饼图) ──────────────────────────────┐│
-│ │ 该人员接待的 SN 按失败原因 7 大类分布 ││
-│ └──────────────────────────────────────────────────────────┘│
-└──────────────────────────────────────────────────────────────┘
-```
-
-**设备清单字段**:
-
-| 列名 | 字段 | 说明 |
-|---|---|---|
-| 设备SN | device_sn | |
-| 首次接待 | first_reception_time | 该人员首次接待此 SN 的时间 |
-| 末次接待 | last_reception_time | 该人员末次接待此 SN 的时间 |
-| 接待次数 | reception_count | 该人员接待此 SN 的 cs_record 条数 |
-| 结果 | result | 成功(绿)/失败(红) |
-| 失败原因 | fail_reason | 归一化 7 大类(成功则为空) |
-| 退款金额 | refund_amount | 失败时显示,成功为 0 |
-| 退款时间 | refund_time | 失败时显示 |
-| 最终状态 | final_status | 已退款/申请中/无需退款等 |
-| 操作 | - | [时序] 跳转 AuditView |
-
-### 4.4 ② 人员成功率趋势(多线对比)
-
-**图表**:ECharts 折线图
-
-**数据来源**:新增后端接口 `GET /api/v1/analytics/refund/personnel-trend`
-
-**交互**:
-- 默认展示成功率 Top3 + Bottom3 人员(共 6 条线)
-- 可通过表格行多选自定义对比人员
-- X 轴:日期(按日/周粒度切换)
-- Y 轴:成功率%
-- 悬浮显示:姓名 + 日期 + 成功率 + 接待SN数
-
-### 4.5 ③ 人员接待量趋势(堆叠柱)
-
-**图表**:ECharts 堆叠柱状图
-
-**数据来源**:同 ② 接口
-
-**展示**:
-- X 轴:日期
-- Y 轴:SN 数
-- 堆叠:每个选中人员一层(颜色区分)
-- 与 ② 联动:选中人员一致
-
-### 4.6 ④ 人员×原因 热力图矩阵
-
-**图表**:ECharts heatmap
-
-**数据来源**:新增后端接口 `GET /api/v1/analytics/refund/person-reason-matrix`
-
-**展示**:
-- 行:人员(按接待量倒序,默认 Top15)
-- 列:7 大原因(不想用了/充值错误/设备故障/网卡卡无网络/套餐问题/卡商问题/其他)
-- 值:该人员在该原因下的**成功率**(颜色:绿=高/红=低)
-- 单元格悬浮:姓名 + 原因 + 接待N个 + 成功M个 + 成功率X%
-
-**交互**:
-- 点击单元格 → 展开抽屉,自动筛选该人员+该原因的设备清单
-- 可切换"成功率/接待量/失败数"三种色阶
-
-### 4.7 后端接口
-
-#### 接口1:人员分析汇总
-
-```
-GET /api/v1/analytics/refund/personnel-analysis
-参数:
- start?: string (YYYY-MM-DD)
- end?: string (YYYY-MM-DD)
- department?: string (企微/店铺/研发)
- group?: string (分组名)
- specialty?: string (专项名)
- staff_type?: string (专职挽回/退款操作/其他人员挽回)
-
-返回:
-{
- "staff_detail": [
- {
- "operator": "企微-叶珍凤",
- "name": "叶珍凤",
- "department": "企微",
- "group": "朱海洋组",
- "position": "组员",
- "staff_type": "专职挽回",
- "total_sn": 45,
- "success_sn": 28,
- "failed_sn": 17,
- "success_rate": 62.22,
- "refund_op_cnt": 12,
- "refund_op_amount": 8400.00,
- "estimated_save_amount": 6720.00, // success_sn × 同类失败平均金额
- "first_reception": "2026-08-01T09:00:00",
- "last_reception": "2026-08-12T15:30:00",
- },
- ...
- ],
- "summary": {
- "total_persons": 15,
- "total_sn": 320,
- "avg_success_rate": 45.6,
- "total_refund_op_amount": 128000.00,
- }
-}
-```
-
-**实现要点**:
-- 复用 `refund_recovery_analysis` 的人员分母/失败 SN 逻辑
-- 新增 `estimated_save_amount` 计算:对每个成功 SN,用同原因类别的失败 SN 平均金额估算
-- 新增 `first_reception / last_reception` 从 cs_records 聚合
-- 支持 department/group/specialty/staff_type 筛选
-
-#### 接口2:人员接待设备清单
-
-```
-GET /api/v1/analytics/refund/person-devices
-参数:
- operator: string (必填,人员别名如 "企微-叶珍凤")
- start?: string
- end?: string
- result?: string (success/failed/all,默认 all)
-
-返回:
-{
- "operator": "企微-叶珍凤",
- "name": "叶珍凤",
- "devices": [
- {
- "device_sn": "ABC123",
- "first_reception_time": "2026-08-05T10:00:00",
- "last_reception_time": "2026-08-08T14:00:00",
- "reception_count": 3,
- "result": "failed", // success/failed
- "fail_reason": "不想用了", // 归一化原因,成功则为 null
- "refund_amount": 280.00, // 失败时显示
- "refund_time": "2026-08-09T12:00:00",
- "final_status": "已退款",
- "refund_no": "RF20260809123", // 退款单号(失败时)
- },
- ...
- ],
- "summary": {
- "total": 45,
- "success": 28,
- "failed": 17,
- "total_refund_amount": 8400.00,
- }
-}
-```
-
-**实现要点**:
-- 从 cs_records 查该 operator 接待的 problem_category='退款' 的 SN 集合
-- 与 global_fail_sns 交集判断 success/failed
-- 失败 SN 关联 refunds 表取金额/时间/单号
-- 按 first_reception_time 倒序
-
-#### 接口3:人员趋势
-
-```
-GET /api/v1/analytics/refund/personnel-trend
-参数:
- start?: string
- end?: string
- operators?: string (逗号分隔,如 "企微-叶珍凤,企微-朱海洋")
- grain?: string (day/week,默认 day)
-
-返回:
-{
- "dates": ["2026-08-01", "2026-08-02", ...],
- "persons": [
- {
- "operator": "企微-叶珍凤",
- "name": "叶珍凤",
- "trend": [
- {"date": "2026-08-01", "total_sn": 5, "success_sn": 3, "failed_sn": 2, "success_rate": 60.0},
- ...
- ]
- },
- ...
- ]
-}
-```
-
-**实现要点**:
-- 复用 refund_recovery_analysis 的 daily_trend 逻辑,但按 operator 拆分
-- 默认返回 Top10 人员的趋势(按接待量),支持 operators 参数自定义
-
-#### 接口4:人员×原因矩阵
-
-```
-GET /api/v1/analytics/refund/person-reason-matrix
-参数:
- start?: string
- end?: string
- top?: int (默认 15,按接待量倒序取 Top N 人员)
-
-返回:
-{
- "reasons": ["不想用了", "充值错误", "设备故障", "网卡卡无网络", "套餐问题", "卡商问题", "其他"],
- "persons": [
- {
- "operator": "企微-叶珍凤",
- "name": "叶珍凤",
- "cells": [
- {"reason": "不想用了", "total_sn": 12, "success_sn": 8, "failed_sn": 4, "success_rate": 66.7},
- {"reason": "充值错误", "total_sn": 5, "success_sn": 2, "failed_sn": 3, "success_rate": 40.0},
- ...
- ]
- },
- ...
- ]
-}
-```
-
-**实现要点**:
-- 对每个人员的接待 SN,按失败原因分组(成功 SN 的原因从 cs_record.problem_type 归一化)
-- 成功 SN 也需要归一化原因(从 cs_record 取 problem_type → 归一化)
-
----
-
-## 五、Tab3:设备分析(P0 核心,反向视角)
-
-### 5.1 布局
-
-```
-┌─ 筛选栏 ────────────────────────────────────────────────────┐
-│ [失败原因:全部▼] [金额区间:全部▼] [接待次数:全部▼] │
-│ [时间范围:近30天▼] [自定义日期] [刷新] │
-└──────────────────────────────────────────────────────────────┘
-
-┌─ ① 设备风险分布 KPI ────────────────────────────────────────┐
-│ 4 张卡片:复退款设备数 | 跨人员接待设备数 | 高金额设备数 │
-│ | 总失败设备数 │
-└──────────────────────────────────────────────────────────────┘
-
-┌─ ② Top20 高风险设备表(失败金额倒序) ──────────────────────┐
-│ 见 5.2 │
-└──────────────────────────────────────────────────────────────┘
-
-┌─ ③ 接待→流失时差分布 ─────┐ ┌─ ④ 设备接待次数分布 ────────┐
-│ 12 col │ │ 12 col │
-└─────────────────────────────┘ └────────────────────────────────┘
-
-┌─ ⑤ 设备详情抽屉(点击某行触发) ────────────────────────────┐
-│ 见 5.3 │
-└──────────────────────────────────────────────────────────────┘
-```
-
-### 5.2 ② Top20 高风险设备表
-
-**数据来源**:新增后端接口 `GET /api/v1/analytics/refund/device-analysis`
-
-**字段清单**:
-
-| 列名 | 字段 | 说明 |
-|---|---|---|
-| 设备SN | device_sn | |
-| 接待次数 | reception_count | 被 cs_record 接待的总次数 |
-| 接待人员数 | receptionist_count | 被几个不同人员接待过(跨人员) |
-| 接待人员 | receptionists | 逗号分隔的姓名列表 |
-| 失败原因 | fail_reason | 归一化 7 大类 |
-| 退款金额 | refund_amount | |
-| 退款时间 | refund_time | |
-| 最终状态 | final_status | 已退款/申请中/无需退款 |
-| 退款单号 | refund_no | |
-| 操作 | - | [详情] [时序] |
-
-**交互**:
-- 默认按退款金额倒序
-- 点击 [详情] → 展开设备详情抽屉(见 5.3)
-- 点击 [时序] → 新窗口打开 AuditView,device_sn 已带入
-
-### 5.3 ⑤ 设备详情抽屉
-
-**触发**:点击设备表的 [详情] 按钮
-
-**布局**:
-
-```
-┌─ 抽屉(右侧滑出,宽度 60vw) ────────────────────────────────┐
-│ │
-│ ┌─ 设备画像 ──────────────────────────────────────────────┐│
-│ │ SN: ABC123 | 设备型号: xxx ││
-│ │ 首次接待: 2026-08-01 09:00 | 末次接待: 2026-08-08 14:00 ││
-│ │ 接待次数: 3 | 接待人员: 2人(叶珍凤,朱海洋) ││
-│ │ 最终结果: 失败(已退款) | 退款金额: ¥1,280 ││
-│ │ 退款时间: 2026-08-09 12:00 | 退款单号: RF20260809123 ││
-│ │ [查看完整时序] → 跳转 AuditView ││
-│ └──────────────────────────────────────────────────────────┘│
-│ │
-│ ┌─ 接待记录时间线 ────────────────────────────────────────┐│
-│ │ 时间 | 接待人 | 问题描述 | 处理结果 | 接待时长 ││
-│ │ 2026-08-01 09:00 | 叶珍凤 | 不想用了 | 沟通挽回中 | 15分 ││
-│ │ 2026-08-05 14:00 | 叶珍凤 | 仍想退款 | 继续沟通 | 20分 ││
-│ │ 2026-08-08 14:00 | 朱海洋 | 坚持退款 | 转退款流程 | 10分 ││
-│ └──────────────────────────────────────────────────────────┘│
-│ │
-│ ┌─ 退款记录 ──────────────────────────────────────────────┐│
-│ │ 退款单号 | 退款金额 | 退款方式 | 状态 | 操作人 | 审核人 ││
-│ │ RF20260809123 | ¥1,280 | 线上 | 已退款 | sys | 张蕾 ││
-│ └──────────────────────────────────────────────────────────┘│
-│ │
-│ ┌─ 关联工单(若有) ──────────────────────────────────────┐│
-│ │ 工单号 | 服务项 | 服务类别 | 登记时间 | 完成时间 | 处理人││
-│ └──────────────────────────────────────────────────────────┘│
-│ │
-│ ┌─ 钱包提现记录(若有) ──────────────────────────────────┐│
-│ │ 提现时间 | 提现金额 | 操作类型 ││
-│ └──────────────────────────────────────────────────────────┘│
-└──────────────────────────────────────────────────────────────┘
-```
-
-### 5.4 ③ 接待→流失时差分布
-
-**图表**:柱状图
-
-**数据来源**:新增后端字段 `loss_time_dist`
-
-**展示**:
-- X 轴:时差分桶(当天/1-3天/3-7天/7-15天/15天+)
-- Y 轴:失败 SN 数
-- 颜色:按金额深浅区分(金额越大颜色越深)
-
-**业务意义**:识别"快流失"vs"慢流失"设备,指导挽回节奏
-
-### 5.5 ④ 设备接待次数分布
-
-**图表**:柱状图 + 折线(双 Y 轴)
-
-**展示**:
-- X 轴:接待次数(1次/2次/3次/4次/5次+)
-- 柱:设备数
-- 线:该桶失败率%
-- 业务意义:接待次数越多失败率是否越高(沟通成本 vs 成功率)
-
-### 5.6 ① 设备风险分布 KPI(4 卡)
-
-| 卡片 | 含义 | 颜色 |
-|---|---|---|
-| 复退款设备数 | 同一 SN 在周期内出现≥2次退款诉求 | 红色 |
-| 跨人员接待设备数 | 被≥2个不同人员接待过的 SN | 橙色 |
-| 高金额设备数 | 退款金额>1000 元的 SN | 紫色 |
-| 总失败设备数 | 周期内失败 SN 总数 | 灰色 |
-
-### 5.7 后端接口
-
-#### 接口5:设备分析
-
-```
-GET /api/v1/analytics/refund/device-analysis
-参数:
- start?: string
- end?: string
- reason?: string (失败原因筛选)
- amount_min?: number
- amount_max?: number
- reception_count_min?: int
- limit?: int (默认 20,Top N)
-
-返回:
-{
- "risk_summary": {
- "repeat_refund_devices": 8, // 复退款设备数
- "cross_person_devices": 12, // 跨人员接待设备数
- "high_amount_devices": 5, // 金额>1000 的设备数
- "total_failed_devices": 45,
- },
- "top_devices": [
- {
- "device_sn": "ABC123",
- "reception_count": 3,
- "receptionist_count": 2,
- "receptionists": ["叶珍凤", "朱海洋"],
- "fail_reason": "不想用了",
- "refund_amount": 1280.00,
- "refund_time": "2026-08-09T12:00:00",
- "final_status": "已退款",
- "refund_no": "RF20260809123",
- },
- ... // Top20
- ],
- "loss_time_dist": [
- {"bucket": "当天", "sn_cnt": 5, "amount": 1200.00},
- {"bucket": "1-3天", "sn_cnt": 12, "amount": 4800.00},
- {"bucket": "3-7天", "sn_cnt": 15, "amount": 7200.00},
- {"bucket": "7-15天", "sn_cnt": 8, "amount": 3600.00},
- {"bucket": "15天+", "sn_cnt": 5, "amount": 2400.00},
- ],
- "reception_count_dist": [
- {"bucket": "1次", "device_cnt": 20, "fail_rate": 35.0},
- {"bucket": "2次", "device_cnt": 15, "fail_rate": 45.0},
- {"bucket": "3次", "device_cnt": 7, "fail_rate": 60.0},
- {"bucket": "4次", "device_cnt": 2, "fail_rate": 75.0},
- {"bucket": "5次+", "device_cnt": 1, "fail_rate": 100.0},
- ]
-}
-```
-
-#### 接口6:设备详情
-
-```
-GET /api/v1/analytics/refund/device-detail?device_sn=ABC123
-参数:
- device_sn: string (必填)
- start?: string
- end?: string
-
-返回:
-{
- "device_sn": "ABC123",
- "device_type": "xxx", // 来自 devices 表(若有)
- "first_reception": "2026-08-01T09:00:00",
- "last_reception": "2026-08-08T14:00:00",
- "reception_count": 3,
- "receptionists": ["叶珍凤", "朱海洋"],
- "result": "failed",
- "fail_reason": "不想用了",
- "refund_amount": 1280.00,
- "refund_time": "2026-08-09T12:00:00",
- "refund_no": "RF20260809123",
- "final_status": "已退款",
- "reception_timeline": [
- {
- "time": "2026-08-01T09:00:00",
- "operator": "企微-叶珍凤",
- "operator_name": "叶珍凤",
- "problem_type": "不想用了",
- "description": "...", // cs_record 描述
- "execution_result": "...",
- "consult_time": 15, // 接待时长(分钟)
- },
- ...
- ],
- "refund_records": [
- {
- "refund_no": "RF20260809123",
- "refund_amount": 1280.00,
- "refund_method": "线上",
- "status": "已退款",
- "operator": "sys",
- "auditor": "张蕾",
- "created_at_raw": "2026-08-09T12:00:00",
- }
- ],
- "tickets": [
- {
- "ticket_no": "GD20260808001",
- "service_item": "不想用了",
- "service_category": "退款",
- "register_time": "2026-08-08T16:00:00",
- "finish_time": "2026-08-09T12:00:00",
- "handler": "企微-朱海洋",
- }
- ],
- "wallet_records": [
- {
- "wallet_id": "W001",
- "op_time": "2026-08-09T13:00:00",
- "op_amount": 1280.00,
- "op_type": "用户提现",
- }
- ]
-}
-```
-
-**实现要点**:
-- 设备画像从 cs_records + refunds 聚合
-- 接待时间线从 cs_records 按时间正序
-- 退款/工单/钱包记录从 refund_wide 关联(已物化)
-- 若 refund_wide 无该 SN,实时查 refunds/service_tickets/wallet_withdrawals
-
----
-
-## 六、Tab4:类型×趋势(P0 核心)
-
-### 6.1 布局
-
-```
-┌─ 筛选栏 ────────────────────────────────────────────────────┐
-│ [时间范围:近30天▼] [粒度:日/周/月▼] [刷新] │
-└──────────────────────────────────────────────────────────────┘
-
-┌─ ① 各原因类型 接待量&成功率(双Y轴) ───────────────────────┐
-│ 24 col │
-└──────────────────────────────────────────────────────────────┘
-
-┌─ ② 各人员类型 接待量&成功率 ────────────────────────────────┐
-│ 24 col │
-└──────────────────────────────────────────────────────────────┘
-
-┌─ ③ 日期×原因 矩阵热力图 ────────────────────────────────────┐
-│ 24 col │
-└──────────────────────────────────────────────────────────────┘
-
-┌─ ④ problem_type 细分 Top10 ──────┐ ┌─ ⑤ 组间对比 ──────────┐
-│ 12 col │ │ 12 col │
-└────────────────────────────────────┘ └──────────────────────────┘
-```
-
-### 6.2 ① 各原因类型 接待量&成功率
-
-**图表**:双 Y 轴(堆叠柱 + 多线)
-
-**展示**:
-- X 轴:日期(按粒度切换)
-- 左 Y 轴:SN 数(堆叠柱,7 大原因各一层)
-- 右 Y 轴:成功率%(7 条线,每原因一条)
-- 悬浮:日期 + 原因 + 接待N个 + 成功M个 + 成功率X%
-
-**业务意义**:看各原因类型的接待量变化 + 成功率波动
-
-### 6.3 ② 各人员类型 接待量&成功率
-
-**图表**:双 Y 轴(分组柱 + 多线)
-
-**展示**:
-- X 轴:日期
-- 左 Y 轴:SN 数(分组柱:专职挽回/其他人员挽回/退款操作)
-- 右 Y 轴:成功率%(3 条线)
-
-**业务意义**:对比专职挽回 vs 其他人员挽回的能力差异
-
-### 6.4 ③ 日期×原因 矩阵热力图
-
-**图表**:ECharts heatmap
-
-**展示**:
-- 行:日期(近 30 天)
-- 列:7 大原因
-- 值:失败 SN 数(颜色深浅)
-- 可切换"失败数/接待数/成功率"三种色阶
-
-### 6.5 ④ problem_type 细分 Top10
-
-**图表**:横向柱状图
-
-**展示**:
-- 取周期内接待量 Top10 的 problem_type 原始值
-- 每条柱:接待 SN 数(按成功/失败堆叠)
-- 旁注:成功率%
-
-**业务意义**:7 大类太粗,看细分哪些 problem_type 流失率高
-
-### 6.6 ⑤ 组间对比
-
-**图表**:横向分组柱 + 表格
-
-**展示**:
-- 5 个组(董梦玲组/吴晓龙组/叶珍凤组/朱海洋组/张蕾组)
-- 每组:接待SN | 成功率 | 退款金额 | 人均挽回估算 | 失败数
-
-### 6.7 后端接口
-
-#### 接口7:类型趋势
-
-```
-GET /api/v1/analytics/refund/type-trend
-参数:
- start?: string
- end?: string
- grain?: string (day/week/month,默认 day)
- dimension?: string (reason/staff_type,默认 reason)
-
-返回(dimension=reason):
-{
- "dates": ["2026-08-01", "2026-08-02", ...],
- "reasons": ["不想用了", "充值错误", "设备故障", "网卡卡无网络", "套餐问题", "卡商问题", "其他"],
- "series": [
- {
- "reason": "不想用了",
- "trend": [
- {"date": "2026-08-01", "total_sn": 5, "success_sn": 3, "failed_sn": 2, "success_rate": 60.0},
- ...
- ]
- },
- ...
- ]
-}
-
-返回(dimension=staff_type):
-{
- "dates": [...],
- "staff_types": ["专职挽回", "其他人员挽回", "退款操作"],
- "series": [
- {
- "staff_type": "专职挽回",
- "trend": [...]
- },
- ...
- ]
-}
-```
-
-**实现要点**:
-- 按 reason:对每日每个 SN 归一化原因(从 cs_record.problem_type 或 refund.refund_reason / service_ticket.service_item 归一化)
-- 按 staff_type:对每日每个 SN 取接待人的 staff_type
-- 成功率复用 refund_recovery 的失败判定逻辑
-
-#### 接口8:problem_type 细分
-
-```
-GET /api/v1/analytics/refund/problem-type-detail
-参数:
- start?: string
- end?: string
- top?: int (默认 10)
-
-返回:
-{
- "items": [
- {
- "problem_type": "不想用了", // cs_record.problem_type 原始值
- "normalized_reason": "不想用了", // 归一化后的 7 大类
- "total_sn": 45,
- "success_sn": 28,
- "failed_sn": 17,
- "success_rate": 62.2,
- "total_amount": 8400.00, // 失败 SN 金额合计
- },
- ...
- ]
-}
-```
-
-#### 接口9:组间对比
-
-```
-GET /api/v1/analytics/refund/group-comparison
-参数:
- start?: string
- end?: string
-
-返回:
-{
- "groups": [
- {
- "group": "朱海洋组",
- "person_cnt": 3,
- "total_sn": 120,
- "success_sn": 72,
- "failed_sn": 48,
- "success_rate": 60.0,
- "total_refund_amount": 24000.00,
- "estimated_save_amount": 18000.00,
- "avg_success_rate": 55.5, // 人均成功率
- },
- ...
- ]
-}
-```
-
----
-
-## 七、Tab5:综合看板(保留微调)
-
-### 7.1 保留内容
-
-原有三段结构不变:
-- 区块1:催单风险(5 视图)
-- 区块2:退款挽回专项(6 视图)
-- 区块3:现金流与退款基础(7 视图)
-
-### 7.2 微调
-
-- 挽回专项区块右上角增加"组间对比"入口按钮 → 点击跳转 Tab4 的 ⑤
-- 现金流区块增加"金额分桶"小图(复用 Tab1 的 amount_buckets)
-
----
-
-## 八、与 AuditView 的打通
-
-### 8.1 跳转规则
-
-| 来源 | 目标 | 参数 |
-|---|---|---|
-| Tab2 人员表 [时序] | AuditView(person) | person_id + 时间范围 |
-| Tab2 设备清单 [时序] | AuditView(person+device) | person_id + device_sn + 时间范围 |
-| Tab3 设备表 [时序] | AuditView(device) | device_sn + 时间范围 |
-| Tab1 Top5设备 [查看全部] | Tab3 设备分析 | 切换 Tab |
-| Tab2 热力图单元格 | Tab2 抽屉(带筛选) | person + reason |
-
-### 8.2 AuditView 改造
-
-现有 AuditView 已支持 person/device 两种模式,需补充:
-- 支持 URL query 同时带 person_id + device_sn(双维度过滤)
-- 时间范围自动从 URL query 继承
-- 新窗口打开(不离开退款看板)
-
-### 8.3 路由
-
-AuditView 路由已存在(在运营看板下),退款看板跳转时用新窗口:
-```js
-const url = `/workbench/audit?person_id=${pid}&device_sn=${sn}&start=${start}&end=${end}`
-window.open(url, '_blank')
-```
-
----
-
-## 九、前端组件清单
-
-### 9.1 新增组件
-
-| 组件 | 位置 | 说明 |
-|---|---|---|
-| PersonnelAnalysis.vue | views/dashboards/refund/ | Tab2 人员分析主体 |
-| DeviceAnalysis.vue | views/dashboards/refund/ | Tab3 设备分析主体 |
-| TypeTrend.vue | views/dashboards/refund/ | Tab4 类型×趋势主体 |
-| PersonDetailDrawer.vue | views/dashboards/refund/ | Tab2 人员详情抽屉 |
-| DeviceDetailDrawer.vue | views/dashboards/refund/ | Tab3 设备详情抽屉 |
-| HeatMapChart.vue | components/ | 通用热力图(人员×原因、日期×原因) |
-
-### 9.2 修改组件
-
-| 组件 | 改动 |
-|---|---|
-| RefundDashboard.vue | 新增 3 个 Tab,调整 Tab 切换逻辑,URL query 同步 |
-| AuditView.vue | 支持双维度(person+device)URL query |
-
-### 9.3 复用组件
-
-- BaseChart.vue(已有,ECharts 封装)
-- ViewItemCard.vue(已有,综合看板视图卡片)
-- el-table / el-drawer / el-tag(Element Plus)
-
----
-
-## 十、后端实现要点
-
-### 10.1 新增接口清单
-
-| 接口 | 方法 | 路径 | 说明 |
-|---|---|---|---|
-| 1 | GET | /api/v1/analytics/refund/personnel-analysis | 人员分析汇总 |
-| 2 | GET | /api/v1/analytics/refund/person-devices | 人员接待设备清单 |
-| 3 | GET | /api/v1/analytics/refund/personnel-trend | 人员趋势 |
-| 4 | GET | /api/v1/analytics/refund/person-reason-matrix | 人员×原因矩阵 |
-| 5 | GET | /api/v1/analytics/refund/device-analysis | 设备分析 |
-| 6 | GET | /api/v1/analytics/refund/device-detail | 设备详情 |
-| 7 | GET | /api/v1/analytics/refund/type-trend | 类型趋势 |
-| 8 | GET | /api/v1/analytics/refund/problem-type-detail | problem_type 细分 |
-| 9 | GET | /api/v1/analytics/refund/group-comparison | 组间对比 |
-
-### 10.2 复用逻辑
-
-所有接口复用 `refund_recovery_analysis` 的核心逻辑:
-- 分母:cs_records.problem_category='退款' 的去重 SN
-- 失败判定:T+7 窗口 + refunds + service_tickets + cs_record 关键词
-- 原因归一化:`_normalize_refund_reason` 7 大类
-- 人员匹配:PersonAlias → Person
-- staff_type 分类:专职挽回/退款操作/其他人员挽回
-- 系统过滤:SYS_OP_BLACKLIST_RR + SYS_KW 描述过滤
-
-### 10.3 性能考虑
-
-- 人员分析、设备分析接口可复用 refund_recovery_analysis 的中间结果(若同请求已计算)
-- type-trend 接口按日期×原因拆分,数据量较大,建议加缓存(5 分钟)
-- device-detail 接口单设备查询,响应快
-- 所有接口支持 start/end 参数,默认近 30 天
-
-### 10.4 文件改动
-
-| 文件 | 改动 |
-|---|---|
-| backend/app/services/analytics.py | 新增 9 个函数(接口1-9),复用 refund_recovery_analysis 的辅助函数 |
-| backend/app/api/v1/analytics.py | 新增 9 个路由 |
-| 无需新增 model | 全部用现有表(cs_records/refunds/service_tickets/refund_wide/persons/person_alias/devices) |
-
----
-
-## 十一、实施计划
-
-### Phase 1:P0 核心(3 个 Tab)
-
-1. **后端**:实现接口 1-4(人员分析相关)
-2. **前端**:Tab2 人员分析 + 人员详情抽屉
-3. **后端**:实现接口 5-6(设备分析相关)
-4. **前端**:Tab3 设备分析 + 设备详情抽屉
-5. **后端**:实现接口 7-9(类型趋势相关)
-6. **前端**:Tab4 类型×趋势
-7. **联调**:AuditView 跳转打通
-
-### Phase 2:Tab1 强化
-
-1. **后端**:refund_recovery_analysis 追加 top_risk_devices + amount_buckets
-2. **前端**:Tab1 新增 Top5 设备快照 + 金额分桶饼图
-
-### Phase 3:Tab5 微调
-
-1. **前端**:综合看板增加组间对比入口 + 金额分桶小图
-
----
-
-## 十二、验收标准
-
-### 12.1 功能验收
-
-- [ ] Tab2 人员对比表展示所有字段(含估算金额/首末接待时间)
-- [ ] Tab2 点击 [详情] 弹出抽屉,展示该人员接待的设备清单(成功/失败可切)
-- [ ] Tab2 人员趋势图支持多选对比,日/周粒度切换
-- [ ] Tab2 人员×原因热力图可点击单元格下钻
-- [ ] Tab3 设备表展示 Top20,支持金额/原因/次数筛选
-- [ ] Tab3 点击 [详情] 弹出抽屉,展示接待时间线 + 退款记录 + 工单 + 钱包
-- [ ] Tab4 各原因类型趋势图(堆叠柱+多线)正常
-- [ ] Tab4 人员类型趋势图正常
-- [ ] Tab4 日期×原因热力图正常
-- [ ] Tab4 problem_type 细分 Top10 正常
-- [ ] Tab4 组间对比正常
-- [ ] AuditView 跳转携带 person_id/device_sn/时间范围参数
-
-### 12.2 数据一致性
-
-- [ ] Tab2 人员接待SN总数 = Tab1 KPI 的 total_sn
-- [ ] Tab3 失败设备总数 = Tab1 KPI 的 failed_sn
-- [ ] Tab4 各原因成功率与 Tab1 reason_dist 一致
-- [ ] 所有口径与运营看板 refund-recovery 接口一致
-
-### 12.3 性能
-
-- [ ] 各接口响应时间 < 3 秒(近 30 天数据)
-- [ ] 热力图渲染 < 1 秒
-- [ ] 抽屉打开 < 1 秒
-
----
-
-## 附录 A:7 大原因归一化规则
-
-(来自 analytics.py `_REASON_NORMALIZE_RULES`)
-
-| 归一化桶 | 关键词 |
-|---|---|
-| 不想用了 | 不想用/不想要/没用了/不需要/退订/取消 |
-| 充值错误 | 充错/充值错/充多/误充/错充 |
-| 设备故障 | 设备/坏了/故障/损坏/不能用/用不了/死机/卡机 |
-| 网卡卡无网络 | 网卡/卡慢/网速/网络慢/网慢/没网/无网/没信号/信号差/掉线 |
-| 套餐问题(流量虚涨价升级) | 套餐/流量/升级/涨价/资费/太贵 |
-| 卡商问题 | 卡商/渠道/代理/上游 |
-| 其他(填写备注) | 兜底 |
-
-## 附录 B:staff_type 分类规则
-
-(来自 analytics.py)
-
-| staff_type | 判定条件 |
-|---|---|
-| 专职挽回 | Person.specialty='退款挽回'(通过 _hr_map 映射) |
-| 退款操作 | 无 CS 接待,仅作为 refunds.operator/service_tickets.handler |
-| 其他人员挽回 | 非专职人员(组长/主管/咨询/套餐/投诉等)接待退款类 SN |
-
-## 附录 C:失败判定三路合并(T+7 窗口)
-
-| 来源 | 表 | 条件 |
-|---|---|---|
-| 退款单 | refunds | status ∉ (无需退款,驳回),created_at_raw 在 [首次接待, 首次接待+7天] |
-| 退款工单 | service_tickets | service_category='退款',status≠终止,register_time 在 T+7 窗口 |
-| CS描述关键词 | cs_records | description/execution_result/solution 含(退款说明/退款后重新充值/要求退回微信/非挽回用户) |
diff --git a/.trae/documents/time-aware-specialty-workload.md b/.trae/documents/time-aware-specialty-workload.md
deleted file mode 100644
index e9b4f22..0000000
--- a/.trae/documents/time-aware-specialty-workload.md
+++ /dev/null
@@ -1,191 +0,0 @@
-# 按天匹配专项的工作量分计算改造
-
-## Context
-
-当前人均效率计算在 `today_staff_efficiency` 中,使用 `Person.specialty`(单一当前值)判定所有工作桶的"本职/转接"系数。如果人员在查询周期内变更了专项(如 8月1-10日咨询接待 → 8月11日起退款挽回),旧工作会被错误地按新专项系数计算。
-
-改造目标:按天匹配专项,对查询周期内每一天使用该天实际生效的专项系数。
-
-## 方案:按天加权平均系数
-
-### 核心思路
-
-不改变 SQL 聚合粒度(仍是按人汇总),而是在 Python 侧预建**按天专项映射表**,对每个专项桶计算**天数加权平均系数**。
-
-**前提假设**:工作分布在各天大致均匀(对于多天查询,这个近似足够准确;对于单天查询,系数完全精确)。
-
-### 具体步骤
-
-#### 1. 预建按天专项映射表
-
-在 `today_staff_efficiency` 的工作量计算循环之前(约 line 4801),新增一段代码:
-
-```python
-# 1. 收集所有有工作记录的人员的 person_id
-work_person_ids = set()
-for r in rows:
- if r.person_id:
- work_person_ids.add(r.person_id)
-
-# 2. 查询 PersonSpecialtyAssignment,获取查询周期内每天的专项
-from datetime import date as _date_cls, timedelta
-query_start = ts.date() if hasattr(ts, 'date') else _date_cls.fromisoformat(str(ts)[:10])
-query_end = te.date() if hasattr(te, 'date') else _date_cls.fromisoformat(str(te)[:10])
-
-# 生成日期列表
-date_list = []
-d = query_start
-while d <= query_end:
- date_list.append(d)
- d += timedelta(days=1)
-
-# 一次性查出所有 work_person_ids 的专项分配历史
-specialty_assignments = db.execute(
- select(
- PersonSpecialtyAssignment.person_id,
- PersonSpecialtyAssignment.effective_from,
- PersonSpecialtyAssignment.effective_to,
- Specialty.name.label("specialty_name"),
- Specialty.difficulty_weight,
- )
- .select_from(PersonSpecialtyAssignment)
- .join(Specialty, PersonSpecialtyAssignment.specialty_id == Specialty.id)
- .where(
- PersonSpecialtyAssignment.person_id.in_(work_person_ids),
- PersonSpecialtyAssignment.is_primary == True,
- # 专项分配区间与查询区间有交集
- or_(
- PersonSpecialtyAssignment.effective_to.is_(None),
- PersonSpecialtyAssignment.effective_to >= query_start,
- ),
- PersonSpecialtyAssignment.effective_from <= query_end,
- )
-).all()
-
-# 3. 构建 person_id → {date: specialty_name} 映射
-person_daily_specialty: dict[int, dict[_date_cls, str]] = {}
-for sa in specialty_assignments:
- pid = sa.person_id
- sp_name = sa.specialty_name
- eff_from = sa.effective_from
- eff_to = sa.effective_to or query_end
- if pid not in person_daily_specialty:
- person_daily_specialty[pid] = {}
- for d in date_list:
- if eff_from <= d <= eff_to:
- # 如果同一天有多个专项分配,取 is_primary=True 的(上面已过滤)
- person_daily_specialty[pid][d] = sp_name
-
-# 4. 构建 person_id → {specialty_name: days_count}
-person_specialty_days: dict[int, dict[str, int]] = {}
-for pid, daily_map in person_daily_specialty.items():
- counts: dict[str, int] = {}
- for sp in daily_map.values():
- counts[sp] = counts.get(sp, 0) + 1
- person_specialty_days[pid] = counts
-```
-
-#### 2. 改造工作量分系数计算
-
-在现有循环中(约 line 4864-4887),将单一 `hr_cat` 替换为按桶计算的加权系数。
-
-**修改前**(line 4864-4887):
-```python
-hr_cat = _hr_map(r.sp, position)
-mgmt = (position in ("主管", "组长", "副主管")) or hr_cat == "管理层"
-
-for (bucket_key, cat_name, sp_set) in CAT_BUCKETS:
- sv, pv = cat_data[bucket_key]
- if sv == 0 and pv == 0:
- continue
- if mgmt:
- own = True
- coef = CAT_DEFAULT_WEIGHT[cat_name]
- elif hr_cat is not None and hr_cat in sp_set:
- own = True
- coef = WEIGHT[hr_cat]
- else:
- own = False
- coef = TRANSFER_PENALTY
- wp = sv * coef
- work_score += wp
-```
-
-**修改后**:
-```python
-position = r.pos or ""
-# 管理岗判定不变
-mgmt = (position in ("主管", "组长", "副主管"))
-# 当前主专项(用于展示,不用于计算)
-hr_cat = _hr_map(r.sp, position)
-
-# 获取该人员的按天专项分布
-pid = r.person_id
-sp_days = person_specialty_days.get(pid, {}) if pid else {}
-total_days = sum(sp_days.values()) if sp_days else 0
-
-for (bucket_key, cat_name, sp_set) in CAT_BUCKETS:
- sv, pv = cat_data[bucket_key]
- if sv == 0 and pv == 0:
- bd[cat_name] = {...}
- continue
-
- if mgmt:
- # 管理岗: 跨类豁免,用大类默认系数(不变)
- own = True
- coef = CAT_DEFAULT_WEIGHT[cat_name]
- elif total_days > 0:
- # 按天加权平均系数
- weighted_coef = 0.0
- own_days = 0
- for sp_name, days in sp_days.items():
- sp_cat = _hr_map(sp_name, position)
- if sp_cat and sp_cat in sp_set:
- weighted_coef += WEIGHT.get(sp_cat, 1.0) * days
- own_days += days
- else:
- weighted_coef += TRANSFER_PENALTY * days
- coef = weighted_coef / total_days
- own = own_days > total_days / 2 # 大部分天数是本职则标记为本职
- else:
- # 无按天数据,回退到原逻辑
- if hr_cat is not None and hr_cat in sp_set:
- own = True
- coef = WEIGHT.get(hr_cat, 1.0)
- else:
- own = False
- coef = TRANSFER_PENALTY
-
- wp = sv * coef; ep = pv * coef
- work_score += wp; eff_score += ep
- bd[cat_name] = {
- "sessions": sv, "people": pv,
- "weight": round(coef, 3), "coef": round(coef, 3), "is_own": own,
- "work_contrib": round(wp, 1), "eff_contrib": round(ep, 1),
- }
-```
-
-### 关键设计决策
-
-1. **天数加权平均**:假设工作在各天均匀分布,用天数比例加权。对于单天查询,结果完全精确;对于多天查询,误差在可接受范围。
-
-2. **管理岗不变**:主管/组长/副主管的跨类豁免逻辑不变,仍使用 `CAT_DEFAULT_WEIGHT`。
-
-3. **回退兼容**:当 `PersonSpecialtyAssignment` 无数据时(新人/未分配专项),回退到原单一 `hr_cat` 逻辑。
-
-4. **性能**:一次 SQL 查询 + Python 字典构建,不增加 SQL 查询次数。
-
-## 变更文件
-
-| 文件 | 变更 |
-|------|------|
-| `backend/app/services/analytics.py` | 在 `today_staff_efficiency` 函数中新增按天专项映射构建 + 改造系数计算逻辑 |
-
-## 验证
-
-1. 修改覃克泳的专项:8月1-10日=咨询接待,8月11日起=退款挽回
-2. 查询8月1-14日人均效率,确认:
- - 咨询接待桶的系数接近 1.0(大部分天数本职)
- - 退款挽回桶的系数接近 2.2(大部分天数本职)
-3. 查询8月11日单天,确认退款挽回桶系数=2.2
-4. 管理岗人员的系数不受影响
\ No newline at end of file
diff --git a/.trae/documents/收尾计划-AI治理大任务前.md b/.trae/documents/收尾计划-AI治理大任务前.md
deleted file mode 100644
index 72ee1b3..0000000
--- a/.trae/documents/收尾计划-AI治理大任务前.md
+++ /dev/null
@@ -1,105 +0,0 @@
-# 收尾计划:AI 治理大任务前的尾巴清理
-
-## Context
-
-用户即将启动 AI 数据治理框架(#162~#165,四张工单、体量较大)。开工前需要把前面工作留下的尾巴收干净,避免"旧账+新账"混在一起。
-
-用户原话:**"因为后续的任务比较大,前面的工作要及时收一下尾巴,然后持续执行"**。
-
-本轮已确认范围(用户拍板):
-- **只收尾巴,然后转大任务**(不做 C-P1-4 权限矩阵、不做 C-P1-8 容器非 root)
-- wallet 备份:**表 + dump 都留到 09-26 到期日统一清**
-
-## 收尾前的现状核查(本轮已完成,均为只读)
-
-| 事项 | 核查结论 |
-|---|---|
-| wallet 备份 dump 可用性 | ✅ 已验证:`wallet_withdrawals_20260919.sql.gz` 解出 69691 行,其中 `COPY` 数据段 **69455 行**,与 `wallet_withdrawals_bak_20260919` 表行数**完全一致** → 备份可恢复 |
-| #161 修复后是否仍有新重复 | ✅ 主表按 `(account_id,op_type,op_time,operator,op_amount) WHERE device_sn IS NULL` 分组重复组 = **0**;部分索引 `uq_wallet_withdrawals_null_sn` 已生效 |
-| 同类风险(可空列进唯一键) | ✅ 已扫描生产库 59 个唯一索引(含可空列 19 个),报出 4 处"重复"经**逐组甄别全部为 NULL 组**(PG 对 NULL 不去重是规范行为,非违约)→ **生产无真违约**,无需修复 |
-| 生产定时备份(C-P1-2) | ❌ **真实缺口**:root/tanzk crontab 均空、无 systemd timer、`/etc/cron.d` 无 csctalon 项 → **生产当前没有任何自动备份** |
-| 备份目录占用 | ⚠️ `/opt/csctalon/backups` = **5.2G**(10 个文件,多为升级时 `upgrade.sh` 自动产生的全量包 760M+/个);磁盘可用 184G |
-
-## 任务 1:补生产每日定时备份(C-P1-2 核心缺口)
-
-**为什么**:`deploy/backup.sh` 早已写好且支持 `--env-file/--output/--keep`,`deploy/README.md §5.1` 也明确建议配 cron 每日跑 —— 但生产**从未配置**。当前仅靠 `upgrade.sh` 升级时顺带备份,意味着"不升级就没有备份",且无异地留存。这是审计 C-P1-2「无备份/恢复方案」的实际缺口。
-
-**做法**(不新增脚本,复用既有 `backup.sh`):
-
-1. 在生产服务器 `/etc/cron.d/csctalon-backup` 落一个 cron 文件(比 `crontab -e` 更可审计、可版本化):
- ```
- # 每日 03:30 备份数据库+附件+登录态, 保留最近 14 份
- 30 3 * * * root /opt/csctalon/current/deploy/backup.sh --env-file /opt/csctalon/.env --output /opt/csctalon/backups --keep 14 >> /var/log/csctalon-backup.log 2>&1
- ```
- - 用 `current` 软链 → 随版本升级自动用最新脚本
- - `--keep 14`(README 推荐值):每日一份 × 14 天
-2. 同步 `deploy/README.md §5.1`:把"建议配置"改为"已配置"的现状说明(一行),并补 `/etc/cron.d/` 方式(原文档只写了 `crontab -e`)。
-3. 加 logrotate(可选但推荐):`/etc/logrotate.d/csctalon-backup` 轮转 `/var/log/csctalon-backup.log`,防日志无限增长。
-
-**验证**:
-- `sudo /opt/csctalon/current/deploy/backup.sh --env-file /opt/csctalon/.env --output /opt/csctalon/backups --keep 14` 手动跑一次 → 产出新包且健康
-- `sudo -n cat /etc/cron.d/csctalon-backup` 确认落盘
-- 检查 `run-parts`/cron 生效:`sudo systemctl status cron` + 次日看 `/var/log/csctalon-backup.log`
-- 抽验备份包可恢复:`tar tzf <新包>` 列出 `csctalon.dump / uploads / browser_states / backup.info`
-
-**风险控制**:
-- `--keep 14` 会清理超出的 `csctalon-backup-*.tar.gz`(不匹配该前缀的 `pre-org-*.dump`、`wallet_*.sql.gz`、`ai_reports_*.dump` **不受影响**,因 `ls -1t csctalon-backup-*.tar.gz` 只匹配该前缀)→ 需在验证时确认手工 dump 仍在
-- 5.2G 现状:新 cron 跑起来后靠 `--keep 14` 自然收敛(每日增量约 770M,14 份约 10G,磁盘 184G 可用,可接受)
-
-## 任务 2:wallet 备份到期清理机制
-
-**用户决定**:表 + dump 都留到 **2026-09-26** 统一清。
-
-**做法**:
-- 不在本轮删除任何东西
-- 在 `/etc/cron.d/csctalon-backup` 同文件追加一条**一次性到期清理**,或在 `docs/审查行动项登记.md` 登记"09-26 清理"待办
-- 推荐:**登记到行动项台账**(C-P1-7 备注补一行),比藏一条 cron 更可见;到期由人确认后删(备份删除是不可逆操作,人工确认更稳)
-
-清理对象(09-26 执行):
-```sql
-DROP TABLE IF EXISTS wallet_withdrawals_bak_20260919; -- 69455 行
-```
-```bash
-sudo rm -f /opt/csctalon/backups/wallet_withdrawals_20260919.sql.gz # 2.5M
-sudo rm -f /opt/csctalon/backups/ai_reports_pre166_20260920.dump # 195K (#166 清理的备份)
-sudo rm -f /opt/csctalon/backups/pre-org-20260914.dump # 108M (09-14 组织调整, 已过期)
-```
-
-## 任务 3:登记同类风险排查结论(闭环 C-P1-7 的"同类排查"尾巴)
-
-**做法**:更新 `docs/审查行动项登记.md` 的 C-P1-7 行,备注补:
-- 同类风险已全库扫描(59 个唯一索引 / 19 个含可空列)
-- 4 处报警经甄别**全为 NULL 组**(PG 规范允许),**无真违约**
-- 扫描脚本方法论(v2 复现索引真实表达式 + 部分条件,修正 v1 裸列名误报)
-
-同时在 `docs/design/AI数据治理框架-20260919.md` 的探测器设计(D1)中,把本次扫描脚本沉淀为 D1 的实现依据(D1 = 唯一索引重复探测器,正好复用 v2 逻辑)。
-
-## 任务 4:清理文档/工单尾巴
-
-- `docs/审查行动项登记.md`:C-P1-7 状态由「待处理」→「✅ 已完成(2026-09-19~20)」,引用 #160/#161
-- 确认 #162~#165 工单正文齐备(已建,open),本轮不动
-
-## 不做(明确记录取舍)
-
-| 项 | 为什么不做 |
-|---|---|
-| C-P1-4 权限矩阵收紧 | 跨后端 7 类端点 + 前端角色控制 + 测试,属独立工作包;用户选择先转大任务 |
-| C-P1-8 容器非 root | Playwright/Chromium/Xvfb 非 root 运行需 `--no-sandbox` 与写盘权限验证,风险最高,需单独排期 |
-| 删除 wallet 备份 | 未到 09-26 到期日,且删除不可逆 |
-| 异地 rsync 备份 | README 有建议但需目标机;本轮先保证"有本地每日备份",异地另议 |
-
-## 验证清单
-
-1. 手动跑一次 `backup.sh` → 新包生成、`tar tzf` 内容齐全、手工 dump 文件未被误删
-2. `/etc/cron.d/csctalon-backup` 落盘且语法正确(`sudo run-parts --test /etc/cron.d` 或等次日日志)
-3. `docs/审查行动项登记.md` C-P1-7 已更新为已完成并含同类排查结论
-4. 提交(携带 `#161`/`#160` 关联)+ 推送
-5. 转 #162 开始治理框架 A(两表 + D1 探测器)
-
-## 关键文件
-
-- [deploy/backup.sh](file:///Users/tanzk/dev/csctalon/deploy/backup.sh) —— 既有备份脚本(复用,不改逻辑)
-- [deploy/README.md](file:///Users/tanzk/dev/csctalon/deploy/README.md) —— §5.1 定时备份章节(补 `/etc/cron.d/` 方式 + 现状)
-- [docs/审查行动项登记.md](file:///Users/tanzk/dev/csctalon/docs/审查行动项登记.md) —— C-P1-7 状态与备注
-- [docs/design/AI数据治理框架-20260919.md](file:///Users/tanzk/dev/csctalon/docs/design/AI数据治理框架-20260919.md) —— D1 探测器实现依据(沉淀扫描脚本)
-- `/tmp/scan_dup_v2.py` + `/tmp/triage_dup.py` —— 本轮扫描脚本(D1 参考实现)
diff --git a/.trae/specs/191-299-closeout/spec.md b/.trae/specs/191-299-closeout/spec.md
deleted file mode 100644
index e5f69b7..0000000
--- a/.trae/specs/191-299-closeout/spec.md
+++ /dev/null
@@ -1,86 +0,0 @@
-# Spec:`#191 收口` + `#299 收口`
-
-**性质**:需求(REQ) · 一次性任务规格(非 META,不约束其他任务)
-**日期**:2026-10-15
-**关联工单**:`#191`(E 路恒 0 行治理,open)· `#299`(device_combo 历史回补,open)
-
----
-
-## 一、已核实事实(2026-10-15,生产 VM400 / release `git-20261015081123`)
-
-| 项 | 事实 | 取证方式 |
-|---|---|---|
-| #191 核验 | **未通过**(EXIT=1) | 容器内 `check_ai_issue_gates.py --issue 191 --allow-real-db` |
-| E 路 scheduled 记录 | 近 **20 天零条** `trigger_type=scheduled`;7 条全部 `manual` | `SELECT ... FROM bms_sync_logs WHERE data_type='refund_record_report'` |
-| E 路时效 | 最新业务日 `2026-10-10`,`lag=5`,`stale=True` | `check_freshness()` |
-| 运行时配置 | 正确:低频源含 `refund_record_report`;`list_low_freq_schedules → {'refund_record_report': {'hour': 15, 'window_days': 3}}` | 容器内直调服务函数 |
-| 容器启动日志 | 16:17 BJT 启动时「低频档回填:今日已执行的小时档 `['2026-10-15:6']`」→ **15 档未执行** | `docker logs csctalon-backend` |
-
-## 二、根因(代码级,已定位到行)
-
-`backend/app/services/bms_scheduler.py` 低频档分支(约 L836–842):
-
-```python
-elif should_run_low_freq(
- now_cn.hour,
- low_freq_hour=now_cn.hour,
- done=False,
- busy=sync_task_manager.is_busy(),
-):
-```
-
-**未传 `deadline_hour`** → 取默认 `LOW_FREQ_DEADLINE_HOUR = 7`;而 `should_run_low_freq` 第 185 行
-`if now_hour >= deadline_hour: return False`,故 **任何 per-source hour ≥ 7 的低频档恒不触发**
-(默认 06:00 档因 6 < 7 仍正常,因此长期未暴露)。
-
-到达 `:59` 后落 `elif now_cn.minute >= 59` 分支 → 记 `abandoned` 入 gap 队列,**无同步尝试 → 不写
-`bms_sync_logs`**,与「零 scheduled 记录」现象完全吻合。
-
-**影响**:E 路(退款记录报表,对账基准)自 #191 于 09-24 引入「按源可配小时」后**从未自动采集**,
-数据仅靠人工手动触发维持,当前落后 5 天。这与 #191 工单注释(L804)声明的语义
-「窗口 = 该源配置小时起 1 小时内」相矛盾。
-
-## 三、变更 A:`#191` 根因修复
-
-- **唯一改动点**:`backend/app/services/bms_scheduler.py` L836–842,补 `deadline_hour=now_cn.hour + 1`。
-- **不改**:`should_run_low_freq` 本体、`:59` abandon 分支、默认 06:00 档行为、fetch 窗口逻辑。
-- **语义恢复**:窗口 = `[该源配置小时, 该源配置小时 + 1)`,与工单注释一致。
-- **测试**:
- 1. `backend/tests/test_source_freshness.py`(或 `test_bms_scheduler_t1.py`)新增用例:
- `should_run_low_freq(15, low_freq_hour=15, deadline_hour=16, done=False, busy=False) is True`
- 与 `... deadline_hour=7 ...` 为 False(锁死根因)。
- 2. **漂移守卫**:断言 `bms_scheduler` 源码低频分支调用 `should_run_low_freq` 必带
- `deadline_hour=now_cn.hour + 1`(仿既有「`expected_by_hour` 必须等于采集种子 `low_freq_hour`」守卫写法,防止两处口径再次分叉)。
- 3. `pytest` + `ruff` 全绿。
-
-## 四、变更 B:`#299` 顺带项 5(覆盖率异常波动告警)——**待用户拍板**
-
-原文:「同步覆盖缺口监控——低频档跑完对比『devices 无套餐比例』异常波动告警(成功≠完整)」。
-
-- **建议实施,且落在监控模块(非采集核心)**:`backend/app/services/source_freshness_service.py`
- 新增只读巡检 `check_device_combo_coverage()`:
- - 指标:设备无有效套餐比例(口径须与 #298/#320/#321 一致,套餐以 `dim_device_combo` 为单一真实源);
- - 触发:比例超阈值(初值 **5%**,当前基线 **2.62%**)→ `record_system_event(category="sync",
- action="device_combo_coverage", level="warning")`,按日节流(复用现有 `_emit_once`)。
- - 该模块即 #191 交付的「数据源时效与空结果告警」巡检,由调度器每小时调用、**只读、零业务写入**,
- 不属于采集链路。
-- **降级选项**:若不实施 → 在 #299 关闭凭证中显式写
- `结论: 以 <理由> 降级验收`,禁止静默关闭。
-
-## 五、发布与验收
-
-- **发布**:`git push origin main` → 节点标准入口 `curl ... upgrade.sh | sudo bash -s -- --git`(禁手拼命令)。发布后核 `VERSION` 与迁移 head。
-- **#191 关闭条件**(工单原文):
- 1. 生产**连续 2 个自然日** `scheduled` 档 `total_rows > 0` **且** `check_freshness` 的 E 路 `lag_days ≤ 1`、`stale=false`(核验脚本 EXIT=0);
- 2. 0 行场景能触发 `system_events` 告警(已有 `#5385` 历史证据);
- 3. 巡检可复现(`check_freshness` 输出);
- 4. 单测 + `ruff` + `pytest` 全绿;
- 5. 若验收窗口内因上游无法达成 → 允许降级并登记待观察项。
-- **#299 关闭凭证**:A–F 逐条已有证据(探针结论、切片折半、全量回补 `min=2024-01-01`、残差 34,523 无流水 / 18,136 待 #321、台账 125 success + 64 split 无残留、17 单测)+ 顺带项 5 结论。
-
-## 六、风险与约束
-
-1. **变更 A 属采集调度链路** —— 触发 AGENTS.md「未经用户确认不得修改采集核心」铁律 → **须用户明示批准后再动手**。
-2. E 路上游 T+1:修复后需观察 1 个自然日确认 15:00 档能取到数(09-24 实验结论复用,不重做探针)。
-3. 变更 B 若被纳入调度触发路径则同属采集链路(亦须批准);仅作独立每小时只读巡检则属监控范围。
-4. 存在 `abandoned` 已入 gap 队列的存量记录:修复后 05:00 缺口清点会重试,属正常收口,不额外处理。
diff --git a/.trae/specs/191-299-closeout/tasks.md b/.trae/specs/191-299-closeout/tasks.md
deleted file mode 100644
index d89ecaf..0000000
--- a/.trae/specs/191-299-closeout/tasks.md
+++ /dev/null
@@ -1,45 +0,0 @@
-# Tasks:`#191 收口` + `#299 收口`
-
-> 依赖 spec.md。**T1 之前需用户对「变更 A 是否放行」明示批准(铁律:未经确认不得修改采集核心)。**
-
-## 阶段 0 · 用户拍板(阻塞门)
-
-- [x] **T0** 用户确认两件事:
- - (a) 放行变更 A(`bms_scheduler.py` 低频分支补 `deadline_hour=now_cn.hour + 1`);
- - (b) 变更 B 选「实施」还是「降级验收」。 → **批准 spec ⇒ (a) 放行、(b) 实施**
-
-## 阶段 1 · #191 根因修复(变更 A)
-
-- [x] **T1** 在 `backend/app/services/bms_scheduler.py` 低频档分支(L836–842)补
- `deadline_hour=now_cn.hour + 1`,并在该行上方补注释说明「窗口 = 该源配置小时起 1 小时」。
-- [x] **T2** 新增/改单测(`backend/tests/test_source_freshness.py` 或 `test_bms_scheduler_t1.py`):
- - 用例 1:`should_run_low_freq(15, low_freq_hour=15, deadline_hour=16, done=False, busy=False) is True`
- - 用例 2:`should_run_low_freq(15, low_freq_hour=15, deadline_hour=7, done=False, busy=False) is False`(锁根因)
- - 用例 3:源码漂移守卫 —— 断言调用点含 `deadline_hour=now_cn.hour + 1`
-- [x] **T3** 本地回归:`backend/.venv/bin/python -m pytest tests/ -q`(全量,期望 0 失败)+ `ruff check`。
- → 全量 EXIT=0;`test_source_freshness.py` 19 passed;ruff 全绿。
-- [x] **T4** 提交(commit message 带 `#191`)→ `git push origin main` → 节点标准入口
- `curl -fsSL http://10.147.17.41:3000/tanzk/csctalon/raw/branch/main/deploy/upgrade.sh | sudo bash -s -- --git`。
- → commit `a8b68f6`;已推送 main;发布 `git-20261015081123 → git-20261015090830`。
-- [x] **T5** 发布后核验:`VERSION` == 新 release;容器 healthy;日志出现「低频档触发 …15:xx 低频源=['refund_record_report']」。
- → current=`git-20261015090830`、healthy、域名 200、部署树含修复行 L845。
- (「低频档触发」日志需等 10-16 15:00 档,见 T6)
-- [ ] **T6** 观察窗口:次日起连续 2 个自然日 15:00 档 `bms_sync_logs(data_type=refund_record_report, trigger_type=scheduled)` 各 1 条且 `total_rows>0`。
-- [ ] **T7** 跑核验脚本(scp + docker cp + `--issue 191 --allow-real-db`),期望 **EXIT=0**;若非 0 且原因在上游 → 按工单第 5 条降级并在凭证登记待观察项。
-- [ ] **T8** 按工单纪律写 `## 关闭凭证核验 (YYYY-MM-DD)`(逐条对应关闭条件 1–5,给可复现命令/SQL),勾选正文 `[x]`,关闭 `#191`。
-
-## 阶段 2 · #299 收口
-
-- [x] **T9** (仅当 T0(b)=实施)在 `backend/app/services/source_freshness_service.py` 新增
- `check_device_combo_coverage()` 只读巡检 + 阈值告警(初值 5%),并接入现有每小时巡检入口;
- 补单测;随阶段 1 一并发布。 → 提交 `a8b68f6`,2 个单测。
-- [x] **T10** 写 `## 关闭凭证核验 (YYYY-MM-DD)` for `#299`(comment 3890)。
-- [x] **T11** 勾选 `#299` 正文 `[ ]` → 关闭(closed_at 2026-10-15T17:19:29+08:00)。
-
-
-## 明令不做
-
-- 不改 `should_run_low_freq` 本体与 `:59` abandon 分支(避免扩大采集语义变更面)。
-- 不改 E 路 fetch 窗口/tag 逻辑(09-24 实验结论已复核,不重做探针)。
-- 不动 `docs/arch/A-20260907-001-*`(LOCKED)——本修复是把实现拉回该文档已锁定的「每天 1 次、窗口 1 小时」语义。
-- 不为 #299 新增前端界面(原方案取舍:入口仅调度自动档 + admin API)。
diff --git a/.workbuddy/memory/2026-08-20.md b/.workbuddy/memory/2026-08-20.md
deleted file mode 100644
index a6cab2e..0000000
--- a/.workbuddy/memory/2026-08-20.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# 2026-08-20 工作日志
-
-## 项目审计
-
-对 CSCTalon 项目进行了全面审计,覆盖五大维度:
-- **架构与配置**: Docker Compose 部署合理,.env 未入库,端口暴露受限
-- **后端代码质量**: 分层清晰但 analytics.py 8508 行严重超标;30+ 处裸 except 吞异常;零测试覆盖;死依赖 passlib
-- **前端代码质量**: Vue 3 懒加载路由 + DOMPurify XSS 防护良好;但无 TypeScript、无 ESLint;SyncDashboard.vue 2010 行超标
-- **数据库**: 56 表完整,37 个 Alembic 迁移单 head,连接池配置合理,启动咨询锁防重复迁移
-- **安全**: bcrypt + JWT token_version 吊销 + 登录限速 + SafeStaticFiles + Fernet 加密 + CSP + 操作日志,安全实现质量高
-
-审计报告已输出至 docs/audit-report-20260820.html。
-
-### 关键发现
-- P0: 零测试覆盖(tests/ 目录为空)
-- P1: 超大文件拆分、裸 except 清理、死依赖清理
-- P2: TypeScript 引入、CI/CD、限速迁移 Redis
-- P3: 加密 key 独立化、前端组件拆分、依赖精确锁定