# Branch Admin Engineer Command Center Implementation Plan

## 1. 目標

在 Branch Admin 增加僅供 `Engineer` Role 使用的「工程師工具」，讓工程師可以：

1. 對指定 CloudLink Connector 執行受控 Modbus 讀取與控制。
2. 由 Branch 直接向已連線 Charge Point 發送完整的 OCPP 1.6 CSMS Command。
3. 查詢每次操作的 request、response、錯誤、耗時及後續狀態時間軸。
4. 以 Fortune（華城）充電樁測試為主要使用情境，同時保留其他廠牌的能力標示。

本文件是實作規劃，不包含本次程式碼或資料庫變更。

## 2. 已確認的產品決策

| 項目 | 決策 |
| --- | --- |
| 使用者 | 新增 `engineer` Role，顯示名稱為 `Engineer` |
| 初始成員 | 將既有帳號 `ADMIN`、`power_user` 額外加入 `engineer` Role |
| Role 繼承 | 不自動授權所有 `administrator` 或 `power_user` Role 成員 |
| 導覽位置 | Branch Admin 側邊欄新增獨立「工程師工具」頁面 |
| 頁面結構 | Charge Point/Connector 選擇器，加上 `Modbus`、`OCPP`、`操作紀錄` 三個分頁 |
| Connector 入口 | Connector 頁面可以 deep-link 到工程師工具並預選 Connector |
| 操作原因 | 不要求填寫 |
| 二次確認 | 不做全面二次確認；僅 Reset、供電開關、修改 Meter ID 顯示一次簡單確認 |
| OCPP 範圍 | 提供全部 19 個 OCPP 1.6 CSMS -> Charge Point Command |
| 未驗證能力 | 仍允許發送，但顯示「未驗證」警告 |
| 已知不支援 | UI 禁用並顯示原因；後端同樣拒絕，不能只依賴前端 |
| OCPP 輸入 | 結構化表單及進階 JSON 模式；兩者都必須通過同一份 OCPP schema 驗證 |
| API 執行方式 | 保留既有 8 個同步 API；新增 Engineer 專用 Command API，在 HTTP request 內完成 WebSocket dispatch，response tracking 採非同步 |
| 長時間操作 | `GetDiagnostics`、`UpdateFirmware` 以操作時間軸追蹤後續 StatusNotification |
| 檔案 URL | V1 由 Engineer 手動輸入 HTTP/HTTPS URL，Branch 不提供檔案上傳或儲存服務 |
| 稽核保留 | `e_engineer_command_log` 保留 365 天 |
| Modbus 控制 | 供電開關刻意呼叫 `controlTestMode()`，不改為 `controlRelay()` |
| 原始 Modbus | 不提供任意 function code、register address 或 raw payload |

## 3. 現況與缺口

### 3.1 Branch OCPP

現有 `OcppController` 提供 8 個 API：

- `RemoteStartTransaction`
- `RemoteStopTransaction`
- `Reset`
- `TriggerMessage`
- `ChangeAvailability`
- `DataTransfer`
- `GetConfiguration`
- `ReserveNow`

主要缺口：

- OCPP 1.6 的另外 11 個 CSMS Command 尚無 Branch API。
- 回應處理採逐 Command 註冊 handler；`ReserveNow` 未註冊 response handler。
- 既有 API 會在 HTTP request 中等待最多 15 秒，不適合 Engineer 操作追蹤及長時間指令。
- 現有 `e_ocpp_message_log` 是傳輸層訊息紀錄，沒有操作者、功能來源、完整操作狀態及跨服務 correlation。
- `FirmwareStatusNotification`、`DiagnosticsStatusNotification` 尚無 Branch incoming handler。

既有 `/api/fev/connector/availability` 及 8 個 `/api/ocpp` API必須維持相容，內部可逐步共用新的 dispatcher，但不得改變現有 response contract。

### 3.2 Charge Point Modbus

