專案

一般

配置概況

討論 #1462

是由 陳國瑋 於 28 天 前更新

## 背景 

 主需求:#1422。 

 原始 mockup 在「案場即時負載」中,將 A17 三個 Charge Group 的 contractCapacity(各 99 kW)直接相加為 297 kW,並據此顯示案場契約容量利用率與剩餘容量。 

 經現有程式碼與 A17 read-only 資料確認: 

 - contractCapacity 是 Charge Group 層級欄位,現有 SlotCalculator 使用它計算該群組可配置的 slots。 
 - 系統目前沒有獨立的 building/site 層級上游契約容量欄位。 
 - 系統也沒有描述各 Charge Group 是否共用上游容量、是否可加總的電力拓撲資料。 
 - 因此直接相加只得到群組排程上限合計,不能可靠宣稱為案場對台電的契約容量或剩餘容量。 

 本票只定義 Dashboard 的唯讀顯示與 read model,不修改任何充電、輪轉或容量配置邏輯。 

 ## LOAD-DEC-001:V1 不推算案場層級契約容量 

 **狀態:已確認(2026-09-03,Ken)** 

 ### 使用者需要知道的資訊 

 「案場即時負載」回答: 

 - 全案場目前合計使用多少充電功率。 
 - 即時功率資料是否仍為最新、可以信任。 
 - 功率隨時間的變化。 

 「群組容量與 Slots」則分別回答每一個 Charge Group 的: 

 - 目前功率。 
 - 該群組自己的 contract capacity/allocated power。 
 - occupied/reserved/available slots 與既有 queue 狀態。 

 兩者不得混成一個未經資料模型證明的案場契約容量。 

 ### V1 顯示規則 

 - 保留案場即時總功率 currentPowerKw 與負載圖表。 
 - 保留 latestMeterAt、freshness/source status,使使用者知道即時數字是否過期。 
 - 移除案場層級的「契約容量」、「契約容量利用率」與「剩餘容量/餘裕」。 
 - 不得以 SUM(group.contractCapacity) 產生 buildingContractCapacityKw。 
 - 各 Charge Group 區塊仍可顯示該群組自己的 currentPowerKw/contractCapacityKw 與 slots。 
 - 群組百分比若顯示,分母只能是該群組自己的 contractCapacity;label/位置必須讓使用者清楚它是群組值。 
 - currentPowerKw 未知時顯示「—」與對應資料狀態,不得以 0 冒充。 
 - 本決策適用所有 building;不得硬編碼 A17 或固定群組數量。 

 ### Frontend 要開發的內容 

 - 「案場即時負載」主數字只顯示 Backend 回傳的 currentPowerKw。 
 - 說明文字改為「各充電群組目前充電功率合計」及資料 freshness 語意。 
 - 移除 site-level contractCapacityKw、utilizationPercent、remainingCapacityKw 的 label、數值、進度條與前端計算。 
 - 不從 Charge Group 陣列自行加總契約容量。 
 - 群組列表仍逐組顯示該組容量/slots,並適應不同群組數量。 
 - Loading、null、Partial、Stale、Error 不得退回假資料或把未知顯示為 0。 

 ### Backend/API 要開發的內容 

 - Live Load 的 REST/WebSocket read model 回傳 currentPowerKw、latestMeterAt 與 freshness/source status。 
 - currentPowerKw 延用 #1424 DEC-004:只彙整 Backend 判定未 stale、enabled Connector 的最新有效 Power value。 
 - V1 的 building-level Live Load DTO/event 不回傳由 Charge Group 容量相加而來的 contractCapacityKw、utilizationPercent、remainingCapacityKw。 
 - Group Capacity read model 仍逐組回傳既有 group contract capacity、allocated power、reserved/occupied/available slots。 
 - 不新增案場契約容量設定欄位、資料表、管理 API 或電力拓撲模型。 
 - 不修改 SlotCalculator、rotation dispatch、queue lifecycle、OCPP/Modbus 指令或任何業務資料。 

 ### A17 驗證基線(不是跨案場規則) 

 查詢日期:2026-09-03。 

 - A17 有 B1、B2、B3 三個 Charge Group,各自 contractCapacity = 99 kW。 
 - 三組欄位數值相加為 297 kW,但只能視為群組排程上限的算術合計。 
 - A17 有 4 個 Charge Point、12 個 Connector,皆已指派群組。 
 - 現有資料無法證明三個群組使用三個完全獨立的上游契約,或可用 297 kW 計算案場餘裕。 

 A17 數字只用來揭露原 mockup 的假設與驗證通用設計,不作為其他案場的固定值。 

 ## LOAD-DEC-002:V1 保留即時/今日/7 日三種負載檢視 

 **狀態:已確認(2026-09-03,Ken)** 

 - 「案場即時負載」保留「即時」、「今日」與「7 日」三個檢視,不移除 7 日。 
 - 7 日負載呈現的是功率(kW)隨時間的變化,與「30 日充電用量」呈現的能量(kWh)/Sessions 不同,兩者不互相取代。 
 - 三個按鈕只切換 read-only 圖表資料,不改變 MeterValue 回報頻率、充電控制、queue、rotation 或 WebSocket 連線。 
 - 本決策先確認三種檢視必須存在;每種檢視的 X 軸時間範圍、bucket 粒度、tick label,以及 Y 軸採瞬時值/平均值/峰值的定義,列為下一項需求決策。 
 - 在軸線定義確認前,mockup 的折線與座標文字仍屬假資料,不可當成 API contract 或驗收基準。 

 ## LOAD-DEC-003:Live Load X/Y 軸與時間 Bucket 定義 

 **狀態:已確認(2026-09-03,Ken)** 

 ### 圖表與主數字的分工 

 - 區塊大數字標示「目前功率」,顯示 currentPowerKw,代表 Backend 在 generatedAt 時間點可確認的案場充電總功率。 
 - 折線圖標示「區間平均總充電功率」,不是契約容量、容量利用率,也不是 kWh。 
 - 所有日期與時間均使用 Backend 回傳的 building timezone;Frontend 不使用 browser timezone 自行切日。 

 ### X 軸正式定義 

 | 模式 | X 軸範圍 | Bucket | 最多資料點 | Tick/Tooltip | 
 |---|---|---:|---:|---| 
 | 即時 | generatedAt 往前 60 分鐘至 generatedAt | 1 分鐘 | 60 | 軸上顯示 HH:mm;tooltip 顯示完整起訖分鐘 | 
 | 今日 | 案場當日 00:00 至 generatedAt | 15 分鐘 | 96 | 軸上顯示 HH:mm;tooltip 顯示日期與 15 分鐘起訖 | 
 | 7 日 | 案場今天加前 6 個日曆日,第一天 00:00 至 generatedAt | 1 小時 | 168 | 軸上以 M/d 分日;tooltip 顯示 M/d HH:mm 與一小時起訖 | 

 - 「7 日」是包含今天的 7 個案場日曆日,不是最近 168 小時。 
 - rangeEndAt/最後 bucket 可以是尚未結束的進行中區間;API 必須以 bucketState = OPEN 標示,避免把時間尚未結束誤認為資料品質異常。 
 - FE 可依寬度減少可見 tick 數量以避免文字重疊,但不得改變資料點、時間範圍或 bucket;tooltip 必須顯示精確時間。 

 ### Y 軸正式定義 

 - 軸名固定為「充電功率(kW)」。 
 - 最小值固定從 0 開始。 
 - 最大值依目前選取 series 的有效值自動取整;不以 297 kW 或任何群組容量合計作為固定上限。 
 - 每個點的數值為該 bucket 的 time-weighted averagePowerKw,顯示到小數點後 2 位。 
 - Tooltip 顯示「區間平均功率 X.XX kW」;0 表示 Backend 確認該區間平均充電功率為 0。 
 - 無法可靠計算的 bucket 回 averagePowerKw = null,並以 dataStatus = PARTIAL/ERROR 表示;圖表顯示缺口及「資料無法取得」,不得用 0 補值。 
 - 本圖只畫一條平均功率線;V1 不同時增加 peak、min、契約容量線或預測線。 

 ### Backend 計算規則 

 - 原始來源為 Power.Active.Import;先依 unit 正規化,A17 目前為 W,API 對 FE 統一回 kW。 
 - 由各 Connector 依時間排序的 Power 讀值重建案場總功率時間線;每個讀值只能沿用至 Backend freshness policy 的有效期限。 
 - 同一時間點的 site power 是各 Connector 當時仍有效的最新讀值合計。 
 - 每個 bucket 對重建後的 site power 做 time-weighted average,不可直接對 e_meter_values rows 做算術平均。 
 - 原因是不同狀態的回報頻率不同;直接平均 raw rows 會讓回報較密集的 Connector 權重過高。 
 - 若正在充電的 Connector 在 freshness cutoff 後仍缺少有效 Power,該 bucket 依 LOAD-DEC-005 回 averagePowerKw = null、dataStatus = PARTIAL;V1 不另建 coverage 門檻。 
 - 歷史 series 由 REST 提供;WebSocket 仍依 #1424 DEC-004 更新 currentPowerKw,不傳送整段 7 日 series。 

 ### A17 Read-only 依據 

 查詢時間:2026-09-03。 

 - 最近 7 日有 14,550 筆 Power.Active.Import,涵蓋 11 個曾回報 Power 的 Connector,單位皆為 W。 
 - 最近 24 小時有 1,342 筆;其中充電中的 Sample.Clock 多數約每 30~34 秒回報,未充電的 Sample.Periodic 多數約每 900~905 秒回報。 
 - 因回報時間與頻率不一致,不能把 raw rows 直接加總或直接平均。 
 - 目前 MeterValueRepository 沒有 Dashboard 歷史負載 aggregate query;V1 需要新增唯讀 query/service,但不新增 MeterValue 寫入或改變回報頻率。 

 ### 外部參考 

 - EIA 說明 kW 是功率,kWh 是一段時間使用的能量;因此 Live Load Y 軸使用 kW,不與 30 日用量的 kWh 混用: 
   https://www.eia.gov/energyexplained/electricity/measuring-electricity.php 
 - 美國能源部企業電費說明以 15 分鐘整合需求示範區間化功率;本提案只借用「區間平均」的清楚語意,不把 Dashboard 數值當成正式需量計費: 
   https://www.energy.gov/sites/default/files/2024-04/understanding-your-utility-bill.pdf 

 ### 已確認項目 

 - [x] 即時 60 分鐘/1 分鐘 bucket。 
 - [x] 今日 00:00 至現在/15 分鐘 bucket。 
 - [x] 7 個案場日曆日/1 小時 bucket。 
 - [x] Y 軸使用 time-weighted averagePowerKw,不使用峰值或 raw sample。 
 - [x] 進行中 bucket、null 與 PARTIAL 的呈現。 

 ## LOAD-DEC-004:REST 提供完整 Series,WebSocket 只更新目前功率與 Bucket 邊界 

 **狀態:已確認(2026-09-03,Ken)** 

 ### 資料通道分工 

 - REST 提供 LOAD-DEC-003 定義的完整歷史 series,作為首次載入、切換 range、手動重新整理、重連或 sequence gap 後的 authoritative snapshot。 
 - LIVE_LOAD_UPDATED WebSocket event 提供 currentPowerKw,以及 LIVE/TODAY/SEVEN_DAYS 三種範圍目前進行中的 open bucket。 
 - WebSocket 不在每次 MeterValue 更新時重送 60/96/168 個歷史 points。 
 - 三種 open buckets 一併傳送,因此 Backend 不需要知道使用者目前選了哪個 tab,也不新增 client subscribe/unsubscribe protocol。 
 - currentPowerKw 與 open bucket 的 averagePowerKw 語意不同,數值可以不同;Frontend 不得互相覆寫。 

 ### WebSocket Bucket Payload 

 每個 open bucket 至少包含: 

 - rangeCode:LIVE/TODAY/SEVEN_DAYS。 
 - bucketStartAt、bucketEndAt。 
 - averagePowerKw。 
 - bucketState = OPEN。 
 - dataStatus、updatedAt/freshness metadata。 

 當時間跨越 bucket 邊界時: 

 - Backend 傳送剛結束 bucket 的 finalized replacement,bucketState = FINALIZED。 
 - 同一事件可同時帶下一個新的 open bucket。 
 - 1 分鐘、15 分鐘與 1 小時邊界可在同一時刻各自完成,event 必須以 rangeCode + bucketStartAt 唯一識別。 
 - 若 finalized bucket 因資料不完整而為 averagePowerKw = null、dataStatus = PARTIAL/ERROR,必須照實傳送,不得沿用上一個有效值或補零。 

 ### Frontend 合併規則 

 - 以 buildingId + rangeCode + bucketStartAt 找到 point。 
 - 相同 key 的 WebSocket bucket replacement 直接取代既有 point,不做累加或重新平均。 
 - 收到新的 open bucket 時,先套用 event 內的 finalized bucket,再加入新的 open bucket。 
 - 每個 range 只保留 LOAD-DEC-003 規定的視窗;LIVE 最多 60、TODAY 最多 96、SEVEN_DAYS 最多 168 points。 
 - FE 不使用 currentPowerKw 自行積分、不依 arrival time 計算 averagePowerKw,也不從其他 range 換算。 
 - sequence gap、schemaVersion 不相容、buildingId 不符或 WebSocket 重連時,不猜測缺失 buckets,重新取得 REST snapshot。 
 - 切換 range 只改變顯示的 cached series,不重建 Dashboard WebSocket。 

 ### 更新與降級 

 - 沿用 #1424 DEC-004 的 server-side 5 秒 coalescing;只有 MeterValue 或 Live Load/freshness 實際改變時才需要 event,不建立固定 5 秒空推送。 
 - WebSocket 中斷時保留最後資料並停止宣稱即時;依既有 60 秒 Overview REST polling fallback 取得完整 snapshot。 
 - WebSocket 恢復後,先取得 authoritative snapshot/確認 sequence,再繼續 bucket replacement。 
 - 本決策不修改 MeterValue 採集頻率,不增加設備指令,也不改變充電、queue 或 rotation 邏輯。 

 ### Acceptance Criteria 

 - [ ] REST 可單獨重建目前選取 range 的完整正確折線。 
 - [ ] 一般 LIVE_LOAD_UPDATED 不包含完整歷史 series,只包含 currentPowerKw 與三種 open buckets。 
 - [ ] bucket 跨界 event 可完成上一 bucket 並建立下一 open bucket,沒有重複或遺失。 
 - [ ] FE 只以 replacement 合併,不自行計算 time-weighted average。 
 - [ ] currentPowerKw 與 averagePowerKw 不會互相覆寫或被誤標。 
 - [ ] sequence gap/重連後以 REST 恢復,不用 client-side 猜測補值。 
 - [ ] 不同 building 的 series、bucket 與 cache 不會混用。 

 ## LOAD-DEC-005:時間狀態與資料品質分離,缺值不推估 

 **狀態:已確認(2026-09-03,Ken)** 

 ### 欄位與語意 

 每個 Live Load bucket 使用兩個互不替代的狀態: 

 - bucketState = OPEN:bucket 的時間區間尚未結束。 
 - bucketState = FINALIZED:bucket 的時間區間已結束,數值不再因正常時間推進而改變。 
 - dataStatus = COMPLETE:截至該 bucket 可計算範圍的資料完整。 
 - dataStatus = PARTIAL:存在無法確認的充電功率,不能產生完整案場平均值。 
 - dataStatus = ERROR:bucket 查詢或計算失敗。 

 OPEN 不等於 PARTIAL。進行中的 bucket 可以是 bucketState = OPEN + dataStatus = COMPLETE;已結束的 bucket 也可能是 bucketState = FINALIZED + dataStatus = PARTIAL。 

 ### Connector 功率判定 

 在重建 site power timeline 的每個時間片段: 

 - Connector 的 fresh runtime/transaction fact 明確顯示沒有充電時,該 Connector 對「充電功率」的貢獻為 0,不要求額外取得 Power MeterValue。 
 - Connector 正在充電時,必須取得仍在 Backend effective freshness cutoff 內、unit 可正規化且數值有效的 Power.Active.Import。 
 - 最新有效 Power 在 cutoff 內可以沿用至下一筆讀值;正常 30 秒/15 分鐘回報間隔不會因每秒沒有新 row 而自動變成缺值。 
 - 正在充電但 Power 缺少、無法解析、為負值或超過 cutoff,該時間片段為 unknown。 
 - Connector 的充電/交易狀態本身無法確認,或離線但仍可能存在 active transaction/offline charging 時,不得假定為 0,該時間片段為 unknown。 

 ### Bucket 計算規則 

 - bucket 內所有需要的時間片段均可確認時,dataStatus = COMPLETE,Backend 回傳 time-weighted averagePowerKw。 
 - 全部可確認且整段沒有充電時,averagePowerKw = 0、dataStatus = COMPLETE。 
 - 只要 bucket 內存在任何超過 freshness cutoff 後仍無法確認的充電功率,averagePowerKw = null、dataStatus = PARTIAL。 
 - bucket 查詢或計算失敗時,averagePowerKw = null、dataStatus = ERROR。 
 - OPEN bucket 只針對 bucketStartAt 至 event occurredAt/rangeEndAt 的已經過期間計算,不對尚未發生的時間做 0 或預測。 
 - finalized replacement 必須重新套用相同規則,不得因 bucket 已結束就把 PARTIAL 改成 COMPLETE。 

 ### V1 明確不採用 

 - 不對缺漏 Power 做線性插值、前後平均或模型預測。 
 - 不把超過 freshness cutoff 的舊讀值繼續沿用。 
 - 不回傳「已知 Connector 的部分合計」冒充完整案場功率。 
 - 不設定 80%/90% 等 coverage 通過門檻。 
 - 不新增 coverage percentage KPI。 
 - 不用 0 代表 unknown、PARTIAL 或 ERROR。 

 ### Frontend 顯示 

 - bucketState = OPEN:顯示「進行中」,但不使用警告色暗示資料異常。 
 - dataStatus = COMPLETE 且數值為 0:折線落在 0,tooltip 顯示 0.00 kW。 
 - dataStatus = PARTIAL:該點顯示缺口,tooltip/輔助文字顯示「部分充電功率無法取得」。 
 - dataStatus = ERROR:該點顯示缺口,tooltip/輔助文字顯示「負載資料計算失敗」。 
 - PARTIAL/ERROR 不得以前一點連線跨過缺口,也不得以 0 繪製。 
 - 狀態不能只靠顏色;文字及螢幕閱讀器必須可辨識。 
 - 正常主 mock 保留 COMPLETE 情境;最終 fixture 另增加 FINALIZED + PARTIAL 的 null gap,不把正常 mock 全部改成故障畫面。 

 ### Acceptance Criteria 

 - [ ] OPEN/FINALIZED 與 COMPLETE/PARTIAL/ERROR 可自由正確組合,不互相代替。 
 - [ ] fresh 且確定未充電的 Connector 可貢獻 0;正在充電卻沒有 fresh Power 時不可貢獻 0。 
 - [ ] 正常回報間隔內可沿用最後有效值;超過 cutoff 不得沿用。 
 - [ ] 任一 unknown 充電功率使 bucket 回 null + PARTIAL,不回部分合計或 coverage 百分比。 
 - [ ] 完整零負載回 0 + COMPLETE,與 null + PARTIAL/ERROR 有不同視覺。 
 - [ ] 圖表不對 null 缺口做補值或跨越連線。 
 - [ ] 本規則只影響 Dashboard read model,不寫回 Connector、Transaction 或 MeterValue。 

 ## LOAD-DEC-006:目前資料品質與歷史缺口的摘要範圍 

 **狀態:已確認(2026-09-03,Ken)** 

 - current Live Load freshness = STALE 或 dataStatus = PARTIAL/ERROR 時,#1463「需留意事項」建立一個即時負載資料不完整 category。 
 - currentPowerKw = 0 + COMPLETE 是可確認的正常零負載,不建立摘要。 
 - bucketState = OPEN 本身不是異常;OPEN + COMPLETE 不建立摘要。 
 - finalized 歷史 bucket 的 null + PARTIAL/ERROR 只在圖表顯示缺口與原因,不進入「需留意事項」。 
 - 不按歷史缺口數量建立多筆摘要或累加頁首數量。 
 - current source 恢復 FRESH/COMPLETE 後,WebSocket/REST replacement 移除對應 category。 
 - 詳細摘要顯示、fixture 與 deep link 規則見 #1463 ATT-DEC-002。 

 ## V1 明確不開發 

 - 案場層級契約容量設定/維護頁。 
 - Charge Group 上游配電拓撲或可加總旗標。 
 - 案場契約容量利用率、剩餘容量或其告警門檻。 
 - 因 Dashboard 顯示而調整群組容量、slots、queue 或 rotation。 
 - 根據 mockup 假資料執行任何營運判斷。 

 若未來確實需要案場契約容量 KPI,應另案定義 authoritative site capacity、共用/獨立上游拓撲、有效期間及權限;不能恢復以群組容量直接相加。 

 ## Acceptance Criteria 

 - [ ] 案場即時負載不顯示或回傳由 group contract capacity 加總產生的案場契約容量。 
 - [ ] UI 不顯示案場契約容量利用率或剩餘容量。 
 - [ ] currentPowerKw、latestMeterAt 與 freshness/source status 可獨立正常呈現。 
 - [ ] 各 Charge Group 自己的 contract capacity 與 slots 仍可閱讀,且不被標示為案場總契約容量。 
 - [ ] 1 個、2 個或多個 Charge Group 均不需要 FE 硬編碼。 
 - [ ] currentPowerKw 為 null/stale/error 時有明確狀態,不顯示假 0。 
 - [ ] Backend 與 FE 都不自行推導 site-level capacity、utilization 或 remaining capacity。 
 - [ ] 本需求只有唯讀查詢與呈現,不改變既有作業邏輯。 

 ## Mock/文件同步 

 - Working mock:documents/dashboard-mockup/a17-operations-dashboard.html。 
 - 本次確認後立即移除案場層級 297 kW、7.4% 與 275.16 kW 餘裕文案。 
 - 各群組自己的 99 kW 與群組內比例保留。 
 - Redmine attachment #1110 保留為初始設計基準;全部需求確認後再上傳整併後的最終 HTML/PDF。 

 ## 需求管理狀態 

 - Parent:#1422 
 - Related:#1424(REST/WebSocket 資料分工與事件契約) 
 - Requirement status:**LOAD-DEC-001~006 已確認;本區塊其餘互動/Empty 細節待逐項確認** status:**LOAD-DEC-001/002/003/004/005 已確認;本區塊仍有告警/互動細節待逐項確認** 
 - Final specification:全部 Dashboard 區塊確認後整併至 #1422 v1.0 文件 
 - Implementation tickets:需求鎖定後才建立 FE/BE/QA child issues 

返回