# v0.7 — 活动智能分析（分析 Agent + 结构化分析报告）

## Problem Statement

评测工程师拿到活动报告时，看到的是一堆聚合数字（通过率、可用性、时延、趋势、分场景汇总），但「这个数字意味着什么、哪里出了问题、接下来该改什么」需要人自己钻进几十条子运行里翻失败对话，耗时且依赖经验。尤其是正式线（实时 ×1）活动跑完后，团队期望直接拿到一份可读的专业研判：总体结论、问题清单、改进建议——目前平台给不出。

## Solution

为活动引入「分析（Analysis）岗位」（CONTEXT.md 已有术语）：一个两阶段分析 Agent，对活动聚合结果做活动级、跨场景的叙述性研判，产出**结构化分析报告**（总体结论 + 问题诊断 + 分场景叙述 + 改善建议），嵌在活动报告抽屉顶部展示，证据可点击下钻到具体子运行。正式线活动完成后自动生成；调试线（加速）活动按需手动生成，结果缓存可重新生成。分析模型在模型配置中心设「分析默认」，活动创建时可覆盖。

## User Stories

1. 作为评测工程师，我希望正式线活动一完成就自动得到分析报告，以便不用记得去点按钮。
2. 作为评测工程师，我希望调试线活动不自动消耗分析模型的 token，以便控制成本。
3. 作为评测工程师，我希望在报告抽屉里对终态活动随时手动生成/重新生成分析，以便在调整数据视角后拿到新研判。
4. 作为评测工程师，我希望进行中的活动不能生成分析，以便报告永远基于稳定数据。
5. 作为评测工程师，我希望报告开头有一段总体结论，以便 30 秒内知道这个活动行不行。
6. 作为评测工程师，我希望看到按严重度排列的问题诊断列表，每条注明涉及场景和证据，以便优先处理大问题。
7. 作为评测工程师，我希望问题证据能直接跳到对应子运行报告，以便核实模型研判是否属实。
8. 作为评测工程师，我希望每个场景有一段叙述性表现分析，以便理解数字背后的行为模式。
9. 作为评测工程师，我希望改善建议按优先级排序且可执行，以便直接转成下一步工作。
10. 作为评测工程师，我希望模型引用的证据都真实存在（不虚构 run_id），以便信任报告。
11. 作为管理员，我希望在模型配置中心把某个 chat 模型设为「分析默认」，以便全平台统一分析口径。
12. 作为管理员，我希望「分析默认」全局唯一、设置时自动互斥，以便不用手动清理旧标记。
13. 作为评测工程师，我希望创建活动时能为该活动单独指定分析模型，以便重要活动用更强的模型。
14. 作为评测工程师，我希望未配置分析模型时得到明确引导（去配置中心设置），以便知道为什么没有分析。
15. 作为评测工程师，我希望分析失败时看到失败原因并能重试，以便区分是模型故障还是数据问题。
16. 作为评测工程师，我希望导出的活动 Markdown 报告包含分析区块，以便离线分享完整报告。
17. 作为评测工程师，我希望分析所用的模型配置被快照记录，以便事后追溯报告出自哪个模型。
18. 作为评测工程师，我希望报告抽屉里能区分「生成中 / 已完成 / 失败」状态，以便知道后台任务进展。

## Implementation Decisions

- **分析岗位**：`ModelPurpose` 枚举新增 `ANALYSIS`。分析模型不按场景绑定（区别于出题/判卷），而是全局默认 + 活动覆盖（CONTEXT.md 术语定义）。
- **分析默认标记**：`model_configs` 表新增 `is_analysis_default` 布尔列（Alembic 迁移，batch mode，server_default 回填 false）。仓储层参照 `is_default` 的 `clear_default` 模式保证全局唯一；仅启用的 chat 能力配置可设为分析默认（校验与 `is_default and not enabled` 同款）。模型配置中心 UI 在 chat 配置上提供「分析默认」标记。
- **活动覆盖**：`campaigns` 表新增可空 `analysis_model_config_id` 列（迁移）；创建活动 API 接受该字段（可空=跟随全局默认）；创建表单左列「时间与速度」区加「分析模型」下拉（选项=启用的 chat 配置，默认项「全局默认（<默认配置名>）」）。
- **分析模型解析**：`活动.analysis_model_config_id ?? 全局分析默认`。两者都无 → 自动触发跳过；手动触发返回 409/400 并提示去配置中心。
- **分析存储**：新表 `campaign_analyses`，每活动一行（upsert 覆盖）：`campaign_id` 唯一、`status`（generating/completed/failed）、`result`（结构化 JSON，可空）、`model_config_id`（快照）、`error`（可空）、`triggered_by`（auto/manual）、`created_at`/`updated_at`。后台任务与 Runs 同款 `asyncio.Task` + `try/finally` 关 Session。
- **API**：`GET /api/campaigns/{id}/analysis` → `{status, result?, error?, model_config_id?, updated_at?}`（从未生成时 status 为 none/空态）；`POST /api/campaigns/{id}/analysis` → 触发生成（已存在则覆盖重跑）；活动为进行中/计划中返回 400；无分析模型可解析返回 400 且 detail 引导配置。
- **自动触发**：`campaign_runner` 写入 COMPLETED 的同一处（campaign_runner.py:232 附近），若 `time_scale == 1` 且可解析到分析模型，则 enqueue 分析后台任务。失败/取消的活动不自动触发。
- **两阶段分析 Agent**（新模块 `evaluation/analysis.py`）：
  - 输入数据：复用 `generate_campaign_report` 的聚合结果（不重算，遵循 ADR-0002/0004 口径）+ 每场景最多 3 条代表性失败对话（取自失败子运行的 turn：用户消息/回复/判定理由，各截断到合理长度）。
  - 阶段一：每个场景一次 LLM 调用（并行 `asyncio.gather`）→ 该场景叙述 + 问题点草稿。
  - 阶段二：汇总各场景产出 + 全局统计 → 总体结论 + 跨场景问题 + 优先级建议。
  - 输出用 JSON schema 约束（prompt 内嵌 schema + 解析校验，解析失败按失败处理可重试）。
  - 证据白名单：`run_id` 必须在提供的数据集内，模型虚构的引用在落库前剔除。