CloudLink Charge Point 已有受控的 Modbus 方法及部分 OCPP `DataTransfer`：

- `MeterRead` 目前只回傳 current、voltage、active power、total energy。
- `RelayControl` 已透過 `controlTestMode()` 實作供電控制。
- Meter Controller 另有 apparent power、power factor、balance、relay state、Meter ID 等能力，但不應由 Browser 直接呼叫 Charge Point REST API。

缺口是建立完整、受控且可稽核的 Branch -> OCPP DataTransfer -> Charge Point Modbus 路徑。

### 3.3 Branch Admin 與權限

- 前端選單由 `/api/menu/list` 及 SS3A function/role mapping 動態產生。
- 前端使用 OpenAPI 產生的 TypeScript types、React Hook Form、Zod、TanStack Query 及既有 UI 元件。
- 必須新增 SS3A role、function、API mapping、i18n 及指定帳號 role mapping，不能只在 React 寫死選單或角色判斷。

## 4. OCPP 1.6 Command Catalog

Engineer OCPP API 提供以下全部 19 個 Command：

| 分類 | Command | 主要欄位 |
| --- | --- | --- |
| Remote operation | `RemoteStartTransaction` | idTag、connectorId、chargingProfile |
| Remote operation | `RemoteStopTransaction` | transactionId |
| Device control | `Reset` | type |
| Device control | `ChangeAvailability` | connectorId、type |
| Device control | `UnlockConnector` | connectorId |
| Request trigger | `TriggerMessage` | requestedMessage、connectorId |
| Vendor extension | `DataTransfer` | vendorId、messageId、data |
| Configuration | `GetConfiguration` | key |
| Configuration | `ChangeConfiguration` | key、value |
| Configuration | `ClearCache` | 無 payload |
| Reservation | `ReserveNow` | connectorId、expiryDate、idTag、reservationId、parentIdTag |
| Reservation | `CancelReservation` | reservationId |
| Smart charging | `SetChargingProfile` | connectorId、csChargingProfiles |
| Smart charging | `ClearChargingProfile` | id、connectorId、chargingProfilePurpose、stackLevel |
| Smart charging | `GetCompositeSchedule` | connectorId、duration、chargingRateUnit |
| Diagnostics | `GetDiagnostics` | location、startTime、stopTime、retries、retryInterval |
| Firmware | `UpdateFirmware` | location、retrieveDate、retries、retryInterval |
| Local auth list | `GetLocalListVersion` | 無 payload |
| Local auth list | `SendLocalList` | listVersion、localAuthorizationList、updateType |

Schema 的基準來源為 `documents/OCPP_1.6_documentation/schemas/json/`。實作時將所需 schema 複製到 Branch runtime resources，並由後端 validator 驗證；不能只靠前端 Zod。

## 5. Modbus Command Catalog

V1 只允許下列 command key：

| Command key | 類型 | 行為 |
| --- | --- | --- |
| `CONNECTION_TEST` | Read | 驗證 Gateway/Slave 是否可通訊 |
| `READ_SUPPLY_STATE` | Read | 讀取目前供電狀態 |
| `READ_VOLTAGE` | Read | 讀取電壓 |
| `READ_CURRENT` | Read | 讀取電流 |
| `READ_ACTIVE_POWER` | Read | 讀取有效功率 |
| `READ_APPARENT_POWER` | Read | 讀取視在功率 |
| `READ_POWER_FACTOR` | Read | 讀取功率因數 |
| `READ_TOTAL_ENERGY` | Read | 讀取累積電量 |
| `READ_BALANCE` | Read | 讀取餘額 |
| `READ_METER_ID` | Read | 讀取 Meter ID |
| `SET_SUPPLY_STATE` | Write | `on/off` 轉成 `controlTestMode(..., 1/0)` |
| `SET_METER_ID` | Write | 設定 1 到 255 的 Meter ID |

明確排除：

