# v0.6 Spec（UX 升级）— 评估活动页 UX 升级

**状态**: ready-for-agent
**领域词汇**: 见 CONTEXT.md「周期评估」章节（评估活动 / 服务周期窗口 / 活动计划 / 可用性）
**关键决策**: ADR-0002（通过率含执行失败）、ADR-0003（活动分期）；本批不改判定/调度/报告聚合语义
**批次说明**: 本 spec 是 v0.6 评估活动功能线的 **UX 升级批次**（①倍速反向输入 ②计划时间轴预览 ④展开行时间轴），与 `spec.md`（v1 静态地基）并存。线③「活动分析 Agent」属新功能，另立 **v0.7** spec。
**工单**: 本 spec 派生 v0.6/issues/06–10。

## Problem Statement

评估活动（Campaign）的能力在 v0.6 静态地基已落地，但**新增/查看活动的页面对用户不友好**：

1. **时间倍速是裸数字**：新增活动时「时间倍速」是一个 `InputNumber`（默认 1，仅一句 tooltip），用户得自己心算「填多大倍速能让 24h 窗口几分钟跑完」。用户真正关心的是「加速后多久跑完」，而不是抽象的倍速值。
2. **活动计划不直观**：计划是一组 `场景 + 偏移小时 + 次数` 的表单行，看不出「在这个窗口的哪些时段、跑哪个场景、跑几次」的时间分布全貌。
3. **看不到活动过程**：活动列表每行不可展开；跑完只能进报告抽屉看聚合曲线，无法直观回看「整个活动过程中，各个子 Run 在时间上如何依次发生、各自成败」。

## Solution

对活动页做三项 UX 升级，**不触碰**判定、调度、报告聚合的任何语义：

- **① 倍速反向输入**：新增活动时用户填「窗口长度」+「希望加速后多久跑完」，系统自动派生并只读展示时间倍速；正式线用「实时」开关（×1）。用户从此按「耗时」思考，而非「倍速」。
- **② 计划时间轴预览**：保留（优化后的）行编辑器做精确录入，其上方增加一条**只读时间轴预览**，把计划条目按偏移画在代表窗口的水平轴上、颜色区分场景、标注次数，随表单实时刷新。
- **④ 展开行时间轴**：活动列表每行可展开为一条**横向窗口时间轴**，把该活动的子 Run 按「加速后窗口偏移」定位成节点、颜色表状态、hover 显详情、点击跳该 Run 报告；进行中活动随现有轮询实时长出新节点。②与④共用同一套时间轴视觉语言。

## User Stories

1. 作为质量负责人，我想在新增活动时直接填「希望这个 24 小时窗口在多久内跑完」（如 1 小时），系统替我算好倍速，以便我不必心算倍速。
2. 作为质量负责人，我想在填写时实时看到「加速后约 X 跑完」，以便确认压缩节奏符合预期。
3. 作为质量负责人，我想在正式线一键选「实时」（真实墙钟、倍速 ×1），以便采到真实的凌晨/高峰表现。
4. 作为质量负责人，当我把目标耗时填得比窗口还长（意味着倍速 <1、比真实还慢）时，我想被拦下或纠正，以便不产生无意义的减速活动。
5. 作为质量负责人，我想在编辑活动计划时看到一条时间轴预览，把每个计划条目按其窗口偏移画出来，以便一眼看清「哪些时段跑哪个场景、跑几次」的分布。
6. 作为质量负责人，我想计划预览用颜色区分不同场景、并标注每个条目的次数，以便区分密集与稀疏时段。
7. 作为质量负责人，我想计划预览随我编辑表单实时更新，以便边填边看。
8. 作为质量负责人，我想在活动列表里展开任意一行，看到这个活动的完整过程时间轴，以便回看整个服务周期里发生了什么。
9. 作为质量负责人，我想过程时间轴把每个子 Run 按其「加速后窗口偏移」定位成节点，以便看清子 Run 在窗口内的时间分布。
10. 作为质量负责人，我想每个节点用颜色表示状态（完成/失败/运行中/取消），以便一眼识别问题时段。
11. 作为质量负责人，我想 hover 节点看到场景、通过率、时延，以便无需展开报告即可速览。
12. 作为质量负责人，我想点击节点直接跳到该子 Run 的报告，以便下钻排查。
13. 作为质量负责人，当我展开一个进行中的活动时，我想时间轴随轮询长出新派生的子 Run，以便实时观察活动推进。
14. 作为质量负责人，我想活动列表/详情处也用「加速后约 X 完成」而非裸倍速展示，以便和新增表单的心智一致。

## Implementation Decisions

