feat: implement workload heatmap module
- Backend (FastAPI):
- Workload heatmap API with load level calculation
- User workload detail endpoint with task breakdown
- Redis caching for workload calculations (1hr TTL)
- Department isolation and access control
- WorkloadSnapshot model for historical data
- Alembic migration for workload_snapshots table
- API Endpoints:
- GET /api/workload/heatmap - Team workload overview
- GET /api/workload/user/{id} - User workload detail
- GET /api/workload/me - Current user workload
- Load Levels:
- normal: <80%, warning: 80-99%, overloaded: >=100%
- Tests:
- 26 unit/API tests
- 15 E2E automated tests
- 77 total tests passing
- OpenSpec:
- add-resource-workload change archived
- resource-management spec updated
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
# Design: add-resource-workload
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────────────┐ ┌───────────────┐
|
||||
│ Frontend │────▶│ Workload API │────▶│ MySQL │
|
||||
│ (Heatmap) │ │ /api/v1/workload │ │ (Snapshots) │
|
||||
└─────────────┘ └─────────────────────┘ └───────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ Redis │
|
||||
│ (Cache) │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
### 1. 負載計算策略:即時計算 vs 快照
|
||||
|
||||
**決定**:採用**混合策略**
|
||||
- **即時計算**:API 請求時計算,結果快取 1 小時
|
||||
- **快照儲存**:每日凌晨儲存歷史快照供趨勢分析
|
||||
|
||||
**理由**:
|
||||
- 即時計算確保數據新鮮度
|
||||
- 快照提供歷史趨勢分析能力
|
||||
- Redis 快取減少計算負擔
|
||||
|
||||
### 2. 週邊界定義
|
||||
|
||||
**決定**:採用 **ISO 8601 週**(週一至週日)
|
||||
|
||||
**理由**:
|
||||
- 國際標準,避免歧義
|
||||
- Python/MySQL 原生支援
|
||||
- 便於未來國際化
|
||||
|
||||
### 3. 負載計算公式
|
||||
|
||||
```
|
||||
週負載 = Σ(該週到期任務的 original_estimate) / 週容量 × 100%
|
||||
```
|
||||
|
||||
**任務計入規則**:
|
||||
- `due_date` 在該週範圍內
|
||||
- `assignee_id` 為目標使用者
|
||||
- `status` 非已完成狀態
|
||||
|
||||
**邊界情況處理**:
|
||||
- `original_estimate` 為空:計為 0(不計入負載)
|
||||
- `capacity` 為 0:顯示為 N/A(避免除以零)
|
||||
|
||||
### 4. 負載等級閾值
|
||||
|
||||
| 等級 | 範圍 | 顏色 | 描述 |
|
||||
|------|------|------|------|
|
||||
| normal | 0-79% | green | 正常 |
|
||||
| warning | 80-99% | yellow | 警告 |
|
||||
| overloaded | ≥100% | red | 超載 |
|
||||
|
||||
### 5. 快取策略
|
||||
|
||||
```
|
||||
快取鍵格式:workload:{user_id}:{week_start}
|
||||
TTL:3600 秒(1 小時)
|
||||
```
|
||||
|
||||
**失效時機**:
|
||||
- 任務建立/更新/刪除
|
||||
- 使用者容量變更
|
||||
|
||||
**實作**:暫不實作主動失效,依賴 TTL 自然過期(Phase 1 簡化)
|
||||
|
||||
### 6. 權限控制
|
||||
|
||||
| 角色 | 可查看範圍 |
|
||||
|------|-----------|
|
||||
| super_admin | 所有使用者 |
|
||||
| manager | 同部門使用者 |
|
||||
| engineer | 僅自己 |
|
||||
|
||||
## API Design
|
||||
|
||||
### GET /api/v1/workload/heatmap
|
||||
|
||||
查詢團隊負載熱圖
|
||||
|
||||
**Query Parameters**:
|
||||
- `week_start`: ISO 日期(預設當週一)
|
||||
- `department_id`: 部門篩選(可選)
|
||||
- `user_ids`: 使用者 ID 陣列(可選)
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"week_start": "2024-01-01",
|
||||
"week_end": "2024-01-07",
|
||||
"users": [
|
||||
{
|
||||
"user_id": "uuid",
|
||||
"user_name": "John Doe",
|
||||
"department_id": "uuid",
|
||||
"department_name": "R&D",
|
||||
"capacity_hours": 40.0,
|
||||
"allocated_hours": 32.5,
|
||||
"load_percentage": 81.25,
|
||||
"load_level": "warning",
|
||||
"task_count": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### GET /api/v1/workload/user/{user_id}
|
||||
|
||||
查詢特定使用者的負載詳情
|
||||
|
||||
**Query Parameters**:
|
||||
- `week_start`: ISO 日期
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"user_id": "uuid",
|
||||
"week_start": "2024-01-01",
|
||||
"capacity_hours": 40.0,
|
||||
"allocated_hours": 32.5,
|
||||
"load_percentage": 81.25,
|
||||
"load_level": "warning",
|
||||
"tasks": [
|
||||
{
|
||||
"task_id": "uuid",
|
||||
"title": "Task Name",
|
||||
"project_name": "Project A",
|
||||
"due_date": "2024-01-05",
|
||||
"original_estimate": 8.0,
|
||||
"status": "in_progress"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### PUT /api/v1/users/{user_id}/capacity
|
||||
|
||||
更新使用者容量
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"capacity": 32.0,
|
||||
"effective_from": "2024-01-08",
|
||||
"effective_until": "2024-01-14",
|
||||
"reason": "年假"
|
||||
}
|
||||
```
|
||||
|
||||
## Data Model
|
||||
|
||||
### pjctrl_workload_snapshots
|
||||
|
||||
儲存歷史負載快照
|
||||
|
||||
```sql
|
||||
CREATE TABLE pjctrl_workload_snapshots (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL,
|
||||
week_start DATE NOT NULL,
|
||||
allocated_hours DECIMAL(8,2) NOT NULL DEFAULT 0,
|
||||
capacity_hours DECIMAL(8,2) NOT NULL DEFAULT 40,
|
||||
load_percentage DECIMAL(5,2) NOT NULL DEFAULT 0,
|
||||
task_count INT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES pjctrl_users(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY uk_user_week (user_id, week_start),
|
||||
INDEX idx_week_start (week_start)
|
||||
);
|
||||
```
|
||||
|
||||
### pjctrl_capacity_adjustments(可選,Phase 1 暫不實作)
|
||||
|
||||
儲存臨時容量調整(如請假)
|
||||
|
||||
```sql
|
||||
CREATE TABLE pjctrl_capacity_adjustments (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL,
|
||||
week_start DATE NOT NULL,
|
||||
adjusted_capacity DECIMAL(5,2) NOT NULL,
|
||||
reason VARCHAR(200),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES pjctrl_users(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY uk_user_week (user_id, week_start)
|
||||
);
|
||||
```
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### 後端結構
|
||||
|
||||
```
|
||||
backend/app/
|
||||
├── api/
|
||||
│ └── workload/
|
||||
│ ├── __init__.py
|
||||
│ └── router.py
|
||||
├── services/
|
||||
│ └── workload_service.py
|
||||
├── models/
|
||||
│ └── workload_snapshot.py
|
||||
└── schemas/
|
||||
└── workload.py
|
||||
```
|
||||
|
||||
### 週起始日計算
|
||||
|
||||
```python
|
||||
from datetime import date, timedelta
|
||||
|
||||
def get_week_start(d: date) -> date:
|
||||
"""取得 ISO 週的週一"""
|
||||
return d - timedelta(days=d.weekday())
|
||||
```
|
||||
|
||||
### Redis 快取範例
|
||||
|
||||
```python
|
||||
async def get_user_workload(user_id: str, week_start: date) -> dict:
|
||||
cache_key = f"workload:{user_id}:{week_start}"
|
||||
cached = redis_client.get(cache_key)
|
||||
if cached:
|
||||
return json.loads(cached)
|
||||
|
||||
result = calculate_workload(user_id, week_start)
|
||||
redis_client.setex(cache_key, 3600, json.dumps(result))
|
||||
return result
|
||||
```
|
||||
@@ -0,0 +1,74 @@
|
||||
# Proposal: add-resource-workload
|
||||
|
||||
## Summary
|
||||
|
||||
實作資源管理系統的核心功能:負載熱圖與容量追蹤。讓主管能夠視覺化了解團隊成員的工作負載狀況,並進行資源分配決策。
|
||||
|
||||
## Motivation
|
||||
|
||||
根據 `project.md` 的核心目標:
|
||||
- 提供**即時資源負載與專案進度分析**
|
||||
- 讓 Unit Managers 獲得**團隊工作負載可見度、資源分配熱圖**
|
||||
- 減輕工程師的**時間回報負擔**
|
||||
|
||||
目前系統已完成 `user-auth` 和 `task-management`,具備:
|
||||
- 使用者資料與容量(capacity)欄位
|
||||
- 任務的時間估算(original_estimate)與實際耗時(time_spent)
|
||||
- 任務指派(assignee_id)與截止日期(due_date)
|
||||
|
||||
基於現有基礎,可以開始計算並展示資源負載資訊。
|
||||
|
||||
## Scope
|
||||
|
||||
### In Scope (Phase 1 - 核心負載功能)
|
||||
|
||||
1. **Workload Heatmap API**
|
||||
- 計算每位成員的週負載百分比
|
||||
- 依負載狀態分類(綠色/黃色/紅色)
|
||||
- 支援時間範圍查詢
|
||||
|
||||
2. **User Capacity Management**
|
||||
- 更新使用者容量設定 API
|
||||
- 容量歷史記錄(未來週數的容量調整,如請假)
|
||||
|
||||
3. **Workload Snapshot Storage**
|
||||
- 建立 `pjctrl_workload_snapshots` 表
|
||||
- 定期快照或即時計算策略
|
||||
|
||||
4. **Team Workload Overview API**
|
||||
- 部門級別的負載總覽
|
||||
- 支援按專案或部門篩選
|
||||
|
||||
### Out of Scope (Future Phases)
|
||||
|
||||
- Multi-Project Health Dashboard(需要更多專案進度數據)
|
||||
- 資源分配不均的自動建議
|
||||
- 資源分配 AI 預測
|
||||
- WebSocket 即時更新
|
||||
|
||||
## Affected Specs
|
||||
|
||||
- `resource-management` - 實作以下需求:
|
||||
- Workload Heatmap(部分)
|
||||
- Capacity Planning(部分)
|
||||
- Team Workload Distribution(部分)
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `user-auth` - 使用者認證與權限
|
||||
- `task-management` - 任務與時間估算數據來源
|
||||
|
||||
## Risks
|
||||
|
||||
| Risk | Impact | Mitigation |
|
||||
|------|--------|------------|
|
||||
| 負載計算效能 | 大量任務時計算緩慢 | 使用 Redis 快取計算結果 |
|
||||
| 任務時間數據不完整 | 無 original_estimate 的任務無法計算 | 提供預設值或排除計算 |
|
||||
| 週邊界定義不一致 | 不同使用者對「週」的理解不同 | 統一使用 ISO 週(週一至週日)|
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] 主管可查看團隊成員的週負載熱圖
|
||||
- [ ] 負載百分比正確反映任務分配時數 vs 容量
|
||||
- [ ] 支援按部門篩選負載視圖
|
||||
- [ ] API 回應時間 < 500ms(快取命中時 < 100ms)
|
||||
@@ -0,0 +1,87 @@
|
||||
# resource-management spec delta
|
||||
|
||||
此變更實作 `resource-management` spec 的核心需求,補充 API 規格與實作細節。
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Workload Heatmap
|
||||
|
||||
系統 SHALL 提供負載熱圖 API,自動統計每人每週分配的任務總時數,並以顏色等級表示負載狀態。
|
||||
|
||||
#### Scenario: 負載正常顯示
|
||||
- **GIVEN** 某人員本週被指派的任務總時數低於其容量的 80%
|
||||
- **WHEN** 主管查詢負載熱圖 API
|
||||
- **THEN** 該人員的 `load_level` 為 `normal`
|
||||
- **AND** 回傳包含 `load_percentage`、`allocated_hours`、`capacity_hours`
|
||||
|
||||
#### Scenario: 負載警告顯示
|
||||
- **GIVEN** 某人員本週被指派的任務總時數達到其容量的 80%-99%
|
||||
- **WHEN** 主管查詢負載熱圖 API
|
||||
- **THEN** 該人員的 `load_level` 為 `warning`
|
||||
|
||||
#### Scenario: 負載超載顯示
|
||||
- **GIVEN** 某人員本週被指派的任務總時數達到或超過其容量的 100%
|
||||
- **WHEN** 主管查詢負載熱圖 API
|
||||
- **THEN** 該人員的 `load_level` 為 `overloaded`
|
||||
|
||||
#### Scenario: 查詢特定週的負載
|
||||
- **GIVEN** 主管需要查看非當週的負載
|
||||
- **WHEN** 主管以 `week_start` 參數查詢負載熱圖 API
|
||||
- **THEN** 系統回傳該週的負載資料
|
||||
|
||||
#### Scenario: 快取機制
|
||||
- **GIVEN** 負載資料已被計算並快取
|
||||
- **WHEN** 相同查詢在 1 小時內再次發生
|
||||
- **THEN** 系統從 Redis 快取回傳結果
|
||||
|
||||
### Requirement: Capacity Planning
|
||||
|
||||
系統 SHALL 支援人員容量規劃,包含預設容量與臨時調整。
|
||||
|
||||
#### Scenario: 設定人員預設容量
|
||||
- **GIVEN** 管理者需要設定人員的週工時上限
|
||||
- **WHEN** 管理者更新使用者的 `capacity` 值
|
||||
- **THEN** 系統儲存新的容量設定
|
||||
- **AND** 後續負載計算使用新容量值
|
||||
|
||||
#### Scenario: 容量為零處理
|
||||
- **GIVEN** 使用者的容量設為 0
|
||||
- **WHEN** 系統計算該使用者的負載
|
||||
- **THEN** `load_percentage` 顯示為 `null`
|
||||
- **AND** `load_level` 顯示為 `unavailable`
|
||||
|
||||
### Requirement: Team Workload Distribution
|
||||
|
||||
系統 SHALL 提供團隊工作分配查詢功能。
|
||||
|
||||
#### Scenario: 部門負載總覽
|
||||
- **GIVEN** 主管需要了解部門整體負載
|
||||
- **WHEN** 主管以 `department_id` 參數查詢負載熱圖 API
|
||||
- **THEN** 僅顯示該部門成員的負載狀況
|
||||
|
||||
#### Scenario: 使用者負載詳情
|
||||
- **GIVEN** 主管需要了解某人的詳細任務分配
|
||||
- **WHEN** 主管查詢使用者負載詳情 API
|
||||
- **THEN** 回傳該週指派給該使用者的所有任務
|
||||
- **AND** 包含每個任務的 `original_estimate` 與 `due_date`
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Workload Data Access Control
|
||||
|
||||
系統 SHALL 限制負載資料的存取權限。
|
||||
|
||||
#### Scenario: 系統管理員查看所有人
|
||||
- **GIVEN** 登入者為 `super_admin`
|
||||
- **WHEN** 查詢負載熱圖 API
|
||||
- **THEN** 可查看所有使用者的負載資料
|
||||
|
||||
#### Scenario: 一般使用者查看自己
|
||||
- **GIVEN** 登入者為一般使用者
|
||||
- **WHEN** 查詢負載熱圖 API 未指定 `user_ids`
|
||||
- **THEN** 僅回傳自己的負載資料
|
||||
|
||||
#### Scenario: 跨部門存取拒絕
|
||||
- **GIVEN** 登入者非系統管理員
|
||||
- **WHEN** 查詢其他部門使用者的負載
|
||||
- **THEN** 系統拒絕存取並回傳 403 Forbidden
|
||||
@@ -0,0 +1,75 @@
|
||||
# Tasks: add-resource-workload
|
||||
|
||||
## Phase 1: 資料模型與基礎設施
|
||||
|
||||
- [x] **1.1** 建立 WorkloadSnapshot model (`backend/app/models/workload_snapshot.py`)
|
||||
- [x] **1.2** 建立 Alembic migration 建立 `pjctrl_workload_snapshots` 表
|
||||
- [x] **1.3** 建立 Workload schemas (`backend/app/schemas/workload.py`)
|
||||
|
||||
## Phase 2: 核心服務層
|
||||
|
||||
- [x] **2.1** 實作 `workload_service.py` 核心邏輯
|
||||
- ISO 週計算函式
|
||||
- 使用者週負載計算
|
||||
- 負載等級判定(normal/warning/overloaded)
|
||||
- [x] **2.2** 實作 Redis 快取整合
|
||||
- 快取讀取/寫入
|
||||
- TTL 設定
|
||||
|
||||
## Phase 3: API 端點
|
||||
|
||||
- [x] **3.1** 建立 workload router (`backend/app/api/workload/router.py`)
|
||||
- [x] **3.2** 實作 `GET /api/workload/heatmap` - 團隊負載熱圖
|
||||
- [x] **3.3** 實作 `GET /api/workload/user/{user_id}` - 使用者負載詳情
|
||||
- [x] **3.4** 實作 `GET /api/workload/me` - 當前使用者負載(替代 3.4 的容量更新)
|
||||
- [x] **3.5** 註冊 workload router 至 main.py
|
||||
|
||||
## Phase 4: 權限控制
|
||||
|
||||
- [x] **4.1** 實作部門隔離邏輯
|
||||
- super_admin 可查看所有
|
||||
- manager 可查看同部門
|
||||
- engineer 僅可查看自己
|
||||
|
||||
## Phase 5: 測試
|
||||
|
||||
- [x] **5.1** 單元測試:負載計算邏輯
|
||||
- [x] **5.2** 單元測試:週邊界計算
|
||||
- [x] **5.3** API 測試:heatmap 端點
|
||||
- [x] **5.4** API 測試:user workload 端點
|
||||
- [x] **5.5** API 測試:權限控制
|
||||
|
||||
## Phase 6: E2E 測試與驗證
|
||||
|
||||
- [x] **6.1** 自動化 E2E 測試:負載熱圖完整流程
|
||||
- 建立測試使用者與任務資料
|
||||
- 驗證負載計算正確性
|
||||
- 驗證負載等級判定
|
||||
- [x] **6.2** 自動化 E2E 測試:權限控制流程
|
||||
- super_admin 可查看所有人
|
||||
- 一般使用者僅能查看自己
|
||||
- 跨部門存取拒絕
|
||||
- [x] **6.3** 自動化 E2E 測試:Redis 快取驗證
|
||||
- 首次請求計算並快取
|
||||
- 二次請求命中快取
|
||||
- [x] **6.4** 更新 API 文件(OpenAPI 自動生成)
|
||||
|
||||
## Dependencies
|
||||
|
||||
```
|
||||
1.1, 1.2 → 可平行執行
|
||||
1.3 → 依賴 1.1
|
||||
2.1 → 依賴 1.1, 1.3
|
||||
2.2 → 依賴 2.1
|
||||
3.1-3.5 → 依賴 2.1, 2.2
|
||||
4.1 → 依賴 3.1
|
||||
5.1-5.5 → 可平行執行,依賴 Phase 3, 4
|
||||
6.1-6.4 → 依賴 Phase 3, 4, 5
|
||||
```
|
||||
|
||||
## Validation Criteria
|
||||
|
||||
每個任務完成後需確認:
|
||||
- 程式碼無語法錯誤
|
||||
- 相關測試通過
|
||||
- 符合現有程式碼風格
|
||||
Reference in New Issue
Block a user