- 清除歷史資料
- 任意 Modbus function code
- 任意 register address/value
- 瀏覽器直接連線 Charge Point REST API
- 對 Fortune、Tesla 或其他非 CloudLink Connector 發送 Modbus command

## 6. 目標架構

```mermaid
sequenceDiagram
    actor E as Engineer
    participant FE as Branch Admin
    participant API as Branch Engineer API
    participant DB as Command Log DB
    participant WS as Branch OCPP WebSocket
    participant CP as Charge Point

    E->>FE: 選擇裝置、Command、填寫 payload
    FE->>API: POST Engineer Command
    API->>API: Role、target、capability、schema 驗證
    API->>DB: 建立 command log + CREATED event
    API->>WS: OCPP CALL(uniqueId, action, payload)
    WS->>CP: 發送指令
    API->>DB: 更新為 SENT
    API-->>FE: 202 + commandLogId + SENT
    CP-->>WS: CALLRESULT 或 CALLERROR
    WS->>DB: 依 uniqueId 更新狀態及 event
    opt Diagnostics/Firmware
        CP->>WS: StatusNotification
        WS->>DB: 附加進度 event
    end
    FE->>API: Poll command detail
    API-->>FE: 狀態、payload、時間軸
```

核心原則：

- HTTP request 內必須完成 command log 建立與 OCPP WebSocket dispatch；OCPP CALL 成功送出後，其 response、後續 status notification 的關聯及稽核不得依賴原始 HTTP connection。Client 離開頁面不會取消已送出的 Command。
- OCPP 傳輸仍由既有 WebSocket/session infrastructure 負責。
- 新流程以 `ocppUniqueId` 查找 command log，使用通用 response observer 更新狀態。
- 原有 response handlers 及同步 request managers 仍可同時接收 response，確保舊 API 相容。
- 所有 Branch 與 CloudLink Charge Point 相關 app log 都加入 `correlationId`；UI 不保存 PID/thread 的整份 raw log。

## 7. Branch Backend 實作

### 7.1 Command catalog 與 validation

建立集中式 `EngineerCommandCatalog`：

- 定義 protocol、command name、target level、risk level、payload schema、terminal result 規則。
- OCPP action 只能從 catalog 選取，不能讓 client 傳任意 action。
- Modbus operation 只能從第 5 節 allowlist 選取。
- OCPP JSON schema validation 同時服務結構化表單及進階 JSON request。
- 驗證 OCPP connectorId 必須屬於所選 Charge Point。
- Charge Point 未連線時在 dispatch 前失敗，狀態記為 `SEND_FAILED/OFFLINE`。
- Operator 一律取自 JWT identity，不接受 request body 覆寫。

### 7.2 Engineer Command API（非同步追蹤）

建議 API：

| Method | Path | 用途 |
| --- | --- | --- |
| GET | `/api/engineer/commands/catalog` | 回傳 command、schema、風險及 capability |
| POST | `/api/engineer/ocpp/commands` | 建立並發送 OCPP command，回傳 HTTP 202 |
| POST | `/api/engineer/modbus/commands` | 建立並發送受控 Modbus command，回傳 HTTP 202 |
| POST | `/api/engineer/commands/search` | 分頁查詢操作紀錄 |
| GET | `/api/engineer/commands/{id}` | 查詢完整 operation 與 event timeline |

OCPP create request 範例：

```json
{
  "chargePointId": "CP001",
  "connectorId": "CP001-001",
  "command": "ChangeConfiguration",
  "payload": {
    "key": "HeartbeatInterval",
    "value": "60"
  }
}
```

Create response 最少包含：

```json
{
  "commandLogId": "uuid",
  "correlationId": "uuid",
  "ocppUniqueId": "uuid",
  "state": "SENT",
  "capability": "UNVERIFIED",
  "createdAt": "2026-07-14T10:00:00+08:00"
}
```

API response 需清楚區分：

