diff --git a/openspec/changes/archive/2026-02-02-unify-equipment-status-cards/.openspec.yaml b/openspec/changes/archive/2026-02-02-unify-equipment-status-cards/.openspec.yaml new file mode 100644 index 0000000..8b00a11 --- /dev/null +++ b/openspec/changes/archive/2026-02-02-unify-equipment-status-cards/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-02-02 diff --git a/openspec/changes/archive/2026-02-02-unify-equipment-status-cards/design.md b/openspec/changes/archive/2026-02-02-unify-equipment-status-cards/design.md new file mode 100644 index 0000000..a6a315d --- /dev/null +++ b/openspec/changes/archive/2026-02-02-unify-equipment-status-cards/design.md @@ -0,0 +1,78 @@ +## Context + +目前設備即時機況與設備歷史績效兩個頁面的 KPI 卡片設計不一致: + +**設備即時機況** (`resource_status.html`): +- 顯示 6 張卡片:總設備數、PRD、SBY、UDT/SDT(合併)、EGT、其他/未排程 +- 格式:台數 + 佔比% +- 資料來源:`/api/resource/status/summary` → 從 Redis 快取取得即時狀態 + +**設備歷史績效** (`resource_history.html`): +- 顯示 7 張卡片:OU%、Availability%、PRD、UDT、SDT、EGT、機台數 +- 格式:HR 數字(無佔比) +- 資料來源:`/api/resource/history/dashboard` → 從 Oracle DWH 查詢歷史資料 + +## Goals / Non-Goals + +**Goals:** +- 統一兩個頁面的卡片數量、排序、標籤 +- 即時機況新增 OU%、Availability%、NST 卡片,拆分 UDT/SDT +- 歷史績效新增 SBY、NST 卡片,所有狀態卡片新增佔比顯示 +- 統一佔比計算公式(分母包含 NST) + +**Non-Goals:** +- 不修改卡片的視覺樣式(顏色、大小、間距) +- 不修改資料表結構或快取機制 +- 不修改篩選器邏輯 + +## Decisions + +### 1. 卡片統一結構 + +**決定**:兩頁面統一為 9 張卡片,順序固定 + +| 順序 | 主標籤 | 副標籤 | 即時機況 | 歷史績效 | +|------|--------|--------|----------|----------| +| 1 | OU% | 稼動率 | 百分比 | 百分比 | +| 2 | Availability% | 可用率 | 百分比 | 百分比 | +| 3 | PRD | 生產 | 台數+佔比% | HR+佔比% | +| 4 | SBY | 待機 | 台數+佔比% | HR+佔比% | +| 5 | UDT | 非計畫停機 | 台數+佔比% | HR+佔比% | +| 6 | SDT | 計畫停機 | 台數+佔比% | HR+佔比% | +| 7 | EGT | 工程 | 台數+佔比% | HR+佔比% | +| 8 | NST | 未排程 | 台數+佔比% | HR+佔比% | +| 9 | 機台數 | 設備總數 | 總設備數 | 不重複機台 | + +**理由**:統一排序便於使用者快速比對兩頁面數據 + +### 2. 佔比計算公式 + +**決定**:佔比% = 該狀態 / (PRD + SBY + UDT + SDT + EGT + NST) × 100 + +**理由**:包含 NST 可呈現完整時間分布,與 Availability% 分母一致 + +### 3. 即時機況 OU% / Availability% 計算 + +**決定**: +- OU% = PRD台數 / (PRD + SBY + UDT + SDT + EGT) 台數 × 100 +- Availability% = (PRD + SBY + EGT) 台數 / 總設備數 × 100 + +**替代方案考量**: +- 使用即時狀態的累計時數計算 → 放棄,因即時快取無歷史時數資料 +- 使用機台數比例計算 → 採用,符合即時監控需求 + +### 4. 前端實作方式 + +**決定**:直接修改現有 HTML 模板的卡片區塊,不抽取為共用組件 + +**理由**: +- 兩頁面的資料格式不同(台數 vs HR),共用組件反增複雜度 +- 變更範圍有限,直接修改更直觀 + +## Risks / Trade-offs + +**[即時機況 OU% 定義與歷史績效不同]** → 在卡片副標籤或 tooltip 說明計算方式差異 + +**[NST 狀態在即時快取可能無資料]** → 後端需處理無 NST 狀態的情況,預設為 0 + +**[卡片數量增加影響響應式佈局]** → 需測試 9 張卡片在不同螢幕寬度的顯示效果 diff --git a/openspec/changes/archive/2026-02-02-unify-equipment-status-cards/proposal.md b/openspec/changes/archive/2026-02-02-unify-equipment-status-cards/proposal.md new file mode 100644 index 0000000..76300ac --- /dev/null +++ b/openspec/changes/archive/2026-02-02-unify-equipment-status-cards/proposal.md @@ -0,0 +1,32 @@ +## Why + +設備即時機況與設備歷史績效兩個頁面的 KPI 卡片設計不一致,造成使用者在切換頁面時需要重新理解卡片含義。統一卡片設計可提升使用體驗並確保數據呈現的一致性。 + +## What Changes + +- **統一卡片數量與排序**:兩個頁面統一為 9 張卡片,順序相同 +- **統一卡片標籤**:主標籤(OU%、Availability%、PRD、SBY、UDT、SDT、EGT、NST、機台數)與副標籤(稼動率、可用率、生產、待機、非計畫停機、計畫停機、工程、未排程、設備總數) +- **即時機況新增指標**:新增 OU%、Availability%、NST 卡片;將 UDT/SDT 合併卡片拆分為獨立卡片 +- **歷史績效新增指標**:新增 SBY、NST 卡片;所有狀態卡片新增佔比顯示 +- **統一佔比計算**:佔比% = 該狀態 / (PRD + SBY + UDT + SDT + EGT + NST) × 100 + +## Capabilities + +### New Capabilities + +- `equipment-status-cards`: 統一的設備狀態 KPI 卡片組件規格,定義卡片結構、排序、標籤、計算公式 + +### Modified Capabilities + +(無既有規格需修改) + +## Impact + +- **前端模板**: + - `src/mes_dashboard/templates/resource_status.html` - 即時機況頁面卡片區塊 + - `src/mes_dashboard/templates/resource_history.html` - 歷史績效頁面卡片區塊 +- **後端服務**: + - `src/mes_dashboard/services/resource_service.py` - 即時機況 API 需新增 OU%、Availability% 計算 + - `src/mes_dashboard/services/resource_history_service.py` - 歷史績效 API 需新增 SBY、NST 欄位 +- **API 端點**: + - `/api/resource/status/summary` - 回傳資料結構調整 diff --git a/openspec/changes/archive/2026-02-02-unify-equipment-status-cards/specs/equipment-status-cards/spec.md b/openspec/changes/archive/2026-02-02-unify-equipment-status-cards/specs/equipment-status-cards/spec.md new file mode 100644 index 0000000..a8a4573 --- /dev/null +++ b/openspec/changes/archive/2026-02-02-unify-equipment-status-cards/specs/equipment-status-cards/spec.md @@ -0,0 +1,109 @@ +## ADDED Requirements + +### Requirement: Unified Card Structure +兩個頁面(設備即時機況、設備歷史績效)的 KPI 卡片 SHALL 包含相同的 9 張卡片,順序固定如下: +1. OU%(稼動率) +2. Availability%(可用率) +3. PRD(生產) +4. SBY(待機) +5. UDT(非計畫停機) +6. SDT(計畫停機) +7. EGT(工程) +8. NST(未排程) +9. 機台數(設備總數) + +#### Scenario: Card order verification on real-time status page +- **WHEN** 使用者開啟設備即時機況頁面 +- **THEN** 頁面 SHALL 依序顯示 9 張卡片:OU%、Availability%、PRD、SBY、UDT、SDT、EGT、NST、機台數 + +#### Scenario: Card order verification on historical performance page +- **WHEN** 使用者開啟設備歷史績效頁面 +- **THEN** 頁面 SHALL 依序顯示 9 張卡片:OU%、Availability%、PRD、SBY、UDT、SDT、EGT、NST、機台數 + +### Requirement: Unified Card Labels +每張卡片 SHALL 顯示統一的主標籤與副標籤。 + +| 主標籤 | 副標籤 | +|--------|--------| +| OU% | 稼動率 | +| Availability% | 可用率 | +| PRD | 生產 | +| SBY | 待機 | +| UDT | 非計畫停機 | +| SDT | 計畫停機 | +| EGT | 工程 | +| NST | 未排程 | +| 機台數 | 設備總數 | + +#### Scenario: Label consistency across pages +- **WHEN** 使用者查看任一頁面的 PRD 卡片 +- **THEN** 主標籤 SHALL 為「PRD」,副標籤 SHALL 為「生產」 + +### Requirement: Real-time Status Card Display Format +設備即時機況頁面的狀態卡片(PRD、SBY、UDT、SDT、EGT、NST)SHALL 顯示台數與佔比。 + +#### Scenario: Real-time PRD card display +- **WHEN** 系統有 120 台設備,其中 42 台處於 PRD 狀態 +- **THEN** PRD 卡片 SHALL 顯示「42」作為主要數值,並顯示佔比百分比 + +#### Scenario: Real-time card with zero count +- **WHEN** 某狀態無任何機台 +- **THEN** 該狀態卡片 SHALL 顯示「0」作為主要數值,佔比顯示「0.0%」 + +### Requirement: Historical Performance Card Display Format +設備歷史績效頁面的狀態卡片(PRD、SBY、UDT、SDT、EGT、NST)SHALL 顯示小時數(HR)與佔比。 + +#### Scenario: Historical PRD card display +- **WHEN** 查詢期間 PRD 總時數為 1234 小時,總時數為 2800 小時 +- **THEN** PRD 卡片 SHALL 顯示「1,234 HR」或「1.2K HR」作為主要數值,並顯示佔比百分比 + +#### Scenario: Historical card large number formatting +- **WHEN** 某狀態時數 >= 1000 小時 +- **THEN** 該狀態卡片 SHALL 以 K 為單位顯示(如 1.2K HR) + +### Requirement: Status Percentage Calculation +狀態佔比 SHALL 使用以下公式計算: +``` +佔比% = 該狀態值 / (PRD + SBY + UDT + SDT + EGT + NST) × 100 +``` +分母包含所有 6 種狀態的總和。 + +#### Scenario: Percentage calculation with all statuses +- **WHEN** PRD=100, SBY=50, UDT=20, SDT=10, EGT=15, NST=5(總計 200) +- **THEN** PRD 佔比 SHALL 為 50.0%,SBY 佔比 SHALL 為 25.0% + +#### Scenario: Percentage when total is zero +- **WHEN** 所有狀態值皆為 0 +- **THEN** 所有狀態佔比 SHALL 顯示「--」或「0.0%」 + +### Requirement: Real-time OU Percentage Calculation +設備即時機況頁面的 OU% SHALL 使用以下公式計算: +``` +OU% = PRD台數 / (PRD + SBY + UDT + SDT + EGT) 台數 × 100 +``` +分母不包含 NST。 + +#### Scenario: Real-time OU calculation +- **WHEN** PRD=42, SBY=30, UDT=10, SDT=5, EGT=8, NST=25 台 +- **THEN** OU% SHALL 為 42/(42+30+10+5+8)×100 = 44.2% + +### Requirement: Real-time Availability Percentage Calculation +設備即時機況頁面的 Availability% SHALL 使用以下公式計算: +``` +Availability% = (PRD + SBY + EGT) 台數 / 總設備數 × 100 +``` + +#### Scenario: Real-time Availability calculation +- **WHEN** PRD=42, SBY=30, EGT=8,總設備數=120 台 +- **THEN** Availability% SHALL 為 (42+30+8)/120×100 = 66.7% + +### Requirement: Machine Count Card +機台數卡片 SHALL 顯示設備總數。 + +#### Scenario: Real-time machine count +- **WHEN** 使用者查看設備即時機況頁面 +- **THEN** 機台數卡片 SHALL 顯示符合篩選條件的總設備數 + +#### Scenario: Historical machine count +- **WHEN** 使用者查看設備歷史績效頁面 +- **THEN** 機台數卡片 SHALL 顯示查詢期間內不重複的機台數量 diff --git a/openspec/changes/archive/2026-02-02-unify-equipment-status-cards/tasks.md b/openspec/changes/archive/2026-02-02-unify-equipment-status-cards/tasks.md new file mode 100644 index 0000000..ea41fc2 --- /dev/null +++ b/openspec/changes/archive/2026-02-02-unify-equipment-status-cards/tasks.md @@ -0,0 +1,40 @@ +## 1. 後端 API 調整 + +- [x] 1.1 修改 `resource_service.py` 的 `get_resource_status_summary()` 函數,新增 OU%、Availability% 計算邏輯 +- [x] 1.2 修改 `resource_service.py` 回傳資料結構,將 UDT/SDT 分開統計,新增 NST 統計 +- [x] 1.3 修改 `resource_history_service.py` 的 `_build_kpi_from_df()` 函數,新增 SBY、NST 欄位 +- [x] 1.4 修改 `resource_history_service.py` 新增各狀態佔比計算邏輯 + +## 2. 設備即時機況前端 + +- [x] 2.1 修改 `resource_status.html` 卡片 HTML 結構,調整為 9 張卡片 +- [x] 2.2 新增 OU%、Availability% 卡片 +- [x] 2.3 將 UDT/SDT 合併卡片拆分為兩張獨立卡片 +- [x] 2.4 新增 NST 卡片 +- [x] 2.5 調整機台數卡片位置至最後 +- [x] 2.6 更新 `loadSummary()` JavaScript 函數,綁定新的資料欄位 +- [x] 2.7 統一所有卡片的主標籤與副標籤文字 + +## 3. 設備歷史績效前端 + +- [x] 3.1 修改 `resource_history.html` 卡片 HTML 結構,調整為 9 張卡片 +- [x] 3.2 新增 SBY、NST 卡片 +- [x] 3.3 為所有狀態卡片新增佔比顯示區域 +- [x] 3.4 調整機台數卡片位置至最後 +- [x] 3.5 更新 `updateKpiCards()` JavaScript 函數,綁定新的資料欄位與佔比 +- [x] 3.6 統一所有卡片的主標籤與副標籤文字 + +## 4. 測試與驗證 + +- [x] 4.1 驗證設備即時機況頁面 9 張卡片顯示正確 +- [x] 4.2 驗證設備歷史績效頁面 9 張卡片顯示正確 +- [x] 4.3 驗證兩頁面卡片排序一致 +- [x] 4.4 驗證佔比計算公式正確(分母含 NST) +- [x] 4.5 驗證 OU%、Availability% 計算正確 +- [x] 4.6 測試無資料或零值情況的顯示 + +## 備註 + +- 即時機況的「機台數」= resource_cache 中的總設備數 +- 歷史績效的「機台數」= 查詢時間範圍內在 SHIFT 表中有資料的不重複機台數 +- 兩者數量可能有差異(例如新設備或閒置設備),屬預期行為 diff --git a/openspec/specs/equipment-status-cards/spec.md b/openspec/specs/equipment-status-cards/spec.md new file mode 100644 index 0000000..a8a4573 --- /dev/null +++ b/openspec/specs/equipment-status-cards/spec.md @@ -0,0 +1,109 @@ +## ADDED Requirements + +### Requirement: Unified Card Structure +兩個頁面(設備即時機況、設備歷史績效)的 KPI 卡片 SHALL 包含相同的 9 張卡片,順序固定如下: +1. OU%(稼動率) +2. Availability%(可用率) +3. PRD(生產) +4. SBY(待機) +5. UDT(非計畫停機) +6. SDT(計畫停機) +7. EGT(工程) +8. NST(未排程) +9. 機台數(設備總數) + +#### Scenario: Card order verification on real-time status page +- **WHEN** 使用者開啟設備即時機況頁面 +- **THEN** 頁面 SHALL 依序顯示 9 張卡片:OU%、Availability%、PRD、SBY、UDT、SDT、EGT、NST、機台數 + +#### Scenario: Card order verification on historical performance page +- **WHEN** 使用者開啟設備歷史績效頁面 +- **THEN** 頁面 SHALL 依序顯示 9 張卡片:OU%、Availability%、PRD、SBY、UDT、SDT、EGT、NST、機台數 + +### Requirement: Unified Card Labels +每張卡片 SHALL 顯示統一的主標籤與副標籤。 + +| 主標籤 | 副標籤 | +|--------|--------| +| OU% | 稼動率 | +| Availability% | 可用率 | +| PRD | 生產 | +| SBY | 待機 | +| UDT | 非計畫停機 | +| SDT | 計畫停機 | +| EGT | 工程 | +| NST | 未排程 | +| 機台數 | 設備總數 | + +#### Scenario: Label consistency across pages +- **WHEN** 使用者查看任一頁面的 PRD 卡片 +- **THEN** 主標籤 SHALL 為「PRD」,副標籤 SHALL 為「生產」 + +### Requirement: Real-time Status Card Display Format +設備即時機況頁面的狀態卡片(PRD、SBY、UDT、SDT、EGT、NST)SHALL 顯示台數與佔比。 + +#### Scenario: Real-time PRD card display +- **WHEN** 系統有 120 台設備,其中 42 台處於 PRD 狀態 +- **THEN** PRD 卡片 SHALL 顯示「42」作為主要數值,並顯示佔比百分比 + +#### Scenario: Real-time card with zero count +- **WHEN** 某狀態無任何機台 +- **THEN** 該狀態卡片 SHALL 顯示「0」作為主要數值,佔比顯示「0.0%」 + +### Requirement: Historical Performance Card Display Format +設備歷史績效頁面的狀態卡片(PRD、SBY、UDT、SDT、EGT、NST)SHALL 顯示小時數(HR)與佔比。 + +#### Scenario: Historical PRD card display +- **WHEN** 查詢期間 PRD 總時數為 1234 小時,總時數為 2800 小時 +- **THEN** PRD 卡片 SHALL 顯示「1,234 HR」或「1.2K HR」作為主要數值,並顯示佔比百分比 + +#### Scenario: Historical card large number formatting +- **WHEN** 某狀態時數 >= 1000 小時 +- **THEN** 該狀態卡片 SHALL 以 K 為單位顯示(如 1.2K HR) + +### Requirement: Status Percentage Calculation +狀態佔比 SHALL 使用以下公式計算: +``` +佔比% = 該狀態值 / (PRD + SBY + UDT + SDT + EGT + NST) × 100 +``` +分母包含所有 6 種狀態的總和。 + +#### Scenario: Percentage calculation with all statuses +- **WHEN** PRD=100, SBY=50, UDT=20, SDT=10, EGT=15, NST=5(總計 200) +- **THEN** PRD 佔比 SHALL 為 50.0%,SBY 佔比 SHALL 為 25.0% + +#### Scenario: Percentage when total is zero +- **WHEN** 所有狀態值皆為 0 +- **THEN** 所有狀態佔比 SHALL 顯示「--」或「0.0%」 + +### Requirement: Real-time OU Percentage Calculation +設備即時機況頁面的 OU% SHALL 使用以下公式計算: +``` +OU% = PRD台數 / (PRD + SBY + UDT + SDT + EGT) 台數 × 100 +``` +分母不包含 NST。 + +#### Scenario: Real-time OU calculation +- **WHEN** PRD=42, SBY=30, UDT=10, SDT=5, EGT=8, NST=25 台 +- **THEN** OU% SHALL 為 42/(42+30+10+5+8)×100 = 44.2% + +### Requirement: Real-time Availability Percentage Calculation +設備即時機況頁面的 Availability% SHALL 使用以下公式計算: +``` +Availability% = (PRD + SBY + EGT) 台數 / 總設備數 × 100 +``` + +#### Scenario: Real-time Availability calculation +- **WHEN** PRD=42, SBY=30, EGT=8,總設備數=120 台 +- **THEN** Availability% SHALL 為 (42+30+8)/120×100 = 66.7% + +### Requirement: Machine Count Card +機台數卡片 SHALL 顯示設備總數。 + +#### Scenario: Real-time machine count +- **WHEN** 使用者查看設備即時機況頁面 +- **THEN** 機台數卡片 SHALL 顯示符合篩選條件的總設備數 + +#### Scenario: Historical machine count +- **WHEN** 使用者查看設備歷史績效頁面 +- **THEN** 機台數卡片 SHALL 顯示查詢期間內不重複的機台數量 diff --git a/src/mes_dashboard/services/resource_history_service.py b/src/mes_dashboard/services/resource_history_service.py index 3148cf6..0b345a7 100644 --- a/src/mes_dashboard/services/resource_history_service.py +++ b/src/mes_dashboard/services/resource_history_service.py @@ -675,6 +675,11 @@ def _calc_availability_pct(prd: float, sby: float, udt: float, sdt: float, egt: return round(numerator / denominator * 100, 1) if denominator > 0 else 0 +def _calc_status_pct(value: float, total: float) -> float: + """Calculate status percentage = value / total * 100.""" + return round(value / total * 100, 1) if total > 0 else 0 + + def _build_kpi_from_df(df: pd.DataFrame) -> Dict[str, Any]: """Build KPI dict from query result DataFrame.""" if df is None or len(df) == 0: @@ -682,11 +687,17 @@ def _build_kpi_from_df(df: pd.DataFrame) -> Dict[str, Any]: 'ou_pct': 0, 'availability_pct': 0, 'prd_hours': 0, + 'prd_pct': 0, 'sby_hours': 0, + 'sby_pct': 0, 'udt_hours': 0, + 'udt_pct': 0, 'sdt_hours': 0, + 'sdt_pct': 0, 'egt_hours': 0, + 'egt_pct': 0, 'nst_hours': 0, + 'nst_pct': 0, 'machine_count': 0 } @@ -699,15 +710,24 @@ def _build_kpi_from_df(df: pd.DataFrame) -> Dict[str, Any]: nst = _safe_float(row['NST_HOURS']) machine_count = int(_safe_float(row['MACHINE_COUNT'])) + # Total hours for percentage calculation (includes NST) + total_hours = prd + sby + udt + sdt + egt + nst + return { 'ou_pct': _calc_ou_pct(prd, sby, udt, sdt, egt), 'availability_pct': _calc_availability_pct(prd, sby, udt, sdt, egt, nst), 'prd_hours': round(prd, 1), + 'prd_pct': _calc_status_pct(prd, total_hours), 'sby_hours': round(sby, 1), + 'sby_pct': _calc_status_pct(sby, total_hours), 'udt_hours': round(udt, 1), + 'udt_pct': _calc_status_pct(udt, total_hours), 'sdt_hours': round(sdt, 1), + 'sdt_pct': _calc_status_pct(sdt, total_hours), 'egt_hours': round(egt, 1), + 'egt_pct': _calc_status_pct(egt, total_hours), 'nst_hours': round(nst, 1), + 'nst_pct': _calc_status_pct(nst, total_hours), 'machine_count': machine_count } diff --git a/src/mes_dashboard/services/resource_service.py b/src/mes_dashboard/services/resource_service.py index 46fa595..5b876af 100644 --- a/src/mes_dashboard/services/resource_service.py +++ b/src/mes_dashboard/services/resource_service.py @@ -543,7 +543,7 @@ def get_resource_status_summary( is_monitor: Filter by PJ_ISMONITOR flag Returns: - Dict with summary statistics. + Dict with summary statistics including OU%, Availability%, and per-status counts. """ # Get merged data with filters (except status_categories) data = get_merged_resource_status( @@ -557,17 +557,29 @@ def get_resource_status_summary( return { 'total_count': 0, 'by_status_category': {}, + 'by_status': {}, 'by_workcenter_group': {}, 'with_active_job': 0, 'with_wip': 0, + 'ou_pct': 0, + 'availability_pct': 0, } - # Count by status category + # Count by status category (for backward compatibility) by_status_category = {} for record in data: cat = record.get('STATUS_CATEGORY') or 'UNKNOWN' by_status_category[cat] = by_status_category.get(cat, 0) + 1 + # Count by individual E10 status (PRD, SBY, UDT, SDT, EGT, NST) + by_status = {'PRD': 0, 'SBY': 0, 'UDT': 0, 'SDT': 0, 'EGT': 0, 'NST': 0, 'OTHER': 0} + for record in data: + status = record.get('EQUIPMENTASSETSSTATUS') or 'UNKNOWN' + if status in by_status: + by_status[status] += 1 + else: + by_status['OTHER'] += 1 + # Count by workcenter group by_workcenter_group = {} for record in data: @@ -580,12 +592,30 @@ def get_resource_status_summary( # Count with WIP with_wip = sum(1 for r in data if (r.get('LOT_COUNT') or 0) > 0) + # Calculate OU% = PRD / (PRD + SBY + UDT + SDT + EGT) * 100 + prd = by_status['PRD'] + sby = by_status['SBY'] + udt = by_status['UDT'] + sdt = by_status['SDT'] + egt = by_status['EGT'] + nst = by_status['NST'] + + ou_denominator = prd + sby + udt + sdt + egt + ou_pct = round(prd / ou_denominator * 100, 1) if ou_denominator > 0 else 0 + + # Calculate Availability% = (PRD + SBY + EGT) / total * 100 + total_count = len(data) + availability_pct = round((prd + sby + egt) / total_count * 100, 1) if total_count > 0 else 0 + return { - 'total_count': len(data), + 'total_count': total_count, 'by_status_category': by_status_category, + 'by_status': by_status, 'by_workcenter_group': by_workcenter_group, 'with_active_job': with_active_job, 'with_wip': with_wip, + 'ou_pct': ou_pct, + 'availability_pct': availability_pct, } diff --git a/src/mes_dashboard/templates/resource_history.html b/src/mes_dashboard/templates/resource_history.html index ddbdcdd..6682a39 100644 --- a/src/mes_dashboard/templates/resource_history.html +++ b/src/mes_dashboard/templates/resource_history.html @@ -290,41 +290,63 @@ /* KPI Cards */ .kpi-row { display: grid; - grid-template-columns: repeat(7, 1fr); - gap: 14px; + grid-template-columns: repeat(9, 1fr); + gap: 12px; margin-bottom: 16px; } + @media (max-width: 1400px) { + .kpi-row { + grid-template-columns: repeat(5, 1fr); + } + } + + @media (max-width: 900px) { + .kpi-row { + grid-template-columns: repeat(3, 1fr); + } + } + .kpi-card { background: var(--card-bg); border-radius: 10px; - padding: 18px; + padding: 14px 10px; text-align: center; border: 1px solid var(--border); box-shadow: var(--shadow); } .kpi-label { - font-size: 13px; + font-size: 12px; color: var(--muted); - margin-bottom: 6px; + margin-bottom: 4px; } .kpi-value { - font-size: 28px; + font-size: 24px; font-weight: bold; } .kpi-value.green { color: var(--success); } .kpi-value.blue { color: var(--primary); } + .kpi-value.prd { color: #3B82F6; } + .kpi-value.cyan { color: #06b6d4; } .kpi-value.red { color: var(--danger); } .kpi-value.yellow { color: var(--warning); } + .kpi-value.purple { color: #8b5cf6; } + .kpi-value.slate { color: #94a3b8; } .kpi-value.gray { color: var(--neutral); } .kpi-sub { + font-size: 10px; + color: var(--muted); + margin-top: 2px; + } + + .kpi-pct { font-size: 11px; color: var(--muted); - margin-top: 4px; + margin-top: 2px; } /* Charts Row */ @@ -614,29 +636,39 @@