Bug #1459
進行中[Backend Bug] 華城 DataTransfer 後 Charge Point 狀態殘留充電中
概述
問題背景¶
A17 華城設備 CBDAX50A-25L-A6-A072 的交易 788 已於 2026-09-01 19:53:10 完成,實體 Connector 隨後於 19:53:30 回報 StatusNotification(Available)。目前 Connector 已為 AVAILABLE、currentTransactionId = NULL,且不存在 ACTIVE transaction,但 e_charge_points.status 仍殘留 CHARGING,使 Branch Admin 的「設備狀態」顯示為充電中。
已確認原因¶
- 2026-09-01 19:53:05,華城透過
DataTransfer(vendorId=FE-EVI, messageId=ChargeErrorNotification)回報交易 788、errorCode=1000。 -
DataTransferHandler將此事件投影成 ConnectorSUSPENDED_EV與 Charge PointCHARGING。 - 後續華城只以
connectorId=1回報Available;現行StatusNotificationHandler僅允許connectorId=0更新 Charge Point,因此無法清除先前的CHARGING。 - 本次 DataTransfer 發生時交易 788 仍是目前交易,因此「找不到 transaction 後 fallback」不是此次 incident 的直接觸發點。
已確認領域前提¶
華城(Fortune / FE-EVI)設備的 Charge Point 與實體 Connector 固定為 1:1,不存在同一華城 CP 下的其他實體 Connector。
預期調整¶
- 為不回報
connectorId=0的 Fortune 設備補相容性 reconciliation:- 收到唯一實體 Connector 的狀態後,可依 1:1 前提同步 Charge Point。
- Connector 回到
AVAILABLE且沒有目前/ACTIVE transaction 時,Charge Point 不得繼續顯示CHARGING。
- 收斂
DataTransferHandler的 transaction correlation:- 有
transactionId且仍對應目前 ACTIVE transaction:允許更新唯一 Connector 與 Charge Point。 - 有
transactionId但已不對應目前 ACTIVE transaction:只留下可稽核紀錄,不覆寫目前 operational status。 - 無
transactionId的設備層事件:可依 Fortune 1:1 前提作用於唯一 Connector;若資料不符合恰好一個 Connector,fail closed 並記錄異常。
- 有
- 非 Fortune 設備與標準
connectorId=0station-level 行為維持不變。
驗收條件¶
- 補單元測試覆蓋 Fortune
SUSPENDED_EV / CHARGING → StopTransaction → Available後 Charge Point 收斂。 - 補單元測試覆蓋 matching、stale/unmatched、無 transactionId 與 Fortune topology 異常。
- 更新 E2E regression case
OCP-017,保留 connection、station operational status、Connector 與 transaction 的分離語意。 - 不以偽造 OCPP event 或直接修改 production DB 作為修正方式。
E2E 初步影響¶
-
Update:
OCP-017 - 初步優先級:P1
- Trigger:Fortune DataTransfer、StatusNotification station projection、Charge Point 清單或 production incident 修正
- Release pack:Charging / OCPP(P0 + P1 + 受影響 P2)
是由 陳國瑋 於 29 天 前更新
需求確認 2¶
Ken 已確認採用 Fortune 1:1 完整狀態同步:
-
AVAILABLE:僅在currentTransactionId = NULL且沒有 ACTIVE transaction 時同步 CP 為AVAILABLE。 -
PREPARING:同步 CP 為PREPARING。 -
CHARGING、SUSPENDED_EV、SUSPENDED_EVSE:同步 CP 為CHARGING。 -
UNAVAILABLE:同步 CP 為UNAVAILABLE。 -
FAULTED:同步 CP 為FAULTED。 -
FINISHING、RESERVED、ENQUEUED:因無安全的 ChargePointStatus 對應,保留原 CP 狀態。
此相容流程只適用於 Fortune 且 topology 必須符合 CP : physical Connector = 1:1;標準 connectorId=0 與非 Fortune 行為維持不變。
是由 陳國瑋 於 29 天 前更新
階段 2:實作規劃(待 Ken 確認)¶
程式設計¶
- 新增
FortuneStatusReconciliationService,集中處理 Fortune 專屬規則:- 以資料庫中
vendor=FE-EVI作為適用邊界。 - 查詢
connectorNo > 0的實體 Connectors;必須恰好一個,並且必須是本次事件的 Connector。 - topology 為 0 或大於 1 時 fail closed,只記錄具 CP/Connector/數量的 warning。
- 統一映射
AVAILABLE / PREPARING / CHARGING / SUSPENDED_* / UNAVAILABLE / FAULTED;FINISHING / RESERVED / ENQUEUED不修改 CP。 -
AVAILABLE只有在currentTransactionId = NULL且沒有 ACTIVE transaction 時才可寫入 CP。 - 只更新
ChargePoint.status;不修改ocppConnectionStatus。
- 以資料庫中
- 調整
StatusNotificationHandler:- 標準
connectorId=0路徑保持原樣。 - Fortune
connectorId>0在 Connector 保存後呼叫 reconciliation service。 - 非 Fortune 的 physical Connector 仍不得更新 CP。
- 標準
- 調整
DataTransferHandler:- 移除「transaction 找不到就更新該 CP 全部 Connectors」的 fallback。
- 有
transactionId時,必須同時符合:唯一實體 Connector 的currentTransactionId相同、transaction row 為 ACTIVE、transaction 屬於同一 Connector;否則 Accepted response 保留,但 operational status 不變。 - 無
transactionId時,才可依 Fortune 1:1 規則更新唯一實體 Connector。 - Connector event 發布與既有 transaction-after-commit 時機維持不變。
-
ConnectorRepository新增只查 physical Connector(connectorNo > 0)的方法;不新增 UNIQUE/FK/CHECK constraint,也不做 migration。
預計異動檔案¶
-
java/ems_branch/src/main/java/com/sylksoft/ems/branch/vendor/fortune/FortuneStatusReconciliationService.java(新增) java/ems_branch/src/main/java/com/sylksoft/ems/branch/ocpp/handler/StatusNotificationHandler.javajava/ems_branch/src/main/java/com/sylksoft/ems/branch/ocpp/handler/DataTransferHandler.javajava/ems_branch/src/main/java/com/sylksoft/ems/branch/repository/ConnectorRepository.javajava/ems_branch/src/test/java/com/sylksoft/ems/branch/ocpp/handler/StatusNotificationHandlerChargePointStatusTest.java-
java/ems_branch/src/test/java/com/sylksoft/ems/branch/ocpp/handler/DataTransferHandlerStatusReconciliationTest.java(新增) documents/E2E_TEST_CASE_INVENTORY.mdai/02-backend-services.mdai/06-domain-glossary.mdCONTEXT.mdredmine-tickets/1459.md
Unit Test 案例¶
- Fortune 唯一 Connector 的完整安全映射。
- Fortune
AVAILABLE:無交易時更新 CP;current pointer 或 DB ACTIVE 任一存在時保留 CP。 - Fortune physical Connector 數量為 0/2 時 fail closed。
- 非 Fortune connectorId>0 不更新 CP;標準 connectorId=0 行為不退化。
- DataTransfer:matching current ACTIVE transaction 成功更新。
- DataTransfer:txId mismatch、COMPLETED、其他 Connector/CP 的交易皆不修改狀態。
- DataTransfer:無 txId 時更新唯一 Connector;topology 異常時不更新。
- DataTransfer 的 Connector event 發布與 CP connection status 分離語意不退化。
E2E Impact Review¶
-
Update
OCP-006(P1):保留標準 connectorId=0 規則,明列 Fortune 1:1 相容例外。 -
Update
OCP-017(P1):A17DataTransfer(SuspendedEV) → StopTransaction → Available後,CP 不得殘留 CHARGING。 -
Add
OCP-018(暫定 P2/Conditional):matching active DataTransfer 可更新;completed/mismatch/跨 CP transaction 不得污染目前狀態。 - Retire:無。
- 執行時納入
OCP-005、CHG-030作 connection/主要狀態轉換 non-regression。 - Release pack:Charging / OCPP(所有適用 P0 + P1 + 受影響 P2)。
-
OCP-011「設備原生回報 connectorId=0」仍維持 Gap;本次是明確的 Fortune compatibility,不宣稱修正 firmware conformance。
驗證與風險控制¶
- 實作後可先執行
mvn -DskipTests package,不啟動測試。 - 多案例 Unit Test command 與部署後 E2E 步驟會依 SOP 另列,等待 Ken 明確回覆「開始測試」/「開始 E2E 測試」後才執行。
- 部署後 A17 只執行已確認的低風險
TriggerMessage(StatusNotification, connectorId=1),以真實回報完成既有狀態 reconciliation;不改 production DB。 - 無 API/DTO、前端、schema、migration 或批次 backfill。
- P0 Core Smoke 不新增案例;若 Conditional E2E 無法執行,必須記錄 owner、風險與 follow-up,不得將 Skip 當 Pass。
是由 陳國瑋 於 29 天 前更新
階段 3:實作狀態與待啟動 Unit Test 計畫¶
已完成¶
- 新增
FortuneStatusReconciliationService,集中處理 stored vendor、唯一 physical Connector、完整安全映射與 transaction lifecycle guard。 -
StatusNotificationHandler保留標準 connectorId=0,新增 Fortune 1:1 physical Connector reconciliation。 -
DataTransferHandler移除找不到 transaction 後全站更新 fallback;有 txId 時驗證 current pointer、ACTIVE row 與相同 Connector owner。 - 新增/更新兩個純 Mockito Unit Test class。
- E2E catalog:Update
OCP-006、UpdateOCP-017、AddOCP-018。 - 知識文件與 domain glossary 已同步。
已執行驗證¶
-
mvn -DskipTests package:BUILD SUCCESS;production 與 test source 均編譯成功,tests skipped。 - 單一隔離 Unit Test:
mvn '-Dtest=StatusNotificationHandlerChargePointStatusTest#handleFortuneAvailableWithoutActiveTransaction_reconcilesChargePoint' test
結果:1 test、0 failures、0 errors、0 skipped,BUILD SUCCESS。 - Catalog validator:PASS,178 cases(P0=56、P1=70、P2=47、P3=5)。
-
git diff --check:PASS。
待 Ken 啟動的 Unit Test 計畫¶
Command:
cd java/ems_branch
mvn -Dtest=StatusNotificationHandlerChargePointStatusTest,DataTransferHandlerStatusReconciliationTest test
涵蓋範圍:
- Fortune 完整 CP status mapping 與無安全映射狀態。
- AVAILABLE 的 current pointer/ACTIVE row 雙 guard。
- 非 Fortune、標準 connectorId=0 與 topology 0/2 non-regression。
- DataTransfer matching ACTIVE、mismatch、COMPLETED、cross-Connector/CP、no-tx 與 stored vendor guard。
- OCPP Accepted response、Connector event 與 connection/operational status 分離。
環境與資料風險:
- 兩個 class 都是 JUnit 5 + Mockito 純 Unit Test。
- 不啟動 Spring context,不使用 integration/test profile。
- 不連接 DB、RabbitMQ、OCPP 設備、A17 或任何外部服務。
- 不寫入共用/production 資料。
等待 Ken 切換模型後明確回覆「開始測試」;收到前不得執行此 command。
是由 陳國瑋 於 29 天 前更新
【階段 4|Unit Test 結果】
Ken 已明確回覆「開始測試」,已執行核准命令:
cd java/ems_branch
mvn -Dtest=StatusNotificationHandlerChargePointStatusTest,DataTransferHandlerStatusReconciliationTest test
最終結果:
- Tests run: 23
- Failures: 0
- Errors: 0
- Skipped: 0
- BUILD SUCCESS
- StatusNotificationHandlerChargePointStatusTest:15 invocations PASS
- DataTransferHandlerStatusReconciliationTest:8 tests PASS
覆蓋範圍:
- Fortune 1:1 完整安全映射與 AVAILABLE current/ACTIVE transaction guard
- DataTransfer matching/mismatch/COMPLETED stale/cross-Connector/CP/no-transaction correlation
- 0 或多個實體 Connector 時 fail closed
- 非 Fortune 與標準 connectorId=0 non-regression
第一次執行時,新 DataTransfer 測試 helper 因混用 Mockito matcher 與 raw value 產生 7 個測試配置 errors;修正為一致使用 eq(...) 後,以相同命令重跑 23/23 PASS。這不是 production code assertion failure,未建立產品缺陷。
其他驗證:
- mvn -DskipTests package:BUILD SUCCESS
- E2E catalog validator:PASS(178 cases)
- git diff --check:PASS
正式報告:
test_report/20260902_001_redmine-1459-fortune-status-reconciliation-unit.md
限制:
- 純 JUnit 5 + Mockito Unit Test,未啟動 Spring、未連 DB/MQ/OCPP/A17。
- 尚未部署,也未執行 A17 TriggerMessage 或 Charging/OCPP E2E。
- 目前停在「測試報告確認」gate,等待 Ken 確認報告格式、案例覆蓋與 Unit Test 結論。
是由 陳國瑋 於 29 天 前更新
【階段 5|Unit Test 報告確認】
Ken 已於 2026-09-02 接受 Unit Test 報告的格式、案例覆蓋與 PASS 結論。
報告:
test_report/20260902_001_redmine-1459-fortune-status-reconciliation-unit.md
後續順序:
- 確認技術文件與 E2E Catalog 更新
- commit/push feature branch
- Ken 手動部署至核准測試環境
- 另列 E2E release pack、實際步驟、環境與資料影響
- 等 Ken 明確回覆「開始 E2E 測試」後,才操作真實 OCPP/A17
Unit Test 的「開始測試」授權不延伸到 E2E;目前尚未發送 TriggerMessage、修改現場狀態或執行任何 E2E。
是由 陳國瑋 於 29 天 前更新
【階段 6/7|文件確認與程式交付】
Ken 已確認技術文件與 E2E Catalog 更新可接受。
已完成:
- Commit:4b0e4128fde2712f024164249b338d5964842001
- Message:fix(#1459): 修正華城充電樁狀態同步
- Branch:feature/redmine-1459-fortune-status-reconciliation
- 已 push 至 origin,local HEAD 與 upstream commit 一致
- Working tree clean
- E2E Catalog validator:PASS(178 cases)
- Unit Test:23/23 PASS
本次沒有建立 MR、沒有 merge、沒有部署。
下一步:
- Ken 手動將上述 feature branch/commit 部署至核准測試環境。
- 確認目標環境、A17 操作時段與可用設備後,另列 Charging/OCPP E2E release pack、實際步驟與資料影響。
- 只有收到 Ken 明確的「開始 E2E 測試」後,才發送真實 OCPP request 或操作 A17。
- 不直接修改 production DB;A17 現有殘留狀態以 TriggerMessage(StatusNotification, connectorId=1) 取得設備真實狀態並由新 reconciliation 收斂。
是由 陳國瑋 於 29 天 前更新
【階段 4|A17 E2E 測試計畫(等待啟動)】
Ken 已確認 commit 4b0e4128fde2712f024164249b338d5964842001 已部署至 A17。此留言只記錄計畫;目前尚未連線 A17,也未發送任何 request。
目標¶
- Environment:A17/ct1 production
- Charge Point:CBDAX50A-25L-A6-A072
- Connector:CBDAX50A-25L-A6-A072-001(OCPP connectorId=1)
- Release pack:#1459 impact-focused Charging/OCPP production pack
- Catalog:REL-004、REL-005、OCP-005、OCP-006、OCP-017、CHG-030;OCP-018 僅做可取得的現場觀察,完整 correlation 另需 isolated simulator
執行步驟¶
TC-E2E-001|部署與連線健康(REL-004/REL-005)¶
- 以 A17 既有安全設定 SSH 連線,只讀取:
- ems_branch container/process 狀態、啟動時間、restart count
- api.jar 是否包含 FortuneStatusReconciliationService
- 部署後 Branch/OCPP log 是否有 startup error、restart loop 或持續 exception
- 以 SELECT/GET 取得 CP、Connector、最近 transactions 與 OCPP 訊息 baseline。
- 驗證 stored vendor=FE-EVI、恰好一支 connectorNo>0、OCPP ONLINE、Heartbeat 新鮮。
TC-E2E-002|Heartbeat 不覆寫 operational status(OCP-005)¶
經 Engineer API 發送一次:
POST https://ct1-api.cloudlinkems.com/api/engineer/ocpp/commands
{
"chargePointId": "CBDAX50A-25L-A6-A072",
"connectorId": null,
"command": "TriggerMessage",
"payload": {
"requestedMessage": "Heartbeat"
}
}
驗證 HTTP 202/SENT、operation detail 最終 COMPLETED/Accepted、收到 request 之後的新 Heartbeat;CP connection 可維持 ONLINE,但 operational status 不得因 Heartbeat 被任意改寫。
TC-E2E-003|Fortune connectorId=1 reconciliation(OCP-006/OCP-017/CHG-030)¶
前置條件必須同時成立:
- stored vendor=FE-EVI
- 恰好一支實體 Connector
- Connector currentTransactionId=NULL
- e_transactions 沒有該 Connector 的 ACTIVE row
- 實體 Connector 已為 AVAILABLE,且設備沒有進行中的充電
經 Engineer API 發送一次:
POST https://ct1-api.cloudlinkems.com/api/engineer/ocpp/commands
{
"chargePointId": "CBDAX50A-25L-A6-A072",
"connectorId": "CBDAX50A-25L-A6-A072-001",
"command": "TriggerMessage",
"payload": {
"requestedMessage": "StatusNotification",
"connectorId": 1
}
}
驗證:
- HTTP 202/SENT,detail 最終 COMPLETED/Accepted。
- e_ocpp_message_log/OCPP log 出現 request 之後的新 StatusNotification(connectorId=1, status=Available)。
- Connector 保持 AVAILABLE、currentTransactionId=NULL,ACTIVE transaction count=0。
- Charge Point status 收斂為 AVAILABLE、ocppConnectionStatus 保持 ONLINE。
- log 有 [FORTUNE-STATUS-RECONCILIATION] 的更新或冪等訊息。
- GET /api/cp/info/{chargePointId} 與 Branch Admin 顯示不再是「充電中」。
- Connector event 正常發布,沒有 exception storm。
若部署重啟後已由設備主動送 StatusNotification 並提前完成 reconciliation,會先以部署後 OCPP log+DB transition 作為主要證據;本次 TriggerMessage 改驗證冪等及最新真實狀態,不把結果誤寫成首次修復。
停止條件¶
以下任一成立即停在唯讀查核,不發送 TriggerMessage:
- CP offline、vendor 不符或實體 Connector 數量不是 1
- currentTransactionId 非 NULL 或存在 ACTIVE transaction
- Connector 不是 AVAILABLE,或無法確認現場沒有充電
- 部署版本無法證明含 #1459 class,或服務有 restart loop/重大 error
資料影響¶
- SELECT/GET/log 查詢:唯讀。
- Engineer API 會新增正常的 e_engineer_command_log/event audit。
- Heartbeat 會更新 last_heartbeat。
- StatusNotification 會更新 Connector 時間欄位,並把符合 guard 的 CP operational status 收斂為設備真實 AVAILABLE;可能發布正常 Connector event。
- 不直接 UPDATE production DB;不建立測試交易;不啟停供電;不送 RemoteStart/RemoteStop/Reset/ChangeAvailability/DataTransfer。
- 上述 audit 與真實 OCPP 訊息保留,不做 cleanup。
OCP-018 限制¶
Engineer API 的 DataTransfer 是 CSMS→CP,方向相反,不能測 CP→Branch DataTransferHandler。以相同 production CP ID 建立 raw WebSocket/重播 stale DataTransfer 會與真實設備 session 衝突,因此 A17 不執行。若部署後自然發生 DataTransfer,只能收集觀察證據;若未發生則標記 NOT RUN,不得宣稱 PASS。完整 OCP-018 需另在 isolated simulator 環境執行。
依 SOP,等待 Ken 明確回覆「開始 E2E 測試」後才執行上述 A17 操作。
是由 陳國瑋 於 29 天 前更新
A17 部署後 E2E 結果(等待報告確認)¶
- 被測 commit:
4b0e4128fde2712f024164249b338d5964842001 - E2E report commit:
e8fe003 - 正式報告:
test_report/20260902_002_redmine-1459-a17-fortune-status-reconciliation-e2e.md - Release pack:Charging/OCPP production regression
- 結果:4/4 PASS,建議 GO、維持 A17 部署
通過證據¶
-
REL-004:A17 Branch running、restartCount=0、
/api-docsHTTP 200,deployed JAR 含FortuneStatusReconciliationService。 -
REL-005/OCP-006/OCP-017/CHG-030:部署前最後仍可見
reportedStatus=CHARGING;部署後 Fortune 重連並回報唯一 ConnectorAvailable,17:18:16 新 reconciliation 將 CP 更新為AVAILABLE。 -
OCP-005:單次
TriggerMessage(Heartbeat)回 HTTP 202;operationfd0522e9-713c-40a3-9604-40e1ede4a334為COMPLETED/Accepted(331 ms),隨即收到新 Heartbeat,CP/Connector operational status 未被覆寫。 -
OCP-006/OCP-017/CHG-030:單次
TriggerMessage(StatusNotification, connectorId=1)回 HTTP 202;operation9e506f3c-7361-4db8-82a9-7fa5874b039e為COMPLETED/Accepted(162 ms),隨即收到Available/NoError。 - 截至 18:46:20:CP=
AVAILABLE/ONLINE、Connector=AVAILABLE、current transaction=NULL、ACTIVE transaction=0、non-terminal Engineer command=0;18:44:10 仍有自然 Heartbeat,Branch restartCount=0,測試及觀察窗無 ERROR/Exception。
安全邊界與未執行案例¶
- 未執行 RemoteStart/Stop、Reset、ChangeAvailability、outbound DataTransfer、raw DB UPDATE 或 fake WebSocket。
-
OCP-018為 P2 Conditional NOT RUN:部署後沒有自然 CP→CSMS DataTransfer,Engineer DataTransfer 又是反方向,不在 production 偽造;主要 matching/stale guards 已由 8 個隔離 Unit cases PASS,後續於 isolated OCPP simulator 或下一次自然事件唯讀補證。 - 只有正常 Engineer audit、Heartbeat 與 StatusNotification 寫入,無需 cleanup。
依 Redmine SOP 階段 5,現等待 Ken 確認 E2E report 格式與案例覆蓋。
是由 陳國瑋 於 29 天 前更新
- 狀態 從 In Progress 變更為 Resolved
E2E 報告確認與結案¶
Ken 已於 2026-09-02 確認接受 A17 E2E 報告的格式、證據與案例覆蓋。
交付¶
- 實作 commit:
4b0e4128fde2712f024164249b338d5964842001 - E2E 報告 commit:
e8fe003 - Branch:
feature/redmine-1459-fortune-status-reconciliation - Unit Test:23/23 PASS
- A17 production E2E:4/4 PASS
- 正式報告:
test_report/20260902_002_redmine-1459-a17-fortune-status-reconciliation-e2e.md - E2E Catalog validator:PASS(178 cases)
A17 最終結果¶
- CP
CBDAX50A-25L-A6-A072:AVAILABLE / ONLINE - Connector:
AVAILABLE - current transaction:
NULL - ACTIVE transaction:0
- Branch restartCount:0
- 測試與後續觀察窗無 ERROR/Exception
- 建議:GO,維持現有部署
OCP-018 保留 P2 Conditional follow-up:不在 production 偽造 CP→CSMS DataTransfer,後續於 isolated OCPP simulator 或自然事件做補充驗證;此項不阻斷 #1459 結案。
本次未由 Agent 建立 MR 或執行 merge。