- HTTP/API validation error：request 根本沒有建立操作。
- `SEND_FAILED`：操作已建立，但 Charge Point offline 或 WebSocket send 失敗。
- `CALL_ERROR`：收到 OCPP CALLERROR，保存 errorCode、description、details。
- `REJECTED`：收到合法 CALLRESULT，但 protocol status 為 Rejected/NotSupported 等。
- `TIMEOUT`：在 vendor-aware response timeout 內未收到 CALLRESULT/CALLERROR。

HTTP `202` 只表示 Branch 已完成 WebSocket dispatch 並開始追蹤結果。Command 本身不需要排入背景佇列；只有 CALLRESULT、CALLERROR 與後續 status notification 由 WebSocket incoming flow 非同步更新 operation。

### 7.3 Request 內 dispatch 與非同步 response observer

新增獨立 `EngineerOcppCommandDispatcher`：

1. 建立 log 後產生 `correlationId` 與 `ocppUniqueId`。
2. 在發送前寫入 MDC，並建立 `DISPATCHING` event。
3. 呼叫既有 `OcppCommandSender` 發送標準 OCPP CALL。
4. 發送成功後在同一 HTTP request 內更新 `SENT` 並回傳 HTTP `202`；不等待 Charge Point 的 CALLRESULT/CALLERROR。
5. 若 WebSocket dispatch 當下失敗，更新為 `SEND_FAILED`，error response 必須包含 commandLogId，方便查詢稽核紀錄。
6. `OcppMessageProcessor` 稍後收到 CALLRESULT/CALLERROR 時，先交給通用 observer 依 uniqueId 更新 Engineer log，再保留既有 response handler 流程。

V1 不要求 durable job queue，也不承諾 Branch process 重啟後重新發送尚未 dispatch 的 Command。服務重啟恢復若有需要，另立需求定義。

這個 observer 不應依賴 19 個獨立 handler。Action 由 command log 或 `e_ocpp_message_log` 的原始 request 查得。

### 7.4 狀態機

建議主要狀態：

```text
CREATED -> VALIDATED -> DISPATCHING -> SENT
SENT -> COMPLETED | REJECTED | CALL_ERROR | TIMEOUT
SENT -> RUNNING -> COMPLETED | FAILED | TIMEOUT | STATUS_UNKNOWN
CREATED/VALIDATED/DISPATCHING -> SEND_FAILED
```

- 一般 command 收到 CALLRESULT 後依 payload status 判定 `COMPLETED` 或 `REJECTED`。
- 沒有 status 的空 CALLRESULT 視該 command 定義判定成功。
- `GetDiagnostics`、`UpdateFirmware` 收到初始 response 後進入 `RUNNING`，後續通知才決定最終狀態。
- Fortune 的同步 response timeout 使用現有 vendor timeout 概念，預設 120 秒；其他 vendor 使用可設定預設值。
- 長時間操作 timeout 另設設定值，不能沿用同步 response timeout。

### 7.5 Firmware/Diagnostics 時間軸

新增 incoming handlers：

- `FirmwareStatusNotificationHandler`
- `DiagnosticsStatusNotificationHandler`

兩者都必須：

1. 回傳符合 OCPP schema 的空 CALLRESULT。
2. 保存原始 status notification 到 OCPP message log。
3. 找出同一 Charge Point 尚在執行的對應 Engineer operation。
4. 寫入 command event 並更新主要狀態。

因 status notification 不帶原始 command uniqueId，V1 對同一 Charge Point 的同類長時間 command 只允許一筆 active operation；重複送出回 HTTP 409，避免錯誤關聯。

### 7.6 Capability matrix

Capability 狀態：

- `VERIFIED`：已在指定 vendor/model/firmware 驗證。
- `UNVERIFIED`：可發送，UI 顯示警告。
- `UNSUPPORTED`：已知不支援，前後端都拒絕。

Fortune 的 19 個 command 初始可設為 `UNVERIFIED`，已經實機驗證者再調整為 `VERIFIED`。未知 vendor 預設為 `UNVERIFIED`，不能假設支援。