- **结构化报告 schema**：
  ```
  {
    "overall": str,                        # 总体结论（一段话）
    "problems": [{"severity": "high"|"medium"|"low",
                   "title": str, "description": str,
                   "scenario_ids": [str],
                   "evidence_run_ids": [str]}],
    "scenario_narratives": [{"scenario_id": str, "narrative": str}],
    "suggestions": [{"priority": int, "text": str}]
  }
  ```
- **前端报告抽屉**：顶部新增「智能分析」区块——状态行（生成中 spinner / 失败 error+重试 / 未生成时的生成按钮与状态说明 + 分析模型名）；完成后渲染：总体结论 callout → 问题诊断列表（严重度 Tag、场景名、证据 chip 点击跳 `/reports?run=`）→ 分场景叙述 → 改善建议（按 priority 排序）。生成/重新生成按钮仅终态活动可用，进行中禁用并提示「活动完成后可生成」。
- **Markdown 导出**：`render_campaign_markdown` 追加分析区块（有 completed 分析时），按 总体结论/问题诊断/分场景叙述/改善建议 拼段。
- **语言**：分析输出一律中文。

## Testing Decisions

好测试只测外部行为，不测实现细节。

- **唯一新 seam：分析服务**（`evaluation/analysis.py`），用假 LLM 客户端注入（先例：`tests/unit/mock_channel.py` 的 MockChannel、llm_score 的假网关）。覆盖：两阶段编排（阶段一并行、阶段二汇总）、JSON 解析失败→ failed 状态、run_id 白名单剔除虚构引用、无失败样例的场景也能产出叙述、无分析模型可解析→明确错误。
- **API 集成测试**（先例 `tests/integration/test_campaigns_api.py`）：GET 空态；POST 触发→后台完成→GET 拿到结果；进行中活动 POST 400；无分析模型 POST 400；覆盖重跑 upsert 不新增行。
- **自动触发**：runner 完成正式线活动后分析任务被 enqueue（用假分析服务断言调用）；加速活动不触发。
- **分析默认唯一性**：仓储/服务层测试（设第二个分析默认时第一个被清；停用配置不能设为分析默认）——先例 `is_default` 现有测试。
- **迁移**：Alembic upgrade 后两表新列存在且回填正确。
- **前端**：无测试框架，`npx tsc --noEmit` + `npm run build` + 浏览器实操验证（生成流程、状态展示、证据跳转、终态禁用）。

## Out of Scope

- **工具调用循环 Agent**（function calling 让模型自主拉数据）——本批固定两阶段编排。
- **HTML/PDF 专业报告页**——本批结构化 JSON + 抽屉原生渲染 + Markdown 导出。
- **分析报告的历史版本**——重新生成即覆盖（upsert），不保留旧版。
- **分析结果的通知推送**（完成提醒、Webhook）。
- **跨活动对比分析**。
- **进行中活动的增量分析**。

## Further Notes

- 分析输入完全来自既有聚合口径（ADR-0002：失败按 0.0 计入；ADR-0004：取消不计入分母），分析 Agent 不重算数字，只做叙述性研判。
- 阶段一按场景并行，场景数 = 该活动 capability_summary 中出现的场景数；阶段二单次调用。
- 分析模型的上下文窗口/最大输出元数据沿用模型配置中心的描述性元数据，不改变网关请求参数（AGENTS.md 模型配置中心约定）。
