# 版本统计「日历耗时 + 人力投入」双口径设计

## Context

当前版本详情下的开发任务/测试用例/Bug 三个 Tab 顶部统计，以及概览页的"总人天投入"，都用**单条任务独立累加**计算耗时。问题：

- 任务 A：09:00–11:00（2h）
- 任务 B：10:00–12:00（2h）

实际只占用了 09:00–12:00 共 3h 工作时段，但当前累加算成 4h。

任务越多重叠越严重，"总耗时"会显著虚高，无法回答"这个版本实际走了几天"。

**目标**：顶部统计同时展示两个口径——

- **日历耗时**：合并区间后的真实工作时段占用（看版本实际走了多久）
- **人力投入**：单条独立累加（看总人力成本，原口径）

## 决策摘要

| 项 | 决策 |
|---|---|
| 算法位置 | `apps/web/lib/work-hours.ts` 新增 `mergeWorkHours` + `calcTwoMetrics` |
| 区间提取 | 各模块（dev-task / test-case / bug）暴露 `xxxIntervals(items)` 函数 |
| 顶部展示 | 三段式："预 X / 日历 Y / 人力 Z"（开发任务）；"日历 Y / 人力 Z"（测试用例 / Bug） |
| 概览 总人天 | 两行展示：日历总耗时 / 人力总投入（含调研/产品/UI/开发/测试/Bug 全程） |
| 个人耗时排名 | 不动（按人汇总天然是人力维度） |
| PlanTab 顶部 | 不动（不在本次范围） |
| 老聚合函数 | 保留（向后兼容，仍然是 manhours） |
| Bug 区间 end 优先级 | `closedAt > resolvedAt > 终态 updatedAt > now` |

## 1. 合并区间算法

`apps/web/lib/work-hours.ts` 新增：

```ts
export interface TimeInterval {
  start: string;
  end: string;
}

// 合并所有重叠区间后，对每段独立调 calcWorkHours 求和
export function mergeWorkHours(intervals: TimeInterval[]): number;

// 双口径汇总
export function calcTwoMetrics(intervals: TimeInterval[]): {
  calendarHours: number;
  manhours: number;
};
```

算法：

1. 过滤掉非法（`!start || !end`）或 `end <= start` 的区间
2. 按 `start` 升序排序
3. 扫描合并：当前与 prev 重叠（`cur.start <= prev.end`）时合并 `prev.end = max(prev.end, cur.end)`，否则把 prev push 出去
4. 对最终的 N 个不重叠区间，逐个调 `calcWorkHours(start, end)` 求和 → `calendarHours`
5. `manhours` = 原始 intervals 各自 `calcWorkHours` 之后求和（不合并）

精度 0.5h，与现有口径一致。未结束任务以 `now` 作为 end 参与合并。

**验证用例**：
- A(9–11) + B(10–12) → 日历 3h，人力 4h
- A(9–11) + B(13–15) + C(10–14) → 合并后 9–15 减去午休 12–13 = 5h；人力 6h
- 跨周末合并：周五 17–周一 10 + 周二 9–11 → 工作时段过滤后两段独立算

## 2. 三个模块的 intervals 提取

**`apps/web/lib/dev-task.ts`** 新增：

```ts
export function devTaskIntervals(tasks: DevTask[], now?: Date): TimeInterval[];
// 取 [actualStartAt, actualEndAt ?? now]，过滤无 actualStartAt 的
```

**`apps/web/lib/test-case.ts`** 新增：

```ts
export function testCaseIntervals(cases: TestCase[], now?: Date): TimeInterval[];
// 取 [startedAt, completedAt ?? now]，过滤无 startedAt 的
```

**`apps/web/lib/bug.ts`** 新增：

```ts
export function bugIntervals(bugs: Bug[], now?: Date): TimeInterval[];
// 取 [createdAt, closedAt ?? resolvedAt ?? (status 终态时 updatedAt) ?? now]
```

**`apps/web/lib/version-plan.ts`** 新增（仅供概览总人天用）：

```ts
export function planIntervals(plans: VersionPlan[], now?: Date): TimeInterval[];
// 取 [actualStartAt, completedAt ?? now]，过滤无 actualStartAt 的
```

老聚合函数（`aggregateDevTaskHours / aggregateTestCaseActualHours / aggregateBugActualHours`）保留，仍然是 manhours 累加，向后兼容（项目顶部卡片、与我相关页继续用）。

## 3. 4 个统计点 UI 调整

**(1) `DevTaskTab` 顶部**：

```
12任务 · 预 96h（12天） · 日历 64h（8天） / 人力 84h（10.5天）
```

- 染色：人力 > 预计 红、< 预计 绿、相等灰
- 日历不染色（中性事实）

**(2) `TestCaseTab` 顶部**：

```
40例 · 通过28 · 失败5 · 通过率85% · 日历 32h（4天） / 人力 56h（7天）
```

**(3) `BugTab` 顶部**：

```
Bug 12 个 · 待修复2 · 修复中3 · ... · 解决率75% · 日历 18h / 人力 24h
```

**(4) 概览-总人天投入**（line 778）：当前一行 `总人天投入 96h（12人天）` 改为两行：

```
日历总耗时    104h（13天）
人力总投入    176h（22人天）
```

数据源：plans + devTasks + testCases + bugs 的 intervals 全部合并后调 `calcTwoMetrics`。

## 4. 不动的部分

- 个人耗时排名（按人累加，本身就是人力维度）
- 阶段耗时（项目维度）卡片（已经是日历口径，用 `calcCalendar` 算最早开始/最晚结束/天数差）
- PlanTab（调研/产品方案/UI 设计）顶部统计
- 项目详情顶部 `HoursStatCard`
- 与我相关页 DevTask 工时显示

## 数据流

```
DevTask + TestCase + Bug + Plan
   │
   ├── devTaskIntervals(tasks)
   ├── testCaseIntervals(cases)
   ├── bugIntervals(bugs)
   └── planIntervals(plans)
       │
       └── work-hours.calcTwoMetrics(intervals)
                │
                ├── DevTaskTab 顶部
                ├── TestCaseTab 顶部
                ├── BugTab 顶部
                └── 概览总人天卡片（4 个 intervals 合并）
```

## 实施清单

新增/修改：

1. `apps/web/lib/work-hours.ts` — `TimeInterval` + `mergeWorkHours` + `calcTwoMetrics`
2. `apps/web/lib/dev-task.ts` — `devTaskIntervals`
3. `apps/web/lib/test-case.ts` — `testCaseIntervals`
4. `apps/web/lib/bug.ts` — `bugIntervals`
5. `apps/web/lib/version-plan.ts` — `planIntervals`
6. `apps/web/components/dev-task/DevTaskTab.tsx` — 三段式
7. `apps/web/components/test-case/TestCaseTab.tsx` — 加日历/人力两段
8. `apps/web/components/bug/BugTab.tsx` — 加日历/人力两段
9. `apps/web/app/versions/[id]/page.tsx` — 概览"总人天"改两行

## 验证

1. 单条任务 09:00–11:00：日历=2h，人力=2h
2. 两条 A(9–11) + B(10–12)：日历=3h，人力=4h
3. 三条 A(9–11) + B(13–15) + C(10–14)：日历=5h（去午休），人力=6h
4. 跨周末合并：周五 17–周一 10 与周二 9–11，分两段算工作时段后再相加
5. `pnpm type-check` + `pnpm build` 0 错误
6. 浏览器进版本详情：开发/测试/Bug 三个 Tab 顶部三段式正确，概览总人天两行正确