建議新增 `e_ocpp_command_capability` 保存 vendor、model、firmware、command、status、note、verifiedAt、verifiedBy；V1 不必製作 capability 管理 UI，可先由 migration seed 及後續維運 patch 更新。

### 7.7 URL 與敏感資料

- `GetDiagnostics.location`、`UpdateFirmware.location` 只接受合法 HTTP/HTTPS URI。
- 拒絕含 user-info 的 URL，例如 `https://user:password@host/path`。
- 稽核資料保存實際發送 payload，列表及一般 UI 顯示使用 masked payload。
- URL query value、idTag 等可能敏感欄位預設遮蔽；完整資料只在 Engineer detail API 的受權限欄位中提供。
- app log 不直接輸出完整敏感 payload，使用 commandLogId、correlationId、action 及結果定位 DB audit。

## 8. Charge Point 實作

### 8.1 新增受控 DataTransfer message

保留既有 `MeterRead`、`RelayControl` contract，另外新增明確的 Engineer messageId，例如：

```text
vendorId: CloudLink
messageId: EngineerModbusCommand
data: JSON string
```

Data 最少包含：

```json
{
  "correlationId": "uuid",
  "connectorId": 1,
  "operation": "READ_POWER_FACTOR",
  "parameters": {}
}
```

Charge Point handler 必須再次檢查 operation allowlist，不能把 `parameters` 當作 raw Modbus request。

### 8.2 Modbus operation mapping

- Read command 呼叫既有 `ConnectorMeterReader` 或 `ModbusService` 的具名方法。
- `SET_SUPPLY_STATE` 固定呼叫 `controlTestMode(host, port, slaveId, timeout, command)`。
- `SET_METER_ID` 使用 `controlMeterId()` 並限制 1 到 255。
- 每個 response 回傳 operation、connectorId、value/unit 或 result、elapsedMs。
- 錯誤回傳穩定 error code，例如 `CONNECTOR_NOT_FOUND`、`GATEWAY_MISSING`、`MODBUS_TIMEOUT`、`INVALID_PARAMETER`、`OPERATION_FAILED`。
- 在 handler scope 將 correlationId 放入 MDC，所有相關 Charge Point log 都可用該 ID 搜尋。

### 8.3 明確不變更的硬體語意

畫面與 Branch API 使用「供電控制」語意，但 CloudLink 智慧電表只能透過 Test Mode 控制：

```text
UI: 供電開啟/關閉
Backend operation: SET_SUPPLY_STATE
Charge Point implementation: controlTestMode(..., 1/0)
```

不得在實作時自行改用 `controlRelay()`，也不額外暴露 Test Mode command。

## 9. Database Plan

### 9.1 `e_engineer_command_log`

建議欄位：

- `id` BINARY(16) UUID
- `protocol`：`OCPP` / `MODBUS`
- `command_name`
- `charge_point_id`
- `connector_id` nullable
- `vendor`、`model`、`firmware_version` snapshot
- `capability_status` snapshot
- `operator_id`
- `correlation_id`
- `ocpp_unique_id` nullable
- `request_payload` JSON/LONGTEXT
- `response_payload` JSON/LONGTEXT nullable
- `state`
- `protocol_status` nullable
- `error_code`、`error_description`、`error_details` nullable
- `created_at`、`sent_at`、`responded_at`、`completed_at`
- `elapsed_ms` nullable

索引至少包含：

- unique `correlation_id`
- unique nullable `ocpp_unique_id`
- `(created_at)`
- `(charge_point_id, created_at)`
- `(connector_id, created_at)`
- `(operator_id, created_at)`
- `(protocol, command_name, state, created_at)`

### 9.2 `e_engineer_command_event`

使用子表保存完整時間軸，避免把多次 status notification 覆蓋在主表：

