專案

一般

配置概況

討論 #1424

是由 陳國瑋 於 15 天 前更新

> **閱讀基準:** [主需求 #1422](https://redmine.sylksoft.com/issues/1422) 與 [HTML Mock v0.3/附件 #1117](https://redmine.sylksoft.com/attachments/1117)。以下沿用主票區塊編號,依畫面由上到下、由左到右說明。Mock 數字皆為假資料;功能適用所有案場,A17 不是固定規則。 

 ## 本票要完成什麼:讓整張 Dashboard 用簡單、一致的方式取得資料 

 本票負責**案場範圍、權限、REST/WebSocket 分工、資料狀態與前後端契約**。畫面/公式分別由 #1423、#1460、#1461、#1462、#1463、#1486 定義,不在本票建立第二套算法。 

 既有 GET /api/connector/dashboard 只有部分Connector統計;管理頁search與單支detail socket可重用,但不能直接當成整張Dashboard的資料來源。需要新的typed Overview與最小Dashboard socket。 

 **先記住三件事:** 

 1. 一次Overview取得一般資料,首載/每5分鐘/手動刷新。 
 2. 一條Dashboard WebSocket只更新「06 Connector即時狀態」與「04 案場即時負載」。 
 3. Socket連不到就提示並保留資料;不另建REST備援、自動重連或gap recovery。 

 ## 1. 先依畫面順序看資料通道 

 「REST-owned」表示由Overview負責更新;「WS-owned live state」表示socket接手後只由socket負責更新,兩者不能互相覆寫。(DEC-031/032) 

 | 主票位置/畫面 | 首次資料 | 後續更新 | Dashboard socket斷線時 | 
 |---|---|---|---| 
 | 01 案場名稱/一般資料時間 | Overview | 共用5分鐘/手動Overview | 照常更新一般資料與其品質 | 
 | 02 需留意事項 | Overview | 同上,REST-only | 照常;不改成其他備援模式 | 
 | 03 六張KPI | Overview | 同上,全部REST | 照常;不訂閱KPI WS事件 | 
 | 04 目前功率/open buckets | Overview bootstrap | LIVE_LOAD_UPDATED replacement | 保留最後值/時間、標停止更新;無資料— | 
 | 04 即時/今日/7日完整歷史 | 一次Overview給三組 | 同一Overview更新歷史;WS另負責open/跨界finalized | 一般歷史可刷新,但不得冒充即時recovery | 
 | 05 群組容量/Slots/Queue摘要 | Overview | 共用5分鐘/手動Overview | 照常,非WS | 
 | 06 五項摘要/Top-8預覽 | Overview bootstrap | CONNECTOR_LIVE_BLOCK_UPDATED整區replacement | 保留最後值/時間、標停止更新;無資料— | 
 | 07 30日用量、03-5今日已完成量 | 同一Overview/Usage read model | 共用5分鐘/手動Overview | 照常,非Usage WS | 
 | 08 結算流程、03-6上月電費 | Overview | 共用5分鐘/手動Overview | 照常,非Billing WS | 

 按需打開的介面另外處理,不混入首頁更新: 

 | 介面 | 資料通道 | 
 |---|---| 
 | 06「查看全部」Full List Drawer 06「查看全部」Drawer | 開啟、搜尋、「摘要分類」/Charge Group 篩選、換頁時 POST 開啟、搜尋、篩選、換頁時POST /api/connector/search,REST server pagination | 
 | 單支Connector detail | 既有detail API;打開時才使用既有單Connector socket,關閉釋放 | 

 「一條Dashboard socket」指首頁本體,不是禁止按需使用既有單支detail flow;也不能因此替每張preview card建立socket。 

 ## 2. 整體資料流程與scope 

 ```text 
 登入使用者 
     ↓ 
 既有 SS3A dashboard function 授權 
     ↓ 
 Backend 解析本 Branch 唯一 enabled Building 
     ├─ 不是恰好1筆 → 設定異常,不載入營運數字 
     └─ 恰好1筆 
           ├─ GET /api/dashboard/overview 
           │        ├─ 一般區塊+三組歷史 
           │        └─ 兩個live blocks初始畫面 
           └─ 短效、一次性Dashboard ticket 
                  ↓ 
              raw WebSocket 
                  ├─ DASHBOARD_LIVE_SNAPSHOT 
                  ├─ CONNECTOR_LIVE_BLOCK_UPDATED 
                  └─ LIVE_LOAD_UPDATED 
 ``` 

 ### 2.1 案場由Backend決定(DEC-029) 

 - 每個EMS Branch恰好一筆enabled Building;零筆/多筆回明確configuration error,FE顯示整頁「案場設定異常」並隱藏營運資料。 
 - 不任選第一筆、不跨Building加總、不顯示假0;不沿用任選預設案場的查詢方式。 
 - Overview/ticket request、WebSocket URL不傳FE指定buildingId;Drawer的Dashboard read mode也依相同resolver。 
 - Overview response、ticket binding、event envelope明確帶相同Backend-resolved buildingId;FE只套用相符資料,cache按buildingId隔離。 
 - Header/Sidebar顯示Backend案場識別,timezone供日期與時間呈現;不提供site selector。 
 - A17只用來驗證,不能出現在固定數字/公式/cutoff/query分支。 

 ### 2.2 權限沿用dashboard function(DEC-030) 

 | 條件 | 應有結果 | 
 |---|---| 
 | administrator/power_user/association/dealer,經既有dashboard function授權 | 相同唯讀Dashboard、Overview與ticket能力 | 
 | 只有adm_user/member/hq/engineer,未取得dashboard function | Overview/ticket為403;無有效ticket不能建立Dashboard socket | 
 | 多role使用者已有允許權限 | 依既有function機制授權,不因同時擁有其他role拒絕 | 

 不新增role/function、不在Java硬編碼role判斷;補Overview/ticket API mapping,依主票以冪等migration確認role-function mapping並驗證重啟後授權快取。 

 FE依function控制menu/route,未授權不先載入或短暫顯示資料;隱藏menu不能代替Backend授權。既有管理頁/Engineer Tools導覽沿用目的頁權限,Dashboard不附帶Start/Stop、Reset、Queue、帳務或工程指令能力。 

 ## 3. Overview REST契約:一個週期、一份一般資料 

 新增typed GET /api/dashboard/overview,回傳案場識別、generatedAt、各source的品質/時間及各section。(DEC-002保留部分/DEC-032) 

 | 時機 | Request | 
 |---|---| 
 | 頁面首次載入 | 一次Overview | 
 | Dashboard保持掛載期間每300秒 | 每輪一次Overview,不是每卡各一次 | 
 | 使用者手動刷新 | 一次Overview,不動socket | 
 | 卸載Dashboard | 釋放timer與socket資源 | 

 同一成功response的REST-owned sections共用generatedAt;需要各別品質判斷的來源另外回dataStatus、freshness、updatedAt及必要lastSuccessfulAt。 

 ### 3.1 接手前後不得覆寫錯的資料 

 ```text 
 首載REST bootstrap 
      ↓ 
 兩個live blocks可以先顯示初始資料 
      ↓ 
 收到DASHBOARD_LIVE_SNAPSHOT → socket接手 
      ↓ 
 之後Overview刷新: 
    一般區塊/歷史 → 可更新 
    Connector live/目前功率與open buckets → 不覆寫 
 ``` 

 只有整頁reload才重取ticket、重新嘗試socket。手動刷新不重建連線;socket中斷也不解除live ownership讓REST冒充備援。 

 ### 3.2 REST失敗不代表全頁歸零 

 單一source失敗只影響相依section,其他成功部分繼續顯示,Overview標PARTIAL或可診斷source status。初次無成功資料顯示Error/—;後續失敗可依各區契約保留**同building**成功cache,附更新失敗與lastSuccessfulAt,不假裝最新。 

 不能catch後回0、不能從preview/其他卡猜值,production不能退回fixture。需留意事項aggregate ERROR依#1463顯示不可取得,不能拿舊cards冒充本次結果;不是所有section都能不分狀態套同一fallback。 

 ## 4. WebSocket契約:三種訊息、區塊取代 

 V1沿用**raw WebSocket,不引入STOMP**。每個掛載的Dashboard本體只有一條案場socket。(DEC-008保留部分/009/031) 

 ### 4.1 Ticket與envelope 

 Ticket短效、一次性,綁定登入管理員與Backend-resolved Building,只有具dashboard function者可取得。 

 | Envelope欄位 | 用途 | 
 |---|---| 
 | schemaVersion | FE判斷是否支援此契約版本 | 
 | type | 下表三種訊息之一 | 
 | buildingId | 案場隔離,不接受不符scope資料 | 
 | occurredAt | 事件資料時間 | 
 | data | 對應type的typed區塊payload | 

 不傳raw DB row、JWT、ticket、currentIdTag或不必要個資;前端不對不支援schema或錯scope猜測套用。 

 ### 4.2 訊息內容 

 | type | 什麼時候/送什麼 | FE如何套用 | 
 |---|---|---| 
 | DASHBOARD_LIVE_SNAPSHOT | 建連後,兩個live blocks完整目前狀態 | 接手目前live state,不等於重送整個Dashboard/全部歷史 | 
 | CONNECTOR_LIVE_BLOCK_UPDATED | Connector五項summary+totals+Backend排序Top-8 | 整個區塊replacement,FE不自己算分類/排名 | 
 | LIVE_LOAD_UPDATED | currentPowerKw、LIVE/TODAY/SEVEN_DAYS三種open bucket;跨界可帶剛結束finalized | 目前值與bucket各自取代,按buildingId+rangeCode+bucketStartAt合併 | 

 不用細碎delta讓FE重算total、bucket、priority或平均功率。Backend可以內部debounce/coalescing保護資源,但不承諾固定5秒更新或P95 2秒SLO,也不為這個要求先建專用架構。 

 ### 4.3 連不到/中斷時就明示 

 | 情況 | 頁首與兩個live blocks | 
 |---|---| 
 | 首次連線失敗 | 明示無法連線;已有初始資料可保留並標未即時更新,沒有資料則—與「即時資料暫時無法取得」。 | 
 | 已連線後中斷 | 明示連線中斷,保留最後成功資料/時間並標「已停止更新」。 | 
 | 其他REST區塊 | 仍依共用Overview正常更新,不因socket中斷停止。 | 

 **不做:** REST polling fallback、定時更新模式切換、自訂capped exponential backoff、jitter、背景自動重連、visibility reconnect、sequence/gap recovery、專用Retry。使用者整頁reload才再嘗試。 

 Connection state與source freshness獨立:連著不代表資料FRESH,斷線也不改寫Backend已回傳的來源品質。 

 ### 4.4 部署前提(DEC-009) 

 V1為**單一active ems_branch instance**。未來多instance/HA的session fan-out與授權一致性另案設計,不在本功能預做。 

 ## 5. 品質欄位:0、未知、失敗、過期分開 

 | 狀態 | 契約與畫面 | 
 |---|---| 
 | COMPLETE且數值0 | 成功確認的0,不是缺值 | 
 | null | 未知,不轉0 | 
 | PARTIAL且Backend有安全值 | 可顯示,明示不完整;FE不自行加出部分合計 | 
 | PARTIAL無安全值 | null/—,例如不完整的案場功率、未全數完成的上月金額 | 
 | ERROR | 文字說明來源無法取得,不冒充正常 | 
 | STALE | 文字標過期與資料時間,不使用正常成功樣式 | 

 (DEC-006保留部分/015/028/033) 

 Backend依**資料來源**設定全系統stale cutoff:Connector、MeterValue、Billing可不同,但所有案場共用;FE不硬編碼秒數。V1不做per-building override、設定table/API/UI、設定權限或audit trail。 

 ## 6. Connector:相同snapshot支援首頁與完整清單 

 ### 6.1 Summary/Top-8契約 

 Backend從完整案場Connector集合分類、排序,再回固定五個bucket、total、abnormalTotal與最多8筆items。(DEC-034~038,詳細#1423) 

 | 不變條件 | 說明 | 
 |---|---| 
 | 五類順序 | NEEDS_ATTENTION、CHARGING、WAITING、AVAILABLE、OTHER | 
 | 分類互斥 | 每Connector恰好一類,count總和=total;NEEDS_ATTENTION.count=abnormalTotal | 
 | 原始事實 | OCPP connection、runtime、Queue分層保留,不被displayBucket取代 | 
 | Unknown/NOT_PLUGGED | Unknown不當AVAILABLE/OTHER;NOT_PLUGGED依#1423既定mapping歸OTHER | 
 | 非空/空 | 非空固定五項含0;空時FE顯示Empty,Backend仍五個0 | 
 | 摘要互動 | 純資訊不可點擊,不進Tab、不發request | 
 | FE預覽 | Desktop≥1240顯示8、Tablet640~1239顯示6、Mobile<640顯示4;不傳viewport/requestedLimit | 
 | Item metadata | displayBucket、isAbnormal、severity、priorityReason;FE不重判 | 
 | Footer | FE只依totals與visible slice算隱藏總數/異常數,不從Top-8推完整統計 | 

 Preview與Full List共用priority:OFFLINE+ACTIVE → FAULTED → OFFLINE/STALE/BLOCKED → SUSPENDED_EVSE/UNAVAILABLE → SUSPENDED_EV → CHARGING → PREPARING/Waiting → AVAILABLE。同層持續時間由久至新、Connector ID穩定排序,詳細多條件處理見#1423 UI-DEC-007。 

 排序是只讀呈現,不改Queue priority;OFFLINE+ACTIVE不產生「離線充電/待reconciliation」推論(主票及#1463/#1486較新決策)。 

 ### 6.2 完整清單仍用既有REST search 

 ```text 
 完整集合(Backend限制目前案場) 
        ↓ 全部條件filter 
        ↓ 共用DASHBOARD_PRIORITY sort 
        ↓ 固定size=20 paginate 
        ↓ Spring Page回應 
 ``` 

 沿用POST /api/connector/search、SearchConnectorDto的page/size與Spring Page metadata;補 keyword 所需的 Charge metadata;補Charge Point ID/車位、`displayBucket`、Charge Group、OpenAPI及contract ID/Charge Group等缺少條件、OpenAPI及contract tests。不得先分頁才filter/sort。 

 Dashboard Full List 的分類欄位命名為「摘要分類」,request 使用 `displayBucket`;未傳為全部,值限 NEEDS_ATTENTION/CHARGING/WAITING/AVAILABLE/OTHER。它直接重用Summary分類,不把 Connector runtime status 與 Queue status 混成另一組選項。既有管理頁的原始 `status` 條件維持相容。(DEC-043) 

 Drawer固定sortMode=DASHBOARD_PRIORITY,不提供使用者sidx/sord控制;/connector/list未用此mode時保留原欄位排序。(DEC-039~041) 

 Request 最小欄位為 page、固定 size=20、keyword、可選 displayBucket、可選 chargeGroupId、固定 sortMode;不得傳 buildingId。Response 每筆至少回 connectorId、chargePointId、location/floor/parkingSpaceNo、chargeGroup、displayBucket、isAbnormal/severity/priorityReason、OCPP connection/runtime/Queue 分層狀態、nullable currentPowerKw、updatedAt/freshness;外層沿用 Spring Page metadata。完整欄位表以 #1423 第6.3節為準。 

 只提供前後頁、頁次/總數;0~20不顯示換頁,多頁first/last按鈕正確。改搜尋/篩選回第1頁。Search/filter/page只存目前page memory,close/reopen保留,reload/navigation重設;不存URL、history、local/sessionStorage、Backend或WS。 

 不提供page-size、頁碼列、直接跳頁、Grid/Table、virtualization或完整inventory WS。 

 ## 7. 其他區塊:只對接既有決策,不另建算法 

 依主票畫面順序: 

 | 區塊 | 本票須遵守的關鍵規則 | 完整公式 | 
 |---|---|---| 
 | 02 Attention | 明確既有狀態、非零類別數、類內distinct、固定順序/路由;只看current負載品質,無推論或新告警 | #1463 ATT-DEC-001~008 | 
 | 03 KPI | enabled CP persisted連線;Connector可服務allowlist;canonical ACTIVE;ELIGIBLE distinct/未插槍互斥;today Usage重用;上月全COMPLETE金額。全部REST、不重複功率 | #1486 KPI-DEC-001~011 | 
 | 04 Live Load | 60分鐘/1分、今天/15分、含今天7日/1小時;時間加權kW;OPEN與品質分離,未知功率null+PARTIAL;無插值/跨缺口線 | #1462 LOAD-DEC-001~007 | 
 | 05 Group/Queue | 群組自身容量/Slots,不加總成案場契約容量;只顯示既有source、status、priority、reason、waitingSince/duration | #1462、#1423 UI-DEC-010/011 | 
 | 07 Usage | 固定30日、COMPLETED最終Wh/開始日、正電量次數、Backend平均;不碰raw MeterValue/費率分段,不顯示時間使用率 | #1461 USG-DEC-001~010 | 
 | 08 結算流程 | 最近已存在資料期間、四列純資訊、Footer既有帳單/請款頁;不等同上月電費的固定上一日曆月 | #1460 BIL-DEC-001~003 | 

 ## 8. 工程分工與交付 

 | 負責方 | 交付內容 | 
 |---|---| 
 | Backend/REST | current-building resolver、DashboardController/Service、typed Overview/各section DTO與source isolation、共用聚合。 | 
 | Backend/WS | ticket、handler、session registry、三種typed message,短效一次性授權與single-instance前提。 | 
 | Backend/Connector | 共用summary/Top-8 ranking;既有search補 displayBucket/keyword/Charge Group、最小response欄位、DASHBOARD_PRIORITY與20筆分頁,管理頁原始status/排序相容。 ranking;既有search條件、DASHBOARD_PRIORITY與20筆分頁,管理頁相容。 | 
 | Backend/Load | current/三range歷史query與時間加權、open/finalized replacement,品質依#1462。 | 
 | Backend/權限與文件 | dashboard API mapping、OpenAPI @Schema、method與DTO欄位註解、security/contract tests。 | 
 | FE | /dashboard頁面、單一Overview hook/300秒timer與socket hook、REST/WS state分離、unmount釋放、全部畫面與states、RWD/keyboard/來源時間。 | 
 | QA/效能 | 權限/scope/typed contract、socket failure無備援、source隔離、各子票數據與RWD、baseline與首站驗證。 | 

 不改OCPP、Queue/Rotation、Slot allocation、charging lifecycle、帳務計算或排程,不新增告警引擎、Dashboard inventory或業務mutation。 

 ## 9. 效能與驗收 

 ### 9.1 先量測,不先加架構(DEC-042) 

 A17或代表性等量資料執行aggregate query EXPLAIN,量測Overview endpoint P50/P95,記錄環境、主要table row count/分布、暖機、併發、sample size、量測起訖,使結果可重現。 

 避免N+1、無界查詢、重複聚合;只有量測證明影響正常使用,才提出query/普通index優化。**不把P95<1.5秒當V1 SLA,不先建snapshot table、專用cache、效能排程或新constraint**。跨案場SLA若日後需要另定資料量/負載模型。 

 ### 9.2 驗收清單 

 - [ ] 唯一Building正常、零/多筆設定錯誤;request不含FE案場選擇、response/ticket/event一致,cache/event不跨scope。 
 - [ ] 四允許role與多role正向、未授權403、無有效ticket拒絕WS;無credential/個資洩漏或mutation。 
 - [ ] 首載/每300秒/手動每輪一個Overview,無各section timer;同response generatedAt一致、source失敗隔離。 
 - [ ] 初次Error不假0/fixture;refresh只保留同案場資料與時間,依各區品質規則顯示。 
 - [ ] socket接手後,timer/手動Overview不覆寫live;斷線時也不形成fallback。 
 - [ ] Dashboard本體一條raw socket、三typed訊息只更新兩區,full list走REST、detail按需釋放。 
 - [ ] 首次失敗/後續中斷都有提示,最後資料/時間保留、無資料—;無backoff、jitter、自動/visibility重連、sequence/gap recovery、專用Retry。 
 - [ ] 其他REST區塊在socket中斷後照常;整頁reload才新建連線。 
 - [ ] 五bucket互斥、Top-8與8/6/4、priority穩定、summary不可點,0/1/2/8/9/30/100與各斷點正確。 
 - [ ] search完整集合依 keyword/displayBucket/Charge Group filter→sort→20分頁、30/100為2/5頁、0~20無換頁;六個「摘要分類」選項、最小response契約與page memory正確,管理頁原始status/sorting無回歸。 search完整集合filter→sort→20分頁、30/100為2/5頁、0~20無換頁;page memory正確,無額外控制,管理頁sorting無回歸。 
 - [ ] Load、Attention、KPI、Usage、Billing公式與來源狀態符合第7節子票;無額外range、時間使用率或預估費用。 
 - [ ] 320px以上無頁面水平捲軸;390/768/1024/1440可讀;文字狀態/focus/對比符合WCAG 2.2 AA基本要求。 
 - [ ] EXPLAIN與P50/P95條件完整,無固定秒數SLA。 
 - [ ] production無API/WS error→fixture fallback。 

 功能實作E2E impact為Add,依主票與各子票新增/更新UI、REST/WS failure、權限、RWD與大量資料case,按priority/trigger納release pack;本次不產生PASS/FAIL或宣稱已跑測試。 

 ## 10. 決策追溯:現行與已撤回分開 

 | 決策 | 現行有效內容/本文位置 | 
 |---|---| 
 | DEC-002 | 保留Overview bootstrap;第3節;fallback部分撤回 | 
 | DEC-006 | Backend管理source cutoff原則;第5節,設定範圍依033 | 
 | DEC-008 | raw WS;第4節,sequence/gap recovery撤回 | 
 | DEC-009~030 | 未被取代的部署/只讀/公式/顯示/權限保留;非兩區WS方案由031取代 | 
 | DEC-029/030 | 唯一enabled Building與dashboard function;第2節 | 
 | DEC-031/032 | 兩區WS、無備援;共用5分鐘Overview;第1、3~4節 | 
 | DEC-033 | 全系統per-source defaults、無案場override;第5節 | 
 | DEC-034~041 | Connector summary/preview/Drawer記憶體/固定分頁排序;第6節 | 
 | DEC-042 | 可重現效能baseline而非1.5秒SLA;第9節 | 
 | DEC-043 | Full List「摘要分類」使用 displayBucket;request/response 最小契約;第1、6、8~9節 | 

 | 已撤回提案 | 取代規則 | 
 |---|---| 
 | DEC-001 FE選building | DEC-029 Backend唯一案場 | 
 | DEC-003 capped exponential backoff/60秒fallback | DEC-031 無備援與自動重連 | 
 | DEC-004 固定5秒coalescing產品契約 | DEC-031 只容許內部資源保護,不承諾秒數 | 
 | DEC-005 Operational Event P95 2秒SLO | DEC-031 不列V1驗收承諾 | 
 | DEC-007 per-building cutoff override | DEC-033 全系統來源預設 | 
 | DEC-014 reconnect/gap recovery部分 | DEC-031撤回;REST歷史+WS區間ownership保留 | 
 | DEC-021/023~028要求Attention/KPI走WS部分 | DEC-031/032改共用REST;各公式/呈現仍保留 | 

 歷史討論仍在原票J5641及更早journals,本次不刪除。現行架構已整理至DEC-043,完成全Dashboard一致性review後才整併v1.0;不以本次文字整理視為已進入實作。 歷史討論仍在原票J5641及更早journals,本次不刪除。現行架構已整理至DEC-042,完成全Dashboard一致性review後才整併v1.0;不以本次文字整理視為已進入實作。 

 ## 需求管理與本次編輯 

 - 本票仍是已確認需求的追蹤票,不代表已完成開發或通過測試;指派、狀態、進度、附件與父子關係均維持原狀。 
 - 全 Dashboard 完成一致性 review 後,才依 #1422 DOC-DEC-001 發布同版號 PDF/Markdown/HTML 套件;現有 v0.3 Draft 不覆寫,本次不發布新版附件。 
 - 2026-09-14:僅重整 Description、說明與排版,將已確認決策併入對應畫面/工程工作;決策編號保留供追溯。E2E impact:No catalog change(沒有修改產品行為、公式或 API 契約);功能實作時仍須遵循主票與本票驗收。 
 - 2026-09-16:依 Ken 確認新增 DEC-043;Full List Drawer 使用 displayBucket 作為「摘要分類」,明列 request/response 最小契約並保留管理頁原始 status 相容性。本輪只更新需求,不變更 WebSocket、作業邏輯、Issue 狀態/進度或附件。

返回