專案

一般

配置概況

動作

Bug #1295

已結束

[Bug] 離峰時段輪充排程錯誤調度「未啟用離峰充電」的連接器(CP003-001 為例)

是由 陳國瑋23 天 前加入. 於 22 天 前更新.

狀態:
Closed
優先權:
High
被分派者:
開始日期:
2026-05-13
完成日期:
預估工時:

概述

問題描述

當充電樁連接器在資料庫中設定 enable_off_peak = 0(未啟用離峰充電),但在 e_connector_priority 表有 charge_status = 'eligible' 記錄時,系統會在離峰時段錯誤地將該連接器納入輪充 dispatch,導致車主在「未啟用離峰充電」的情況下被排程離峰充電。

受影響實例: 寶台A17(ct1-ssh.cloudlinkems.com),CP003-001(enable_off_peak=0,B3 群組)


觸發條件

  1. e_connectors.enable_off_peak = 0(FALSE,非 NULL)
  2. e_connector_priority.charge_status = 'eligible'
  3. 離峰時段到來,ChargingOrchestrator.rotationDispatchTick 觸發

證據(寶台A17,2026-05-12 00:00)

資料庫狀態

connector_id  = CP003-001
charge_point_id= CP003
enable_off_peak= \0 (0x00, FALSE,非 NULL)
charge_group  = B3
charge_status = eligible
status_reason = NULL
priority      = 15

API Log(ems_branch api-2026-05-12-1.log.gz)

00:00:00.018  離峰起點時重新亂數排序完成 - chargeGroupId=B3, connectorIds=[CP003-005, CP003-001, CP003-004]
00:00:00.045  rotationDispatchTick 輪充調度,佔用 slot 完成 - connectorId=CP003-001, userId=U2510220002, priority=2
00:00:00.046  不經過輪充檢查的RemoteStart - connectorId=CP003-001, userId=U2510220002, chargePointId=CP003
00:00:00.047  收到遠端啟動充電請求 - connectorId=CP003-001, chargePointId=CP003, userId=U2510220002
...
00:02:14.230  開始交易,交易ID: 239, 連接器ID: CP003-001, 住戶ID: TAG20251022000000002

cp_transactions 記錄

transaction_id   = 239
connector_id     = CP003-001
start_time       = 2026-05-12 00:02:14
end_time         = 2026-05-12 03:09:42
energy_consumed  = 22870 Wh
is_offline       = 0
reason           = EVDisconnected

根本原因(Root Cause)

問題程式碼位置

ChargingOrchestrator.rotationDispatchTickdispatchEligibleConnectorsloadEligiblePriorities

原因說明

1. OffPeakEnqueuePolicy.enqueueForOffPeak 有正確的 enable_off_peak 檢查:

if (enabledOffPeak == null || enabledOffPeak.equals(Boolean.FALSE)) {
    log.warn("enqueueForOffPeak skipped - connectorId={}, reason={}, enableOffPeak={}, msg=off-peak not enabled");
    return OffPeakEnqueueResult.SKIPPED;
}

enable_off_peak=0 時,enqueueForOffPeak 正確 Skip,不更新 DB 記錄。

2. dispatchEligibleConnectors(或 loadEligiblePriorities)缺少 enable_off_peak 過濾:
e_connector_priority 表篩選 charge_status = ELIGIBLE 時,沒有 JOIN e_connectors 確認 enable_off_peak 是否為 true。
因此 enable_off_peak=0 的 CP003-001(當時 charge_status=eligible)仍然通過 filter。

3. 排程繞過了 OffPeakEnqueuePolicy 的檢查dispatchEligibleConnectors 直接 dispatch,沒有再次確認 enable_off_peak

修復方向

dispatchEligibleConnectorsloadEligiblePriorities 的查詢邏輯中,加入 e_connectors.enable_off_peak = TRUE 的過濾條件。

  • 選項A(推薦):loadEligiblePriorities 的 SQL JOIN e_connectors 表時加 WHERE c.enable_off_peak = TRUE
  • 選項B:dispatchEligibleConnectors 的 Predicate filter 中加 connector.getEnableOffPeak() == Boolean.TRUE

受影響範圍

所有 enable_off_peak=0e_connector_prioritycharge_status=eligible 記錄的連接器,在離峰時段都有被錯誤 dispatch 的風險。


查閱的 Log 檔案

