]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor(ocpp2): extract buildTransactionEvent as standalone function, remove dead...
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 18 Mar 2026 18:32:25 +0000 (19:32 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 18 Mar 2026 18:32:25 +0000 (19:32 +0100)
Extract buildTransactionEvent from OCPP20ServiceUtils static method to
exported standalone function, consistent with buildStatusNotificationRequest
and buildMeterValue.

Remove unused context overload from buildTransactionEvent and
sendTransactionEvent — all production callers pass triggerReason directly.

Remove dead code: OCPP20TransactionContext interface (not in OCPP 2.0.1
specs), selectTriggerReason method, TransactionContextFixtures, and
associated tests.

src/charging-station/ocpp/2.0/OCPP20RequestService.ts
src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts
src/charging-station/ocpp/index.ts
src/types/ocpp/2.0/Transaction.ts
tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts
tests/charging-station/ocpp/2.0/OCPP20TestUtils.ts

index 40d5b8d31f9da116d469c0e1eef5cc258cc86be8..313301de152b42046c3e982c29f355c4e459467a 100644 (file)
@@ -1,7 +1,6 @@
 import type { ValidateFunction } from 'ajv'
 
 import type { ChargingStation } from '../../../charging-station/index.js'
-import type { OCPP20TransactionEventOptions } from '../../../types/ocpp/2.0/Transaction.js'
 import type { OCPPResponseService } from '../OCPPResponseService.js'
 
 import { OCPPError } from '../../../exception/index.js'
@@ -13,8 +12,6 @@ import {
   type JsonType,
   OCPP20RequestCommand,
   type OCPP20SignCertificateRequest,
-  OCPP20TransactionEventEnumType,
-  OCPP20TriggerReasonEnumType,
   OCPPVersion,
   type RequestParams,
 } from '../../../types/index.js'
@@ -23,7 +20,7 @@ import { OCPPRequestService } from '../OCPPRequestService.js'
 import { buildStatusNotificationRequest } from '../OCPPServiceUtils.js'
 import { generatePkcs10Csr } from './Asn1DerUtils.js'
 import { OCPP20Constants } from './OCPP20Constants.js'
-import { OCPP20ServiceUtils } from './OCPP20ServiceUtils.js'
+import { buildTransactionEvent, OCPP20ServiceUtils } from './OCPP20ServiceUtils.js'
 
 const moduleName = 'OCPP20RequestService'
 
@@ -204,35 +201,8 @@ export class OCPP20RequestService extends OCPPRequestService {
           commandParams.status as ConnectorStatusEnum,
           commandParams.evseId as number | undefined
         ) as unknown as Request
-      case OCPP20RequestCommand.TRANSACTION_EVENT: {
-        const eventType = commandParams.eventType as OCPP20TransactionEventEnumType
-        const triggerReason: OCPP20TriggerReasonEnumType =
-          commandParams.triggerReason != null
-            ? (commandParams.triggerReason as OCPP20TriggerReasonEnumType)
-            : eventType === OCPP20TransactionEventEnumType.Ended
-              ? OCPP20TriggerReasonEnumType.RemoteStop
-              : OCPP20TriggerReasonEnumType.Authorized
-        const evse = commandParams.evse as undefined | { connectorId?: number; id?: number }
-        const connectorId: number =
-          commandParams.connectorId != null
-            ? (commandParams.connectorId as number)
-            : (evse?.connectorId ?? evse?.id ?? 1)
-        const transactionId: string =
-          commandParams.transactionId != null
-            ? (commandParams.transactionId as string)
-            : eventType === OCPP20TransactionEventEnumType.Ended
-              ? (chargingStation.getConnectorStatus(connectorId)?.transactionId?.toString() ??
-                generateUUID())
-              : generateUUID()
-        return OCPP20ServiceUtils.buildTransactionEvent(
-          chargingStation,
-          eventType,
-          triggerReason,
-          connectorId,
-          transactionId,
-          commandParams as unknown as OCPP20TransactionEventOptions
-        ) as unknown as Request
-      }
+      case OCPP20RequestCommand.TRANSACTION_EVENT:
+        return buildTransactionEvent(chargingStation, commandParams) as unknown as Request
       default: {
         // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
         const errorMsg = `Unsupported OCPP command ${commandName as string} for payload building`
index 968ec58a6311fce8665e65b0cc7260b3cfbea7bf..e684ec4ac3fb5f26ea74dbdbe882e86760fde386 100644 (file)
@@ -1,5 +1,3 @@
-/* eslint-disable @typescript-eslint/unified-signatures */
-
 import { secondsToMilliseconds } from 'date-fns'
 
 import { type ChargingStation, resetConnectorStatus } from '../../../charging-station/index.js'
@@ -7,6 +5,7 @@ import { OCPPError } from '../../../exception/index.js'
 import {
   ConnectorStatusEnum,
   ErrorType,
+  type JsonObject,
   OCPP20ComponentName,
   OCPP20IncomingRequestCommand,
   OCPP20RequestCommand,
@@ -26,19 +25,18 @@ import {
 import {
   type OCPP20EVSEType,
   OCPP20ReasonEnumType,
-  type OCPP20TransactionContext,
   type OCPP20TransactionEventOptions,
   type OCPP20TransactionType,
 } from '../../../types/ocpp/2.0/Transaction.js'
 import {
   Constants,
   convertToIntOrNaN,
+  generateUUID,
   logger,
   validateIdentifierString,
 } from '../../../utils/index.js'
 import { getConfigurationKey } from '../../ConfigurationKeyUtils.js'
 import { OCPPServiceUtils, sendAndSetConnectorStatus } from '../OCPPServiceUtils.js'
-import { OCPP20Constants } from './OCPP20Constants.js'
 import { OCPP20VariableManager } from './OCPP20VariableManager.js'
 
 const moduleName = 'OCPP20ServiceUtils'
@@ -86,169 +84,6 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     [OCPP20RequestCommand.TRANSACTION_EVENT, 'TransactionEvent'],
   ]
 
-  /**
-   * Build a TransactionEvent request according to OCPP 2.0.1 specification
-   *
-   * This method creates a properly formatted TransactionEventRequest that complies with
-   * OCPP 2.0.1 requirements including F01, E01, E06, and TriggerReason specifications.
-   *
-   * Key features:
-   * - Automatic per-EVSE sequence number management
-   * - Full TriggerReason validation (21 enum values)
-   * - EVSE/connector mapping and validation
-   * - Transaction UUID handling
-   * - Comprehensive parameter validation
-   * @param chargingStation - The charging station instance
-   * @param eventType - Transaction event type (Started, Updated, Ended)
-   * @param triggerReason - Reason that triggered the event (21 OCPP 2.0.1 values)
-   * @param connectorId - Connector identifier
-   * @param transactionId - Transaction UUID (required for all events)
-   * @param options - Optional parameters for the transaction event
-   * @param options.evseId
-   * @param options.idToken
-   * @param options.meterValue
-   * @param options.chargingState
-   * @param options.stoppedReason
-   * @param options.remoteStartId
-   * @param options.cableMaxCurrent
-   * @param options.numberOfPhasesUsed
-   * @param options.offline
-   * @param options.reservationId
-   * @param options.customData
-   * @returns Promise<OCPP20TransactionEventRequest> - Built transaction event request
-   * @throws {OCPPError} When parameters are invalid or EVSE mapping fails
-   */
-  public static buildTransactionEvent (
-    chargingStation: ChargingStation,
-    eventType: OCPP20TransactionEventEnumType,
-    context: OCPP20TransactionContext,
-    connectorId: number,
-    transactionId: string,
-    options?: OCPP20TransactionEventOptions
-  ): OCPP20TransactionEventRequest
-  public static buildTransactionEvent (
-    chargingStation: ChargingStation,
-    eventType: OCPP20TransactionEventEnumType,
-    triggerReason: OCPP20TriggerReasonEnumType,
-    connectorId: number,
-    transactionId: string,
-    options?: OCPP20TransactionEventOptions
-  ): OCPP20TransactionEventRequest
-  public static buildTransactionEvent (
-    chargingStation: ChargingStation,
-    eventType: OCPP20TransactionEventEnumType,
-    triggerReasonOrContext: OCPP20TransactionContext | OCPP20TriggerReasonEnumType,
-    connectorId: number,
-    transactionId: string,
-    options: OCPP20TransactionEventOptions = {}
-  ): OCPP20TransactionEventRequest {
-    const isContext = typeof triggerReasonOrContext === 'object'
-    const triggerReason = isContext
-      ? this.selectTriggerReason(eventType, triggerReasonOrContext)
-      : triggerReasonOrContext
-
-    // Validate transaction ID format (must be non-empty string ≤36 characters per OCPP 2.0.1)
-    if (!validateIdentifierString(transactionId, 36)) {
-      const errorMsg = `Invalid transaction ID format (must be non-empty string ≤36 characters): ${transactionId}`
-      logger.error(
-        `${chargingStation.logPrefix()} ${moduleName}.buildTransactionEvent: ${errorMsg}`
-      )
-      throw new OCPPError(ErrorType.PROPERTY_CONSTRAINT_VIOLATION, errorMsg)
-    }
-
-    const evseId = options.evseId ?? chargingStation.getEvseIdByConnectorId(connectorId)
-    if (evseId == null) {
-      const errorMsg = `Cannot find EVSE ID for connector ${connectorId.toString()}`
-      logger.error(
-        `${chargingStation.logPrefix()} ${moduleName}.buildTransactionEvent: ${errorMsg}`
-      )
-      throw new OCPPError(ErrorType.PROPERTY_CONSTRAINT_VIOLATION, errorMsg)
-    }
-
-    const connectorStatus = chargingStation.getConnectorStatus(connectorId)
-    if (connectorStatus == null) {
-      const errorMsg = `Cannot find connector status for connector ${connectorId.toString()}`
-      logger.error(
-        `${chargingStation.logPrefix()} ${moduleName}.buildTransactionEvent: ${errorMsg}`
-      )
-      throw new OCPPError(ErrorType.PROPERTY_CONSTRAINT_VIOLATION, errorMsg)
-    }
-
-    // Per-EVSE sequence number management (OCPP 2.0.1 §1.3.2.1)
-    if (connectorStatus.transactionSeqNo == null) {
-      connectorStatus.transactionSeqNo = 0
-    } else {
-      connectorStatus.transactionSeqNo = connectorStatus.transactionSeqNo + 1
-    }
-
-    // Build EVSE object (E01.FR.16: only include in first TransactionEvent after EV connected)
-    let evse: OCPP20EVSEType | undefined
-    if (connectorStatus.transactionEvseSent !== true) {
-      evse = { id: evseId }
-      if (connectorId !== evseId) {
-        evse.connectorId = connectorId
-      }
-      connectorStatus.transactionEvseSent = true
-    }
-
-    const transactionInfo: OCPP20TransactionType = {
-      transactionId: transactionId as UUIDv4,
-    }
-
-    if (options.chargingState !== undefined) {
-      transactionInfo.chargingState = options.chargingState
-    }
-    if (options.stoppedReason !== undefined) {
-      transactionInfo.stoppedReason = options.stoppedReason
-    }
-    if (options.remoteStartId !== undefined) {
-      transactionInfo.remoteStartId = options.remoteStartId
-    }
-
-    const transactionEventRequest: OCPP20TransactionEventRequest = {
-      eventType,
-      seqNo: connectorStatus.transactionSeqNo,
-      timestamp: new Date(),
-      transactionInfo,
-      triggerReason,
-    }
-
-    // E01.FR.16: Include evse only in first TransactionEvent
-    if (evse !== undefined) {
-      transactionEventRequest.evse = evse
-    }
-
-    // E03.FR.01: Include idToken only once per transaction (first event after authorization)
-    if (options.idToken !== undefined && connectorStatus.transactionIdTokenSent !== true) {
-      transactionEventRequest.idToken = options.idToken
-      connectorStatus.transactionIdTokenSent = true
-    }
-    if (options.meterValue !== undefined && options.meterValue.length > 0) {
-      transactionEventRequest.meterValue = options.meterValue
-    }
-    if (options.cableMaxCurrent !== undefined) {
-      transactionEventRequest.cableMaxCurrent = options.cableMaxCurrent
-    }
-    if (options.numberOfPhasesUsed !== undefined) {
-      transactionEventRequest.numberOfPhasesUsed = options.numberOfPhasesUsed
-    }
-    if (options.offline !== undefined) {
-      transactionEventRequest.offline = options.offline
-    }
-    if (options.reservationId !== undefined) {
-      transactionEventRequest.reservationId = options.reservationId
-    }
-    if (options.customData !== undefined) {
-      transactionEventRequest.customData = options.customData
-    }
-
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.buildTransactionEvent: Building TransactionEvent for trigger ${triggerReason}`
-    )
-
-    return transactionEventRequest
-  }
-
   /**
    * OCPP 2.0 Incoming Request Service validator configurations
    * @returns Array of validator configuration tuples
@@ -535,146 +370,6 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     }
   }
 
-  /**
-   * Intelligently select appropriate TriggerReason based on transaction context
-   *
-   * This method implements the E02.FR.17 requirement for context-aware TriggerReason selection.
-   * It analyzes the transaction context to determine the most appropriate TriggerReason according
-   * to OCPP 2.0.1 specification and best practices.
-   *
-   * Selection Logic (by priority):
-   * 1. Remote commands (RequestStartTransaction, RequestStopTransaction, etc.) -> RemoteStart/RemoteStop
-   * 2. Authorization events (token presented) -> Authorized/StopAuthorized/Deauthorized
-   * 3. Cable physical actions -> CablePluggedIn
-   * 4. Charging state transitions -> ChargingStateChanged
-   * 5. System events (EV detection, communication) -> EVDetected/EVDeparted/EVCommunicationLost
-   * 6. Meter value events -> MeterValuePeriodic/MeterValueClock
-   * 7. Energy/Time limits -> EnergyLimitReached/TimeLimitReached
-   * 8. Abnormal conditions -> AbnormalCondition
-   * @param eventType - The transaction event type (Started, Updated, Ended)
-   * @param context - Context information describing the trigger source and details
-   * @returns OCPP20TriggerReasonEnumType - The most appropriate trigger reason
-   */
-  public static selectTriggerReason (
-    eventType: OCPP20TransactionEventEnumType,
-    context: OCPP20TransactionContext
-  ): OCPP20TriggerReasonEnumType {
-    const candidates = OCPP20Constants.TriggerReasonMapping.filter(
-      entry => entry.source === context.source
-    )
-
-    for (const entry of candidates) {
-      if (context.source === 'remote_command' && context.command != null) {
-        if (
-          (context.command === OCPP20IncomingRequestCommand.REQUEST_START_TRANSACTION &&
-            entry.triggerReason === OCPP20TriggerReasonEnumType.RemoteStart) ||
-          (context.command === OCPP20IncomingRequestCommand.REQUEST_STOP_TRANSACTION &&
-            entry.triggerReason === OCPP20TriggerReasonEnumType.RemoteStop) ||
-          (context.command === OCPP20IncomingRequestCommand.RESET &&
-            entry.triggerReason === OCPP20TriggerReasonEnumType.ResetCommand) ||
-          (context.command === OCPP20IncomingRequestCommand.TRIGGER_MESSAGE &&
-            entry.triggerReason === OCPP20TriggerReasonEnumType.Trigger) ||
-          (context.command === OCPP20IncomingRequestCommand.UNLOCK_CONNECTOR &&
-            entry.triggerReason === OCPP20TriggerReasonEnumType.UnlockCommand)
-        ) {
-          return entry.triggerReason
-        }
-      }
-
-      if (context.source === 'local_authorization' && context.authorizationMethod != null) {
-        if (context.isDeauthorized === true) {
-          if (entry.triggerReason === OCPP20TriggerReasonEnumType.Deauthorized) {
-            return entry.triggerReason
-          }
-        } else if (
-          (context.authorizationMethod === 'groupIdToken' ||
-            context.authorizationMethod === 'idToken') &&
-          entry.triggerReason === OCPP20TriggerReasonEnumType.Authorized
-        ) {
-          return entry.triggerReason
-        } else if (
-          context.authorizationMethod === 'stopAuthorized' &&
-          entry.triggerReason === OCPP20TriggerReasonEnumType.StopAuthorized
-        ) {
-          return entry.triggerReason
-        }
-      }
-
-      if (context.source === 'cable_action' && context.cableState != null) {
-        if (
-          (context.cableState === 'detected' &&
-            entry.triggerReason === OCPP20TriggerReasonEnumType.EVDetected) ||
-          (context.cableState === 'plugged_in' &&
-            entry.triggerReason === OCPP20TriggerReasonEnumType.CablePluggedIn) ||
-          (context.cableState === 'unplugged' &&
-            entry.triggerReason === OCPP20TriggerReasonEnumType.EVDeparted)
-        ) {
-          return entry.triggerReason
-        }
-      }
-
-      if (
-        context.source === 'charging_state' &&
-        context.chargingStateChange != null &&
-        entry.triggerReason === OCPP20TriggerReasonEnumType.ChargingStateChanged
-      ) {
-        return entry.triggerReason
-      }
-
-      if (context.source === 'system_event' && context.systemEvent != null) {
-        if (
-          (context.systemEvent === 'ev_communication_lost' &&
-            entry.triggerReason === OCPP20TriggerReasonEnumType.EVCommunicationLost) ||
-          (context.systemEvent === 'ev_connect_timeout' &&
-            entry.triggerReason === OCPP20TriggerReasonEnumType.EVConnectTimeout) ||
-          (context.systemEvent === 'ev_departed' &&
-            entry.triggerReason === OCPP20TriggerReasonEnumType.EVDeparted) ||
-          (context.systemEvent === 'ev_detected' &&
-            entry.triggerReason === OCPP20TriggerReasonEnumType.EVDetected)
-        ) {
-          return entry.triggerReason
-        }
-      }
-
-      if (context.source === 'meter_value') {
-        if (
-          (context.isSignedDataReceived === true &&
-            entry.triggerReason === OCPP20TriggerReasonEnumType.SignedDataReceived) ||
-          (context.isPeriodicMeterValue === true &&
-            entry.triggerReason === OCPP20TriggerReasonEnumType.MeterValuePeriodic) ||
-          (context.isSignedDataReceived !== true &&
-            context.isPeriodicMeterValue !== true &&
-            entry.triggerReason === OCPP20TriggerReasonEnumType.MeterValueClock)
-        ) {
-          return entry.triggerReason
-        }
-      }
-
-      if (
-        (context.source === 'energy_limit' &&
-          entry.triggerReason === OCPP20TriggerReasonEnumType.EnergyLimitReached) ||
-        (context.source === 'time_limit' &&
-          entry.triggerReason === OCPP20TriggerReasonEnumType.TimeLimitReached) ||
-        (context.source === 'external_limit' &&
-          entry.triggerReason === OCPP20TriggerReasonEnumType.ChargingRateChanged)
-      ) {
-        return entry.triggerReason
-      }
-
-      if (
-        context.source === 'abnormal_condition' &&
-        entry.triggerReason === OCPP20TriggerReasonEnumType.AbnormalCondition
-      ) {
-        return entry.triggerReason
-      }
-    }
-
-    logger.warn(
-      `${moduleName}.selectTriggerReason: No matching context found for source '${context.source}', defaulting to Trigger`
-    )
-    return OCPP20TriggerReasonEnumType.Trigger
-  }
-
   public static async sendQueuedTransactionEvents (
     chargingStation: ChargingStation,
     connectorId: number
@@ -715,38 +410,15 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     }
   }
 
-  public static async sendTransactionEvent (
-    chargingStation: ChargingStation,
-    eventType: OCPP20TransactionEventEnumType,
-    context: OCPP20TransactionContext,
-    connectorId: number,
-    transactionId: string,
-    options?: OCPP20TransactionEventOptions
-  ): Promise<OCPP20TransactionEventResponse>
   public static async sendTransactionEvent (
     chargingStation: ChargingStation,
     eventType: OCPP20TransactionEventEnumType,
     triggerReason: OCPP20TriggerReasonEnumType,
     connectorId: number,
     transactionId: string,
-    options?: OCPP20TransactionEventOptions
-  ): Promise<OCPP20TransactionEventResponse>
-  // Implementation with union type + type guard
-  public static async sendTransactionEvent (
-    chargingStation: ChargingStation,
-    eventType: OCPP20TransactionEventEnumType,
-    triggerReasonOrContext: OCPP20TransactionContext | OCPP20TriggerReasonEnumType,
-    connectorId: number,
-    transactionId: string,
     options: OCPP20TransactionEventOptions = {}
   ): Promise<OCPP20TransactionEventResponse> {
     try {
-      // Type guard: distinguish between context object and direct trigger reason
-      const isContext = typeof triggerReasonOrContext === 'object'
-      const triggerReason = isContext
-        ? this.selectTriggerReason(eventType, triggerReasonOrContext)
-        : triggerReasonOrContext
-
       const connectorStatus = chargingStation.getConnectorStatus(connectorId)
       if (connectorStatus == null) {
         const errorMsg = `Cannot find connector status for connector ${connectorId.toString()}`
@@ -758,7 +430,7 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
 
       // Offline: build and queue pre-built payload (sent as-is via rawPayload on reconnect)
       if (!chargingStation.isWebSocketConnectionOpened()) {
-        const transactionEventRequest = OCPP20ServiceUtils.buildTransactionEvent(
+        const transactionEventRequest = buildTransactionEvent(
           chargingStation,
           eventType,
           triggerReason,
@@ -804,3 +476,161 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils {
     }
   }
 }
+export function buildTransactionEvent (
+  chargingStation: ChargingStation,
+  eventType: OCPP20TransactionEventEnumType,
+  triggerReason: OCPP20TriggerReasonEnumType,
+  connectorId: number,
+  transactionId: string,
+  options?: OCPP20TransactionEventOptions
+): OCPP20TransactionEventRequest
+export function buildTransactionEvent (
+  chargingStation: ChargingStation,
+  commandParams: JsonObject
+): OCPP20TransactionEventRequest
+/**
+ *
+ * @param chargingStation
+ * @param eventTypeOrParams
+ * @param triggerReasonArg
+ * @param connectorIdArg
+ * @param transactionIdArg
+ * @param options
+ */
+export function buildTransactionEvent (
+  chargingStation: ChargingStation,
+  eventTypeOrParams: JsonObject | OCPP20TransactionEventEnumType,
+  triggerReasonArg?: OCPP20TriggerReasonEnumType,
+  connectorIdArg?: number,
+  transactionIdArg?: string,
+  options: OCPP20TransactionEventOptions = {}
+): OCPP20TransactionEventRequest {
+  let eventType: OCPP20TransactionEventEnumType
+  let triggerReason: OCPP20TriggerReasonEnumType
+  let connectorId: number
+  let transactionId: string
+
+  if (typeof eventTypeOrParams === 'object') {
+    const params = eventTypeOrParams
+    eventType = params.eventType as OCPP20TransactionEventEnumType
+    triggerReason =
+      params.triggerReason != null
+        ? (params.triggerReason as OCPP20TriggerReasonEnumType)
+        : eventType === OCPP20TransactionEventEnumType.Ended
+          ? OCPP20TriggerReasonEnumType.RemoteStop
+          : OCPP20TriggerReasonEnumType.Authorized
+    const evse = params.evse as undefined | { connectorId?: number; id?: number }
+    connectorId =
+      params.connectorId != null
+        ? (params.connectorId as number)
+        : (evse?.connectorId ?? evse?.id ?? 1)
+    transactionId =
+      params.transactionId != null
+        ? (params.transactionId as string)
+        : eventType === OCPP20TransactionEventEnumType.Ended
+          ? (chargingStation.getConnectorStatus(connectorId)?.transactionId?.toString() ??
+            generateUUID())
+          : generateUUID()
+    options = params as unknown as OCPP20TransactionEventOptions
+  } else {
+    eventType = eventTypeOrParams
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    triggerReason = triggerReasonArg!
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    connectorId = connectorIdArg!
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    transactionId = transactionIdArg!
+  }
+
+  if (!validateIdentifierString(transactionId, 36)) {
+    const errorMsg = `Invalid transaction ID format (must be non-empty string ≤36 characters): ${transactionId}`
+    logger.error(`${chargingStation.logPrefix()} ${moduleName}.buildTransactionEvent: ${errorMsg}`)
+    throw new OCPPError(ErrorType.PROPERTY_CONSTRAINT_VIOLATION, errorMsg)
+  }
+
+  const evseId = options.evseId ?? chargingStation.getEvseIdByConnectorId(connectorId)
+  if (evseId == null) {
+    const errorMsg = `Cannot find EVSE ID for connector ${connectorId.toString()}`
+    logger.error(`${chargingStation.logPrefix()} ${moduleName}.buildTransactionEvent: ${errorMsg}`)
+    throw new OCPPError(ErrorType.PROPERTY_CONSTRAINT_VIOLATION, errorMsg)
+  }
+
+  const connectorStatus = chargingStation.getConnectorStatus(connectorId)
+  if (connectorStatus == null) {
+    const errorMsg = `Cannot find connector status for connector ${connectorId.toString()}`
+    logger.error(`${chargingStation.logPrefix()} ${moduleName}.buildTransactionEvent: ${errorMsg}`)
+    throw new OCPPError(ErrorType.PROPERTY_CONSTRAINT_VIOLATION, errorMsg)
+  }
+
+  if (connectorStatus.transactionSeqNo == null) {
+    connectorStatus.transactionSeqNo = 0
+  } else {
+    connectorStatus.transactionSeqNo = connectorStatus.transactionSeqNo + 1
+  }
+
+  // E01.FR.16: only include EVSE in first TransactionEvent
+  let evse: OCPP20EVSEType | undefined
+  if (connectorStatus.transactionEvseSent !== true) {
+    evse = { id: evseId }
+    if (connectorId !== evseId) {
+      evse.connectorId = connectorId
+    }
+    connectorStatus.transactionEvseSent = true
+  }
+
+  const transactionInfo: OCPP20TransactionType = {
+    transactionId: transactionId as UUIDv4,
+  }
+
+  if (options.chargingState !== undefined) {
+    transactionInfo.chargingState = options.chargingState
+  }
+  if (options.stoppedReason !== undefined) {
+    transactionInfo.stoppedReason = options.stoppedReason
+  }
+  if (options.remoteStartId !== undefined) {
+    transactionInfo.remoteStartId = options.remoteStartId
+  }
+
+  const transactionEventRequest: OCPP20TransactionEventRequest = {
+    eventType,
+    seqNo: connectorStatus.transactionSeqNo,
+    timestamp: new Date(),
+    transactionInfo,
+    triggerReason,
+  }
+
+  if (evse !== undefined) {
+    transactionEventRequest.evse = evse
+  }
+
+  // E03.FR.01: Include idToken only once per transaction
+  if (options.idToken !== undefined && connectorStatus.transactionIdTokenSent !== true) {
+    transactionEventRequest.idToken = options.idToken
+    connectorStatus.transactionIdTokenSent = true
+  }
+  if (options.meterValue !== undefined && options.meterValue.length > 0) {
+    transactionEventRequest.meterValue = options.meterValue
+  }
+  if (options.cableMaxCurrent !== undefined) {
+    transactionEventRequest.cableMaxCurrent = options.cableMaxCurrent
+  }
+  if (options.numberOfPhasesUsed !== undefined) {
+    transactionEventRequest.numberOfPhasesUsed = options.numberOfPhasesUsed
+  }
+  if (options.offline !== undefined) {
+    transactionEventRequest.offline = options.offline
+  }
+  if (options.reservationId !== undefined) {
+    transactionEventRequest.reservationId = options.reservationId
+  }
+  if (options.customData !== undefined) {
+    transactionEventRequest.customData = options.customData
+  }
+
+  logger.debug(
+    `${chargingStation.logPrefix()} ${moduleName}.buildTransactionEvent: Building TransactionEvent for trigger ${triggerReason}`
+  )
+
+  return transactionEventRequest
+}
index 0aab4035501aebcaa77740959b9ab0dcf5e95cbe..960b0952c095f487ea6b4765ae47ffb2a8a13029 100644 (file)
@@ -8,13 +8,14 @@ export { OCPP16ResponseService } from './1.6/OCPP16ResponseService.js'
 export { OCPP20IncomingRequestService } from './2.0/OCPP20IncomingRequestService.js'
 export { OCPP20RequestService } from './2.0/OCPP20RequestService.js'
 export { OCPP20ResponseService } from './2.0/OCPP20ResponseService.js'
-export { OCPP20ServiceUtils } from './2.0/OCPP20ServiceUtils.js'
+export { buildTransactionEvent, OCPP20ServiceUtils } from './2.0/OCPP20ServiceUtils.js'
 export { OCPP20VariableManager } from './2.0/OCPP20VariableManager.js'
 export { OCPPAuthServiceFactory } from './auth/services/OCPPAuthServiceFactory.js'
 export { OCPPIncomingRequestService } from './OCPPIncomingRequestService.js'
 export { OCPPRequestService } from './OCPPRequestService.js'
 export {
   buildMeterValue,
+  buildStatusNotificationRequest,
   buildTransactionEndMeterValue,
   getMessageTypeString,
   isIdTagAuthorized,
index bf11c9d5ee0418d58c2c74ceaf26a1f9bb468a1d..415efe47d2a99c4907cb5ffc0dd393e440a8eb75 100644 (file)
@@ -2,7 +2,6 @@ import type { JsonObject } from '../../JsonType.js'
 import type { UUIDv4 } from '../../UUID.js'
 import type { CustomDataType } from './Common.js'
 import type { OCPP20MeterValue } from './MeterValues.js'
-import type { OCPP20IncomingRequestCommand } from './Requests.js'
 
 export enum CostKindEnumType {
   CarbonDioxideEmission = 'CarbonDioxideEmission',
@@ -267,58 +266,6 @@ export interface OCPP20MessageContentType extends JsonObject {
   format: OCPP20MessageFormatEnumType
   language?: string
 }
-
-/**
- * Context information for intelligent TriggerReason selection
- * Used by OCPP20ServiceUtils.selectTriggerReason() to determine appropriate trigger reason
- */
-export interface OCPP20TransactionContext {
-  /** Abnormal condition type (for abnormal_condition source) */
-  abnormalCondition?: string
-
-  /** Authorization method used (for local_authorization source) */
-  authorizationMethod?: 'groupIdToken' | 'idToken' | 'stopAuthorized'
-
-  /** Cable connection state (for cable_action source) */
-  cableState?: 'detected' | 'plugged_in' | 'unplugged'
-
-  /** Charging state change details (for charging_state source) */
-  chargingStateChange?: {
-    from?: OCPP20ChargingStateEnumType
-    to?: OCPP20ChargingStateEnumType
-  }
-
-  /** Specific command that triggered the event (for remote_command source) */
-  command?: OCPP20IncomingRequestCommand
-
-  hasRemoteStartId?: boolean
-
-  isDeauthorized?: boolean
-
-  /** Additional context flags */
-  isOffline?: boolean
-
-  /** Whether this is a periodic meter value event */
-  isPeriodicMeterValue?: boolean
-
-  /** Whether this is a signed data reception event */
-  isSignedDataReceived?: boolean
-  /** Source of the transaction event - command, authorization, physical action, etc. */
-  source:
-    | 'abnormal_condition'
-    | 'cable_action'
-    | 'charging_state'
-    | 'energy_limit'
-    | 'external_limit'
-    | 'local_authorization'
-    | 'meter_value'
-    | 'remote_command'
-    | 'system_event'
-    | 'time_limit'
-  /** System event details (for system_event source) */
-  systemEvent?: 'ev_communication_lost' | 'ev_connect_timeout' | 'ev_departed' | 'ev_detected'
-}
-
 /**
  * Optional parameters for building and sending TransactionEvent requests.
  * Aligned with OCPP 2.0.1 TransactionEvent.req optional fields.
index dd1d4ac54227e2cb6326be09343956dafef43700..88cbc11a9b545edb6dd09400896a0a15bab046ca 100644 (file)
@@ -16,20 +16,21 @@ import { afterEach, beforeEach, describe, it, mock } from 'node:test'
 import type { ChargingStation } from '../../../../src/charging-station/ChargingStation.js'
 import type { EmptyObject } from '../../../../src/types/index.js'
 
-import { OCPP20ServiceUtils } from '../../../../src/charging-station/ocpp/2.0/OCPP20ServiceUtils.js'
+import {
+  buildTransactionEvent,
+  OCPP20ServiceUtils,
+} from '../../../../src/charging-station/ocpp/2.0/OCPP20ServiceUtils.js'
 import {
   ConnectorStatusEnum,
   OCPP20TransactionEventEnumType,
   OCPP20TriggerReasonEnumType,
   OCPPVersion,
 } from '../../../../src/types/index.js'
-import { OCPP20IncomingRequestCommand } from '../../../../src/types/ocpp/2.0/Requests.js'
 import {
   OCPP20ChargingStateEnumType,
   OCPP20IdTokenEnumType,
   type OCPP20IdTokenType,
   OCPP20ReasonEnumType,
-  type OCPP20TransactionContext,
   type OCPP20TransactionType,
 } from '../../../../src/types/ocpp/2.0/Transaction.js'
 import { Constants, generateUUID } from '../../../../src/utils/index.js'
@@ -42,7 +43,6 @@ import {
   type MockStationWithTracking,
   resetConnectorTransactionState,
   resetLimits,
-  TransactionContextFixtures,
 } from './OCPP20TestUtils.js'
 // ============================================================================
 // Transaction Flow Patterns for Parameterized Testing
@@ -59,7 +59,6 @@ const TRANSACTION_FLOWS = [
     id: 'cableFirst',
     includeIdToken: false,
     name: 'E02 - Cable-First',
-    startContext: TransactionContextFixtures.cablePluggedIn(),
   },
   {
     description: 'E03 IdToken-First',
@@ -67,7 +66,6 @@ const TRANSACTION_FLOWS = [
     id: 'idTokenFirst',
     includeIdToken: true,
     name: 'E03 - IdToken-First',
-    startContext: TransactionContextFixtures.idTokenAuthorized(),
   },
   {
     description: 'Remote Start',
@@ -75,7 +73,6 @@ const TRANSACTION_FLOWS = [
     id: 'remoteStart',
     includeIdToken: false,
     name: 'Remote Start',
-    startContext: TransactionContextFixtures.remoteStart(),
   },
 ] as const
 
@@ -116,7 +113,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
         // Reset sequence number to simulate new transaction
         OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
-        const transactionEvent = OCPP20ServiceUtils.buildTransactionEvent(
+        const transactionEvent = buildTransactionEvent(
           mockStation,
           OCPP20TransactionEventEnumType.Started,
           triggerReason,
@@ -151,7 +148,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
         OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
         // Build first event (Started)
-        const startEvent = OCPP20ServiceUtils.buildTransactionEvent(
+        const startEvent = buildTransactionEvent(
           mockStation,
           OCPP20TransactionEventEnumType.Started,
           OCPP20TriggerReasonEnumType.Authorized,
@@ -160,7 +157,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
         )
 
         // Build second event (Updated)
-        const updateEvent = OCPP20ServiceUtils.buildTransactionEvent(
+        const updateEvent = buildTransactionEvent(
           mockStation,
           OCPP20TransactionEventEnumType.Updated,
           OCPP20TriggerReasonEnumType.MeterValuePeriodic,
@@ -169,7 +166,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
         )
 
         // Build third event (Ended)
-        const endEvent = OCPP20ServiceUtils.buildTransactionEvent(
+        const endEvent = buildTransactionEvent(
           mockStation,
           OCPP20TransactionEventEnumType.Ended,
           OCPP20TriggerReasonEnumType.StopAuthorized,
@@ -205,7 +202,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           reservationId: 67890,
         }
 
-        const transactionEvent = OCPP20ServiceUtils.buildTransactionEvent(
+        const transactionEvent = buildTransactionEvent(
           mockStation,
           OCPP20TransactionEventEnumType.Updated,
           OCPP20TriggerReasonEnumType.ChargingStateChanged,
@@ -237,7 +234,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           'this-string-is-way-too-long-for-a-valid-transaction-id-exceeds-36-chars'
 
         try {
-          OCPP20ServiceUtils.buildTransactionEvent(
+          buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
             OCPP20TriggerReasonEnumType.Authorized,
@@ -284,7 +281,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
         OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
         for (const triggerReason of triggerReasons) {
-          const transactionEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const transactionEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Updated,
             triggerReason,
@@ -360,7 +357,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
         const connectorId = 1
 
         // First, build a transaction event to set sequence number
-        OCPP20ServiceUtils.buildTransactionEvent(
+        buildTransactionEvent(
           mockStation,
           OCPP20TransactionEventEnumType.Started,
           OCPP20TriggerReasonEnumType.Authorized,
@@ -397,7 +394,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
 
         OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
-        const transactionEvent = OCPP20ServiceUtils.buildTransactionEvent(
+        const transactionEvent = buildTransactionEvent(
           mockStation,
           OCPP20TransactionEventEnumType.Started,
           OCPP20TriggerReasonEnumType.Authorized,
@@ -458,7 +455,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
 
         OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
-        const transactionEvent = OCPP20ServiceUtils.buildTransactionEvent(
+        const transactionEvent = buildTransactionEvent(
           mockStation,
           OCPP20TransactionEventEnumType.Started,
           OCPP20TriggerReasonEnumType.Authorized,
@@ -478,626 +475,276 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
       })
     })
 
-    // FR: E01.FR.04 - TriggerReason selection based on transaction context
-    await describe('Context-Aware TriggerReason Selection', async () => {
-      await describe('selectTriggerReason', async () => {
-        await it('should select RemoteStart for remote_command context with RequestStartTransaction', () => {
-          const context: OCPP20TransactionContext = {
-            command: OCPP20IncomingRequestCommand.REQUEST_START_TRANSACTION,
-            source: 'remote_command',
-          }
-
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Started,
-            context
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.RemoteStart)
-        })
-
-        await it('should select RemoteStop for remote_command context with RequestStopTransaction', () => {
-          const context: OCPP20TransactionContext = {
-            command: OCPP20IncomingRequestCommand.REQUEST_STOP_TRANSACTION,
-            source: 'remote_command',
-          }
-
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Ended,
-            context
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.RemoteStop)
-        })
-
-        await it('should select UnlockCommand for remote_command context with UnlockConnector', () => {
-          const context: OCPP20TransactionContext = {
-            command: OCPP20IncomingRequestCommand.UNLOCK_CONNECTOR,
-            source: 'remote_command',
-          }
-
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Updated,
-            context
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.UnlockCommand)
-        })
-
-        await it('should select ResetCommand for remote_command context with Reset', () => {
-          const context: OCPP20TransactionContext = {
-            command: OCPP20IncomingRequestCommand.RESET,
-            source: 'remote_command',
-          }
-
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Ended,
-            context
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.ResetCommand)
-        })
-
-        await it('should select Trigger for remote_command context with TriggerMessage', () => {
-          const context: OCPP20TransactionContext = {
-            command: OCPP20IncomingRequestCommand.TRIGGER_MESSAGE,
-            source: 'remote_command',
-          }
-
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Updated,
-            context
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Trigger)
-        })
-
-        await it('should select Authorized for local_authorization context with idToken', () => {
-          const context: OCPP20TransactionContext = {
-            authorizationMethod: 'idToken',
-            source: 'local_authorization',
-          }
-
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Started,
-            context
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Authorized)
-        })
-
-        await it('should select StopAuthorized for local_authorization context with stopAuthorized', () => {
-          const context: OCPP20TransactionContext = {
-            authorizationMethod: 'stopAuthorized',
-            source: 'local_authorization',
-          }
-
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Ended,
-            context
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.StopAuthorized)
-        })
-
-        await it('should select Deauthorized when isDeauthorized flag is true', () => {
-          const context: OCPP20TransactionContext = {
-            authorizationMethod: 'idToken',
-            isDeauthorized: true,
-            source: 'local_authorization',
-          }
-
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Ended,
-            context
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Deauthorized)
-        })
-
-        await it('should select ChargingStateChanged for charging_state context', () => {
-          const context: OCPP20TransactionContext = {
-            chargingStateChange: {
-              from: OCPP20ChargingStateEnumType.Idle,
-              to: OCPP20ChargingStateEnumType.Charging,
-            },
-            source: 'charging_state',
-          }
-
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Updated,
-            context
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.ChargingStateChanged)
-        })
-
-        await it('should select MeterValuePeriodic for meter_value context with periodic flag', () => {
-          const context: OCPP20TransactionContext = {
-            isPeriodicMeterValue: true,
-            source: 'meter_value',
-          }
-
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Updated,
-            context
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.MeterValuePeriodic)
-        })
-
-        await it('should select MeterValueClock for meter_value context without periodic flag', () => {
-          const context: OCPP20TransactionContext = {
-            isPeriodicMeterValue: false,
-            source: 'meter_value',
-          }
-
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Updated,
-            context
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.MeterValueClock)
-        })
-
-        await it('should select SignedDataReceived when isSignedDataReceived flag is true', () => {
-          const context: OCPP20TransactionContext = {
-            isSignedDataReceived: true,
-            source: 'meter_value',
-          }
+    await describe('sendTransactionEvent with context parameter', async () => {
+      await it('should send TransactionEvent with context-aware TriggerReason selection', async () => {
+        const connectorId = 1
+        const transactionId = generateUUID()
 
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Updated,
-            context
-          )
+        const response = await OCPP20ServiceUtils.sendTransactionEvent(
+          mockStation,
+          OCPP20TransactionEventEnumType.Started,
+          OCPP20TriggerReasonEnumType.CablePluggedIn,
+          connectorId,
+          transactionId
+        )
 
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.SignedDataReceived)
-        })
+        // Validate response structure
+        assert.notStrictEqual(response, undefined)
+        assert.strictEqual(typeof response, 'object')
+      })
 
-        await it('should select appropriate system events for system_event context', () => {
-          const testCases = [
-            {
-              expected: OCPP20TriggerReasonEnumType.EVDeparted,
-              systemEvent: 'ev_departed' as const,
-            },
-            {
-              expected: OCPP20TriggerReasonEnumType.EVDetected,
-              systemEvent: 'ev_detected' as const,
-            },
-            {
-              expected: OCPP20TriggerReasonEnumType.EVCommunicationLost,
-              systemEvent: 'ev_communication_lost' as const,
-            },
-            {
-              expected: OCPP20TriggerReasonEnumType.EVConnectTimeout,
-              systemEvent: 'ev_connect_timeout' as const,
+      await it('should handle context-aware error scenarios gracefully', async () => {
+        // Create error mock for this test
+        const { station: errorMockChargingStation } = createMockChargingStation({
+          baseName: TEST_CHARGING_STATION_BASE_NAME,
+          connectorsCount: 1,
+          evseConfiguration: { evsesCount: 1 },
+          heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+          ocppRequestService: {
+            requestHandler: () => {
+              throw new Error('Context test error')
             },
-          ]
-
-          for (const testCase of testCases) {
-            const context: OCPP20TransactionContext = {
-              source: 'system_event',
-              systemEvent: testCase.systemEvent,
-            }
-
-            const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-              OCPP20TransactionEventEnumType.Updated,
-              context
-            )
-
-            assert.strictEqual(triggerReason, testCase.expected)
-          }
+          },
+          stationInfo: {
+            ocppStrictCompliance: true,
+            ocppVersion: OCPPVersion.VERSION_201,
+          },
+          websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
         })
 
-        await it('should select EnergyLimitReached for energy_limit context', () => {
-          const context: OCPP20TransactionContext = {
-            source: 'energy_limit',
-          }
+        const connectorId = 1
+        const transactionId = generateUUID()
 
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
+        try {
+          await OCPP20ServiceUtils.sendTransactionEvent(
+            errorMockChargingStation,
             OCPP20TransactionEventEnumType.Ended,
-            context
+            OCPP20TriggerReasonEnumType.AbnormalCondition,
+            connectorId,
+            transactionId
           )
+          throw new Error('Should have thrown error')
+        } catch (error) {
+          assert.ok((error as Error).message.includes('Context test error'))
+        }
+      })
+    })
 
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.EnergyLimitReached)
-        })
+    await describe('Backward Compatibility', async () => {
+      await it('should maintain compatibility with existing buildTransactionEvent calls', () => {
+        const connectorId = 1
+        const transactionId = generateUUID()
 
-        await it('should select TimeLimitReached for time_limit context', () => {
-          const context: OCPP20TransactionContext = {
-            source: 'time_limit',
-          }
+        OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Ended,
-            context
-          )
+        // Old method call should still work
+        const oldEvent = buildTransactionEvent(
+          mockStation,
+          OCPP20TransactionEventEnumType.Started,
+          OCPP20TriggerReasonEnumType.Authorized,
+          connectorId,
+          transactionId
+        )
 
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.TimeLimitReached)
-        })
+        assert.strictEqual(oldEvent.eventType, OCPP20TransactionEventEnumType.Started)
+        assert.strictEqual(oldEvent.triggerReason, OCPP20TriggerReasonEnumType.Authorized)
+        assert.strictEqual(oldEvent.seqNo, 0)
+      })
 
-        await it('should select AbnormalCondition for abnormal_condition context', () => {
-          const context: OCPP20TransactionContext = {
-            abnormalCondition: 'OverCurrent',
-            source: 'abnormal_condition',
-          }
+      await it('should maintain compatibility with existing sendTransactionEvent calls', async () => {
+        const connectorId = 1
+        const transactionId = generateUUID()
 
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Ended,
-            context
-          )
+        // Old method call should still work
+        const response = await OCPP20ServiceUtils.sendTransactionEvent(
+          mockStation,
+          OCPP20TransactionEventEnumType.Started,
+          OCPP20TriggerReasonEnumType.Authorized,
+          connectorId,
+          transactionId
+        )
 
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.AbnormalCondition)
-        })
+        assert.notStrictEqual(response, undefined)
+        assert.strictEqual(typeof response, 'object')
+      })
+    })
+  })
 
-        await it('should handle priority ordering with multiple applicable contexts', () => {
-          // Test context with multiple applicable triggers - priority should be respected
-          const context: OCPP20TransactionContext = {
-            cableState: 'plugged_in', // Even lower priority
-            command: OCPP20IncomingRequestCommand.REQUEST_START_TRANSACTION,
-            isDeauthorized: true, // Lower priority but should be overridden
-            source: 'remote_command', // High priority
-          }
+  // ==========================================================================
+  // Parameterized Transaction Flow Tests (E02, E03, Remote Start)
+  // ==========================================================================
+  await describe('Transaction Flow Patterns', async () => {
+    let mockStation: ChargingStation
 
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Started,
-            context
-          )
+    beforeEach(() => {
+      const { station } = createMockChargingStation({
+        baseName: TEST_CHARGING_STATION_BASE_NAME,
+        connectorsCount: 3,
+        evseConfiguration: { evsesCount: 3 },
+        heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+        ocppRequestService: {
+          requestHandler: async () => Promise.resolve({} as EmptyObject),
+        },
+        stationInfo: {
+          ocppStrictCompliance: true,
+          ocppVersion: OCPPVersion.VERSION_201,
+        },
+        websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+      })
+      mockStation = station
+      resetLimits(mockStation)
+    })
 
-          // Should select RemoteStart (priority 1) over Deauthorized (priority 2) or CablePluggedIn (priority 3)
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.RemoteStart)
-        })
+    afterEach(() => {
+      standardCleanup()
+    })
 
-        await it('should fallback to Trigger for unknown context source', () => {
-          const context: OCPP20TransactionContext = {
-            source: 'unknown_source' as OCPP20TransactionContext['source'], // Invalid source to test fallback
-          }
+    for (const {
+      description,
+      expectedStartTrigger,
+      id,
+      includeIdToken,
+      name,
+    } of TRANSACTION_FLOWS) {
+      await describe(`${name} Flow`, async () => {
+        await it(`should build correct Started event for ${description}`, () => {
+          const connectorId = 1
+          const transactionId = generateUUID()
+          const idToken: OCPP20IdTokenType | undefined = includeIdToken
+            ? { idToken: `${id.toUpperCase()}_TOKEN_001`, type: OCPP20IdTokenEnumType.ISO14443 }
+            : undefined
+
+          OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
+          const startedEvent = buildTransactionEvent(
+            mockStation,
             OCPP20TransactionEventEnumType.Started,
-            context
+            expectedStartTrigger,
+            connectorId,
+            transactionId,
+            idToken != null ? { idToken } : undefined
           )
 
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Trigger)
-        })
+          assert.strictEqual(startedEvent.eventType, OCPP20TransactionEventEnumType.Started)
+          assert.strictEqual(startedEvent.triggerReason, expectedStartTrigger)
+          assert.strictEqual(startedEvent.seqNo, 0)
+          assert.strictEqual(startedEvent.transactionInfo.transactionId, transactionId)
 
-        await it('should fallback to Trigger for incomplete context', () => {
-          const context: OCPP20TransactionContext = {
-            source: 'remote_command',
-            // Missing command field
+          if (includeIdToken) {
+            assert.notStrictEqual(startedEvent.idToken, undefined)
+            assert.strictEqual(startedEvent.idToken?.idToken, `${id.toUpperCase()}_TOKEN_001`)
           }
-
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Started,
-            context
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Trigger)
         })
-      })
 
-      await describe('buildTransactionEvent with context parameter', async () => {
-        await it('should build TransactionEvent with auto-selected TriggerReason from context', () => {
+        await it(`should support complete ${description} transaction lifecycle`, () => {
           const connectorId = 1
           const transactionId = generateUUID()
-          const context: OCPP20TransactionContext = {
-            command: OCPP20IncomingRequestCommand.REQUEST_START_TRANSACTION,
-            source: 'remote_command',
-          }
+          const idToken: OCPP20IdTokenType | undefined = includeIdToken
+            ? {
+                idToken: `${id.toUpperCase()}_LIFECYCLE_001`,
+                type: OCPP20IdTokenEnumType.ISO14443,
+              }
+            : undefined
 
           OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
-          const transactionEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          // Step 1: Started event
+          const startedEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
-            context,
+            expectedStartTrigger,
             connectorId,
-            transactionId
-          )
-
-          assert.strictEqual(transactionEvent.eventType, OCPP20TransactionEventEnumType.Started)
-          assert.strictEqual(
-            transactionEvent.triggerReason,
-            OCPP20TriggerReasonEnumType.RemoteStart
+            transactionId,
+            idToken != null ? { idToken } : undefined
           )
-          assert.strictEqual(transactionEvent.seqNo, 0)
-          assert.strictEqual(transactionEvent.transactionInfo.transactionId, transactionId)
-        })
-
-        await it('should pass through optional parameters correctly', () => {
-          const connectorId = 2
-          const transactionId = generateUUID()
-          const context: OCPP20TransactionContext = {
-            authorizationMethod: 'idToken',
-            source: 'local_authorization',
-          }
-          const options = {
-            chargingState: OCPP20ChargingStateEnumType.Charging,
-            idToken: {
-              idToken: 'CONTEXT_TEST_TOKEN',
-              type: OCPP20IdTokenEnumType.ISO14443,
-            },
-          }
 
-          const transactionEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          // Step 2: Charging state change
+          const chargingEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Updated,
-            context,
+            OCPP20TriggerReasonEnumType.ChargingStateChanged,
             connectorId,
             transactionId,
-            options
-          )
-
-          assert.strictEqual(transactionEvent.triggerReason, OCPP20TriggerReasonEnumType.Authorized)
-          assert.strictEqual(transactionEvent.idToken?.idToken, 'CONTEXT_TEST_TOKEN')
-          assert.strictEqual(
-            transactionEvent.transactionInfo.chargingState,
-            OCPP20ChargingStateEnumType.Charging
+            { chargingState: OCPP20ChargingStateEnumType.Charging }
           )
-        })
-      })
-
-      await describe('sendTransactionEvent with context parameter', async () => {
-        await it('should send TransactionEvent with context-aware TriggerReason selection', async () => {
-          const connectorId = 1
-          const transactionId = generateUUID()
-          const context: OCPP20TransactionContext = {
-            cableState: 'plugged_in',
-            source: 'cable_action',
-          }
 
-          const response = await OCPP20ServiceUtils.sendTransactionEvent(
+          // Step 3: Ended event
+          const endedEvent = buildTransactionEvent(
             mockStation,
-            OCPP20TransactionEventEnumType.Started,
-            context,
+            OCPP20TransactionEventEnumType.Ended,
+            OCPP20TriggerReasonEnumType.StopAuthorized,
             connectorId,
             transactionId
           )
 
-          // Validate response structure
-          assert.notStrictEqual(response, undefined)
-          assert.strictEqual(typeof response, 'object')
-        })
-
-        await it('should handle context-aware error scenarios gracefully', async () => {
-          // Create error mock for this test
-          const { station: errorMockChargingStation } = createMockChargingStation({
-            baseName: TEST_CHARGING_STATION_BASE_NAME,
-            connectorsCount: 1,
-            evseConfiguration: { evsesCount: 1 },
-            heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
-            ocppRequestService: {
-              requestHandler: () => {
-                throw new Error('Context test error')
-              },
-            },
-            stationInfo: {
-              ocppStrictCompliance: true,
-              ocppVersion: OCPPVersion.VERSION_201,
-            },
-            websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
-          })
-
-          const connectorId = 1
-          const transactionId = generateUUID()
-          const context: OCPP20TransactionContext = {
-            abnormalCondition: 'TestError',
-            source: 'abnormal_condition',
-          }
+          // Validate event sequence
+          assert.strictEqual(startedEvent.seqNo, 0)
+          assert.strictEqual(chargingEvent.seqNo, 1)
+          assert.strictEqual(endedEvent.seqNo, 2)
 
-          try {
-            await OCPP20ServiceUtils.sendTransactionEvent(
-              errorMockChargingStation,
-              OCPP20TransactionEventEnumType.Ended,
-              context,
-              connectorId,
-              transactionId
-            )
-            throw new Error('Should have thrown error')
-          } catch (error) {
-            assert.ok((error as Error).message.includes('Context test error'))
-          }
+          // All events share same transaction ID
+          assert.strictEqual(startedEvent.transactionInfo.transactionId, transactionId)
+          assert.strictEqual(chargingEvent.transactionInfo.transactionId, transactionId)
+          assert.strictEqual(endedEvent.transactionInfo.transactionId, transactionId)
         })
-      })
 
-      await describe('Backward Compatibility', async () => {
-        await it('should maintain compatibility with existing buildTransactionEvent calls', () => {
-          const connectorId = 1
-          const transactionId = generateUUID()
+        await it(`should maintain independent sequence numbers on different connectors for ${description}`, () => {
+          const connector1 = 1
+          const connector2 = 2
+          const transaction1Id = generateUUID()
+          const transaction2Id = generateUUID()
 
-          OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
+          OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connector1)
+          OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connector2)
 
-          // Old method call should still work
-          const oldEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          // Start transaction on connector 1
+          const conn1Event1 = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
-            OCPP20TriggerReasonEnumType.Authorized,
-            connectorId,
-            transactionId
+            expectedStartTrigger,
+            connector1,
+            transaction1Id
           )
 
-          assert.strictEqual(oldEvent.eventType, OCPP20TransactionEventEnumType.Started)
-          assert.strictEqual(oldEvent.triggerReason, OCPP20TriggerReasonEnumType.Authorized)
-          assert.strictEqual(oldEvent.seqNo, 0)
-        })
-
-        await it('should maintain compatibility with existing sendTransactionEvent calls', async () => {
-          const connectorId = 1
-          const transactionId = generateUUID()
-
-          // Old method call should still work
-          const response = await OCPP20ServiceUtils.sendTransactionEvent(
+          // Start transaction on connector 2
+          const conn2Event1 = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
-            OCPP20TriggerReasonEnumType.Authorized,
-            connectorId,
-            transactionId
+            expectedStartTrigger,
+            connector2,
+            transaction2Id
           )
 
-          assert.notStrictEqual(response, undefined)
-          assert.strictEqual(typeof response, 'object')
-        })
-      })
-    })
-
-    // ==========================================================================
-    // Parameterized Transaction Flow Tests (E02, E03, Remote Start)
-    // ==========================================================================
-    await describe('Transaction Flow Patterns', async () => {
-      for (const {
-        description,
-        expectedStartTrigger,
-        id,
-        includeIdToken,
-        name,
-        startContext,
-      } of TRANSACTION_FLOWS) {
-        await describe(`${name} Flow`, async () => {
-          await it(`should select ${expectedStartTrigger} trigger for ${description} transaction start`, () => {
-            const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-              OCPP20TransactionEventEnumType.Started,
-              startContext
-            )
-            assert.strictEqual(triggerReason, expectedStartTrigger)
-          })
-
-          await it(`should build correct Started event for ${description}`, () => {
-            const connectorId = 1
-            const transactionId = generateUUID()
-            const idToken: OCPP20IdTokenType | undefined = includeIdToken
-              ? { idToken: `${id.toUpperCase()}_TOKEN_001`, type: OCPP20IdTokenEnumType.ISO14443 }
-              : undefined
-
-            OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
-
-            const startedEvent = OCPP20ServiceUtils.buildTransactionEvent(
-              mockStation,
-              OCPP20TransactionEventEnumType.Started,
-              expectedStartTrigger,
-              connectorId,
-              transactionId,
-              idToken != null ? { idToken } : undefined
-            )
-
-            assert.strictEqual(startedEvent.eventType, OCPP20TransactionEventEnumType.Started)
-            assert.strictEqual(startedEvent.triggerReason, expectedStartTrigger)
-            assert.strictEqual(startedEvent.seqNo, 0)
-            assert.strictEqual(startedEvent.transactionInfo.transactionId, transactionId)
-
-            if (includeIdToken) {
-              assert.notStrictEqual(startedEvent.idToken, undefined)
-              assert.strictEqual(startedEvent.idToken?.idToken, `${id.toUpperCase()}_TOKEN_001`)
-            }
-          })
-
-          await it(`should support complete ${description} transaction lifecycle`, () => {
-            const connectorId = 1
-            const transactionId = generateUUID()
-            const idToken: OCPP20IdTokenType | undefined = includeIdToken
-              ? {
-                  idToken: `${id.toUpperCase()}_LIFECYCLE_001`,
-                  type: OCPP20IdTokenEnumType.ISO14443,
-                }
-              : undefined
-
-            OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
-
-            // Step 1: Started event
-            const startedEvent = OCPP20ServiceUtils.buildTransactionEvent(
-              mockStation,
-              OCPP20TransactionEventEnumType.Started,
-              expectedStartTrigger,
-              connectorId,
-              transactionId,
-              idToken != null ? { idToken } : undefined
-            )
-
-            // Step 2: Charging state change
-            const chargingEvent = OCPP20ServiceUtils.buildTransactionEvent(
-              mockStation,
-              OCPP20TransactionEventEnumType.Updated,
-              OCPP20TriggerReasonEnumType.ChargingStateChanged,
-              connectorId,
-              transactionId,
-              { chargingState: OCPP20ChargingStateEnumType.Charging }
-            )
-
-            // Step 3: Ended event
-            const endedEvent = OCPP20ServiceUtils.buildTransactionEvent(
-              mockStation,
-              OCPP20TransactionEventEnumType.Ended,
-              OCPP20TriggerReasonEnumType.StopAuthorized,
-              connectorId,
-              transactionId
-            )
-
-            // Validate event sequence
-            assert.strictEqual(startedEvent.seqNo, 0)
-            assert.strictEqual(chargingEvent.seqNo, 1)
-            assert.strictEqual(endedEvent.seqNo, 2)
-
-            // All events share same transaction ID
-            assert.strictEqual(startedEvent.transactionInfo.transactionId, transactionId)
-            assert.strictEqual(chargingEvent.transactionInfo.transactionId, transactionId)
-            assert.strictEqual(endedEvent.transactionInfo.transactionId, transactionId)
-          })
-
-          await it(`should maintain independent sequence numbers on different connectors for ${description}`, () => {
-            const connector1 = 1
-            const connector2 = 2
-            const transaction1Id = generateUUID()
-            const transaction2Id = generateUUID()
-
-            OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connector1)
-            OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connector2)
-
-            // Start transaction on connector 1
-            const conn1Event1 = OCPP20ServiceUtils.buildTransactionEvent(
-              mockStation,
-              OCPP20TransactionEventEnumType.Started,
-              expectedStartTrigger,
-              connector1,
-              transaction1Id
-            )
+          // Update connector 1
+          const conn1Event2 = buildTransactionEvent(
+            mockStation,
+            OCPP20TransactionEventEnumType.Updated,
+            OCPP20TriggerReasonEnumType.ChargingStateChanged,
+            connector1,
+            transaction1Id
+          )
 
-            // Start transaction on connector 2
-            const conn2Event1 = OCPP20ServiceUtils.buildTransactionEvent(
-              mockStation,
-              OCPP20TransactionEventEnumType.Started,
-              expectedStartTrigger,
-              connector2,
-              transaction2Id
-            )
+          // Update connector 2
+          const conn2Event2 = buildTransactionEvent(
+            mockStation,
+            OCPP20TransactionEventEnumType.Updated,
+            OCPP20TriggerReasonEnumType.ChargingStateChanged,
+            connector2,
+            transaction2Id
+          )
 
-            // Update connector 1
-            const conn1Event2 = OCPP20ServiceUtils.buildTransactionEvent(
-              mockStation,
-              OCPP20TransactionEventEnumType.Updated,
-              OCPP20TriggerReasonEnumType.ChargingStateChanged,
-              connector1,
-              transaction1Id
-            )
+          // Verify independent sequence numbers
+          assert.strictEqual(conn1Event1.seqNo, 0)
+          assert.strictEqual(conn1Event2.seqNo, 1)
+          assert.strictEqual(conn2Event1.seqNo, 0)
+          assert.strictEqual(conn2Event2.seqNo, 1)
 
-            // Update connector 2
-            const conn2Event2 = OCPP20ServiceUtils.buildTransactionEvent(
-              mockStation,
-              OCPP20TransactionEventEnumType.Updated,
-              OCPP20TriggerReasonEnumType.ChargingStateChanged,
-              connector2,
-              transaction2Id
-            )
-
-            // Verify independent sequence numbers
-            assert.strictEqual(conn1Event1.seqNo, 0)
-            assert.strictEqual(conn1Event2.seqNo, 1)
-            assert.strictEqual(conn2Event1.seqNo, 0)
-            assert.strictEqual(conn2Event2.seqNo, 1)
-
-            // Verify independent transaction IDs
-            assert.strictEqual(conn1Event1.transactionInfo.transactionId, transaction1Id)
-            assert.strictEqual(conn2Event1.transactionInfo.transactionId, transaction2Id)
-          })
+          // Verify independent transaction IDs
+          assert.strictEqual(conn1Event1.transactionInfo.transactionId, transaction1Id)
+          assert.strictEqual(conn2Event1.transactionInfo.transactionId, transaction2Id)
         })
-      }
-    })
+      })
+    }
 
     // ==========================================================================
     // E02 Cable-First Specific Tests
@@ -1115,7 +762,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
           // Step 1: Cable plugged in (Started)
-          const cablePluggedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const cablePluggedEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
             OCPP20TriggerReasonEnumType.CablePluggedIn,
@@ -1124,7 +771,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           )
 
           // Step 2: EV detected (Updated)
-          const evDetectedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const evDetectedEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Updated,
             OCPP20TriggerReasonEnumType.EVDetected,
@@ -1133,7 +780,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           )
 
           // Step 3: Charging starts (Updated with ChargingStateChanged)
-          const chargingStartedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const chargingStartedEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Updated,
             OCPP20TriggerReasonEnumType.ChargingStateChanged,
@@ -1165,7 +812,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
           // Start transaction with cable plug
-          const startEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const startEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
             OCPP20TriggerReasonEnumType.CablePluggedIn,
@@ -1174,7 +821,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           )
 
           // End transaction with EV departure (cable removal)
-          const endEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const endEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Ended,
             OCPP20TriggerReasonEnumType.EVDeparted,
@@ -1200,28 +847,28 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
 
           // Build full cable-first flow
           const events = [
-            OCPP20ServiceUtils.buildTransactionEvent(
+            buildTransactionEvent(
               mockStation,
               OCPP20TransactionEventEnumType.Started,
               OCPP20TriggerReasonEnumType.CablePluggedIn,
               connectorId,
               transactionId
             ),
-            OCPP20ServiceUtils.buildTransactionEvent(
+            buildTransactionEvent(
               mockStation,
               OCPP20TransactionEventEnumType.Updated,
               OCPP20TriggerReasonEnumType.EVDetected,
               connectorId,
               transactionId
             ),
-            OCPP20ServiceUtils.buildTransactionEvent(
+            buildTransactionEvent(
               mockStation,
               OCPP20TransactionEventEnumType.Updated,
               OCPP20TriggerReasonEnumType.Authorized,
               connectorId,
               transactionId
             ),
-            OCPP20ServiceUtils.buildTransactionEvent(
+            buildTransactionEvent(
               mockStation,
               OCPP20TransactionEventEnumType.Updated,
               OCPP20TriggerReasonEnumType.ChargingStateChanged,
@@ -1319,7 +966,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           // Cable-first flow with suspended state
           const events = [
             // 1. Cable plugged
-            OCPP20ServiceUtils.buildTransactionEvent(
+            buildTransactionEvent(
               mockStation,
               OCPP20TransactionEventEnumType.Started,
               OCPP20TriggerReasonEnumType.CablePluggedIn,
@@ -1327,7 +974,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
               transactionId
             ),
             // 2. Start charging
-            OCPP20ServiceUtils.buildTransactionEvent(
+            buildTransactionEvent(
               mockStation,
               OCPP20TransactionEventEnumType.Updated,
               OCPP20TriggerReasonEnumType.ChargingStateChanged,
@@ -1336,7 +983,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
               { chargingState: OCPP20ChargingStateEnumType.Charging }
             ),
             // 3. Suspended by EV
-            OCPP20ServiceUtils.buildTransactionEvent(
+            buildTransactionEvent(
               mockStation,
               OCPP20TransactionEventEnumType.Updated,
               OCPP20TriggerReasonEnumType.ChargingStateChanged,
@@ -1345,7 +992,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
               { chargingState: OCPP20ChargingStateEnumType.SuspendedEV }
             ),
             // 4. Resume charging
-            OCPP20ServiceUtils.buildTransactionEvent(
+            buildTransactionEvent(
               mockStation,
               OCPP20TransactionEventEnumType.Updated,
               OCPP20TriggerReasonEnumType.ChargingStateChanged,
@@ -1354,7 +1001,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
               { chargingState: OCPP20ChargingStateEnumType.Charging }
             ),
             // 5. EV departed
-            OCPP20ServiceUtils.buildTransactionEvent(
+            buildTransactionEvent(
               mockStation,
               OCPP20TransactionEventEnumType.Ended,
               OCPP20TriggerReasonEnumType.EVDeparted,
@@ -1375,88 +1022,6 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
         })
       })
 
-      await describe('Context-Based Cable Event Trigger Selection', async () => {
-        await it('should select CablePluggedIn from cable_action context with plugged_in state', () => {
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Started,
-            TransactionContextFixtures.cablePluggedIn()
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.CablePluggedIn)
-        })
-
-        await it('should select EVDetected from cable_action context with detected state', () => {
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Updated,
-            TransactionContextFixtures.evDetected()
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.EVDetected)
-        })
-
-        await it('should select EVDeparted from cable_action context with unplugged state', () => {
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Ended,
-            TransactionContextFixtures.evDeparted()
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.EVDeparted)
-        })
-      })
-    })
-
-    // ==========================================================================
-    // E03 IdToken-First Specific Tests
-    // ==========================================================================
-    await describe('E03 - IdToken-First Pre-Authorization', async () => {
-      beforeEach(() => {
-        resetConnectorTransactionState(mockStation)
-      })
-
-      await describe('E03.FR.13 - Trigger Reason Selection', async () => {
-        await it('should select groupIdToken trigger for group authorization', () => {
-          const context: OCPP20TransactionContext = {
-            authorizationMethod: 'groupIdToken',
-            source: 'local_authorization',
-          }
-
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Started,
-            context
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Authorized)
-        })
-
-        await it('should differentiate IdToken-first from Cable-first by trigger reason', () => {
-          // IdToken-first: Authorized trigger
-          const idTokenFirstContext: OCPP20TransactionContext = {
-            authorizationMethod: 'idToken',
-            source: 'local_authorization',
-          }
-
-          // Cable-first: CablePluggedIn trigger
-          const cableFirstContext: OCPP20TransactionContext = {
-            cableState: 'plugged_in',
-            source: 'cable_action',
-          }
-
-          const idTokenTrigger = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Started,
-            idTokenFirstContext
-          )
-
-          const cableTrigger = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Started,
-            cableFirstContext
-          )
-
-          assert.strictEqual(idTokenTrigger, OCPP20TriggerReasonEnumType.Authorized)
-          assert.strictEqual(cableTrigger, OCPP20TriggerReasonEnumType.CablePluggedIn)
-          assert.notStrictEqual(idTokenTrigger, cableTrigger)
-        })
-      })
-
       await describe('E03.FR.01 - IdToken in TransactionEvent', async () => {
         await it('should include idToken in first TransactionEvent after authorization', () => {
           const connectorId = 1
@@ -1469,7 +1034,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
           // Build Started event with idToken (E03.FR.01: IdToken must be in first event)
-          const startedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const startedEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
             OCPP20TriggerReasonEnumType.Authorized,
@@ -1498,7 +1063,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
           // First event includes idToken
-          const startedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const startedEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
             OCPP20TriggerReasonEnumType.Authorized,
@@ -1508,7 +1073,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           )
 
           // Second event should NOT include idToken (flag is set after first inclusion)
-          const updatedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const updatedEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Updated,
             OCPP20TriggerReasonEnumType.ChargingStateChanged,
@@ -1533,7 +1098,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
             type: OCPP20IdTokenEnumType.ISO14443,
           }
 
-          const rfidEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const rfidEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
             OCPP20TriggerReasonEnumType.Authorized,
@@ -1557,7 +1122,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
             type: OCPP20IdTokenEnumType.eMAID,
           }
 
-          const emaidEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const emaidEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
             OCPP20TriggerReasonEnumType.Authorized,
@@ -1583,7 +1148,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
           // E03 Step 1: IdToken presented and authorized (Started with Authorized trigger)
-          const authorizedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const authorizedEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
             OCPP20TriggerReasonEnumType.Authorized,
@@ -1593,7 +1158,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           )
 
           // E03 Step 2: Cable connected (Updated event)
-          const cableConnectedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const cableConnectedEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Updated,
             OCPP20TriggerReasonEnumType.CablePluggedIn,
@@ -1602,7 +1167,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           )
 
           // E03 Step 3: Charging starts
-          const chargingEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const chargingEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Updated,
             OCPP20TriggerReasonEnumType.ChargingStateChanged,
@@ -1612,7 +1177,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           )
 
           // E03 Step 4: Transaction ends
-          const endedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const endedEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Ended,
             OCPP20TriggerReasonEnumType.StopAuthorized,
@@ -1663,7 +1228,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
             connectorStatus.transactionIdTokenSent = undefined
           }
 
-          const e03Start = OCPP20ServiceUtils.buildTransactionEvent(
+          const e03Start = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
             OCPP20TriggerReasonEnumType.Authorized,
@@ -1678,7 +1243,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
             connectorStatus.transactionIdTokenSent = undefined
           }
 
-          const e02Start = OCPP20ServiceUtils.buildTransactionEvent(
+          const e02Start = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
             OCPP20TriggerReasonEnumType.CablePluggedIn,
@@ -1708,7 +1273,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
           // E03.FR.05: User authorizes with IdToken
-          const authorizedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const authorizedEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
             OCPP20TriggerReasonEnumType.Authorized,
@@ -1718,7 +1283,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           )
 
           // E03.FR.06: Cable not connected within timeout - transaction ends with Timeout
-          const timeoutEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const timeoutEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Ended,
             OCPP20TriggerReasonEnumType.EVConnectTimeout,
@@ -1745,21 +1310,6 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
       })
 
       await describe('Authorization Status in E03', async () => {
-        await it('should support Deauthorized trigger for rejected authorization', () => {
-          const context: OCPP20TransactionContext = {
-            authorizationMethod: 'idToken',
-            isDeauthorized: true,
-            source: 'local_authorization',
-          }
-
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Ended,
-            context
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Deauthorized)
-        })
-
         await it('should handle transaction end after token revocation', () => {
           const connectorId = 1
           const transactionId = generateUUID()
@@ -1771,7 +1321,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
           // Transaction started with authorization
-          const startEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const startEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
             OCPP20TriggerReasonEnumType.Authorized,
@@ -1781,7 +1331,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           )
 
           // Transaction ended due to deauthorization (e.g., token revoked mid-session)
-          const revokedEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const revokedEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Ended,
             OCPP20TriggerReasonEnumType.Deauthorized,
@@ -1793,20 +1343,6 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           assert.strictEqual(revokedEvent.eventType, OCPP20TransactionEventEnumType.Ended)
           assert.strictEqual(revokedEvent.triggerReason, OCPP20TriggerReasonEnumType.Deauthorized)
         })
-
-        await it('should support StopAuthorized trigger for normal transaction end', () => {
-          const context: OCPP20TransactionContext = {
-            authorizationMethod: 'stopAuthorized',
-            source: 'local_authorization',
-          }
-
-          const triggerReason = OCPP20ServiceUtils.selectTriggerReason(
-            OCPP20TransactionEventEnumType.Ended,
-            context
-          )
-
-          assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.StopAuthorized)
-        })
       })
 
       await describe('E03.FR.07/08 - Sequence Numbers and Transaction ID', async () => {
@@ -1821,7 +1357,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
           const events = [
-            OCPP20ServiceUtils.buildTransactionEvent(
+            buildTransactionEvent(
               mockStation,
               OCPP20TransactionEventEnumType.Started,
               OCPP20TriggerReasonEnumType.Authorized,
@@ -1829,28 +1365,28 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
               transactionId,
               { idToken }
             ),
-            OCPP20ServiceUtils.buildTransactionEvent(
+            buildTransactionEvent(
               mockStation,
               OCPP20TransactionEventEnumType.Updated,
               OCPP20TriggerReasonEnumType.CablePluggedIn,
               connectorId,
               transactionId
             ),
-            OCPP20ServiceUtils.buildTransactionEvent(
+            buildTransactionEvent(
               mockStation,
               OCPP20TransactionEventEnumType.Updated,
               OCPP20TriggerReasonEnumType.ChargingStateChanged,
               connectorId,
               transactionId
             ),
-            OCPP20ServiceUtils.buildTransactionEvent(
+            buildTransactionEvent(
               mockStation,
               OCPP20TransactionEventEnumType.Updated,
               OCPP20TriggerReasonEnumType.MeterValuePeriodic,
               connectorId,
               transactionId
             ),
-            OCPP20ServiceUtils.buildTransactionEvent(
+            buildTransactionEvent(
               mockStation,
               OCPP20TransactionEventEnumType.Ended,
               OCPP20TriggerReasonEnumType.StopAuthorized,
@@ -1876,7 +1412,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           // E03.FR.08: transactionId MUST be unique
           assert.notStrictEqual(transaction1Id, transaction2Id)
 
-          const event1 = OCPP20ServiceUtils.buildTransactionEvent(
+          const event1 = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
             OCPP20TriggerReasonEnumType.Authorized,
@@ -1886,7 +1422,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
 
           OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
-          const event2 = OCPP20ServiceUtils.buildTransactionEvent(
+          const event2 = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Started,
             OCPP20TriggerReasonEnumType.Authorized,
@@ -2566,7 +2102,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
         OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
 
         // Send initial Started event
-        const startEvent = OCPP20ServiceUtils.buildTransactionEvent(
+        const startEvent = buildTransactionEvent(
           mockStation,
           OCPP20TransactionEventEnumType.Started,
           OCPP20TriggerReasonEnumType.Authorized,
@@ -2577,7 +2113,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
 
         // Send multiple periodic events (simulating timer ticks)
         for (let i = 1; i <= 3; i++) {
-          const periodicEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const periodicEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Updated,
             OCPP20TriggerReasonEnumType.MeterValuePeriodic,
@@ -2661,7 +2197,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
 
         // Simulate full transaction lifecycle with periodic updates
         // 1. Started event (seqNo: 0)
-        const startEvent = OCPP20ServiceUtils.buildTransactionEvent(
+        const startEvent = buildTransactionEvent(
           mockStation,
           OCPP20TransactionEventEnumType.Started,
           OCPP20TriggerReasonEnumType.Authorized,
@@ -2672,7 +2208,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
 
         // 2. Multiple periodic updates (seqNo: 1, 2, 3)
         for (let i = 1; i <= 3; i++) {
-          const updateEvent = OCPP20ServiceUtils.buildTransactionEvent(
+          const updateEvent = buildTransactionEvent(
             mockStation,
             OCPP20TransactionEventEnumType.Updated,
             OCPP20TriggerReasonEnumType.MeterValuePeriodic,
@@ -2683,7 +2219,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
         }
 
         // 3. Ended event (seqNo: 4)
-        const endEvent = OCPP20ServiceUtils.buildTransactionEvent(
+        const endEvent = buildTransactionEvent(
           mockStation,
           OCPP20TransactionEventEnumType.Ended,
           OCPP20TriggerReasonEnumType.StopAuthorized,
@@ -2702,14 +2238,14 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
         OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, 2)
 
         // Build events for connector 1
-        const event1Start = OCPP20ServiceUtils.buildTransactionEvent(
+        const event1Start = buildTransactionEvent(
           mockStation,
           OCPP20TransactionEventEnumType.Started,
           OCPP20TriggerReasonEnumType.Authorized,
           1,
           transactionId1
         )
-        const event1Update = OCPP20ServiceUtils.buildTransactionEvent(
+        const event1Update = buildTransactionEvent(
           mockStation,
           OCPP20TransactionEventEnumType.Updated,
           OCPP20TriggerReasonEnumType.MeterValuePeriodic,
@@ -2718,14 +2254,14 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
         )
 
         // Build events for connector 2
-        const event2Start = OCPP20ServiceUtils.buildTransactionEvent(
+        const event2Start = buildTransactionEvent(
           mockStation,
           OCPP20TransactionEventEnumType.Started,
           OCPP20TriggerReasonEnumType.Authorized,
           2,
           transactionId2
         )
-        const event2Update = OCPP20ServiceUtils.buildTransactionEvent(
+        const event2Update = buildTransactionEvent(
           mockStation,
           OCPP20TransactionEventEnumType.Updated,
           OCPP20TriggerReasonEnumType.MeterValuePeriodic,
index 61625ea0b7b24a9614a29f7d2033d5cbe537ec54..bdd112a5a03d0fed4da3a84adbf2a4d0645889f3 100644 (file)
@@ -16,10 +16,7 @@ import type {
   OCPP20RequestCommand,
   OCSPRequestDataType,
 } from '../../../../src/types/index.js'
-import type {
-  OCPP20IdTokenType,
-  OCPP20TransactionContext,
-} from '../../../../src/types/ocpp/2.0/Transaction.js'
+import type { OCPP20IdTokenType } from '../../../../src/types/ocpp/2.0/Transaction.js'
 
 import { OCPP20RequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20RequestService.js'
 import { OCPP20ResponseService } from '../../../../src/charging-station/ocpp/2.0/OCPP20ResponseService.js'
@@ -30,7 +27,6 @@ import {
   OCPP20RequiredVariableName,
   OCPPVersion,
 } from '../../../../src/types/index.js'
-import { OCPP20IncomingRequestCommand } from '../../../../src/types/ocpp/2.0/Requests.js'
 import { OCPP20IdTokenEnumType } from '../../../../src/types/ocpp/2.0/Transaction.js'
 import { Constants } from '../../../../src/utils/index.js'
 import { TEST_CHARGING_STATION_BASE_NAME } from '../../ChargingStationTestConstants.js'
@@ -448,200 +444,6 @@ export const IdTokenFixtures = {
   }),
 } as const
 
-/**
- * Pre-built TransactionContext factories for common flow patterns.
- * Use these to create standardized contexts for different transaction flows.
- */
-export const TransactionContextFixtures = {
-  // ===== Local Authorization Contexts =====
-
-  /**
-   * Abnormal condition (with optional condition type).
-   * @param condition - The abnormal condition type.
-   * @returns An OCPP20TransactionContext for abnormal conditions.
-   */
-  abnormalCondition: (condition = 'OverCurrent'): OCPP20TransactionContext => ({
-    abnormalCondition: condition,
-    source: 'abnormal_condition',
-  }),
-
-  /**
-   * Cable plugged in (E02 cable-first start).
-   * @returns An OCPP20TransactionContext for cable plugged in.
-   */
-  cablePluggedIn: (): OCPP20TransactionContext => ({
-    cableState: 'plugged_in',
-    source: 'cable_action',
-  }),
-
-  /**
-   * Deauthorization (token revoked or invalid).
-   * @returns An OCPP20TransactionContext for deauthorization.
-   */
-  deauthorized: (): OCPP20TransactionContext => ({
-    authorizationMethod: 'idToken',
-    isDeauthorized: true,
-    source: 'local_authorization',
-  }),
-
-  // ===== Cable Action Contexts (E02 flow) =====
-
-  /**
-   * Energy limit reached.
-   * @returns An OCPP20TransactionContext for energy limit reached.
-   */
-  energyLimitReached: (): OCPP20TransactionContext => ({
-    source: 'energy_limit',
-  }),
-
-  /**
-   * EV communication lost.
-   * @returns An OCPP20TransactionContext for EV communication lost.
-   */
-  evCommunicationLost: (): OCPP20TransactionContext => ({
-    source: 'system_event',
-    systemEvent: 'ev_communication_lost',
-  }),
-
-  /**
-   * EV connect timeout.
-   * @returns An OCPP20TransactionContext for EV connect timeout.
-   */
-  evConnectTimeout: (): OCPP20TransactionContext => ({
-    source: 'system_event',
-    systemEvent: 'ev_connect_timeout',
-  }),
-
-  // ===== Remote Command Contexts =====
-
-  /**
-   * Cable unplugged / EV departed.
-   * @returns An OCPP20TransactionContext for EV departure.
-   */
-  evDeparted: (): OCPP20TransactionContext => ({
-    cableState: 'unplugged',
-    source: 'cable_action',
-  }),
-
-  /**
-   * EV detected after cable connection.
-   * @returns An OCPP20TransactionContext for EV detection.
-   */
-  evDetected: (): OCPP20TransactionContext => ({
-    cableState: 'detected',
-    source: 'cable_action',
-  }),
-
-  /**
-   * IdToken-first authorization (E03 flow start).
-   * @param authorizationMethod - The authorization method used.
-   * @returns An OCPP20TransactionContext for IdToken authorization.
-   */
-  idTokenAuthorized: (
-    authorizationMethod: 'groupIdToken' | 'idToken' = 'idToken'
-  ): OCPP20TransactionContext => ({
-    authorizationMethod,
-    source: 'local_authorization',
-  }),
-
-  /**
-   * Clock-aligned meter value.
-   * @returns An OCPP20TransactionContext for clock-aligned meter values.
-   */
-  meterValueClock: (): OCPP20TransactionContext => ({
-    isPeriodicMeterValue: false,
-    source: 'meter_value',
-  }),
-
-  /**
-   * Periodic meter value (sampled interval).
-   * @returns An OCPP20TransactionContext for periodic meter values.
-   */
-  meterValuePeriodic: (): OCPP20TransactionContext => ({
-    isPeriodicMeterValue: true,
-    source: 'meter_value',
-  }),
-
-  // ===== Meter Value Contexts =====
-
-  /**
-   * Remote start transaction request.
-   * @returns An OCPP20TransactionContext for remote start.
-   */
-  remoteStart: (): OCPP20TransactionContext => ({
-    command: OCPP20IncomingRequestCommand.REQUEST_START_TRANSACTION,
-    source: 'remote_command',
-  }),
-
-  /**
-   * Remote stop transaction request.
-   * @returns An OCPP20TransactionContext for remote stop.
-   */
-  remoteStop: (): OCPP20TransactionContext => ({
-    command: OCPP20IncomingRequestCommand.REQUEST_STOP_TRANSACTION,
-    source: 'remote_command',
-  }),
-
-  /**
-   * Reset command.
-   * @returns An OCPP20TransactionContext for reset.
-   */
-  reset: (): OCPP20TransactionContext => ({
-    command: OCPP20IncomingRequestCommand.RESET,
-    source: 'remote_command',
-  }),
-
-  // ===== System Event Contexts =====
-
-  /**
-   * Signed data received.
-   * @returns An OCPP20TransactionContext for signed data.
-   */
-  signedData: (): OCPP20TransactionContext => ({
-    isSignedDataReceived: true,
-    source: 'meter_value',
-  }),
-
-  /**
-   * Stop authorized by local token presentation.
-   * @returns An OCPP20TransactionContext for stop authorization.
-   */
-  stopAuthorized: (): OCPP20TransactionContext => ({
-    authorizationMethod: 'stopAuthorized',
-    source: 'local_authorization',
-  }),
-
-  // ===== Limit Contexts =====
-
-  /**
-   * Time limit reached.
-   * @returns An OCPP20TransactionContext for time limit.
-   */
-  timeLimitReached: (): OCPP20TransactionContext => ({
-    source: 'time_limit',
-  }),
-
-  /**
-   * Trigger message command.
-   * @returns An OCPP20TransactionContext for trigger message.
-   */
-  triggerMessage: (): OCPP20TransactionContext => ({
-    command: OCPP20IncomingRequestCommand.TRIGGER_MESSAGE,
-    source: 'remote_command',
-  }),
-
-  // ===== Abnormal Condition Contexts =====
-
-  /**
-   * Unlock connector command.
-   * @returns An OCPP20TransactionContext for unlock connector.
-   */
-  unlockConnector: (): OCPP20TransactionContext => ({
-    command: OCPP20IncomingRequestCommand.UNLOCK_CONNECTOR,
-    source: 'remote_command',
-  }),
-} as const
-
 /**
  * Pre-built mock station fixtures for Reset command testing.
  * These factories create properly configured MockChargingStation instances
@@ -706,8 +508,6 @@ export interface TransactionFlowPattern {
   expectedStartTrigger: string
   /** Whether to include idToken in Started event */
   includeIdToken: boolean
-  /** Context to use for Started event (determines initial trigger reason) */
-  startContext: OCPP20TransactionContext
 }
 
 /**
@@ -719,19 +519,16 @@ export const TransactionFlowPatterns: TransactionFlowPattern[] = [
     description: 'E02 Cable-First: CablePluggedIn → Charging → EVDeparted',
     expectedStartTrigger: 'CablePluggedIn',
     includeIdToken: false,
-    startContext: TransactionContextFixtures.cablePluggedIn(),
   },
   {
     description: 'E03 IdToken-First: Authorized → Cable → Charging → StopAuthorized',
     expectedStartTrigger: 'Authorized',
     includeIdToken: true,
-    startContext: TransactionContextFixtures.idTokenAuthorized(),
   },
   {
     description: 'Remote Start: RemoteStart → Charging → RemoteStop',
     expectedStartTrigger: 'RemoteStart',
     includeIdToken: false,
-    startContext: TransactionContextFixtures.remoteStart(),
   },
 ] as const