- `id` BINARY(16) UUID
- `command_log_id` FK
- `sequence_no`
- `event_type`
- `state`
- `payload` JSON/LONGTEXT nullable
- `message`
- `created_at`

索引及唯一條件：

- unique `(command_log_id, sequence_no)`
- index `(command_log_id, created_at)`

### 9.3 Migration 與 retention

新增可重複執行的 SQL patch，包含：

- command log/event/capability tables
- Engineer role/function/API/i18n mappings
- `ADMIN`、`power_user` 的 member-role mappings
- 365 天 cleanup task；刪除主表時先刪 event，或以 FK cascade 處理

Hibernate entity 與 SQL patch 必須一致；integration-test schema 也要同步新增必要表格。

## 10. SS3A 權限 Plan

建議 function tree：

```text
engineer_tool_mgmt              /engineer-tools       menu
engineer_tool_execute           execute OCPP/Modbus
engineer_tool_log_list          search logs
engineer_tool_log_view          view detail/timeline
```

Migration 必須：

1. 新增 `ss3a_role.engineer` 及 zh_TW/en_US i18n。
2. 新增 function、function i18n、API records 及 function-API mappings。
3. 將上述 function 只授權給 `engineer` Role。
4. 將 `ADMIN`、`power_user` member 加入 `engineer` Role。
5. 不把 function 直接加到 `administrator` 或 `power_user` Role。

所有 POST/search/detail API 都要有後端權限測試，不能只測側邊欄是否隱藏。

## 11. Frontend Implementation Guidance

### 11.1 路由與整體 layout

新增 `/engineer-tools` 頁面，沿用現有 dashboard shell。頁面採全寬工作區，不做 landing page，也不要把整個 section 包成巢狀 Card。

第一列固定為 target bar：

- Charge Point searchable select
- Connector searchable select；只在 command 需要 connector 時必填
- 線上狀態
- vendor、model、firmware
- capability badge

Target bar 下方使用三個 tabs：

- `Modbus`
- `OCPP`
- `操作紀錄`

Desktop 可使用左側 command list、右側 form/result 的兩欄布局；mobile 改為上下排列。command list 需有固定寬度或 responsive grid，切換 command 時不能造成整頁寬度跳動。

### 11.2 Modbus tab

- 只在 Connector Type 為 CloudLink 時啟用。
- command 依「連線與狀態」、「電力讀值」、「控制」分組。
- Read command 直接執行，不顯示確認 modal。
- `SET_SUPPLY_STATE`、`SET_METER_ID` 顯示一次簡單確認。
- 顯示結構化 result、單位、耗時、commandLogId 及 correlationId。
- UI 文案使用「供電控制」，可用次要說明標示底層為智慧電表 Test Mode；不新增獨立 Test Mode 控制。

### 11.3 OCPP tab

- 19 個 command 依第 4 節分類顯示，支援搜尋。
- target bar 顯示 `VERIFIED`、`UNVERIFIED`、`UNSUPPORTED` badge。
- `UNVERIFIED` 可執行，送出按鈕附近顯示非阻斷警告。
- `UNSUPPORTED` 禁用並顯示 capability note。
- 使用 segmented control 切換「表單」與「JSON」。
- 表單由 catalog/schema metadata 產生或映射；JSON mode 使用同一份 payload state，切換模式不可丟資料。
- JSON schema error 顯示到欄位/path，例如 `/csChargingProfiles/chargingSchedule/...`。
- `Reset` 送出前顯示一次簡單確認；其他 OCPP command 不增加二次確認。
- 建立成功後立即顯示 operation status panel，TanStack Query 輪詢 detail API；進入 terminal state 後停止輪詢。

### 11.4 操作紀錄 tab

Filter：

- 時間範圍
- protocol
- command
- Charge Point/Connector
- operator
- state/result
- correlationId / ocppUniqueId

Table 欄位：

- 執行時間
- protocol / command
- target
- operator
- state/result
- elapsed
- correlationId

點擊 row 開啟 detail drawer 或獨立 detail view，內容包含：