檔案路徑 內容
/opt/ems/ems_branch/api/logs/api-2026-05-12-1.log.gz 寶台A17 ems_branch API 全日行為(含 ChargingOrchestrator)
/opt/ems/ems_branch/api/logs/scheduler-2026-05-12-1.log.gz ems_branch scheduler 排程 Log
/opt/ems/ems_cp3/api/logs/ocpp-modbus-2026-05-12-1.log.gz CP003 充電樁 Modbus/OCPP Log

查閱的 Database Table

Table 用途
e_connectors 連接器設定(enable_off_peak, charge_group)
e_connector_priority 排程優先順序(charge_status=eligible, priority)
cp_transactions 充電交易記錄(is_offline, energy_consumed)
cp_connectors 連接器狀態
e_charge_group 充電群組設定
s_user 車主帳號

相關程式碼(反編譯自 api.jar)

  • ChargingOrchestrator.rotationDispatchTick(行 2157, 2724, 341, 374)
  • ChargingOrchestrator.dispatchEligibleConnectors
  • OffPeakEnqueuePolicy.enqueueForOffPeak(有正確檢查但未被 dispatchEligibleConnectors 呼叫)
  • ConnectorPriorityRepository.findByChargeGroupAndChargeStatusOrderByPriorityAsc

是由 陳國瑋23 天 前更新

  • 主旨[Bug] 離峰時段輪充排程錯誤調度未啟用離峰充電的連接器 變更為 test update

是由 陳國瑋23 天 前更新

  • 主旨test update 變更為 [Bug] 離峰時段輪充排程錯誤調度「未啟用離峰充電」的連接器(CP003-001 為例)
  • 概述 已更新 (差異)

是由 陳國瑋23 天 前更新

待 Ken 確認

實作方向(選項A,推薦):
loadEligiblePriorities 的 SQL JOIN e_connectors 表時加 WHERE c.enable_off_peak = TRUE

Claude Code 實作前,請先確認 ChargingOrchestratorloadEligiblePriorities 方法的完整實作(需透過 source code jar 確認 SQL)。

不應由 OffPeakEnqueuePolicy.enqueueForOffPeak 修補,因為該方法本身邏輯是對的,問題在於 dispatch 端根本沒有經過它。

是由 陳國瑋23 天 前更新

  • 被分派者 設定為 陳國瑋

是由 陳國瑋23 天 前更新

  • 狀態New 變更為 Resolved

Bug #1295 已修復

修復內容

修復位置: java/ems_branch/src/main/java/com/sylksoft/ems/branch/api/cp/ConnectorService.java

updateConnector() 方法的 setEnableOffPeak 之前加入:

if (Boolean.TRUE.equals(entity.getEnableOffPeak()) && !Boolean.TRUE.equals(dto.getEnableOffPeak())) {
    connectorPriorityRepository.deleteByConnectorId(entity.getId());
}

修復說明:

  • enableOffPeaktrue 改為 false(或 null)時,主動刪除 e_connector_priority 中對應的 ELIGIBLE 記錄
  • 這樣下次 loadEligiblePriorities() 撈取排隊中的 connector 時,該 connector 不會再出現
  • 這是治本的修復:從源頭堵住髒資料殘留

測試:
已新增單元測試 ConnectorServiceUpdateOffPeakTest(3 個測試案例,全數通過):

  • disableOffPeak_shouldDeleteConnectorPriority
  • enableOffPeak_unchanged_shouldNotDelete
  • enableOffPeak_turnedOn_shouldNotDelete

Branch: feature/redmine-1295-off-peak-dispatch-bug → 已 merge 至 private
Commit: 7b90cb6(fix), 77bb520(test)

已驗證修復的環境:
寶台A17(ct1-ssh.cloudlinkems.com),connector CP003-001(enable_off_peak=0,B3 群組)

是由 陳國瑋23 天 前更新

Bug #1295 已修復。修復:當 enableOffPeak 從 true 改為 false 時,呼叫 connectorPriorityRepository.deleteByConnectorId() 清除殘留記錄。Branch: feature/redmine-1295-off-peak-dispatch-bug,已 merge 至 private。Commit: 7b90cb6 (fix), 77bb520 (test)。測試:ConnectorServiceUpdateOffPeakTest 3/3 passed。

是由 陳國瑋22 天 前更新

【5/14 時序證據補充(排除 5/13 pre-fix log)】

依 Ken 指示,5/13 00:00 那批紀錄屬於程式更新前,故本次不列入 bug 是否復發的判斷。
本次只採計 5/14 修正後的紀錄。

結論