### ① 倍速反向输入（前端为主，API 契约不变）
- 新增活动表单把「时间倍速」输入替换为两项：**窗口长度**（沿用预设下拉 6/12/24/48/72h）+ **目标加速耗时**（数值 + 单位，默认分钟）。
- 前端派生 `time_scale = window_seconds / target_real_seconds`，作为**只读**展示（如「倍速 ×24 · 加速后约 1 小时跑完」），提交时仍在 payload 里发 `time_scale`。**后端 `POST /campaigns` 契约与 `Campaign.time_scale` 语义完全不变**。
- 「实时」开关：选中即 `target_real_seconds = window_seconds` → `time_scale = 1.0`，并禁用目标耗时输入。
- 约束：目标耗时必须 ≤ 窗口长度（即 `time_scale ≥ 1`）；违反时表单校验拦截（对应 US-4）。
- 派生与反算（由 scale+window 反推「加速后耗时」用于列表/详情展示）抽成**纯前端 util**（如 `campaignTime.ts` 的 `deriveTimeScale` / `acceleratedDuration`），集中口径、便于复用。

### ② 计划时间轴预览（纯前端）
- 新增一个**只读时间轴预览组件**，输入 = 表单当前的 `window_seconds` + `plan` 条目 + 场景名映射，输出 = 一条水平窗口轴，条目按 `offset_seconds` 定位，颜色按 `scenario_id` 稳定分配，标注 `count`。
- 不涉及任何后端改动；数据完全来自表单本地状态，随编辑实时重渲染。
- 与④的过程时间轴共享底层展示（见下「共享组件」）。

### ④ 展开行时间轴（新后端纯接缝 + 前端展开行）
- **新后端纯函数接缝** `build_campaign_timeline(campaign, runs) -> list[CampaignTimelineEntry]`（放 `evaluation/report.py` 或紧邻的模块），每条含：`run_id / scenario_id / scenario_name / offset_seconds（加速后窗口偏移，clamp 到 [0, window]）/ status / pass_rate / avg_latency_ms / started_at`。
  - 偏移计算复用现有报告口径：`offset = (run.started_at - campaign.started_at).total_seconds() * time_scale`，clamp 到 `[0, window_seconds]`；未开始的子 Run（无 started_at）与取消 Run 的处理在此接缝内明确定义。
  - `pass_rate` 直接取子 Run `summary.pass_rate`（权威，只读不重算，沿 v0.5/v0.6 约定）。
- **新端点** `GET /campaigns/{id}/timeline` → `{entries: CampaignTimelineEntry[]}`，返回逐 Run 扁平数组（区别于报告的 12 桶聚合曲线）。
- **前端**：活动列表行改为 `expandable`，展开区渲染横向窗口时间轴：子 Run 节点按 `offset_seconds` 定位、颜色=状态、hover 显场景/通过率/时延、点击跳 `/reports?run={run_id}`。展开进行中活动时，随现有 5s 轮询刷新时间轴数据。

### 共享组件
- ②的计划预览与④的过程时间轴共用一个**底层横向时间轴展示组件**（窗口轴 + 定位标记 + 颜色 + hover），两处用不同数据适配：②喂「计划条目」，④喂「子 Run」。保证视觉语言一致，避免两套实现。

### API 契约（新增项）
- `GET /campaigns/{id}/timeline` → `{ "entries": [ { "run_id", "scenario_id", "scenario_name", "offset_seconds", "status", "pass_rate", "avg_latency_ms", "started_at" } ] }`。
- 其余活动端点、`Campaign` 模型、`time_scale` 语义均不变。

## Testing Decisions

- **好测试只测外部行为**：接缝喂构造好的输入、断言输出，不测内部实现细节。
- **主接缝：`build_campaign_timeline`（纯函数）** —— 唯一新增的可测后端接缝。单测覆盖：正常子 Run 的偏移计算（含 `time_scale` 压缩）、clamp 到窗口边界、未开始/取消/失败子 Run 的处理、字段映射（pass_rate 取自 summary、场景名映射）、空活动。**Prior art**：`tests/unit/test_report.py` / `test_campaign_report.py`（同样喂 campaign+runs 断言聚合 dict 的模式）。
- **端点集成测试**：`GET /campaigns/{id}/timeline` 的 200/404、字段形状。**Prior art**：`tests/integration/test_runs_api.py`、现有 campaigns router 测试。
- **前端无测试框架**：①倍速反向输入（派生/反算/实时开关/≤窗口校验）、②计划预览、④展开时间轴，均以 `npx tsc --noEmit` + `npm run build` + 浏览器实操验证（含进行中活动的轮询刷新）。`deriveTimeScale`/`acceleratedDuration` 写成纯函数，便于将来接入前端测试。

## Out of Scope

- **线③ 活动分析 Agent + 服务质量改善建议**：新功能，另立 **v0.7** spec（含新增「分析」模型岗位、全局默认分析模型 + 活动可覆盖、多步分析 Agent、结构化分析报告、按需生成+缓存）。
- **计划的拖拽编辑**：本批时间轴预览为**只读**；拖拽调偏移/点击空白新增等交互不做。
- **跨活动对比**、活动列表分页/筛选增强，均不在本批。

## Further Notes

- ②预览与④过程时间轴的共享底层组件是本批的关键复用点，也是唯一较有设计含量的前端接缝——先把它的输入接口设计干净（喂「带窗口偏移的标记数组」），两处适配即可。
- 列表/详情的「加速后约 X 完成」展示与新增表单的派生口径必须走同一个纯 util，避免两处算法漂移（呼应 v0.6 起「口径收敛到一处」的做法）。