- 裝置 snapshot
- masked request/response JSON
- OCPP CALLERROR details
- command event timeline
- commandLogId、correlationId、ocppUniqueId 的複製按鈕

長字串使用 monospace、wrap 及 copy icon，不能讓 JSON 或 ID 撐破 drawer。

### 11.5 Connector deep-link

在既有 Connector status/action 區加入工具 icon button，導向：

```text
/engineer-tools?connectorId={id}&tab=ocpp
```

只有具 `Engineer` Role/function 的使用者顯示。目標頁仍須重新由 API 驗證 target 與權限。

### 11.6 前端技術要求

- 完成 Branch OpenAPI 後執行 `yarn openapi:refresh`，使用產生的 types。
- 沿用既有 React Hook Form、Zod、TanStack Query、shadcn 元件及 toast/error pattern。
- command catalog 是畫面能力與 schema 的唯一後端來源，不在 FE 重複寫 vendor allowlist。
- FE 可維護 presentation metadata，例如中文標籤、欄位排序及分組，但 command key 必須來自 API contract。
- 實作後執行 lint/build，並檢查 desktop/mobile 無重疊、截斷或布局跳動。

## 12. Test Plan

### 12.1 Branch unit tests

- 19 個 OCPP command 都能從 catalog 解析並通過合法 schema。
- 每個 command 的 required field、enum、nested payload 及 additional property 驗證。
- 任意 action、任意 Modbus operation、跨 Charge Point connector 被拒絕。
- capability 的 VERIFIED/UNVERIFIED/UNSUPPORTED 行為。
- state machine 不允許非法倒退或重複 terminal transition。
- URL scheme、user-info、Meter ID range 驗證。
- JWT operator 覆蓋 request operator 的攻擊情境不存在。

### 12.2 Branch integration tests

- Engineer 可呼叫 catalog、execute、search、detail。
- administrator/power_user Role 本身不能呼叫；只有額外持有 engineer Role 的指定 member 可以。
- 非 Engineer 回 403，未登入回 401。
- fake OCPP session 對 19 個 command 驗證 CALL action、uniqueId、payload。
- CALLRESULT、CALLERROR、offline、send failure、timeout 都正確更新 log/event。
- 舊 8 個 `/api/ocpp` API 及 `/api/fev` regression tests。
- cleanup 只刪除超過 365 天資料。

### 12.3 Charge Point tests

- 12 個受控 Modbus operation 的 normal/abnormal cases。
- 非 allowlist operation、raw register/function payload 被拒絕。
- `SET_SUPPLY_STATE` spy 驗證呼叫 `controlTestMode()`，且沒有呼叫 `controlRelay()`。
- Gateway missing、connector missing、Modbus timeout、invalid Meter ID 的穩定 error response。
- correlationId 在 DataTransfer response 及 MDC log 可查。

### 12.4 OCPP long-running tests

- Firmware status progression：Downloaded/Downloading/Installing/Installed 及 failure states。
- Diagnostics status progression：Uploading/Uploaded 及 failure states。
- 同一 Charge Point 重複 active long-running command 回 409。
- 無可關聯 operation 的 status notification 仍正常回 CALLRESULT，並記錄 orphan warning，不可影響 WebSocket session。

### 12.5 Fortune 實機驗證矩陣

每個 Fortune model/firmware 對 19 個 command 記錄：

- request payload
- CALLRESULT/CALLERROR
- response latency
- protocol status
- 實際設備行為
- capability 結論及備註

測試後以 migration/維運 patch 將 capability 從 `UNVERIFIED` 更新為 `VERIFIED` 或 `UNSUPPORTED`。

## 13. 分階段實作順序

### Phase 1: Database、Role 與 operation foundation

- 建立 command log/event/capability tables。
- 建立 Engineer role/function/API mappings，授權 `ADMIN`、`power_user`。
- 實作 audit repository/service、state machine、365-day cleanup。
- 實作 catalog、schema validator、search/detail API。