5/14 修正後,Bug 仍然復發。
CP003-001 在 enable_off_peak = 0 的情況下,仍被離峰輪充流程錯誤派發 RemoteStart。

證據一:Connector 狀態不應被派發

目前資料庫查詢結果:

  • e_connectors.id = CP003-001
  • charge_group = B3
  • enable_off_peak = 0
  • status = AVAILABLE

但同時 e_connector_priority 仍存在:

  • connector_id = CP003-001
  • charge_status = eligible

這表示:即使 connector 本身未啟用離峰充電,priority queue 內仍保有 eligible 狀態,後續 dispatch 仍可能選到它。

證據二:5/14 04:30 再次被錯誤派發

branch / remote transaction log

  • 2026-05-14 04:30:10 e_remote_transaction_log
    • operation_type = START
    • connector_id = CP003-001
    • status = SUCCESS

ems-cp3-api log 時序

  • 04:30:10 收到 RemoteStartTransaction
  • 04:30:10 [CP003]-CP003-001 電表連接檢查通過
  • 04:30:11 電表切換成功:合閘
  • 04:30:11 連接器狀態:AVAILABLE -> PREPARING
  • 04:30:11 開始電流檢測等待(最大 360000ms)
  • 04:30:45 ~ 04:35:52 多次輪詢,CP003-001 持續 0.0A、繼電器 閉合
  • 04:36:13 電流檢測超時,未檢測到電流流動
  • 04:36:17 清理完成,狀態 PREPARING -> AVAILABLE,並已斷閘

這段證明:
CP003-001 在未啟用離峰充電的前提下,仍被系統主動合閘並進入 PREPARING,最後因無電流而超時清理。

證據三:不是單次誤觸,後續仍持續復發

e_remote_transaction_log 在 5/14 後續還有:

  • 2026-05-14 05:00:10 CP003-001 START SUCCESS
  • 2026-05-14 05:30:10 CP003-001 START SUCCESS
  • (另有 2026-05-14 11:00:00 / 11:30:10 / 12:00:10 / 12:30:10 / 13:00:10 / 13:30:10 持續出現同類 START 紀錄)

表示 04:30 那次 timeout cleanup 並沒有阻止後續 dispatch,問題不是一次性殘留,而是 dispatch 條件本身仍會選中 enable_off_peak=0 的 connector。

排除項目

  • 2026-05-14 00:18:45 有一筆 CP003-001 START,該筆帶有 operator_id=U2510220002,較像人工/使用者操作,不列入本 bug 主證據。

目前推定根因

高機率仍是 dispatch / eligible 載入邏輯只看 e_connector_priority.charge_status = eligible
但沒有在派發當下再次檢查 e_connectors.enable_off_peak = true
因此 CP003-001 雖然 off peak 已關閉,仍可被 queue 選中。

是由 陳國瑋22 天 前更新

  • 狀態Resolved 變更為 Closed

Fix 已部署至 private branch

修正內容

  • 方案 BloadEligiblePriorities() 改用新 JPQL query,直接 JOIN e_connectors 過濾 enableOffPeak=false
  • 方案 AdispatchEligibleConnectors() 發 RemoteStart 前加 guard,enable_off_peak=false 的 stale record 直接刪除

修改檔案

  • ConnectorPriorityRepository.java — 新增 findByChargeGroupWithOffPeakEnabled()
  • ChargingOrchestrator.javaloadEligiblePriorities() + dispatchEligibleConnectors()
  • ChargingOrchestratorDispatchOffPeakGuardTest.java — 2 個單元測試

Commit

  • 88d4443 on feature/redmine-1295-off-peak-dispatch-bug
  • 已 merge 至 private branch(commit d0b4471

驗證

  • mvn compile ✅
  • mvn test -Dtest=ChargingOrchestratorDispatchOffPeakGuardTest ✅ 2/2 PASS

是由 陳國瑋22 天 前更新

Fix 已部署至 private branch

修正內容

  • 方案 B:loadEligiblePriorities() 改用新 JPQL query,直接 JOIN e_connectors 過濾 enableOffPeak=false
  • 方案 A:dispatchEligibleConnectors() 發 RemoteStart 前加 guard,enable_off_peak=false 的 stale record 直接刪除

Commit

  • 88d4443 on feature/redmine-1295-off-peak-dispatch-bug
  • 已 merge 至 private branch(d0b4471)

驗證

  • mvn compile ✅
  • mvn test -Dtest=ChargingOrchestratorDispatchOffPeakGuardTest ✅ 2/2 PASS
動作

匯出至 Atom PDF