# 版本统计「日历耗时 + 人力投入」双口径设计 ## 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 顶部三段式正确,概览总人天两行正确