### Phase 2: OCPP dispatch 與非同步 response tracking

- 實作 request 內 dispatch、HTTP 202 response、generic response observer。
- 先接通現有 8 個 command，補上 `ReserveNow` response 缺口。
- 確認舊 8 個 API 及 FEV API 相容。

### Phase 3: 補齊全部 19 個 OCPP command

- 加入其餘 11 個 command schema/validation/dispatch。
- 加入 CALLRESULT/CALLERROR audit。
- 加入 Firmware/Diagnostics incoming handlers 及 timeline correlation。
- 產出完整 OpenAPI contract。

### Phase 4: CloudLink Modbus command bridge

- Branch 增加 Modbus create API 及 CloudLink-only target validation。
- Charge Point 增加受控 DataTransfer handler。
- 完成 12 個 operation、錯誤碼與 correlation log。

### Phase 5: Branch Admin UI

- 新增動態選單、target bar、三個 tabs。
- 完成 schema-driven OCPP form/JSON mode、Modbus controls、log timeline。
- 加入 Connector deep-link。
- 執行 OpenAPI refresh、lint、build 及 responsive verification。

### Phase 6: Fortune 實機驗證與 capability 收斂

- 逐 command 執行 Fortune test matrix。
- 修正 vendor-specific payload/timeout 差異，但不破壞標準 OCPP schema。
- 更新 capability seed/patch 及測試報告。

## 14. 驗收條件

功能完成至少需要符合：

1. 只有持有 Engineer Role 的使用者看得到並呼叫得到工程師工具。
2. `ADMIN`、`power_user` 已額外持有 Engineer Role。
3. Fortune 可由同一個 Engineer API 嘗試全部 19 個 OCPP Command。
4. `UNVERIFIED` 可發送、`UNSUPPORTED` 前後端都禁止。
5. 所有 payload 都通過對應 OCPP schema；無任意 action 入口。
6. Modbus 僅限 CloudLink 及 12 個 allowlist operation。
7. 供電控制實際呼叫 `controlTestMode()`。
8. 每次操作都有 commandLogId、operator、target、request、response/error、elapsed、correlationId。
9. Firmware/Diagnostics 能顯示後續狀態時間軸。
10. 現有 8 個 OCPP API、FEV API 及一般 Branch Admin 功能無 regression。
11. 操作紀錄可搜尋、展開並保留 365 天。
12. FE 在 desktop/mobile 可完整操作，無文字或控制項重疊。

## 15. 主要風險與控制

| 風險 | 控制方式 |
| --- | --- |
| Fortune 回應慢 | vendor-aware response timeout；HTTP API 不同步等待 |
| Vendor 實作偏離 OCPP schema | 保存 CALLERROR/raw response；用 capability note 記錄，不放寬任意 action |
| 長時間通知無 uniqueId | 每 CP/command 限制單一 active operation，按 CP 與 operation 關聯 |
| 敏感 payload 外洩 | DB 權限、UI masking、app log 不輸出完整 payload |
| Engineer 指令誤操作 | Role 隔離、controlled catalog、指定高風險 command 單次確認、完整 audit |
| Modbus 被當成 raw console | Branch 與 Charge Point 雙重 allowlist validation |
| 新流程破壞現有 OCPP API | 保留既有 endpoints/contracts，以 observer 旁路更新 audit |
| Capability 判斷過度樂觀 | 未知預設 UNVERIFIED，必須經 Fortune 實機矩陣後才標 VERIFIED |

## 16. 不在 V1 範圍

- Branch 韌體或 diagnostics 檔案儲存服務
- 任意 OCPP action/raw WebSocket frame console
- 任意 Modbus register/function console
- Capability 管理 UI
- 全系統集中式 log 平台
- CloudLink Charge Point 實作全部 19 個 CSMS command handler
- 自動把未來 administrator/power_user 成員加入 Engineer Role
