diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2e4ef8e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+Results_IoT_EV/
diff --git a/acndata_sessions.json b/acndata_sessions.json
new file mode 100644
index 0000000..f2b552d
--- /dev/null
+++ b/acndata_sessions.json
@@ -0,0 +1,3910 @@
+{
+ "_meta": {
+ "end": "Mon, 02 Jan 2023 01:42:00 GMT",
+ "min_kWh": 1,
+ "site": "iot-campus",
+ "start": "Sun, 19 Mar 2023 04:40:00 GMT"
+ },
+ "_items": [
+ {
+ "_id": "session_0000",
+ "clusterID": "0004",
+ "connectionTime": "Sun, 19 Mar 2023 04:40:00 GMT",
+ "disconnectTime": "Sun, 19 Mar 2023 08:15:00 GMT",
+ "doneChargingTime": "Sun, 19 Mar 2023 06:38:00 GMT",
+ "kWhDelivered": 23.926,
+ "sessionID": "site0-station0-0",
+ "siteID": "0004",
+ "spaceID": "SP-39",
+ "stationID": "ST-123",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6866",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 29.104,
+ "milesRequested": 87.3,
+ "minutesAvailable": 215,
+ "modifiedAt": "Sun, 19 Mar 2023 04:40:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 19 Mar 2023 09:11:00 GMT",
+ "userID": 4578
+ }
+ ]
+ },
+ {
+ "_id": "session_0001",
+ "clusterID": "0003",
+ "connectionTime": "Thu, 16 Feb 2023 06:30:00 GMT",
+ "disconnectTime": "Thu, 16 Feb 2023 08:01:00 GMT",
+ "doneChargingTime": "Thu, 16 Feb 2023 07:25:00 GMT",
+ "kWhDelivered": 39.011,
+ "sessionID": "site1-station1-1",
+ "siteID": "0005",
+ "spaceID": "SP-91",
+ "stationID": "ST-155",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3407",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 51.378,
+ "milesRequested": 154.1,
+ "minutesAvailable": 91,
+ "modifiedAt": "Thu, 16 Feb 2023 06:30:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 16 Feb 2023 08:09:00 GMT",
+ "userID": 2618
+ }
+ ]
+ },
+ {
+ "_id": "session_0002",
+ "clusterID": "0001",
+ "connectionTime": "Wed, 08 Mar 2023 18:20:00 GMT",
+ "disconnectTime": "Wed, 08 Mar 2023 20:53:00 GMT",
+ "doneChargingTime": "Wed, 08 Mar 2023 18:58:00 GMT",
+ "kWhDelivered": 35.779,
+ "sessionID": "site2-station2-2",
+ "siteID": "0003",
+ "spaceID": "SP-56",
+ "stationID": "ST-081",
+ "timezone": "America/Los_Angeles",
+ "userID": "U4350",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 45.222,
+ "milesRequested": 135.7,
+ "minutesAvailable": 153,
+ "modifiedAt": "Wed, 08 Mar 2023 18:20:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 08 Mar 2023 21:47:00 GMT",
+ "userID": 8815
+ }
+ ]
+ },
+ {
+ "_id": "session_0003",
+ "clusterID": "0001",
+ "connectionTime": "Fri, 10 Feb 2023 15:04:00 GMT",
+ "disconnectTime": "Fri, 10 Feb 2023 18:10:00 GMT",
+ "doneChargingTime": "Fri, 10 Feb 2023 17:37:00 GMT",
+ "kWhDelivered": 41.226,
+ "sessionID": "site3-station3-3",
+ "siteID": "0001",
+ "spaceID": "SP-93",
+ "stationID": "ST-103",
+ "timezone": "America/Los_Angeles",
+ "userID": "U1018",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 52.2,
+ "milesRequested": 156.6,
+ "minutesAvailable": 186,
+ "modifiedAt": "Fri, 10 Feb 2023 15:04:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 10 Feb 2023 18:13:00 GMT",
+ "userID": 9086
+ }
+ ]
+ },
+ {
+ "_id": "session_0004",
+ "clusterID": "0002",
+ "connectionTime": "Fri, 17 Mar 2023 16:55:00 GMT",
+ "disconnectTime": "Fri, 17 Mar 2023 19:11:00 GMT",
+ "doneChargingTime": "Fri, 17 Mar 2023 18:40:00 GMT",
+ "kWhDelivered": 19.634,
+ "sessionID": "site4-station4-4",
+ "siteID": "0005",
+ "spaceID": "SP-29",
+ "stationID": "ST-062",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3334",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 37.043,
+ "milesRequested": 111.1,
+ "minutesAvailable": 136,
+ "modifiedAt": "Fri, 17 Mar 2023 16:55:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 17 Mar 2023 19:57:00 GMT",
+ "userID": 8339
+ }
+ ]
+ },
+ {
+ "_id": "session_0005",
+ "clusterID": "0001",
+ "connectionTime": "Mon, 09 Jan 2023 15:15:00 GMT",
+ "disconnectTime": "Mon, 09 Jan 2023 16:35:00 GMT",
+ "doneChargingTime": "Mon, 09 Jan 2023 15:55:00 GMT",
+ "kWhDelivered": 27.857,
+ "sessionID": "site0-station5-5",
+ "siteID": "0003",
+ "spaceID": "SP-71",
+ "stationID": "ST-075",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3044",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 46.514,
+ "milesRequested": 139.5,
+ "minutesAvailable": 80,
+ "modifiedAt": "Mon, 09 Jan 2023 15:15:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 09 Jan 2023 17:31:00 GMT",
+ "userID": 6451
+ }
+ ]
+ },
+ {
+ "_id": "session_0006",
+ "clusterID": "0004",
+ "connectionTime": "Thu, 16 Mar 2023 11:13:00 GMT",
+ "disconnectTime": "Thu, 16 Mar 2023 14:17:00 GMT",
+ "doneChargingTime": "Thu, 16 Mar 2023 13:51:00 GMT",
+ "kWhDelivered": 32.143,
+ "sessionID": "site1-station6-6",
+ "siteID": "0001",
+ "spaceID": "SP-77",
+ "stationID": "ST-099",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6194",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 43.895,
+ "milesRequested": 131.7,
+ "minutesAvailable": 184,
+ "modifiedAt": "Thu, 16 Mar 2023 11:13:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 16 Mar 2023 15:08:00 GMT",
+ "userID": 4966
+ }
+ ]
+ },
+ {
+ "_id": "session_0007",
+ "clusterID": "0003",
+ "connectionTime": "Fri, 27 Jan 2023 18:14:00 GMT",
+ "disconnectTime": "Fri, 27 Jan 2023 19:45:00 GMT",
+ "doneChargingTime": "Fri, 27 Jan 2023 19:21:00 GMT",
+ "kWhDelivered": 13.403,
+ "sessionID": "site2-station7-7",
+ "siteID": "0004",
+ "spaceID": "SP-09",
+ "stationID": "ST-023",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3133",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 25.658,
+ "milesRequested": 77.0,
+ "minutesAvailable": 91,
+ "modifiedAt": "Fri, 27 Jan 2023 18:14:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 27 Jan 2023 20:37:00 GMT",
+ "userID": 1633
+ }
+ ]
+ },
+ {
+ "_id": "session_0008",
+ "clusterID": "0004",
+ "connectionTime": "Sun, 19 Mar 2023 00:22:00 GMT",
+ "disconnectTime": "Sun, 19 Mar 2023 02:56:00 GMT",
+ "doneChargingTime": "Sun, 19 Mar 2023 01:02:00 GMT",
+ "kWhDelivered": 46.539,
+ "sessionID": "site3-station8-8",
+ "siteID": "0005",
+ "spaceID": "SP-36",
+ "stationID": "ST-134",
+ "timezone": "America/Los_Angeles",
+ "userID": "U4858",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 57.351,
+ "milesRequested": 172.1,
+ "minutesAvailable": 154,
+ "modifiedAt": "Sun, 19 Mar 2023 00:22:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 19 Mar 2023 03:40:00 GMT",
+ "userID": 7871
+ }
+ ]
+ },
+ {
+ "_id": "session_0009",
+ "clusterID": "0003",
+ "connectionTime": "Thu, 23 Feb 2023 02:21:00 GMT",
+ "disconnectTime": "Thu, 23 Feb 2023 04:48:00 GMT",
+ "doneChargingTime": "Thu, 23 Feb 2023 03:51:00 GMT",
+ "kWhDelivered": 34.711,
+ "sessionID": "site4-station9-9",
+ "siteID": "0001",
+ "spaceID": "SP-42",
+ "stationID": "ST-157",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2889",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 54.636,
+ "milesRequested": 163.9,
+ "minutesAvailable": 147,
+ "modifiedAt": "Thu, 23 Feb 2023 02:21:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 23 Feb 2023 05:19:00 GMT",
+ "userID": 6493
+ }
+ ]
+ },
+ {
+ "_id": "session_0010",
+ "clusterID": "0002",
+ "connectionTime": "Sun, 19 Mar 2023 06:06:00 GMT",
+ "disconnectTime": "Sun, 19 Mar 2023 07:45:00 GMT",
+ "doneChargingTime": "Sun, 19 Mar 2023 07:14:00 GMT",
+ "kWhDelivered": 37.917,
+ "sessionID": "site0-station10-10",
+ "siteID": "0003",
+ "spaceID": "SP-22",
+ "stationID": "ST-086",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7981",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 40.26,
+ "milesRequested": 120.8,
+ "minutesAvailable": 99,
+ "modifiedAt": "Sun, 19 Mar 2023 06:06:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 19 Mar 2023 07:46:00 GMT",
+ "userID": 2648
+ }
+ ]
+ },
+ {
+ "_id": "session_0011",
+ "clusterID": "0001",
+ "connectionTime": "Mon, 13 Mar 2023 14:44:00 GMT",
+ "disconnectTime": "Mon, 13 Mar 2023 17:30:00 GMT",
+ "doneChargingTime": "Mon, 13 Mar 2023 15:41:00 GMT",
+ "kWhDelivered": 14.845,
+ "sessionID": "site1-station11-11",
+ "siteID": "0001",
+ "spaceID": "SP-16",
+ "stationID": "ST-163",
+ "timezone": "America/Los_Angeles",
+ "userID": "U4088",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 31.187,
+ "milesRequested": 93.6,
+ "minutesAvailable": 166,
+ "modifiedAt": "Mon, 13 Mar 2023 14:44:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 13 Mar 2023 18:14:00 GMT",
+ "userID": 2961
+ }
+ ]
+ },
+ {
+ "_id": "session_0012",
+ "clusterID": "0001",
+ "connectionTime": "Sun, 05 Feb 2023 22:36:00 GMT",
+ "disconnectTime": "Mon, 06 Feb 2023 00:06:00 GMT",
+ "doneChargingTime": "Sun, 05 Feb 2023 23:19:00 GMT",
+ "kWhDelivered": 49.123,
+ "sessionID": "site2-station12-12",
+ "siteID": "0002",
+ "spaceID": "SP-24",
+ "stationID": "ST-184",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3029",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 49.851,
+ "milesRequested": 149.6,
+ "minutesAvailable": 90,
+ "modifiedAt": "Sun, 05 Feb 2023 22:36:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 06 Feb 2023 00:59:00 GMT",
+ "userID": 4450
+ }
+ ]
+ },
+ {
+ "_id": "session_0013",
+ "clusterID": "0001",
+ "connectionTime": "Wed, 08 Mar 2023 12:29:00 GMT",
+ "disconnectTime": "Wed, 08 Mar 2023 15:03:00 GMT",
+ "doneChargingTime": "Wed, 08 Mar 2023 13:04:00 GMT",
+ "kWhDelivered": 6.025,
+ "sessionID": "site3-station13-13",
+ "siteID": "0003",
+ "spaceID": "SP-09",
+ "stationID": "ST-057",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2179",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 14.537,
+ "milesRequested": 43.6,
+ "minutesAvailable": 154,
+ "modifiedAt": "Wed, 08 Mar 2023 12:29:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 08 Mar 2023 15:46:00 GMT",
+ "userID": 5932
+ }
+ ]
+ },
+ {
+ "_id": "session_0014",
+ "clusterID": "0001",
+ "connectionTime": "Thu, 02 Feb 2023 05:13:00 GMT",
+ "disconnectTime": "Thu, 02 Feb 2023 07:47:00 GMT",
+ "doneChargingTime": "Thu, 02 Feb 2023 07:24:00 GMT",
+ "kWhDelivered": 27.664,
+ "sessionID": "site4-station14-14",
+ "siteID": "0004",
+ "spaceID": "SP-26",
+ "stationID": "ST-067",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6874",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 28.452,
+ "milesRequested": 85.4,
+ "minutesAvailable": 154,
+ "modifiedAt": "Thu, 02 Feb 2023 05:13:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 02 Feb 2023 07:50:00 GMT",
+ "userID": 8704
+ }
+ ]
+ },
+ {
+ "_id": "session_0015",
+ "clusterID": "0001",
+ "connectionTime": "Sat, 18 Mar 2023 15:21:00 GMT",
+ "disconnectTime": "Sat, 18 Mar 2023 18:27:00 GMT",
+ "doneChargingTime": "Sat, 18 Mar 2023 18:06:00 GMT",
+ "kWhDelivered": 35.269,
+ "sessionID": "site0-station15-15",
+ "siteID": "0002",
+ "spaceID": "SP-21",
+ "stationID": "ST-088",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9674",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 54.6,
+ "milesRequested": 163.8,
+ "minutesAvailable": 186,
+ "modifiedAt": "Sat, 18 Mar 2023 15:21:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sat, 18 Mar 2023 19:11:00 GMT",
+ "userID": 2920
+ }
+ ]
+ },
+ {
+ "_id": "session_0016",
+ "clusterID": "0003",
+ "connectionTime": "Fri, 24 Feb 2023 15:42:00 GMT",
+ "disconnectTime": "Fri, 24 Feb 2023 19:20:00 GMT",
+ "doneChargingTime": "Fri, 24 Feb 2023 17:55:00 GMT",
+ "kWhDelivered": 5.594,
+ "sessionID": "site1-station16-16",
+ "siteID": "0003",
+ "spaceID": "SP-50",
+ "stationID": "ST-169",
+ "timezone": "America/Los_Angeles",
+ "userID": "U5111",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 19.22,
+ "milesRequested": 57.7,
+ "minutesAvailable": 218,
+ "modifiedAt": "Fri, 24 Feb 2023 15:42:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 24 Feb 2023 19:31:00 GMT",
+ "userID": 1203
+ }
+ ]
+ },
+ {
+ "_id": "session_0017",
+ "clusterID": "0002",
+ "connectionTime": "Sun, 12 Feb 2023 00:24:00 GMT",
+ "disconnectTime": "Sun, 12 Feb 2023 01:46:00 GMT",
+ "doneChargingTime": "Sun, 12 Feb 2023 01:04:00 GMT",
+ "kWhDelivered": 7.056,
+ "sessionID": "site2-station17-17",
+ "siteID": "0004",
+ "spaceID": "SP-46",
+ "stationID": "ST-157",
+ "timezone": "America/Los_Angeles",
+ "userID": "U5716",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 12.674,
+ "milesRequested": 38.0,
+ "minutesAvailable": 82,
+ "modifiedAt": "Sun, 12 Feb 2023 00:24:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 12 Feb 2023 02:33:00 GMT",
+ "userID": 6885
+ }
+ ]
+ },
+ {
+ "_id": "session_0018",
+ "clusterID": "0001",
+ "connectionTime": "Fri, 24 Feb 2023 01:28:00 GMT",
+ "disconnectTime": "Fri, 24 Feb 2023 04:42:00 GMT",
+ "doneChargingTime": "Fri, 24 Feb 2023 04:26:00 GMT",
+ "kWhDelivered": 18.963,
+ "sessionID": "site3-station18-18",
+ "siteID": "0001",
+ "spaceID": "SP-77",
+ "stationID": "ST-050",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6478",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 33.933,
+ "milesRequested": 101.8,
+ "minutesAvailable": 194,
+ "modifiedAt": "Fri, 24 Feb 2023 01:28:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 24 Feb 2023 05:27:00 GMT",
+ "userID": 4922
+ }
+ ]
+ },
+ {
+ "_id": "session_0019",
+ "clusterID": "0004",
+ "connectionTime": "Sat, 21 Jan 2023 15:21:00 GMT",
+ "disconnectTime": "Sat, 21 Jan 2023 18:23:00 GMT",
+ "doneChargingTime": "Sat, 21 Jan 2023 17:35:00 GMT",
+ "kWhDelivered": 44.394,
+ "sessionID": "site4-station19-19",
+ "siteID": "0001",
+ "spaceID": "SP-52",
+ "stationID": "ST-180",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7852",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 55.757,
+ "milesRequested": 167.3,
+ "minutesAvailable": 182,
+ "modifiedAt": "Sat, 21 Jan 2023 15:21:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sat, 21 Jan 2023 19:08:00 GMT",
+ "userID": 1766
+ }
+ ]
+ },
+ {
+ "_id": "session_0020",
+ "clusterID": "0004",
+ "connectionTime": "Mon, 16 Jan 2023 09:55:00 GMT",
+ "disconnectTime": "Mon, 16 Jan 2023 12:17:00 GMT",
+ "doneChargingTime": "Mon, 16 Jan 2023 12:09:00 GMT",
+ "kWhDelivered": 36.568,
+ "sessionID": "site0-station0-20",
+ "siteID": "0005",
+ "spaceID": "SP-78",
+ "stationID": "ST-194",
+ "timezone": "America/Los_Angeles",
+ "userID": "U1001",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 45.495,
+ "milesRequested": 136.5,
+ "minutesAvailable": 142,
+ "modifiedAt": "Mon, 16 Jan 2023 09:55:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 16 Jan 2023 12:33:00 GMT",
+ "userID": 9103
+ }
+ ]
+ },
+ {
+ "_id": "session_0021",
+ "clusterID": "0004",
+ "connectionTime": "Tue, 31 Jan 2023 00:02:00 GMT",
+ "disconnectTime": "Tue, 31 Jan 2023 03:28:00 GMT",
+ "doneChargingTime": "Tue, 31 Jan 2023 01:41:00 GMT",
+ "kWhDelivered": 7.244,
+ "sessionID": "site1-station1-21",
+ "siteID": "0002",
+ "spaceID": "SP-71",
+ "stationID": "ST-163",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2367",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 23.698,
+ "milesRequested": 71.1,
+ "minutesAvailable": 206,
+ "modifiedAt": "Tue, 31 Jan 2023 00:02:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 31 Jan 2023 03:57:00 GMT",
+ "userID": 3138
+ }
+ ]
+ },
+ {
+ "_id": "session_0022",
+ "clusterID": "0002",
+ "connectionTime": "Sat, 01 Apr 2023 03:44:00 GMT",
+ "disconnectTime": "Sat, 01 Apr 2023 04:58:00 GMT",
+ "doneChargingTime": "Sat, 01 Apr 2023 04:07:00 GMT",
+ "kWhDelivered": 35.533,
+ "sessionID": "site2-station2-22",
+ "siteID": "0001",
+ "spaceID": "SP-92",
+ "stationID": "ST-194",
+ "timezone": "America/Los_Angeles",
+ "userID": "U1038",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 41.857,
+ "milesRequested": 125.6,
+ "minutesAvailable": 74,
+ "modifiedAt": "Sat, 01 Apr 2023 03:44:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sat, 01 Apr 2023 05:58:00 GMT",
+ "userID": 9656
+ }
+ ]
+ },
+ {
+ "_id": "session_0023",
+ "clusterID": "0003",
+ "connectionTime": "Sun, 26 Feb 2023 01:04:00 GMT",
+ "disconnectTime": "Sun, 26 Feb 2023 02:13:00 GMT",
+ "doneChargingTime": "Sun, 26 Feb 2023 01:49:00 GMT",
+ "kWhDelivered": 32.376,
+ "sessionID": "site3-station3-23",
+ "siteID": "0003",
+ "spaceID": "SP-89",
+ "stationID": "ST-047",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2641",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 36.346,
+ "milesRequested": 109.0,
+ "minutesAvailable": 69,
+ "modifiedAt": "Sun, 26 Feb 2023 01:04:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 26 Feb 2023 02:20:00 GMT",
+ "userID": 7499
+ }
+ ]
+ },
+ {
+ "_id": "session_0024",
+ "clusterID": "0001",
+ "connectionTime": "Mon, 27 Feb 2023 11:05:00 GMT",
+ "disconnectTime": "Mon, 27 Feb 2023 11:47:00 GMT",
+ "doneChargingTime": "Mon, 27 Feb 2023 11:45:00 GMT",
+ "kWhDelivered": 46.125,
+ "sessionID": "site4-station4-24",
+ "siteID": "0003",
+ "spaceID": "SP-18",
+ "stationID": "ST-168",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9533",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 62.121,
+ "milesRequested": 186.4,
+ "minutesAvailable": 42,
+ "modifiedAt": "Mon, 27 Feb 2023 11:05:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 27 Feb 2023 12:04:00 GMT",
+ "userID": 6686
+ }
+ ]
+ },
+ {
+ "_id": "session_0025",
+ "clusterID": "0003",
+ "connectionTime": "Wed, 11 Jan 2023 19:27:00 GMT",
+ "disconnectTime": "Wed, 11 Jan 2023 21:01:00 GMT",
+ "doneChargingTime": "Wed, 11 Jan 2023 20:26:00 GMT",
+ "kWhDelivered": 5.836,
+ "sessionID": "site0-station5-25",
+ "siteID": "0005",
+ "spaceID": "SP-41",
+ "stationID": "ST-094",
+ "timezone": "America/Los_Angeles",
+ "userID": "U1688",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 6.649,
+ "milesRequested": 19.9,
+ "minutesAvailable": 94,
+ "modifiedAt": "Wed, 11 Jan 2023 19:27:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 11 Jan 2023 21:55:00 GMT",
+ "userID": 9101
+ }
+ ]
+ },
+ {
+ "_id": "session_0026",
+ "clusterID": "0002",
+ "connectionTime": "Tue, 07 Mar 2023 04:10:00 GMT",
+ "disconnectTime": "Tue, 07 Mar 2023 07:48:00 GMT",
+ "doneChargingTime": "Tue, 07 Mar 2023 06:27:00 GMT",
+ "kWhDelivered": 21.76,
+ "sessionID": "site1-station6-26",
+ "siteID": "0004",
+ "spaceID": "SP-76",
+ "stationID": "ST-075",
+ "timezone": "America/Los_Angeles",
+ "userID": "U1145",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 32.519,
+ "milesRequested": 97.6,
+ "minutesAvailable": 218,
+ "modifiedAt": "Tue, 07 Mar 2023 04:10:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 07 Mar 2023 08:15:00 GMT",
+ "userID": 3474
+ }
+ ]
+ },
+ {
+ "_id": "session_0027",
+ "clusterID": "0001",
+ "connectionTime": "Thu, 26 Jan 2023 00:49:00 GMT",
+ "disconnectTime": "Thu, 26 Jan 2023 03:17:00 GMT",
+ "doneChargingTime": "Thu, 26 Jan 2023 02:34:00 GMT",
+ "kWhDelivered": 21.524,
+ "sessionID": "site2-station7-27",
+ "siteID": "0001",
+ "spaceID": "SP-35",
+ "stationID": "ST-042",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3448",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 23.398,
+ "milesRequested": 70.2,
+ "minutesAvailable": 148,
+ "modifiedAt": "Thu, 26 Jan 2023 00:49:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 26 Jan 2023 04:07:00 GMT",
+ "userID": 5743
+ }
+ ]
+ },
+ {
+ "_id": "session_0028",
+ "clusterID": "0002",
+ "connectionTime": "Fri, 03 Feb 2023 04:24:00 GMT",
+ "disconnectTime": "Fri, 03 Feb 2023 07:35:00 GMT",
+ "doneChargingTime": "Fri, 03 Feb 2023 06:25:00 GMT",
+ "kWhDelivered": 18.203,
+ "sessionID": "site3-station8-28",
+ "siteID": "0001",
+ "spaceID": "SP-40",
+ "stationID": "ST-046",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9569",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 27.764,
+ "milesRequested": 83.3,
+ "minutesAvailable": 191,
+ "modifiedAt": "Fri, 03 Feb 2023 04:24:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 03 Feb 2023 07:43:00 GMT",
+ "userID": 2161
+ }
+ ]
+ },
+ {
+ "_id": "session_0029",
+ "clusterID": "0004",
+ "connectionTime": "Sat, 28 Jan 2023 21:11:00 GMT",
+ "disconnectTime": "Sun, 29 Jan 2023 01:00:00 GMT",
+ "doneChargingTime": "Sat, 28 Jan 2023 23:14:00 GMT",
+ "kWhDelivered": 18.465,
+ "sessionID": "site4-station9-29",
+ "siteID": "0004",
+ "spaceID": "SP-44",
+ "stationID": "ST-088",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3036",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 20.638,
+ "milesRequested": 61.9,
+ "minutesAvailable": 229,
+ "modifiedAt": "Sat, 28 Jan 2023 21:11:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 29 Jan 2023 01:21:00 GMT",
+ "userID": 2900
+ }
+ ]
+ },
+ {
+ "_id": "session_0030",
+ "clusterID": "0002",
+ "connectionTime": "Mon, 06 Mar 2023 00:17:00 GMT",
+ "disconnectTime": "Mon, 06 Mar 2023 03:38:00 GMT",
+ "doneChargingTime": "Mon, 06 Mar 2023 02:44:00 GMT",
+ "kWhDelivered": 18.586,
+ "sessionID": "site0-station10-30",
+ "siteID": "0002",
+ "spaceID": "SP-81",
+ "stationID": "ST-145",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7153",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 33.281,
+ "milesRequested": 99.8,
+ "minutesAvailable": 201,
+ "modifiedAt": "Mon, 06 Mar 2023 00:17:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 06 Mar 2023 03:40:00 GMT",
+ "userID": 2424
+ }
+ ]
+ },
+ {
+ "_id": "session_0031",
+ "clusterID": "0001",
+ "connectionTime": "Sat, 07 Jan 2023 07:49:00 GMT",
+ "disconnectTime": "Sat, 07 Jan 2023 08:55:00 GMT",
+ "doneChargingTime": "Sat, 07 Jan 2023 08:30:00 GMT",
+ "kWhDelivered": 14.948,
+ "sessionID": "site1-station11-31",
+ "siteID": "0004",
+ "spaceID": "SP-72",
+ "stationID": "ST-133",
+ "timezone": "America/Los_Angeles",
+ "userID": "U5748",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 22.644,
+ "milesRequested": 67.9,
+ "minutesAvailable": 66,
+ "modifiedAt": "Sat, 07 Jan 2023 07:49:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sat, 07 Jan 2023 09:42:00 GMT",
+ "userID": 9005
+ }
+ ]
+ },
+ {
+ "_id": "session_0032",
+ "clusterID": "0002",
+ "connectionTime": "Tue, 14 Mar 2023 02:02:00 GMT",
+ "disconnectTime": "Tue, 14 Mar 2023 06:22:00 GMT",
+ "doneChargingTime": "Tue, 14 Mar 2023 04:51:00 GMT",
+ "kWhDelivered": 14.776,
+ "sessionID": "site2-station12-32",
+ "siteID": "0003",
+ "spaceID": "SP-75",
+ "stationID": "ST-199",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3729",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 16.449,
+ "milesRequested": 49.3,
+ "minutesAvailable": 260,
+ "modifiedAt": "Tue, 14 Mar 2023 02:02:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 14 Mar 2023 07:05:00 GMT",
+ "userID": 4144
+ }
+ ]
+ },
+ {
+ "_id": "session_0033",
+ "clusterID": "0004",
+ "connectionTime": "Thu, 02 Feb 2023 23:11:00 GMT",
+ "disconnectTime": "Fri, 03 Feb 2023 00:08:00 GMT",
+ "doneChargingTime": "Fri, 03 Feb 2023 00:00:00 GMT",
+ "kWhDelivered": 43.861,
+ "sessionID": "site3-station13-33",
+ "siteID": "0002",
+ "spaceID": "SP-16",
+ "stationID": "ST-128",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7520",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 57.901,
+ "milesRequested": 173.7,
+ "minutesAvailable": 57,
+ "modifiedAt": "Thu, 02 Feb 2023 23:11:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 03 Feb 2023 01:00:00 GMT",
+ "userID": 4395
+ }
+ ]
+ },
+ {
+ "_id": "session_0034",
+ "clusterID": "0004",
+ "connectionTime": "Tue, 28 Feb 2023 16:16:00 GMT",
+ "disconnectTime": "Tue, 28 Feb 2023 18:28:00 GMT",
+ "doneChargingTime": "Tue, 28 Feb 2023 16:46:00 GMT",
+ "kWhDelivered": 33.054,
+ "sessionID": "site4-station14-34",
+ "siteID": "0004",
+ "spaceID": "SP-47",
+ "stationID": "ST-140",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3479",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 35.147,
+ "milesRequested": 105.4,
+ "minutesAvailable": 132,
+ "modifiedAt": "Tue, 28 Feb 2023 16:16:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 28 Feb 2023 18:41:00 GMT",
+ "userID": 8993
+ }
+ ]
+ },
+ {
+ "_id": "session_0035",
+ "clusterID": "0004",
+ "connectionTime": "Sat, 14 Jan 2023 20:11:00 GMT",
+ "disconnectTime": "Sat, 14 Jan 2023 23:46:00 GMT",
+ "doneChargingTime": "Sat, 14 Jan 2023 22:55:00 GMT",
+ "kWhDelivered": 35.597,
+ "sessionID": "site0-station15-35",
+ "siteID": "0003",
+ "spaceID": "SP-64",
+ "stationID": "ST-128",
+ "timezone": "America/Los_Angeles",
+ "userID": "U4309",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 53.15,
+ "milesRequested": 159.4,
+ "minutesAvailable": 215,
+ "modifiedAt": "Sat, 14 Jan 2023 20:11:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 15 Jan 2023 00:26:00 GMT",
+ "userID": 4584
+ }
+ ]
+ },
+ {
+ "_id": "session_0036",
+ "clusterID": "0001",
+ "connectionTime": "Mon, 02 Jan 2023 05:12:00 GMT",
+ "disconnectTime": "Mon, 02 Jan 2023 08:29:00 GMT",
+ "doneChargingTime": "Mon, 02 Jan 2023 06:59:00 GMT",
+ "kWhDelivered": 45.455,
+ "sessionID": "site1-station16-36",
+ "siteID": "0005",
+ "spaceID": "SP-19",
+ "stationID": "ST-066",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3554",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 61.817,
+ "milesRequested": 185.5,
+ "minutesAvailable": 197,
+ "modifiedAt": "Mon, 02 Jan 2023 05:12:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 02 Jan 2023 09:16:00 GMT",
+ "userID": 5823
+ }
+ ]
+ },
+ {
+ "_id": "session_0037",
+ "clusterID": "0001",
+ "connectionTime": "Tue, 07 Mar 2023 16:46:00 GMT",
+ "disconnectTime": "Tue, 07 Mar 2023 19:14:00 GMT",
+ "doneChargingTime": "Tue, 07 Mar 2023 19:06:00 GMT",
+ "kWhDelivered": 8.809,
+ "sessionID": "site2-station17-37",
+ "siteID": "0001",
+ "spaceID": "SP-29",
+ "stationID": "ST-034",
+ "timezone": "America/Los_Angeles",
+ "userID": "U1665",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 26.187,
+ "milesRequested": 78.6,
+ "minutesAvailable": 148,
+ "modifiedAt": "Tue, 07 Mar 2023 16:46:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 07 Mar 2023 20:05:00 GMT",
+ "userID": 1250
+ }
+ ]
+ },
+ {
+ "_id": "session_0038",
+ "clusterID": "0004",
+ "connectionTime": "Sat, 11 Mar 2023 09:50:00 GMT",
+ "disconnectTime": "Sat, 11 Mar 2023 12:46:00 GMT",
+ "doneChargingTime": "Sat, 11 Mar 2023 12:04:00 GMT",
+ "kWhDelivered": 12.231,
+ "sessionID": "site3-station18-38",
+ "siteID": "0003",
+ "spaceID": "SP-65",
+ "stationID": "ST-098",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9680",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 15.207,
+ "milesRequested": 45.6,
+ "minutesAvailable": 176,
+ "modifiedAt": "Sat, 11 Mar 2023 09:50:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sat, 11 Mar 2023 13:41:00 GMT",
+ "userID": 1550
+ }
+ ]
+ },
+ {
+ "_id": "session_0039",
+ "clusterID": "0001",
+ "connectionTime": "Wed, 22 Feb 2023 13:38:00 GMT",
+ "disconnectTime": "Wed, 22 Feb 2023 15:47:00 GMT",
+ "doneChargingTime": "Wed, 22 Feb 2023 14:21:00 GMT",
+ "kWhDelivered": 41.23,
+ "sessionID": "site4-station19-39",
+ "siteID": "0004",
+ "spaceID": "SP-97",
+ "stationID": "ST-053",
+ "timezone": "America/Los_Angeles",
+ "userID": "U5745",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 56.387,
+ "milesRequested": 169.2,
+ "minutesAvailable": 129,
+ "modifiedAt": "Wed, 22 Feb 2023 13:38:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 22 Feb 2023 16:37:00 GMT",
+ "userID": 7845
+ }
+ ]
+ },
+ {
+ "_id": "session_0040",
+ "clusterID": "0002",
+ "connectionTime": "Fri, 17 Mar 2023 11:45:00 GMT",
+ "disconnectTime": "Fri, 17 Mar 2023 15:56:00 GMT",
+ "doneChargingTime": "Fri, 17 Mar 2023 14:08:00 GMT",
+ "kWhDelivered": 48.751,
+ "sessionID": "site0-station0-40",
+ "siteID": "0001",
+ "spaceID": "SP-85",
+ "stationID": "ST-001",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3981",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 60.9,
+ "milesRequested": 182.7,
+ "minutesAvailable": 251,
+ "modifiedAt": "Fri, 17 Mar 2023 11:45:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 17 Mar 2023 16:46:00 GMT",
+ "userID": 9304
+ }
+ ]
+ },
+ {
+ "_id": "session_0041",
+ "clusterID": "0003",
+ "connectionTime": "Wed, 22 Feb 2023 05:45:00 GMT",
+ "disconnectTime": "Wed, 22 Feb 2023 07:52:00 GMT",
+ "doneChargingTime": "Wed, 22 Feb 2023 07:10:00 GMT",
+ "kWhDelivered": 27.208,
+ "sessionID": "site1-station1-41",
+ "siteID": "0004",
+ "spaceID": "SP-50",
+ "stationID": "ST-099",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2020",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 32.448,
+ "milesRequested": 97.3,
+ "minutesAvailable": 127,
+ "modifiedAt": "Wed, 22 Feb 2023 05:45:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 22 Feb 2023 07:56:00 GMT",
+ "userID": 3086
+ }
+ ]
+ },
+ {
+ "_id": "session_0042",
+ "clusterID": "0004",
+ "connectionTime": "Mon, 23 Jan 2023 01:59:00 GMT",
+ "disconnectTime": "Mon, 23 Jan 2023 05:05:00 GMT",
+ "doneChargingTime": "Mon, 23 Jan 2023 03:32:00 GMT",
+ "kWhDelivered": 20.031,
+ "sessionID": "site2-station2-42",
+ "siteID": "0004",
+ "spaceID": "SP-19",
+ "stationID": "ST-126",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2337",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 38.868,
+ "milesRequested": 116.6,
+ "minutesAvailable": 186,
+ "modifiedAt": "Mon, 23 Jan 2023 01:59:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 23 Jan 2023 05:58:00 GMT",
+ "userID": 3480
+ }
+ ]
+ },
+ {
+ "_id": "session_0043",
+ "clusterID": "0001",
+ "connectionTime": "Thu, 16 Mar 2023 02:06:00 GMT",
+ "disconnectTime": "Thu, 16 Mar 2023 04:48:00 GMT",
+ "doneChargingTime": "Thu, 16 Mar 2023 03:56:00 GMT",
+ "kWhDelivered": 32.53,
+ "sessionID": "site3-station3-43",
+ "siteID": "0001",
+ "spaceID": "SP-61",
+ "stationID": "ST-200",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3480",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 40.264,
+ "milesRequested": 120.8,
+ "minutesAvailable": 162,
+ "modifiedAt": "Thu, 16 Mar 2023 02:06:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 16 Mar 2023 04:50:00 GMT",
+ "userID": 1531
+ }
+ ]
+ },
+ {
+ "_id": "session_0044",
+ "clusterID": "0003",
+ "connectionTime": "Fri, 24 Feb 2023 19:00:00 GMT",
+ "disconnectTime": "Fri, 24 Feb 2023 22:14:00 GMT",
+ "doneChargingTime": "Fri, 24 Feb 2023 21:58:00 GMT",
+ "kWhDelivered": 19.576,
+ "sessionID": "site4-station4-44",
+ "siteID": "0002",
+ "spaceID": "SP-50",
+ "stationID": "ST-199",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9033",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 33.578,
+ "milesRequested": 100.7,
+ "minutesAvailable": 194,
+ "modifiedAt": "Fri, 24 Feb 2023 19:00:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 24 Feb 2023 22:54:00 GMT",
+ "userID": 1985
+ }
+ ]
+ },
+ {
+ "_id": "session_0045",
+ "clusterID": "0003",
+ "connectionTime": "Sat, 25 Feb 2023 20:49:00 GMT",
+ "disconnectTime": "Sun, 26 Feb 2023 00:26:00 GMT",
+ "doneChargingTime": "Sat, 25 Feb 2023 23:08:00 GMT",
+ "kWhDelivered": 47.026,
+ "sessionID": "site0-station5-45",
+ "siteID": "0002",
+ "spaceID": "SP-50",
+ "stationID": "ST-076",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2991",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 60.035,
+ "milesRequested": 180.1,
+ "minutesAvailable": 217,
+ "modifiedAt": "Sat, 25 Feb 2023 20:49:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 26 Feb 2023 01:06:00 GMT",
+ "userID": 4098
+ }
+ ]
+ },
+ {
+ "_id": "session_0046",
+ "clusterID": "0001",
+ "connectionTime": "Wed, 04 Jan 2023 19:24:00 GMT",
+ "disconnectTime": "Wed, 04 Jan 2023 22:20:00 GMT",
+ "doneChargingTime": "Wed, 04 Jan 2023 21:24:00 GMT",
+ "kWhDelivered": 39.048,
+ "sessionID": "site1-station6-46",
+ "siteID": "0001",
+ "spaceID": "SP-06",
+ "stationID": "ST-125",
+ "timezone": "America/Los_Angeles",
+ "userID": "U5184",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 48.157,
+ "milesRequested": 144.5,
+ "minutesAvailable": 176,
+ "modifiedAt": "Wed, 04 Jan 2023 19:24:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 04 Jan 2023 22:43:00 GMT",
+ "userID": 9520
+ }
+ ]
+ },
+ {
+ "_id": "session_0047",
+ "clusterID": "0004",
+ "connectionTime": "Fri, 03 Mar 2023 00:00:00 GMT",
+ "disconnectTime": "Fri, 03 Mar 2023 03:58:00 GMT",
+ "doneChargingTime": "Fri, 03 Mar 2023 02:45:00 GMT",
+ "kWhDelivered": 14.719,
+ "sessionID": "site2-station7-47",
+ "siteID": "0005",
+ "spaceID": "SP-40",
+ "stationID": "ST-030",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3386",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 16.589,
+ "milesRequested": 49.8,
+ "minutesAvailable": 238,
+ "modifiedAt": "Fri, 03 Mar 2023 00:00:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 03 Mar 2023 04:54:00 GMT",
+ "userID": 7914
+ }
+ ]
+ },
+ {
+ "_id": "session_0048",
+ "clusterID": "0002",
+ "connectionTime": "Sun, 26 Mar 2023 08:03:00 GMT",
+ "disconnectTime": "Sun, 26 Mar 2023 10:43:00 GMT",
+ "doneChargingTime": "Sun, 26 Mar 2023 08:44:00 GMT",
+ "kWhDelivered": 23.704,
+ "sessionID": "site3-station8-48",
+ "siteID": "0001",
+ "spaceID": "SP-58",
+ "stationID": "ST-111",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7832",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 25.688,
+ "milesRequested": 77.1,
+ "minutesAvailable": 160,
+ "modifiedAt": "Sun, 26 Mar 2023 08:03:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 26 Mar 2023 10:49:00 GMT",
+ "userID": 9136
+ }
+ ]
+ },
+ {
+ "_id": "session_0049",
+ "clusterID": "0003",
+ "connectionTime": "Sun, 26 Mar 2023 00:12:00 GMT",
+ "disconnectTime": "Sun, 26 Mar 2023 03:27:00 GMT",
+ "doneChargingTime": "Sun, 26 Mar 2023 01:55:00 GMT",
+ "kWhDelivered": 8.534,
+ "sessionID": "site4-station9-49",
+ "siteID": "0001",
+ "spaceID": "SP-45",
+ "stationID": "ST-090",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3914",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 9.941,
+ "milesRequested": 29.8,
+ "minutesAvailable": 195,
+ "modifiedAt": "Sun, 26 Mar 2023 00:12:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 26 Mar 2023 03:43:00 GMT",
+ "userID": 4776
+ }
+ ]
+ },
+ {
+ "_id": "session_0050",
+ "clusterID": "0002",
+ "connectionTime": "Thu, 16 Mar 2023 21:08:00 GMT",
+ "disconnectTime": "Thu, 16 Mar 2023 23:10:00 GMT",
+ "doneChargingTime": "Thu, 16 Mar 2023 23:01:00 GMT",
+ "kWhDelivered": 45.205,
+ "sessionID": "site0-station10-50",
+ "siteID": "0001",
+ "spaceID": "SP-96",
+ "stationID": "ST-002",
+ "timezone": "America/Los_Angeles",
+ "userID": "U5804",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 49.365,
+ "milesRequested": 148.1,
+ "minutesAvailable": 122,
+ "modifiedAt": "Thu, 16 Mar 2023 21:08:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 16 Mar 2023 23:48:00 GMT",
+ "userID": 1403
+ }
+ ]
+ },
+ {
+ "_id": "session_0051",
+ "clusterID": "0004",
+ "connectionTime": "Mon, 27 Mar 2023 05:42:00 GMT",
+ "disconnectTime": "Mon, 27 Mar 2023 09:05:00 GMT",
+ "doneChargingTime": "Mon, 27 Mar 2023 08:36:00 GMT",
+ "kWhDelivered": 11.385,
+ "sessionID": "site1-station11-51",
+ "siteID": "0003",
+ "spaceID": "SP-91",
+ "stationID": "ST-067",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3132",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 20.466,
+ "milesRequested": 61.4,
+ "minutesAvailable": 203,
+ "modifiedAt": "Mon, 27 Mar 2023 05:42:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 27 Mar 2023 09:59:00 GMT",
+ "userID": 4409
+ }
+ ]
+ },
+ {
+ "_id": "session_0052",
+ "clusterID": "0003",
+ "connectionTime": "Fri, 03 Feb 2023 07:19:00 GMT",
+ "disconnectTime": "Fri, 03 Feb 2023 10:04:00 GMT",
+ "doneChargingTime": "Fri, 03 Feb 2023 09:04:00 GMT",
+ "kWhDelivered": 18.336,
+ "sessionID": "site2-station12-52",
+ "siteID": "0002",
+ "spaceID": "SP-76",
+ "stationID": "ST-021",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2680",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 35.904,
+ "milesRequested": 107.7,
+ "minutesAvailable": 165,
+ "modifiedAt": "Fri, 03 Feb 2023 07:19:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 03 Feb 2023 10:22:00 GMT",
+ "userID": 6042
+ }
+ ]
+ },
+ {
+ "_id": "session_0053",
+ "clusterID": "0003",
+ "connectionTime": "Sun, 15 Jan 2023 13:40:00 GMT",
+ "disconnectTime": "Sun, 15 Jan 2023 17:30:00 GMT",
+ "doneChargingTime": "Sun, 15 Jan 2023 15:36:00 GMT",
+ "kWhDelivered": 47.556,
+ "sessionID": "site3-station13-53",
+ "siteID": "0005",
+ "spaceID": "SP-32",
+ "stationID": "ST-061",
+ "timezone": "America/Los_Angeles",
+ "userID": "U4013",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 63.644,
+ "milesRequested": 190.9,
+ "minutesAvailable": 230,
+ "modifiedAt": "Sun, 15 Jan 2023 13:40:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 15 Jan 2023 17:39:00 GMT",
+ "userID": 7102
+ }
+ ]
+ },
+ {
+ "_id": "session_0054",
+ "clusterID": "0001",
+ "connectionTime": "Wed, 08 Feb 2023 13:02:00 GMT",
+ "disconnectTime": "Wed, 08 Feb 2023 15:23:00 GMT",
+ "doneChargingTime": "Wed, 08 Feb 2023 13:33:00 GMT",
+ "kWhDelivered": 32.054,
+ "sessionID": "site4-station14-54",
+ "siteID": "0002",
+ "spaceID": "SP-54",
+ "stationID": "ST-077",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7828",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 39.93,
+ "milesRequested": 119.8,
+ "minutesAvailable": 141,
+ "modifiedAt": "Wed, 08 Feb 2023 13:02:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 08 Feb 2023 15:31:00 GMT",
+ "userID": 3331
+ }
+ ]
+ },
+ {
+ "_id": "session_0055",
+ "clusterID": "0003",
+ "connectionTime": "Fri, 24 Feb 2023 03:13:00 GMT",
+ "disconnectTime": "Fri, 24 Feb 2023 05:59:00 GMT",
+ "doneChargingTime": "Fri, 24 Feb 2023 05:21:00 GMT",
+ "kWhDelivered": 20.955,
+ "sessionID": "site0-station15-55",
+ "siteID": "0005",
+ "spaceID": "SP-08",
+ "stationID": "ST-097",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7694",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 25.916,
+ "milesRequested": 77.7,
+ "minutesAvailable": 166,
+ "modifiedAt": "Fri, 24 Feb 2023 03:13:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 24 Feb 2023 06:39:00 GMT",
+ "userID": 7835
+ }
+ ]
+ },
+ {
+ "_id": "session_0056",
+ "clusterID": "0001",
+ "connectionTime": "Wed, 29 Mar 2023 21:25:00 GMT",
+ "disconnectTime": "Thu, 30 Mar 2023 00:03:00 GMT",
+ "doneChargingTime": "Wed, 29 Mar 2023 23:07:00 GMT",
+ "kWhDelivered": 21.722,
+ "sessionID": "site1-station16-56",
+ "siteID": "0002",
+ "spaceID": "SP-14",
+ "stationID": "ST-071",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2837",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 40.769,
+ "milesRequested": 122.3,
+ "minutesAvailable": 158,
+ "modifiedAt": "Wed, 29 Mar 2023 21:25:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 30 Mar 2023 00:16:00 GMT",
+ "userID": 3521
+ }
+ ]
+ },
+ {
+ "_id": "session_0057",
+ "clusterID": "0002",
+ "connectionTime": "Tue, 14 Mar 2023 18:08:00 GMT",
+ "disconnectTime": "Tue, 14 Mar 2023 22:20:00 GMT",
+ "doneChargingTime": "Tue, 14 Mar 2023 20:22:00 GMT",
+ "kWhDelivered": 13.342,
+ "sessionID": "site2-station17-57",
+ "siteID": "0002",
+ "spaceID": "SP-59",
+ "stationID": "ST-088",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9574",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 21.776,
+ "milesRequested": 65.3,
+ "minutesAvailable": 252,
+ "modifiedAt": "Tue, 14 Mar 2023 18:08:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 14 Mar 2023 22:45:00 GMT",
+ "userID": 6822
+ }
+ ]
+ },
+ {
+ "_id": "session_0058",
+ "clusterID": "0004",
+ "connectionTime": "Sun, 12 Feb 2023 10:12:00 GMT",
+ "disconnectTime": "Sun, 12 Feb 2023 11:55:00 GMT",
+ "doneChargingTime": "Sun, 12 Feb 2023 10:54:00 GMT",
+ "kWhDelivered": 14.164,
+ "sessionID": "site3-station18-58",
+ "siteID": "0005",
+ "spaceID": "SP-60",
+ "stationID": "ST-002",
+ "timezone": "America/Los_Angeles",
+ "userID": "U4583",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 14.201,
+ "milesRequested": 42.6,
+ "minutesAvailable": 103,
+ "modifiedAt": "Sun, 12 Feb 2023 10:12:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 12 Feb 2023 12:43:00 GMT",
+ "userID": 2875
+ }
+ ]
+ },
+ {
+ "_id": "session_0059",
+ "clusterID": "0004",
+ "connectionTime": "Sun, 12 Mar 2023 07:06:00 GMT",
+ "disconnectTime": "Sun, 12 Mar 2023 09:52:00 GMT",
+ "doneChargingTime": "Sun, 12 Mar 2023 08:43:00 GMT",
+ "kWhDelivered": 12.03,
+ "sessionID": "site4-station19-59",
+ "siteID": "0001",
+ "spaceID": "SP-87",
+ "stationID": "ST-128",
+ "timezone": "America/Los_Angeles",
+ "userID": "U4807",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 26.156,
+ "milesRequested": 78.5,
+ "minutesAvailable": 166,
+ "modifiedAt": "Sun, 12 Mar 2023 07:06:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 12 Mar 2023 10:30:00 GMT",
+ "userID": 7640
+ }
+ ]
+ },
+ {
+ "_id": "session_0060",
+ "clusterID": "0003",
+ "connectionTime": "Thu, 26 Jan 2023 19:51:00 GMT",
+ "disconnectTime": "Thu, 26 Jan 2023 20:31:00 GMT",
+ "doneChargingTime": "Thu, 26 Jan 2023 20:16:00 GMT",
+ "kWhDelivered": 44.767,
+ "sessionID": "site0-station0-60",
+ "siteID": "0004",
+ "spaceID": "SP-68",
+ "stationID": "ST-149",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7491",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 45.578,
+ "milesRequested": 136.7,
+ "minutesAvailable": 40,
+ "modifiedAt": "Thu, 26 Jan 2023 19:51:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 26 Jan 2023 20:48:00 GMT",
+ "userID": 2671
+ }
+ ]
+ },
+ {
+ "_id": "session_0061",
+ "clusterID": "0002",
+ "connectionTime": "Fri, 10 Mar 2023 06:51:00 GMT",
+ "disconnectTime": "Fri, 10 Mar 2023 09:00:00 GMT",
+ "doneChargingTime": "Fri, 10 Mar 2023 08:15:00 GMT",
+ "kWhDelivered": 44.208,
+ "sessionID": "site1-station1-61",
+ "siteID": "0005",
+ "spaceID": "SP-11",
+ "stationID": "ST-010",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2154",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 57.659,
+ "milesRequested": 173.0,
+ "minutesAvailable": 129,
+ "modifiedAt": "Fri, 10 Mar 2023 06:51:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 10 Mar 2023 09:18:00 GMT",
+ "userID": 6007
+ }
+ ]
+ },
+ {
+ "_id": "session_0062",
+ "clusterID": "0002",
+ "connectionTime": "Sat, 18 Feb 2023 21:45:00 GMT",
+ "disconnectTime": "Sat, 18 Feb 2023 23:47:00 GMT",
+ "doneChargingTime": "Sat, 18 Feb 2023 23:32:00 GMT",
+ "kWhDelivered": 43.675,
+ "sessionID": "site2-station2-62",
+ "siteID": "0001",
+ "spaceID": "SP-54",
+ "stationID": "ST-075",
+ "timezone": "America/Los_Angeles",
+ "userID": "U5632",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 61.489,
+ "milesRequested": 184.5,
+ "minutesAvailable": 122,
+ "modifiedAt": "Sat, 18 Feb 2023 21:45:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 19 Feb 2023 00:20:00 GMT",
+ "userID": 3202
+ }
+ ]
+ },
+ {
+ "_id": "session_0063",
+ "clusterID": "0004",
+ "connectionTime": "Wed, 22 Feb 2023 13:52:00 GMT",
+ "disconnectTime": "Wed, 22 Feb 2023 17:45:00 GMT",
+ "doneChargingTime": "Wed, 22 Feb 2023 16:25:00 GMT",
+ "kWhDelivered": 28.912,
+ "sessionID": "site3-station3-63",
+ "siteID": "0003",
+ "spaceID": "SP-38",
+ "stationID": "ST-114",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7091",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 37.128,
+ "milesRequested": 111.4,
+ "minutesAvailable": 233,
+ "modifiedAt": "Wed, 22 Feb 2023 13:52:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 22 Feb 2023 17:58:00 GMT",
+ "userID": 3257
+ }
+ ]
+ },
+ {
+ "_id": "session_0064",
+ "clusterID": "0002",
+ "connectionTime": "Sun, 15 Jan 2023 15:07:00 GMT",
+ "disconnectTime": "Sun, 15 Jan 2023 17:27:00 GMT",
+ "doneChargingTime": "Sun, 15 Jan 2023 15:58:00 GMT",
+ "kWhDelivered": 22.164,
+ "sessionID": "site4-station4-64",
+ "siteID": "0005",
+ "spaceID": "SP-86",
+ "stationID": "ST-077",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6800",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 33.985,
+ "milesRequested": 102.0,
+ "minutesAvailable": 140,
+ "modifiedAt": "Sun, 15 Jan 2023 15:07:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 15 Jan 2023 17:34:00 GMT",
+ "userID": 8744
+ }
+ ]
+ },
+ {
+ "_id": "session_0065",
+ "clusterID": "0004",
+ "connectionTime": "Thu, 09 Mar 2023 21:40:00 GMT",
+ "disconnectTime": "Fri, 10 Mar 2023 00:13:00 GMT",
+ "doneChargingTime": "Thu, 09 Mar 2023 23:46:00 GMT",
+ "kWhDelivered": 26.999,
+ "sessionID": "site0-station5-65",
+ "siteID": "0001",
+ "spaceID": "SP-57",
+ "stationID": "ST-077",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3339",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 37.03,
+ "milesRequested": 111.1,
+ "minutesAvailable": 153,
+ "modifiedAt": "Thu, 09 Mar 2023 21:40:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 10 Mar 2023 00:43:00 GMT",
+ "userID": 9118
+ }
+ ]
+ },
+ {
+ "_id": "session_0066",
+ "clusterID": "0001",
+ "connectionTime": "Fri, 06 Jan 2023 02:28:00 GMT",
+ "disconnectTime": "Fri, 06 Jan 2023 05:54:00 GMT",
+ "doneChargingTime": "Fri, 06 Jan 2023 05:27:00 GMT",
+ "kWhDelivered": 20.993,
+ "sessionID": "site1-station6-66",
+ "siteID": "0005",
+ "spaceID": "SP-09",
+ "stationID": "ST-176",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2333",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 28.812,
+ "milesRequested": 86.4,
+ "minutesAvailable": 206,
+ "modifiedAt": "Fri, 06 Jan 2023 02:28:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 06 Jan 2023 05:55:00 GMT",
+ "userID": 7473
+ }
+ ]
+ },
+ {
+ "_id": "session_0067",
+ "clusterID": "0003",
+ "connectionTime": "Sun, 01 Jan 2023 22:01:00 GMT",
+ "disconnectTime": "Sun, 01 Jan 2023 23:58:00 GMT",
+ "doneChargingTime": "Sun, 01 Jan 2023 23:53:00 GMT",
+ "kWhDelivered": 32.939,
+ "sessionID": "site2-station7-67",
+ "siteID": "0002",
+ "spaceID": "SP-19",
+ "stationID": "ST-193",
+ "timezone": "America/Los_Angeles",
+ "userID": "U5717",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 52.933,
+ "milesRequested": 158.8,
+ "minutesAvailable": 117,
+ "modifiedAt": "Sun, 01 Jan 2023 22:01:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 02 Jan 2023 00:05:00 GMT",
+ "userID": 2727
+ }
+ ]
+ },
+ {
+ "_id": "session_0068",
+ "clusterID": "0004",
+ "connectionTime": "Thu, 09 Feb 2023 20:07:00 GMT",
+ "disconnectTime": "Thu, 09 Feb 2023 23:55:00 GMT",
+ "doneChargingTime": "Thu, 09 Feb 2023 22:24:00 GMT",
+ "kWhDelivered": 22.286,
+ "sessionID": "site3-station8-68",
+ "siteID": "0002",
+ "spaceID": "SP-58",
+ "stationID": "ST-182",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3416",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 28.9,
+ "milesRequested": 86.7,
+ "minutesAvailable": 228,
+ "modifiedAt": "Thu, 09 Feb 2023 20:07:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 10 Feb 2023 00:16:00 GMT",
+ "userID": 6178
+ }
+ ]
+ },
+ {
+ "_id": "session_0069",
+ "clusterID": "0004",
+ "connectionTime": "Fri, 13 Jan 2023 02:17:00 GMT",
+ "disconnectTime": "Fri, 13 Jan 2023 05:26:00 GMT",
+ "doneChargingTime": "Fri, 13 Jan 2023 03:30:00 GMT",
+ "kWhDelivered": 24.986,
+ "sessionID": "site4-station9-69",
+ "siteID": "0004",
+ "spaceID": "SP-63",
+ "stationID": "ST-100",
+ "timezone": "America/Los_Angeles",
+ "userID": "U4607",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 40.773,
+ "milesRequested": 122.3,
+ "minutesAvailable": 189,
+ "modifiedAt": "Fri, 13 Jan 2023 02:17:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 13 Jan 2023 05:37:00 GMT",
+ "userID": 8197
+ }
+ ]
+ },
+ {
+ "_id": "session_0070",
+ "clusterID": "0002",
+ "connectionTime": "Mon, 27 Mar 2023 21:32:00 GMT",
+ "disconnectTime": "Mon, 27 Mar 2023 23:59:00 GMT",
+ "doneChargingTime": "Mon, 27 Mar 2023 22:44:00 GMT",
+ "kWhDelivered": 7.241,
+ "sessionID": "site0-station10-70",
+ "siteID": "0001",
+ "spaceID": "SP-24",
+ "stationID": "ST-094",
+ "timezone": "America/Los_Angeles",
+ "userID": "U1933",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 15.008,
+ "milesRequested": 45.0,
+ "minutesAvailable": 147,
+ "modifiedAt": "Mon, 27 Mar 2023 21:32:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 28 Mar 2023 00:44:00 GMT",
+ "userID": 3840
+ }
+ ]
+ },
+ {
+ "_id": "session_0071",
+ "clusterID": "0003",
+ "connectionTime": "Sun, 22 Jan 2023 13:08:00 GMT",
+ "disconnectTime": "Sun, 22 Jan 2023 16:42:00 GMT",
+ "doneChargingTime": "Sun, 22 Jan 2023 16:04:00 GMT",
+ "kWhDelivered": 8.898,
+ "sessionID": "site1-station11-71",
+ "siteID": "0003",
+ "spaceID": "SP-53",
+ "stationID": "ST-118",
+ "timezone": "America/Los_Angeles",
+ "userID": "U1883",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 26.275,
+ "milesRequested": 78.8,
+ "minutesAvailable": 214,
+ "modifiedAt": "Sun, 22 Jan 2023 13:08:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 22 Jan 2023 17:21:00 GMT",
+ "userID": 9456
+ }
+ ]
+ },
+ {
+ "_id": "session_0072",
+ "clusterID": "0004",
+ "connectionTime": "Thu, 02 Mar 2023 22:05:00 GMT",
+ "disconnectTime": "Fri, 03 Mar 2023 02:44:00 GMT",
+ "doneChargingTime": "Fri, 03 Mar 2023 00:45:00 GMT",
+ "kWhDelivered": 47.033,
+ "sessionID": "site2-station12-72",
+ "siteID": "0003",
+ "spaceID": "SP-91",
+ "stationID": "ST-122",
+ "timezone": "America/Los_Angeles",
+ "userID": "U4529",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 58.652,
+ "milesRequested": 176.0,
+ "minutesAvailable": 279,
+ "modifiedAt": "Thu, 02 Mar 2023 22:05:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 03 Mar 2023 03:31:00 GMT",
+ "userID": 5356
+ }
+ ]
+ },
+ {
+ "_id": "session_0073",
+ "clusterID": "0001",
+ "connectionTime": "Thu, 05 Jan 2023 04:15:00 GMT",
+ "disconnectTime": "Thu, 05 Jan 2023 04:52:00 GMT",
+ "doneChargingTime": "Thu, 05 Jan 2023 04:46:00 GMT",
+ "kWhDelivered": 20.741,
+ "sessionID": "site3-station13-73",
+ "siteID": "0002",
+ "spaceID": "SP-09",
+ "stationID": "ST-110",
+ "timezone": "America/Los_Angeles",
+ "userID": "U4639",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 26.537,
+ "milesRequested": 79.6,
+ "minutesAvailable": 37,
+ "modifiedAt": "Thu, 05 Jan 2023 04:15:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 05 Jan 2023 05:02:00 GMT",
+ "userID": 7496
+ }
+ ]
+ },
+ {
+ "_id": "session_0074",
+ "clusterID": "0001",
+ "connectionTime": "Tue, 21 Feb 2023 02:27:00 GMT",
+ "disconnectTime": "Tue, 21 Feb 2023 04:41:00 GMT",
+ "doneChargingTime": "Tue, 21 Feb 2023 03:43:00 GMT",
+ "kWhDelivered": 20.278,
+ "sessionID": "site4-station14-74",
+ "siteID": "0003",
+ "spaceID": "SP-42",
+ "stationID": "ST-138",
+ "timezone": "America/Los_Angeles",
+ "userID": "U8466",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 22.325,
+ "milesRequested": 67.0,
+ "minutesAvailable": 134,
+ "modifiedAt": "Tue, 21 Feb 2023 02:27:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 21 Feb 2023 04:53:00 GMT",
+ "userID": 5186
+ }
+ ]
+ },
+ {
+ "_id": "session_0075",
+ "clusterID": "0003",
+ "connectionTime": "Tue, 03 Jan 2023 22:58:00 GMT",
+ "disconnectTime": "Wed, 04 Jan 2023 01:36:00 GMT",
+ "doneChargingTime": "Wed, 04 Jan 2023 01:31:00 GMT",
+ "kWhDelivered": 21.592,
+ "sessionID": "site0-station15-75",
+ "siteID": "0002",
+ "spaceID": "SP-26",
+ "stationID": "ST-065",
+ "timezone": "America/Los_Angeles",
+ "userID": "U5936",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 25.784,
+ "milesRequested": 77.4,
+ "minutesAvailable": 158,
+ "modifiedAt": "Tue, 03 Jan 2023 22:58:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 04 Jan 2023 01:48:00 GMT",
+ "userID": 9468
+ }
+ ]
+ },
+ {
+ "_id": "session_0076",
+ "clusterID": "0001",
+ "connectionTime": "Mon, 20 Mar 2023 14:41:00 GMT",
+ "disconnectTime": "Mon, 20 Mar 2023 17:11:00 GMT",
+ "doneChargingTime": "Mon, 20 Mar 2023 16:39:00 GMT",
+ "kWhDelivered": 20.477,
+ "sessionID": "site1-station16-76",
+ "siteID": "0003",
+ "spaceID": "SP-71",
+ "stationID": "ST-019",
+ "timezone": "America/Los_Angeles",
+ "userID": "U1150",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 34.727,
+ "milesRequested": 104.2,
+ "minutesAvailable": 150,
+ "modifiedAt": "Mon, 20 Mar 2023 14:41:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 20 Mar 2023 17:41:00 GMT",
+ "userID": 9115
+ }
+ ]
+ },
+ {
+ "_id": "session_0077",
+ "clusterID": "0004",
+ "connectionTime": "Wed, 08 Mar 2023 06:55:00 GMT",
+ "disconnectTime": "Wed, 08 Mar 2023 09:13:00 GMT",
+ "doneChargingTime": "Wed, 08 Mar 2023 09:07:00 GMT",
+ "kWhDelivered": 41.405,
+ "sessionID": "site2-station17-77",
+ "siteID": "0004",
+ "spaceID": "SP-16",
+ "stationID": "ST-022",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2334",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 60.983,
+ "milesRequested": 182.9,
+ "minutesAvailable": 138,
+ "modifiedAt": "Wed, 08 Mar 2023 06:55:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 08 Mar 2023 10:11:00 GMT",
+ "userID": 2617
+ }
+ ]
+ },
+ {
+ "_id": "session_0078",
+ "clusterID": "0001",
+ "connectionTime": "Fri, 17 Mar 2023 13:00:00 GMT",
+ "disconnectTime": "Fri, 17 Mar 2023 14:51:00 GMT",
+ "doneChargingTime": "Fri, 17 Mar 2023 13:59:00 GMT",
+ "kWhDelivered": 46.366,
+ "sessionID": "site3-station18-78",
+ "siteID": "0004",
+ "spaceID": "SP-72",
+ "stationID": "ST-194",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7463",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 55.179,
+ "milesRequested": 165.5,
+ "minutesAvailable": 111,
+ "modifiedAt": "Fri, 17 Mar 2023 13:00:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 17 Mar 2023 15:47:00 GMT",
+ "userID": 3955
+ }
+ ]
+ },
+ {
+ "_id": "session_0079",
+ "clusterID": "0003",
+ "connectionTime": "Tue, 24 Jan 2023 01:32:00 GMT",
+ "disconnectTime": "Tue, 24 Jan 2023 04:25:00 GMT",
+ "doneChargingTime": "Tue, 24 Jan 2023 03:57:00 GMT",
+ "kWhDelivered": 42.821,
+ "sessionID": "site4-station19-79",
+ "siteID": "0003",
+ "spaceID": "SP-56",
+ "stationID": "ST-028",
+ "timezone": "America/Los_Angeles",
+ "userID": "U5677",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 48.401,
+ "milesRequested": 145.2,
+ "minutesAvailable": 173,
+ "modifiedAt": "Tue, 24 Jan 2023 01:32:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 24 Jan 2023 04:33:00 GMT",
+ "userID": 9864
+ }
+ ]
+ },
+ {
+ "_id": "session_0080",
+ "clusterID": "0004",
+ "connectionTime": "Tue, 14 Mar 2023 03:40:00 GMT",
+ "disconnectTime": "Tue, 14 Mar 2023 06:22:00 GMT",
+ "doneChargingTime": "Tue, 14 Mar 2023 04:51:00 GMT",
+ "kWhDelivered": 39.986,
+ "sessionID": "site0-station0-80",
+ "siteID": "0005",
+ "spaceID": "SP-82",
+ "stationID": "ST-067",
+ "timezone": "America/Los_Angeles",
+ "userID": "U5465",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 50.267,
+ "milesRequested": 150.8,
+ "minutesAvailable": 162,
+ "modifiedAt": "Tue, 14 Mar 2023 03:40:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 14 Mar 2023 06:40:00 GMT",
+ "userID": 1271
+ }
+ ]
+ },
+ {
+ "_id": "session_0081",
+ "clusterID": "0002",
+ "connectionTime": "Thu, 12 Jan 2023 03:22:00 GMT",
+ "disconnectTime": "Thu, 12 Jan 2023 07:59:00 GMT",
+ "doneChargingTime": "Thu, 12 Jan 2023 06:19:00 GMT",
+ "kWhDelivered": 9.451,
+ "sessionID": "site1-station1-81",
+ "siteID": "0002",
+ "spaceID": "SP-37",
+ "stationID": "ST-189",
+ "timezone": "America/Los_Angeles",
+ "userID": "U1105",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 24.109,
+ "milesRequested": 72.3,
+ "minutesAvailable": 277,
+ "modifiedAt": "Thu, 12 Jan 2023 03:22:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 12 Jan 2023 08:44:00 GMT",
+ "userID": 9774
+ }
+ ]
+ },
+ {
+ "_id": "session_0082",
+ "clusterID": "0001",
+ "connectionTime": "Fri, 17 Feb 2023 05:39:00 GMT",
+ "disconnectTime": "Fri, 17 Feb 2023 09:37:00 GMT",
+ "doneChargingTime": "Fri, 17 Feb 2023 07:48:00 GMT",
+ "kWhDelivered": 10.481,
+ "sessionID": "site2-station2-82",
+ "siteID": "0005",
+ "spaceID": "SP-46",
+ "stationID": "ST-059",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9956",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 23.385,
+ "milesRequested": 70.2,
+ "minutesAvailable": 238,
+ "modifiedAt": "Fri, 17 Feb 2023 05:39:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 17 Feb 2023 09:40:00 GMT",
+ "userID": 5623
+ }
+ ]
+ },
+ {
+ "_id": "session_0083",
+ "clusterID": "0003",
+ "connectionTime": "Sat, 21 Jan 2023 12:00:00 GMT",
+ "disconnectTime": "Sat, 21 Jan 2023 13:29:00 GMT",
+ "doneChargingTime": "Sat, 21 Jan 2023 13:21:00 GMT",
+ "kWhDelivered": 18.838,
+ "sessionID": "site3-station3-83",
+ "siteID": "0004",
+ "spaceID": "SP-37",
+ "stationID": "ST-150",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3807",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 25.384,
+ "milesRequested": 76.2,
+ "minutesAvailable": 89,
+ "modifiedAt": "Sat, 21 Jan 2023 12:00:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sat, 21 Jan 2023 14:02:00 GMT",
+ "userID": 1253
+ }
+ ]
+ },
+ {
+ "_id": "session_0084",
+ "clusterID": "0002",
+ "connectionTime": "Mon, 20 Feb 2023 16:18:00 GMT",
+ "disconnectTime": "Mon, 20 Feb 2023 19:28:00 GMT",
+ "doneChargingTime": "Mon, 20 Feb 2023 18:47:00 GMT",
+ "kWhDelivered": 31.344,
+ "sessionID": "site4-station4-84",
+ "siteID": "0004",
+ "spaceID": "SP-20",
+ "stationID": "ST-046",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9363",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 31.85,
+ "milesRequested": 95.6,
+ "minutesAvailable": 190,
+ "modifiedAt": "Mon, 20 Feb 2023 16:18:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 20 Feb 2023 19:51:00 GMT",
+ "userID": 3221
+ }
+ ]
+ },
+ {
+ "_id": "session_0085",
+ "clusterID": "0002",
+ "connectionTime": "Sat, 11 Mar 2023 23:15:00 GMT",
+ "disconnectTime": "Sun, 12 Mar 2023 02:07:00 GMT",
+ "doneChargingTime": "Sun, 12 Mar 2023 00:27:00 GMT",
+ "kWhDelivered": 39.866,
+ "sessionID": "site0-station5-85",
+ "siteID": "0002",
+ "spaceID": "SP-94",
+ "stationID": "ST-034",
+ "timezone": "America/Los_Angeles",
+ "userID": "U4828",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 51.256,
+ "milesRequested": 153.8,
+ "minutesAvailable": 172,
+ "modifiedAt": "Sat, 11 Mar 2023 23:15:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 12 Mar 2023 03:05:00 GMT",
+ "userID": 6791
+ }
+ ]
+ },
+ {
+ "_id": "session_0086",
+ "clusterID": "0001",
+ "connectionTime": "Sat, 25 Feb 2023 17:17:00 GMT",
+ "disconnectTime": "Sat, 25 Feb 2023 20:24:00 GMT",
+ "doneChargingTime": "Sat, 25 Feb 2023 20:08:00 GMT",
+ "kWhDelivered": 27.431,
+ "sessionID": "site1-station6-86",
+ "siteID": "0005",
+ "spaceID": "SP-77",
+ "stationID": "ST-092",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9015",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 45.788,
+ "milesRequested": 137.4,
+ "minutesAvailable": 187,
+ "modifiedAt": "Sat, 25 Feb 2023 17:17:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sat, 25 Feb 2023 21:04:00 GMT",
+ "userID": 6057
+ }
+ ]
+ },
+ {
+ "_id": "session_0087",
+ "clusterID": "0004",
+ "connectionTime": "Mon, 02 Jan 2023 11:36:00 GMT",
+ "disconnectTime": "Mon, 02 Jan 2023 14:03:00 GMT",
+ "doneChargingTime": "Mon, 02 Jan 2023 12:52:00 GMT",
+ "kWhDelivered": 12.337,
+ "sessionID": "site2-station7-87",
+ "siteID": "0005",
+ "spaceID": "SP-41",
+ "stationID": "ST-181",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2290",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 29.919,
+ "milesRequested": 89.8,
+ "minutesAvailable": 147,
+ "modifiedAt": "Mon, 02 Jan 2023 11:36:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 02 Jan 2023 14:44:00 GMT",
+ "userID": 3251
+ }
+ ]
+ },
+ {
+ "_id": "session_0088",
+ "clusterID": "0004",
+ "connectionTime": "Sat, 25 Feb 2023 08:07:00 GMT",
+ "disconnectTime": "Sat, 25 Feb 2023 11:39:00 GMT",
+ "doneChargingTime": "Sat, 25 Feb 2023 10:09:00 GMT",
+ "kWhDelivered": 41.976,
+ "sessionID": "site3-station8-88",
+ "siteID": "0001",
+ "spaceID": "SP-27",
+ "stationID": "ST-010",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6163",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 57.683,
+ "milesRequested": 173.0,
+ "minutesAvailable": 212,
+ "modifiedAt": "Sat, 25 Feb 2023 08:07:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sat, 25 Feb 2023 11:51:00 GMT",
+ "userID": 5089
+ }
+ ]
+ },
+ {
+ "_id": "session_0089",
+ "clusterID": "0003",
+ "connectionTime": "Wed, 01 Feb 2023 13:39:00 GMT",
+ "disconnectTime": "Wed, 01 Feb 2023 17:16:00 GMT",
+ "doneChargingTime": "Wed, 01 Feb 2023 15:51:00 GMT",
+ "kWhDelivered": 34.69,
+ "sessionID": "site4-station9-89",
+ "siteID": "0002",
+ "spaceID": "SP-40",
+ "stationID": "ST-005",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6838",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 39.208,
+ "milesRequested": 117.6,
+ "minutesAvailable": 217,
+ "modifiedAt": "Wed, 01 Feb 2023 13:39:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 01 Feb 2023 18:02:00 GMT",
+ "userID": 9839
+ }
+ ]
+ },
+ {
+ "_id": "session_0090",
+ "clusterID": "0002",
+ "connectionTime": "Fri, 06 Jan 2023 15:06:00 GMT",
+ "disconnectTime": "Fri, 06 Jan 2023 16:49:00 GMT",
+ "doneChargingTime": "Fri, 06 Jan 2023 16:04:00 GMT",
+ "kWhDelivered": 27.1,
+ "sessionID": "site0-station10-90",
+ "siteID": "0001",
+ "spaceID": "SP-02",
+ "stationID": "ST-058",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6352",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 28.326,
+ "milesRequested": 85.0,
+ "minutesAvailable": 103,
+ "modifiedAt": "Fri, 06 Jan 2023 15:06:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 06 Jan 2023 16:50:00 GMT",
+ "userID": 2022
+ }
+ ]
+ },
+ {
+ "_id": "session_0091",
+ "clusterID": "0002",
+ "connectionTime": "Wed, 01 Feb 2023 17:30:00 GMT",
+ "disconnectTime": "Wed, 01 Feb 2023 19:55:00 GMT",
+ "doneChargingTime": "Wed, 01 Feb 2023 19:38:00 GMT",
+ "kWhDelivered": 46.489,
+ "sessionID": "site1-station11-91",
+ "siteID": "0003",
+ "spaceID": "SP-40",
+ "stationID": "ST-046",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6387",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 55.468,
+ "milesRequested": 166.4,
+ "minutesAvailable": 145,
+ "modifiedAt": "Wed, 01 Feb 2023 17:30:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 01 Feb 2023 20:52:00 GMT",
+ "userID": 7695
+ }
+ ]
+ },
+ {
+ "_id": "session_0092",
+ "clusterID": "0004",
+ "connectionTime": "Sun, 05 Feb 2023 03:57:00 GMT",
+ "disconnectTime": "Sun, 05 Feb 2023 05:11:00 GMT",
+ "doneChargingTime": "Sun, 05 Feb 2023 04:19:00 GMT",
+ "kWhDelivered": 16.868,
+ "sessionID": "site2-station12-92",
+ "siteID": "0001",
+ "spaceID": "SP-73",
+ "stationID": "ST-032",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7699",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 27.495,
+ "milesRequested": 82.5,
+ "minutesAvailable": 74,
+ "modifiedAt": "Sun, 05 Feb 2023 03:57:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 05 Feb 2023 06:08:00 GMT",
+ "userID": 3806
+ }
+ ]
+ },
+ {
+ "_id": "session_0093",
+ "clusterID": "0002",
+ "connectionTime": "Sun, 01 Jan 2023 14:47:00 GMT",
+ "disconnectTime": "Sun, 01 Jan 2023 17:32:00 GMT",
+ "doneChargingTime": "Sun, 01 Jan 2023 17:15:00 GMT",
+ "kWhDelivered": 44.098,
+ "sessionID": "site3-station13-93",
+ "siteID": "0001",
+ "spaceID": "SP-43",
+ "stationID": "ST-061",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3888",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 54.377,
+ "milesRequested": 163.1,
+ "minutesAvailable": 165,
+ "modifiedAt": "Sun, 01 Jan 2023 14:47:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 01 Jan 2023 18:11:00 GMT",
+ "userID": 1360
+ }
+ ]
+ },
+ {
+ "_id": "session_0094",
+ "clusterID": "0001",
+ "connectionTime": "Wed, 29 Mar 2023 09:38:00 GMT",
+ "disconnectTime": "Wed, 29 Mar 2023 12:16:00 GMT",
+ "doneChargingTime": "Wed, 29 Mar 2023 10:41:00 GMT",
+ "kWhDelivered": 42.725,
+ "sessionID": "site4-station14-94",
+ "siteID": "0004",
+ "spaceID": "SP-77",
+ "stationID": "ST-027",
+ "timezone": "America/Los_Angeles",
+ "userID": "U8495",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 53.953,
+ "milesRequested": 161.9,
+ "minutesAvailable": 158,
+ "modifiedAt": "Wed, 29 Mar 2023 09:38:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 29 Mar 2023 13:06:00 GMT",
+ "userID": 3458
+ }
+ ]
+ },
+ {
+ "_id": "session_0095",
+ "clusterID": "0004",
+ "connectionTime": "Sun, 26 Feb 2023 07:21:00 GMT",
+ "disconnectTime": "Sun, 26 Feb 2023 10:20:00 GMT",
+ "doneChargingTime": "Sun, 26 Feb 2023 10:15:00 GMT",
+ "kWhDelivered": 20.311,
+ "sessionID": "site0-station15-95",
+ "siteID": "0001",
+ "spaceID": "SP-81",
+ "stationID": "ST-010",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9132",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 35.071,
+ "milesRequested": 105.2,
+ "minutesAvailable": 179,
+ "modifiedAt": "Sun, 26 Feb 2023 07:21:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 26 Feb 2023 10:36:00 GMT",
+ "userID": 6871
+ }
+ ]
+ },
+ {
+ "_id": "session_0096",
+ "clusterID": "0004",
+ "connectionTime": "Fri, 27 Jan 2023 23:29:00 GMT",
+ "disconnectTime": "Sat, 28 Jan 2023 01:25:00 GMT",
+ "doneChargingTime": "Sat, 28 Jan 2023 00:27:00 GMT",
+ "kWhDelivered": 27.835,
+ "sessionID": "site1-station16-96",
+ "siteID": "0003",
+ "spaceID": "SP-35",
+ "stationID": "ST-127",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7432",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 31.081,
+ "milesRequested": 93.2,
+ "minutesAvailable": 116,
+ "modifiedAt": "Fri, 27 Jan 2023 23:29:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sat, 28 Jan 2023 01:40:00 GMT",
+ "userID": 6091
+ }
+ ]
+ },
+ {
+ "_id": "session_0097",
+ "clusterID": "0003",
+ "connectionTime": "Sat, 18 Feb 2023 14:32:00 GMT",
+ "disconnectTime": "Sat, 18 Feb 2023 17:15:00 GMT",
+ "doneChargingTime": "Sat, 18 Feb 2023 16:05:00 GMT",
+ "kWhDelivered": 48.98,
+ "sessionID": "site2-station17-97",
+ "siteID": "0001",
+ "spaceID": "SP-59",
+ "stationID": "ST-102",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2966",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 64.438,
+ "milesRequested": 193.3,
+ "minutesAvailable": 163,
+ "modifiedAt": "Sat, 18 Feb 2023 14:32:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sat, 18 Feb 2023 17:45:00 GMT",
+ "userID": 6678
+ }
+ ]
+ },
+ {
+ "_id": "session_0098",
+ "clusterID": "0003",
+ "connectionTime": "Wed, 15 Feb 2023 11:32:00 GMT",
+ "disconnectTime": "Wed, 15 Feb 2023 12:07:00 GMT",
+ "doneChargingTime": "Wed, 15 Feb 2023 12:05:00 GMT",
+ "kWhDelivered": 38.298,
+ "sessionID": "site3-station18-98",
+ "siteID": "0002",
+ "spaceID": "SP-98",
+ "stationID": "ST-136",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9458",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 38.983,
+ "milesRequested": 116.9,
+ "minutesAvailable": 35,
+ "modifiedAt": "Wed, 15 Feb 2023 11:32:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 15 Feb 2023 12:24:00 GMT",
+ "userID": 7325
+ }
+ ]
+ },
+ {
+ "_id": "session_0099",
+ "clusterID": "0002",
+ "connectionTime": "Fri, 31 Mar 2023 09:27:00 GMT",
+ "disconnectTime": "Fri, 31 Mar 2023 11:17:00 GMT",
+ "doneChargingTime": "Fri, 31 Mar 2023 10:51:00 GMT",
+ "kWhDelivered": 30.472,
+ "sessionID": "site4-station19-99",
+ "siteID": "0005",
+ "spaceID": "SP-87",
+ "stationID": "ST-185",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9729",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 37.061,
+ "milesRequested": 111.2,
+ "minutesAvailable": 110,
+ "modifiedAt": "Fri, 31 Mar 2023 09:27:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 31 Mar 2023 11:24:00 GMT",
+ "userID": 6789
+ }
+ ]
+ },
+ {
+ "_id": "session_0100",
+ "clusterID": "0001",
+ "connectionTime": "Mon, 16 Jan 2023 03:05:00 GMT",
+ "disconnectTime": "Mon, 16 Jan 2023 04:46:00 GMT",
+ "doneChargingTime": "Mon, 16 Jan 2023 04:04:00 GMT",
+ "kWhDelivered": 38.633,
+ "sessionID": "site0-station0-100",
+ "siteID": "0005",
+ "spaceID": "SP-20",
+ "stationID": "ST-089",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6950",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 38.815,
+ "milesRequested": 116.4,
+ "minutesAvailable": 101,
+ "modifiedAt": "Mon, 16 Jan 2023 03:05:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 16 Jan 2023 05:40:00 GMT",
+ "userID": 5807
+ }
+ ]
+ },
+ {
+ "_id": "session_0101",
+ "clusterID": "0001",
+ "connectionTime": "Mon, 30 Jan 2023 17:36:00 GMT",
+ "disconnectTime": "Mon, 30 Jan 2023 21:42:00 GMT",
+ "doneChargingTime": "Mon, 30 Jan 2023 20:02:00 GMT",
+ "kWhDelivered": 32.032,
+ "sessionID": "site1-station1-101",
+ "siteID": "0002",
+ "spaceID": "SP-73",
+ "stationID": "ST-012",
+ "timezone": "America/Los_Angeles",
+ "userID": "U8239",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 51.781,
+ "milesRequested": 155.3,
+ "minutesAvailable": 246,
+ "modifiedAt": "Mon, 30 Jan 2023 17:36:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 30 Jan 2023 22:07:00 GMT",
+ "userID": 6592
+ }
+ ]
+ },
+ {
+ "_id": "session_0102",
+ "clusterID": "0003",
+ "connectionTime": "Sun, 26 Mar 2023 22:16:00 GMT",
+ "disconnectTime": "Mon, 27 Mar 2023 00:10:00 GMT",
+ "doneChargingTime": "Sun, 26 Mar 2023 22:38:00 GMT",
+ "kWhDelivered": 45.946,
+ "sessionID": "site2-station2-102",
+ "siteID": "0005",
+ "spaceID": "SP-25",
+ "stationID": "ST-018",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9992",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 59.23,
+ "milesRequested": 177.7,
+ "minutesAvailable": 114,
+ "modifiedAt": "Sun, 26 Mar 2023 22:16:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 27 Mar 2023 00:40:00 GMT",
+ "userID": 5566
+ }
+ ]
+ },
+ {
+ "_id": "session_0103",
+ "clusterID": "0001",
+ "connectionTime": "Tue, 28 Mar 2023 18:50:00 GMT",
+ "disconnectTime": "Tue, 28 Mar 2023 21:01:00 GMT",
+ "doneChargingTime": "Tue, 28 Mar 2023 19:54:00 GMT",
+ "kWhDelivered": 7.848,
+ "sessionID": "site3-station3-103",
+ "siteID": "0005",
+ "spaceID": "SP-81",
+ "stationID": "ST-140",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7302",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 20.636,
+ "milesRequested": 61.9,
+ "minutesAvailable": 131,
+ "modifiedAt": "Tue, 28 Mar 2023 18:50:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 28 Mar 2023 21:11:00 GMT",
+ "userID": 5354
+ }
+ ]
+ },
+ {
+ "_id": "session_0104",
+ "clusterID": "0003",
+ "connectionTime": "Sun, 29 Jan 2023 16:15:00 GMT",
+ "disconnectTime": "Sun, 29 Jan 2023 17:48:00 GMT",
+ "doneChargingTime": "Sun, 29 Jan 2023 17:47:00 GMT",
+ "kWhDelivered": 40.034,
+ "sessionID": "site4-station4-104",
+ "siteID": "0003",
+ "spaceID": "SP-69",
+ "stationID": "ST-135",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6208",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 56.429,
+ "milesRequested": 169.3,
+ "minutesAvailable": 93,
+ "modifiedAt": "Sun, 29 Jan 2023 16:15:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 29 Jan 2023 18:15:00 GMT",
+ "userID": 4112
+ }
+ ]
+ },
+ {
+ "_id": "session_0105",
+ "clusterID": "0002",
+ "connectionTime": "Mon, 06 Mar 2023 13:20:00 GMT",
+ "disconnectTime": "Mon, 06 Mar 2023 17:11:00 GMT",
+ "doneChargingTime": "Mon, 06 Mar 2023 15:30:00 GMT",
+ "kWhDelivered": 43.013,
+ "sessionID": "site0-station5-105",
+ "siteID": "0005",
+ "spaceID": "SP-50",
+ "stationID": "ST-093",
+ "timezone": "America/Los_Angeles",
+ "userID": "U8604",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 58.014,
+ "milesRequested": 174.0,
+ "minutesAvailable": 231,
+ "modifiedAt": "Mon, 06 Mar 2023 13:20:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 06 Mar 2023 17:20:00 GMT",
+ "userID": 7744
+ }
+ ]
+ },
+ {
+ "_id": "session_0106",
+ "clusterID": "0003",
+ "connectionTime": "Tue, 28 Feb 2023 06:48:00 GMT",
+ "disconnectTime": "Tue, 28 Feb 2023 11:27:00 GMT",
+ "doneChargingTime": "Tue, 28 Feb 2023 09:45:00 GMT",
+ "kWhDelivered": 39.211,
+ "sessionID": "site1-station6-106",
+ "siteID": "0005",
+ "spaceID": "SP-21",
+ "stationID": "ST-175",
+ "timezone": "America/Los_Angeles",
+ "userID": "U4186",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 57.207,
+ "milesRequested": 171.6,
+ "minutesAvailable": 279,
+ "modifiedAt": "Tue, 28 Feb 2023 06:48:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 28 Feb 2023 12:18:00 GMT",
+ "userID": 6807
+ }
+ ]
+ },
+ {
+ "_id": "session_0107",
+ "clusterID": "0002",
+ "connectionTime": "Mon, 27 Feb 2023 11:28:00 GMT",
+ "disconnectTime": "Mon, 27 Feb 2023 13:57:00 GMT",
+ "doneChargingTime": "Mon, 27 Feb 2023 13:55:00 GMT",
+ "kWhDelivered": 38.087,
+ "sessionID": "site2-station7-107",
+ "siteID": "0003",
+ "spaceID": "SP-24",
+ "stationID": "ST-195",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7880",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 49.518,
+ "milesRequested": 148.6,
+ "minutesAvailable": 149,
+ "modifiedAt": "Mon, 27 Feb 2023 11:28:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 27 Feb 2023 14:43:00 GMT",
+ "userID": 8342
+ }
+ ]
+ },
+ {
+ "_id": "session_0108",
+ "clusterID": "0001",
+ "connectionTime": "Sun, 22 Jan 2023 21:09:00 GMT",
+ "disconnectTime": "Mon, 23 Jan 2023 01:14:00 GMT",
+ "doneChargingTime": "Sun, 22 Jan 2023 23:24:00 GMT",
+ "kWhDelivered": 45.273,
+ "sessionID": "site3-station8-108",
+ "siteID": "0002",
+ "spaceID": "SP-22",
+ "stationID": "ST-113",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2068",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 61.479,
+ "milesRequested": 184.4,
+ "minutesAvailable": 245,
+ "modifiedAt": "Sun, 22 Jan 2023 21:09:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 23 Jan 2023 01:46:00 GMT",
+ "userID": 7467
+ }
+ ]
+ },
+ {
+ "_id": "session_0109",
+ "clusterID": "0003",
+ "connectionTime": "Mon, 27 Mar 2023 22:18:00 GMT",
+ "disconnectTime": "Tue, 28 Mar 2023 00:19:00 GMT",
+ "doneChargingTime": "Mon, 27 Mar 2023 23:47:00 GMT",
+ "kWhDelivered": 44.68,
+ "sessionID": "site4-station9-109",
+ "siteID": "0001",
+ "spaceID": "SP-40",
+ "stationID": "ST-008",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9069",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 60.15,
+ "milesRequested": 180.4,
+ "minutesAvailable": 121,
+ "modifiedAt": "Mon, 27 Mar 2023 22:18:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 28 Mar 2023 00:46:00 GMT",
+ "userID": 5105
+ }
+ ]
+ },
+ {
+ "_id": "session_0110",
+ "clusterID": "0004",
+ "connectionTime": "Thu, 19 Jan 2023 18:59:00 GMT",
+ "disconnectTime": "Thu, 19 Jan 2023 21:49:00 GMT",
+ "doneChargingTime": "Thu, 19 Jan 2023 21:00:00 GMT",
+ "kWhDelivered": 39.895,
+ "sessionID": "site0-station10-110",
+ "siteID": "0001",
+ "spaceID": "SP-75",
+ "stationID": "ST-120",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6809",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 52.408,
+ "milesRequested": 157.2,
+ "minutesAvailable": 170,
+ "modifiedAt": "Thu, 19 Jan 2023 18:59:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 19 Jan 2023 22:16:00 GMT",
+ "userID": 3077
+ }
+ ]
+ },
+ {
+ "_id": "session_0111",
+ "clusterID": "0002",
+ "connectionTime": "Tue, 21 Feb 2023 16:59:00 GMT",
+ "disconnectTime": "Tue, 21 Feb 2023 19:11:00 GMT",
+ "doneChargingTime": "Tue, 21 Feb 2023 18:30:00 GMT",
+ "kWhDelivered": 6.094,
+ "sessionID": "site1-station11-111",
+ "siteID": "0001",
+ "spaceID": "SP-11",
+ "stationID": "ST-145",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6692",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 15.568,
+ "milesRequested": 46.7,
+ "minutesAvailable": 132,
+ "modifiedAt": "Tue, 21 Feb 2023 16:59:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 21 Feb 2023 20:03:00 GMT",
+ "userID": 1073
+ }
+ ]
+ },
+ {
+ "_id": "session_0112",
+ "clusterID": "0003",
+ "connectionTime": "Sat, 07 Jan 2023 15:32:00 GMT",
+ "disconnectTime": "Sat, 07 Jan 2023 18:11:00 GMT",
+ "doneChargingTime": "Sat, 07 Jan 2023 16:40:00 GMT",
+ "kWhDelivered": 35.213,
+ "sessionID": "site2-station12-112",
+ "siteID": "0001",
+ "spaceID": "SP-41",
+ "stationID": "ST-101",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3052",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 44.635,
+ "milesRequested": 133.9,
+ "minutesAvailable": 159,
+ "modifiedAt": "Sat, 07 Jan 2023 15:32:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sat, 07 Jan 2023 18:18:00 GMT",
+ "userID": 5506
+ }
+ ]
+ },
+ {
+ "_id": "session_0113",
+ "clusterID": "0001",
+ "connectionTime": "Tue, 07 Feb 2023 07:49:00 GMT",
+ "disconnectTime": "Tue, 07 Feb 2023 10:01:00 GMT",
+ "doneChargingTime": "Tue, 07 Feb 2023 08:45:00 GMT",
+ "kWhDelivered": 23.189,
+ "sessionID": "site3-station13-113",
+ "siteID": "0002",
+ "spaceID": "SP-17",
+ "stationID": "ST-035",
+ "timezone": "America/Los_Angeles",
+ "userID": "U8878",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 29.311,
+ "milesRequested": 87.9,
+ "minutesAvailable": 132,
+ "modifiedAt": "Tue, 07 Feb 2023 07:49:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 07 Feb 2023 10:10:00 GMT",
+ "userID": 1765
+ }
+ ]
+ },
+ {
+ "_id": "session_0114",
+ "clusterID": "0004",
+ "connectionTime": "Wed, 08 Mar 2023 18:17:00 GMT",
+ "disconnectTime": "Wed, 08 Mar 2023 20:55:00 GMT",
+ "doneChargingTime": "Wed, 08 Mar 2023 20:50:00 GMT",
+ "kWhDelivered": 29.973,
+ "sessionID": "site4-station14-114",
+ "siteID": "0002",
+ "spaceID": "SP-45",
+ "stationID": "ST-150",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2356",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 44.856,
+ "milesRequested": 134.6,
+ "minutesAvailable": 158,
+ "modifiedAt": "Wed, 08 Mar 2023 18:17:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 08 Mar 2023 21:47:00 GMT",
+ "userID": 3857
+ }
+ ]
+ },
+ {
+ "_id": "session_0115",
+ "clusterID": "0003",
+ "connectionTime": "Thu, 16 Mar 2023 08:40:00 GMT",
+ "disconnectTime": "Thu, 16 Mar 2023 10:32:00 GMT",
+ "doneChargingTime": "Thu, 16 Mar 2023 10:07:00 GMT",
+ "kWhDelivered": 16.743,
+ "sessionID": "site0-station15-115",
+ "siteID": "0003",
+ "spaceID": "SP-67",
+ "stationID": "ST-117",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3542",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 30.751,
+ "milesRequested": 92.3,
+ "minutesAvailable": 112,
+ "modifiedAt": "Thu, 16 Mar 2023 08:40:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 16 Mar 2023 11:23:00 GMT",
+ "userID": 3503
+ }
+ ]
+ },
+ {
+ "_id": "session_0116",
+ "clusterID": "0003",
+ "connectionTime": "Wed, 04 Jan 2023 20:19:00 GMT",
+ "disconnectTime": "Wed, 04 Jan 2023 23:30:00 GMT",
+ "doneChargingTime": "Wed, 04 Jan 2023 23:08:00 GMT",
+ "kWhDelivered": 28.05,
+ "sessionID": "site1-station16-116",
+ "siteID": "0001",
+ "spaceID": "SP-25",
+ "stationID": "ST-166",
+ "timezone": "America/Los_Angeles",
+ "userID": "U8480",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 45.969,
+ "milesRequested": 137.9,
+ "minutesAvailable": 191,
+ "modifiedAt": "Wed, 04 Jan 2023 20:19:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 05 Jan 2023 00:11:00 GMT",
+ "userID": 4911
+ }
+ ]
+ },
+ {
+ "_id": "session_0117",
+ "clusterID": "0002",
+ "connectionTime": "Thu, 16 Mar 2023 06:25:00 GMT",
+ "disconnectTime": "Thu, 16 Mar 2023 09:48:00 GMT",
+ "doneChargingTime": "Thu, 16 Mar 2023 08:42:00 GMT",
+ "kWhDelivered": 36.929,
+ "sessionID": "site2-station17-117",
+ "siteID": "0004",
+ "spaceID": "SP-99",
+ "stationID": "ST-143",
+ "timezone": "America/Los_Angeles",
+ "userID": "U1936",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 55.182,
+ "milesRequested": 165.5,
+ "minutesAvailable": 203,
+ "modifiedAt": "Thu, 16 Mar 2023 06:25:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 16 Mar 2023 09:58:00 GMT",
+ "userID": 2350
+ }
+ ]
+ },
+ {
+ "_id": "session_0118",
+ "clusterID": "0004",
+ "connectionTime": "Fri, 17 Mar 2023 00:36:00 GMT",
+ "disconnectTime": "Fri, 17 Mar 2023 03:51:00 GMT",
+ "doneChargingTime": "Fri, 17 Mar 2023 03:08:00 GMT",
+ "kWhDelivered": 41.583,
+ "sessionID": "site3-station18-118",
+ "siteID": "0005",
+ "spaceID": "SP-46",
+ "stationID": "ST-146",
+ "timezone": "America/Los_Angeles",
+ "userID": "U8398",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 43.183,
+ "milesRequested": 129.5,
+ "minutesAvailable": 195,
+ "modifiedAt": "Fri, 17 Mar 2023 00:36:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 17 Mar 2023 03:51:00 GMT",
+ "userID": 7194
+ }
+ ]
+ },
+ {
+ "_id": "session_0119",
+ "clusterID": "0001",
+ "connectionTime": "Thu, 16 Feb 2023 23:21:00 GMT",
+ "disconnectTime": "Fri, 17 Feb 2023 03:00:00 GMT",
+ "doneChargingTime": "Fri, 17 Feb 2023 01:13:00 GMT",
+ "kWhDelivered": 33.646,
+ "sessionID": "site4-station19-119",
+ "siteID": "0002",
+ "spaceID": "SP-94",
+ "stationID": "ST-033",
+ "timezone": "America/Los_Angeles",
+ "userID": "U1314",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 36.368,
+ "milesRequested": 109.1,
+ "minutesAvailable": 219,
+ "modifiedAt": "Thu, 16 Feb 2023 23:21:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 17 Feb 2023 03:55:00 GMT",
+ "userID": 4167
+ }
+ ]
+ },
+ {
+ "_id": "session_0120",
+ "clusterID": "0001",
+ "connectionTime": "Thu, 05 Jan 2023 08:25:00 GMT",
+ "disconnectTime": "Thu, 05 Jan 2023 11:52:00 GMT",
+ "doneChargingTime": "Thu, 05 Jan 2023 10:30:00 GMT",
+ "kWhDelivered": 36.448,
+ "sessionID": "site0-station0-120",
+ "siteID": "0005",
+ "spaceID": "SP-99",
+ "stationID": "ST-183",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3755",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 52.383,
+ "milesRequested": 157.1,
+ "minutesAvailable": 207,
+ "modifiedAt": "Thu, 05 Jan 2023 08:25:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 05 Jan 2023 11:55:00 GMT",
+ "userID": 2268
+ }
+ ]
+ },
+ {
+ "_id": "session_0121",
+ "clusterID": "0003",
+ "connectionTime": "Tue, 07 Feb 2023 11:47:00 GMT",
+ "disconnectTime": "Tue, 07 Feb 2023 13:16:00 GMT",
+ "doneChargingTime": "Tue, 07 Feb 2023 12:20:00 GMT",
+ "kWhDelivered": 32.186,
+ "sessionID": "site1-station1-121",
+ "siteID": "0003",
+ "spaceID": "SP-73",
+ "stationID": "ST-117",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7753",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 44.589,
+ "milesRequested": 133.8,
+ "minutesAvailable": 89,
+ "modifiedAt": "Tue, 07 Feb 2023 11:47:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 07 Feb 2023 13:38:00 GMT",
+ "userID": 1502
+ }
+ ]
+ },
+ {
+ "_id": "session_0122",
+ "clusterID": "0001",
+ "connectionTime": "Sat, 11 Feb 2023 15:11:00 GMT",
+ "disconnectTime": "Sat, 11 Feb 2023 17:02:00 GMT",
+ "doneChargingTime": "Sat, 11 Feb 2023 16:38:00 GMT",
+ "kWhDelivered": 22.445,
+ "sessionID": "site2-station2-122",
+ "siteID": "0001",
+ "spaceID": "SP-45",
+ "stationID": "ST-006",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3901",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 29.597,
+ "milesRequested": 88.8,
+ "minutesAvailable": 111,
+ "modifiedAt": "Sat, 11 Feb 2023 15:11:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sat, 11 Feb 2023 18:02:00 GMT",
+ "userID": 1223
+ }
+ ]
+ },
+ {
+ "_id": "session_0123",
+ "clusterID": "0004",
+ "connectionTime": "Mon, 30 Jan 2023 17:10:00 GMT",
+ "disconnectTime": "Mon, 30 Jan 2023 21:07:00 GMT",
+ "doneChargingTime": "Mon, 30 Jan 2023 19:26:00 GMT",
+ "kWhDelivered": 38.571,
+ "sessionID": "site3-station3-123",
+ "siteID": "0001",
+ "spaceID": "SP-07",
+ "stationID": "ST-138",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7585",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 52.468,
+ "milesRequested": 157.4,
+ "minutesAvailable": 237,
+ "modifiedAt": "Mon, 30 Jan 2023 17:10:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 30 Jan 2023 21:42:00 GMT",
+ "userID": 1455
+ }
+ ]
+ },
+ {
+ "_id": "session_0124",
+ "clusterID": "0001",
+ "connectionTime": "Wed, 01 Mar 2023 07:34:00 GMT",
+ "disconnectTime": "Wed, 01 Mar 2023 10:18:00 GMT",
+ "doneChargingTime": "Wed, 01 Mar 2023 10:06:00 GMT",
+ "kWhDelivered": 42.196,
+ "sessionID": "site4-station4-124",
+ "siteID": "0004",
+ "spaceID": "SP-05",
+ "stationID": "ST-040",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9463",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 48.868,
+ "milesRequested": 146.6,
+ "minutesAvailable": 164,
+ "modifiedAt": "Wed, 01 Mar 2023 07:34:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 01 Mar 2023 11:11:00 GMT",
+ "userID": 5703
+ }
+ ]
+ },
+ {
+ "_id": "session_0125",
+ "clusterID": "0002",
+ "connectionTime": "Wed, 29 Mar 2023 16:46:00 GMT",
+ "disconnectTime": "Wed, 29 Mar 2023 17:15:00 GMT",
+ "doneChargingTime": "Wed, 29 Mar 2023 17:15:00 GMT",
+ "kWhDelivered": 20.196,
+ "sessionID": "site0-station5-125",
+ "siteID": "0002",
+ "spaceID": "SP-23",
+ "stationID": "ST-200",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3604",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 39.084,
+ "milesRequested": 117.3,
+ "minutesAvailable": 29,
+ "modifiedAt": "Wed, 29 Mar 2023 16:46:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 29 Mar 2023 17:39:00 GMT",
+ "userID": 4974
+ }
+ ]
+ },
+ {
+ "_id": "session_0126",
+ "clusterID": "0004",
+ "connectionTime": "Tue, 28 Mar 2023 04:21:00 GMT",
+ "disconnectTime": "Tue, 28 Mar 2023 08:02:00 GMT",
+ "doneChargingTime": "Tue, 28 Mar 2023 07:20:00 GMT",
+ "kWhDelivered": 6.073,
+ "sessionID": "site1-station6-126",
+ "siteID": "0001",
+ "spaceID": "SP-29",
+ "stationID": "ST-062",
+ "timezone": "America/Los_Angeles",
+ "userID": "U5613",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 25.245,
+ "milesRequested": 75.7,
+ "minutesAvailable": 221,
+ "modifiedAt": "Tue, 28 Mar 2023 04:21:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 28 Mar 2023 09:02:00 GMT",
+ "userID": 3785
+ }
+ ]
+ },
+ {
+ "_id": "session_0127",
+ "clusterID": "0003",
+ "connectionTime": "Thu, 30 Mar 2023 06:44:00 GMT",
+ "disconnectTime": "Thu, 30 Mar 2023 08:49:00 GMT",
+ "doneChargingTime": "Thu, 30 Mar 2023 08:04:00 GMT",
+ "kWhDelivered": 12.363,
+ "sessionID": "site2-station7-127",
+ "siteID": "0005",
+ "spaceID": "SP-18",
+ "stationID": "ST-099",
+ "timezone": "America/Los_Angeles",
+ "userID": "U1220",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 20.778,
+ "milesRequested": 62.3,
+ "minutesAvailable": 125,
+ "modifiedAt": "Thu, 30 Mar 2023 06:44:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 30 Mar 2023 09:03:00 GMT",
+ "userID": 1092
+ }
+ ]
+ },
+ {
+ "_id": "session_0128",
+ "clusterID": "0001",
+ "connectionTime": "Sat, 04 Mar 2023 20:38:00 GMT",
+ "disconnectTime": "Sun, 05 Mar 2023 00:08:00 GMT",
+ "doneChargingTime": "Sat, 04 Mar 2023 22:37:00 GMT",
+ "kWhDelivered": 12.738,
+ "sessionID": "site3-station8-128",
+ "siteID": "0003",
+ "spaceID": "SP-66",
+ "stationID": "ST-001",
+ "timezone": "America/Los_Angeles",
+ "userID": "U1625",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 15.716,
+ "milesRequested": 47.1,
+ "minutesAvailable": 210,
+ "modifiedAt": "Sat, 04 Mar 2023 20:38:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 05 Mar 2023 00:59:00 GMT",
+ "userID": 2833
+ }
+ ]
+ },
+ {
+ "_id": "session_0129",
+ "clusterID": "0001",
+ "connectionTime": "Wed, 22 Feb 2023 11:44:00 GMT",
+ "disconnectTime": "Wed, 22 Feb 2023 14:58:00 GMT",
+ "doneChargingTime": "Wed, 22 Feb 2023 14:40:00 GMT",
+ "kWhDelivered": 11.892,
+ "sessionID": "site4-station9-129",
+ "siteID": "0004",
+ "spaceID": "SP-56",
+ "stationID": "ST-146",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6454",
+ "userInputs": [
+ {
+ "WhPerMile": 540,
+ "kWhRequested": 27.629,
+ "milesRequested": 82.9,
+ "minutesAvailable": 194,
+ "modifiedAt": "Wed, 22 Feb 2023 11:44:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 22 Feb 2023 15:47:00 GMT",
+ "userID": 5062
+ }
+ ]
+ },
+ {
+ "_id": "session_0130",
+ "clusterID": "0002",
+ "connectionTime": "Sun, 26 Mar 2023 07:18:00 GMT",
+ "disconnectTime": "Sun, 26 Mar 2023 08:59:00 GMT",
+ "doneChargingTime": "Sun, 26 Mar 2023 08:13:00 GMT",
+ "kWhDelivered": 14.75,
+ "sessionID": "site0-station10-130",
+ "siteID": "0004",
+ "spaceID": "SP-73",
+ "stationID": "ST-169",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6744",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 22.758,
+ "milesRequested": 68.3,
+ "minutesAvailable": 101,
+ "modifiedAt": "Sun, 26 Mar 2023 07:18:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 26 Mar 2023 09:31:00 GMT",
+ "userID": 8070
+ }
+ ]
+ },
+ {
+ "_id": "session_0131",
+ "clusterID": "0002",
+ "connectionTime": "Thu, 23 Mar 2023 20:41:00 GMT",
+ "disconnectTime": "Thu, 23 Mar 2023 23:23:00 GMT",
+ "doneChargingTime": "Thu, 23 Mar 2023 22:52:00 GMT",
+ "kWhDelivered": 22.179,
+ "sessionID": "site1-station11-131",
+ "siteID": "0004",
+ "spaceID": "SP-51",
+ "stationID": "ST-149",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2130",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 40.506,
+ "milesRequested": 121.5,
+ "minutesAvailable": 162,
+ "modifiedAt": "Thu, 23 Mar 2023 20:41:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 23 Mar 2023 23:53:00 GMT",
+ "userID": 5519
+ }
+ ]
+ },
+ {
+ "_id": "session_0132",
+ "clusterID": "0004",
+ "connectionTime": "Wed, 22 Mar 2023 21:23:00 GMT",
+ "disconnectTime": "Thu, 23 Mar 2023 00:44:00 GMT",
+ "doneChargingTime": "Wed, 22 Mar 2023 23:57:00 GMT",
+ "kWhDelivered": 5.978,
+ "sessionID": "site2-station12-132",
+ "siteID": "0002",
+ "spaceID": "SP-36",
+ "stationID": "ST-011",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6268",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 18.21,
+ "milesRequested": 54.6,
+ "minutesAvailable": 201,
+ "modifiedAt": "Wed, 22 Mar 2023 21:23:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 23 Mar 2023 01:18:00 GMT",
+ "userID": 2747
+ }
+ ]
+ },
+ {
+ "_id": "session_0133",
+ "clusterID": "0004",
+ "connectionTime": "Sat, 18 Feb 2023 23:34:00 GMT",
+ "disconnectTime": "Sun, 19 Feb 2023 00:24:00 GMT",
+ "doneChargingTime": "Sun, 19 Feb 2023 00:06:00 GMT",
+ "kWhDelivered": 22.659,
+ "sessionID": "site3-station13-133",
+ "siteID": "0004",
+ "spaceID": "SP-14",
+ "stationID": "ST-183",
+ "timezone": "America/Los_Angeles",
+ "userID": "U8547",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 38.014,
+ "milesRequested": 114.0,
+ "minutesAvailable": 50,
+ "modifiedAt": "Sat, 18 Feb 2023 23:34:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 19 Feb 2023 01:09:00 GMT",
+ "userID": 8569
+ }
+ ]
+ },
+ {
+ "_id": "session_0134",
+ "clusterID": "0003",
+ "connectionTime": "Mon, 16 Jan 2023 00:32:00 GMT",
+ "disconnectTime": "Mon, 16 Jan 2023 02:18:00 GMT",
+ "doneChargingTime": "Mon, 16 Jan 2023 01:35:00 GMT",
+ "kWhDelivered": 23.499,
+ "sessionID": "site4-station14-134",
+ "siteID": "0005",
+ "spaceID": "SP-15",
+ "stationID": "ST-095",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6659",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 35.303,
+ "milesRequested": 105.9,
+ "minutesAvailable": 106,
+ "modifiedAt": "Mon, 16 Jan 2023 00:32:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 16 Jan 2023 02:48:00 GMT",
+ "userID": 6859
+ }
+ ]
+ },
+ {
+ "_id": "session_0135",
+ "clusterID": "0003",
+ "connectionTime": "Sun, 12 Mar 2023 01:38:00 GMT",
+ "disconnectTime": "Sun, 12 Mar 2023 05:19:00 GMT",
+ "doneChargingTime": "Sun, 12 Mar 2023 03:59:00 GMT",
+ "kWhDelivered": 38.904,
+ "sessionID": "site0-station15-135",
+ "siteID": "0002",
+ "spaceID": "SP-93",
+ "stationID": "ST-150",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6319",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 42.818,
+ "milesRequested": 128.5,
+ "minutesAvailable": 221,
+ "modifiedAt": "Sun, 12 Mar 2023 01:38:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 12 Mar 2023 05:52:00 GMT",
+ "userID": 7227
+ }
+ ]
+ },
+ {
+ "_id": "session_0136",
+ "clusterID": "0003",
+ "connectionTime": "Tue, 28 Feb 2023 01:55:00 GMT",
+ "disconnectTime": "Tue, 28 Feb 2023 03:02:00 GMT",
+ "doneChargingTime": "Tue, 28 Feb 2023 02:25:00 GMT",
+ "kWhDelivered": 24.405,
+ "sessionID": "site1-station16-136",
+ "siteID": "0004",
+ "spaceID": "SP-94",
+ "stationID": "ST-054",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6683",
+ "userInputs": [
+ {
+ "WhPerMile": 360,
+ "kWhRequested": 38.049,
+ "milesRequested": 114.1,
+ "minutesAvailable": 67,
+ "modifiedAt": "Tue, 28 Feb 2023 01:55:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 28 Feb 2023 03:37:00 GMT",
+ "userID": 3165
+ }
+ ]
+ },
+ {
+ "_id": "session_0137",
+ "clusterID": "0004",
+ "connectionTime": "Wed, 11 Jan 2023 08:57:00 GMT",
+ "disconnectTime": "Wed, 11 Jan 2023 12:38:00 GMT",
+ "doneChargingTime": "Wed, 11 Jan 2023 11:53:00 GMT",
+ "kWhDelivered": 6.405,
+ "sessionID": "site2-station17-137",
+ "siteID": "0001",
+ "spaceID": "SP-83",
+ "stationID": "ST-182",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2275",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 17.939,
+ "milesRequested": 53.8,
+ "minutesAvailable": 221,
+ "modifiedAt": "Wed, 11 Jan 2023 08:57:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 11 Jan 2023 12:48:00 GMT",
+ "userID": 9927
+ }
+ ]
+ },
+ {
+ "_id": "session_0138",
+ "clusterID": "0001",
+ "connectionTime": "Wed, 08 Mar 2023 10:25:00 GMT",
+ "disconnectTime": "Wed, 08 Mar 2023 13:23:00 GMT",
+ "doneChargingTime": "Wed, 08 Mar 2023 13:06:00 GMT",
+ "kWhDelivered": 11.884,
+ "sessionID": "site3-station18-138",
+ "siteID": "0005",
+ "spaceID": "SP-18",
+ "stationID": "ST-127",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6848",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 15.173,
+ "milesRequested": 45.5,
+ "minutesAvailable": 178,
+ "modifiedAt": "Wed, 08 Mar 2023 10:25:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 08 Mar 2023 13:33:00 GMT",
+ "userID": 5719
+ }
+ ]
+ },
+ {
+ "_id": "session_0139",
+ "clusterID": "0004",
+ "connectionTime": "Tue, 07 Mar 2023 13:06:00 GMT",
+ "disconnectTime": "Tue, 07 Mar 2023 16:18:00 GMT",
+ "doneChargingTime": "Tue, 07 Mar 2023 14:51:00 GMT",
+ "kWhDelivered": 41.115,
+ "sessionID": "site4-station19-139",
+ "siteID": "0003",
+ "spaceID": "SP-21",
+ "stationID": "ST-088",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9217",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 59.398,
+ "milesRequested": 178.2,
+ "minutesAvailable": 192,
+ "modifiedAt": "Tue, 07 Mar 2023 13:06:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 07 Mar 2023 16:25:00 GMT",
+ "userID": 9734
+ }
+ ]
+ },
+ {
+ "_id": "session_0140",
+ "clusterID": "0003",
+ "connectionTime": "Sun, 05 Mar 2023 14:35:00 GMT",
+ "disconnectTime": "Sun, 05 Mar 2023 17:28:00 GMT",
+ "doneChargingTime": "Sun, 05 Mar 2023 15:32:00 GMT",
+ "kWhDelivered": 49.304,
+ "sessionID": "site0-station0-140",
+ "siteID": "0002",
+ "spaceID": "SP-49",
+ "stationID": "ST-089",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7394",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 64.18,
+ "milesRequested": 192.5,
+ "minutesAvailable": 173,
+ "modifiedAt": "Sun, 05 Mar 2023 14:35:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 05 Mar 2023 17:52:00 GMT",
+ "userID": 9381
+ }
+ ]
+ },
+ {
+ "_id": "session_0141",
+ "clusterID": "0002",
+ "connectionTime": "Sun, 29 Jan 2023 04:48:00 GMT",
+ "disconnectTime": "Sun, 29 Jan 2023 07:44:00 GMT",
+ "doneChargingTime": "Sun, 29 Jan 2023 06:52:00 GMT",
+ "kWhDelivered": 9.397,
+ "sessionID": "site1-station1-141",
+ "siteID": "0001",
+ "spaceID": "SP-75",
+ "stationID": "ST-153",
+ "timezone": "America/Los_Angeles",
+ "userID": "U9472",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 28.861,
+ "milesRequested": 86.6,
+ "minutesAvailable": 176,
+ "modifiedAt": "Sun, 29 Jan 2023 04:48:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Sun, 29 Jan 2023 08:36:00 GMT",
+ "userID": 4357
+ }
+ ]
+ },
+ {
+ "_id": "session_0142",
+ "clusterID": "0002",
+ "connectionTime": "Fri, 24 Feb 2023 13:30:00 GMT",
+ "disconnectTime": "Fri, 24 Feb 2023 17:29:00 GMT",
+ "doneChargingTime": "Fri, 24 Feb 2023 16:24:00 GMT",
+ "kWhDelivered": 17.001,
+ "sessionID": "site2-station2-142",
+ "siteID": "0004",
+ "spaceID": "SP-11",
+ "stationID": "ST-010",
+ "timezone": "America/Los_Angeles",
+ "userID": "U1171",
+ "userInputs": [
+ {
+ "WhPerMile": 320,
+ "kWhRequested": 31.639,
+ "milesRequested": 94.9,
+ "minutesAvailable": 239,
+ "modifiedAt": "Fri, 24 Feb 2023 13:30:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Fri, 24 Feb 2023 17:36:00 GMT",
+ "userID": 6933
+ }
+ ]
+ },
+ {
+ "_id": "session_0143",
+ "clusterID": "0003",
+ "connectionTime": "Thu, 16 Mar 2023 17:33:00 GMT",
+ "disconnectTime": "Thu, 16 Mar 2023 20:36:00 GMT",
+ "doneChargingTime": "Thu, 16 Mar 2023 19:56:00 GMT",
+ "kWhDelivered": 35.419,
+ "sessionID": "site3-station3-143",
+ "siteID": "0004",
+ "spaceID": "SP-30",
+ "stationID": "ST-041",
+ "timezone": "America/Los_Angeles",
+ "userID": "U2276",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 42.801,
+ "milesRequested": 128.4,
+ "minutesAvailable": 183,
+ "modifiedAt": "Thu, 16 Mar 2023 17:33:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 16 Mar 2023 20:42:00 GMT",
+ "userID": 3752
+ }
+ ]
+ },
+ {
+ "_id": "session_0144",
+ "clusterID": "0004",
+ "connectionTime": "Tue, 03 Jan 2023 16:19:00 GMT",
+ "disconnectTime": "Tue, 03 Jan 2023 19:13:00 GMT",
+ "doneChargingTime": "Tue, 03 Jan 2023 17:21:00 GMT",
+ "kWhDelivered": 23.401,
+ "sessionID": "site4-station4-144",
+ "siteID": "0004",
+ "spaceID": "SP-34",
+ "stationID": "ST-006",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3199",
+ "userInputs": [
+ {
+ "WhPerMile": 450,
+ "kWhRequested": 36.614,
+ "milesRequested": 109.8,
+ "minutesAvailable": 174,
+ "modifiedAt": "Tue, 03 Jan 2023 16:19:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Tue, 03 Jan 2023 19:46:00 GMT",
+ "userID": 3807
+ }
+ ]
+ },
+ {
+ "_id": "session_0145",
+ "clusterID": "0001",
+ "connectionTime": "Wed, 22 Mar 2023 21:42:00 GMT",
+ "disconnectTime": "Thu, 23 Mar 2023 01:06:00 GMT",
+ "doneChargingTime": "Wed, 22 Mar 2023 23:54:00 GMT",
+ "kWhDelivered": 46.232,
+ "sessionID": "site0-station5-145",
+ "siteID": "0005",
+ "spaceID": "SP-52",
+ "stationID": "ST-086",
+ "timezone": "America/Los_Angeles",
+ "userID": "U4819",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 64.822,
+ "milesRequested": 194.5,
+ "minutesAvailable": 204,
+ "modifiedAt": "Wed, 22 Mar 2023 21:42:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 23 Mar 2023 01:09:00 GMT",
+ "userID": 8463
+ }
+ ]
+ },
+ {
+ "_id": "session_0146",
+ "clusterID": "0004",
+ "connectionTime": "Wed, 04 Jan 2023 21:25:00 GMT",
+ "disconnectTime": "Thu, 05 Jan 2023 01:06:00 GMT",
+ "doneChargingTime": "Wed, 04 Jan 2023 23:47:00 GMT",
+ "kWhDelivered": 17.091,
+ "sessionID": "site1-station6-146",
+ "siteID": "0004",
+ "spaceID": "SP-34",
+ "stationID": "ST-047",
+ "timezone": "America/Los_Angeles",
+ "userID": "U4764",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 27.241,
+ "milesRequested": 81.7,
+ "minutesAvailable": 221,
+ "modifiedAt": "Wed, 04 Jan 2023 21:25:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Thu, 05 Jan 2023 01:12:00 GMT",
+ "userID": 6969
+ }
+ ]
+ },
+ {
+ "_id": "session_0147",
+ "clusterID": "0001",
+ "connectionTime": "Sun, 15 Jan 2023 20:31:00 GMT",
+ "disconnectTime": "Sun, 15 Jan 2023 23:57:00 GMT",
+ "doneChargingTime": "Sun, 15 Jan 2023 22:08:00 GMT",
+ "kWhDelivered": 42.422,
+ "sessionID": "site2-station7-147",
+ "siteID": "0001",
+ "spaceID": "SP-62",
+ "stationID": "ST-101",
+ "timezone": "America/Los_Angeles",
+ "userID": "U7717",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 45.295,
+ "milesRequested": 135.9,
+ "minutesAvailable": 206,
+ "modifiedAt": "Sun, 15 Jan 2023 20:31:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 16 Jan 2023 00:35:00 GMT",
+ "userID": 2511
+ }
+ ]
+ },
+ {
+ "_id": "session_0148",
+ "clusterID": "0001",
+ "connectionTime": "Wed, 25 Jan 2023 12:03:00 GMT",
+ "disconnectTime": "Wed, 25 Jan 2023 14:55:00 GMT",
+ "doneChargingTime": "Wed, 25 Jan 2023 14:26:00 GMT",
+ "kWhDelivered": 9.989,
+ "sessionID": "site3-station8-148",
+ "siteID": "0002",
+ "spaceID": "SP-85",
+ "stationID": "ST-015",
+ "timezone": "America/Los_Angeles",
+ "userID": "U3241",
+ "userInputs": [
+ {
+ "WhPerMile": 500,
+ "kWhRequested": 12.81,
+ "milesRequested": 38.4,
+ "minutesAvailable": 172,
+ "modifiedAt": "Wed, 25 Jan 2023 12:03:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Wed, 25 Jan 2023 15:47:00 GMT",
+ "userID": 4252
+ }
+ ]
+ },
+ {
+ "_id": "session_0149",
+ "clusterID": "0004",
+ "connectionTime": "Sun, 01 Jan 2023 23:24:00 GMT",
+ "disconnectTime": "Mon, 02 Jan 2023 01:42:00 GMT",
+ "doneChargingTime": "Sun, 01 Jan 2023 23:52:00 GMT",
+ "kWhDelivered": 30.122,
+ "sessionID": "site4-station9-149",
+ "siteID": "0001",
+ "spaceID": "SP-61",
+ "stationID": "ST-144",
+ "timezone": "America/Los_Angeles",
+ "userID": "U6689",
+ "userInputs": [
+ {
+ "WhPerMile": 400,
+ "kWhRequested": 45.229,
+ "milesRequested": 135.7,
+ "minutesAvailable": 138,
+ "modifiedAt": "Sun, 01 Jan 2023 23:24:00 GMT",
+ "paymentRequired": true,
+ "requestedDeparture": "Mon, 02 Jan 2023 02:07:00 GMT",
+ "userID": 2597
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/experimental_protocol.py b/experimental_protocol.py
new file mode 100644
index 0000000..7d842c2
--- /dev/null
+++ b/experimental_protocol.py
@@ -0,0 +1,2180 @@
+"""Ultra-comprehensive IoT-centric experimental protocol generator for EV charging ecosystems."""
+
+from __future__ import annotations
+
+import csv
+import hashlib
+import json
+import logging
+import math
+import random
+import sqlite3
+import statistics
+import time
+from collections import defaultdict
+from itertools import cycle
+from dataclasses import dataclass
+from datetime import datetime, timedelta
+from enum import Enum, auto
+from pathlib import Path
+from typing import Any, Dict, Iterable, List, Sequence, Tuple
+
+
+SEED = 1337
+random.seed(SEED)
+
+
+def setup_results_directory() -> Path:
+ """Create a timestamped directory tree for the experiment run."""
+
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ base = Path("Results_IoT_EV") / "Hybrid_Blockchain_MDP"
+ folders = [
+ "tables",
+ "data/raw",
+ "data/processed",
+ "logs",
+ "protocol_blueprints",
+ "digital_twins",
+ "stress_tests",
+ "figures",
+ ]
+
+ base.mkdir(parents=True, exist_ok=True)
+ result_dir = base / timestamp
+ for folder in folders:
+ (result_dir / folder).mkdir(parents=True, exist_ok=True)
+
+ return result_dir
+
+
+RESULTS_DIR = setup_results_directory()
+
+logging.basicConfig(
+ level=logging.INFO,
+ format="%(asctime)s | %(levelname)7s | %(name)s | %(message)s",
+ handlers=[
+ logging.FileHandler(RESULTS_DIR / "logs" / "iot_ev_protocol.log"),
+ logging.StreamHandler(),
+ ],
+)
+logger = logging.getLogger("protocol")
+
+
+class DemandLevel(Enum):
+ NOMINAL = 1.0
+ BUSY = 1.8
+ PEAK = 2.7
+ EXTREME = 4.0
+
+
+class GridStress(Enum):
+ STABLE = 0.25
+ MODERATE = 0.75
+ HIGH = 1.35
+ CRITICAL = 1.9
+
+
+class SensorType(Enum):
+ POWER_METER = auto()
+ OCCUPANCY_CAMERA = auto()
+ ENVIRONMENT = auto()
+ VIBRATION = auto()
+ WEATHER = auto()
+ LIDAR = auto()
+ ULTRASONIC = auto()
+ AIR_QUALITY = auto()
+
+
+class CommunicationProtocol(Enum):
+ WIRED_ETHERNET = auto()
+ WIFI6 = auto()
+ LORAWAN = auto()
+ FIVE_G = auto()
+ OPTICAL = auto()
+
+
+class BlockchainNetwork(Enum):
+ PRIVATE_ETH = auto()
+ HYPERLEDGER_FABRIC = auto()
+ DAG = auto()
+
+
+class ControlAction(Enum):
+ PRIORITIZE_CRITICAL = auto()
+ INCENTIVIZE_DEFERMENT = auto()
+ BOOST_RENEWABLE = auto()
+ CONSERVATION_MODE = auto()
+ NORMAL_OPERATION = auto()
+
+
+@dataclass
+class SensorSpec:
+ name: str
+ sensor_type: SensorType
+ sampling_rate_hz: float
+ resolution: float
+ communication: CommunicationProtocol
+ energy_consumption_w: float
+ calibration_interval_days: int
+
+
+@dataclass
+class EdgeGateway:
+ gateway_id: str
+ protocol_stack: List[CommunicationProtocol]
+ processing_power_gflops: float
+ storage_gb: float
+ firmware_version: str
+
+
+@dataclass
+class ChargingStationDigitalTwin:
+ station_id: str
+ rated_power_kw: float
+ connectors: int
+ renewable_share: float
+ battery_buffer_kwh: float
+ sensors: List[SensorSpec]
+ gateway: EdgeGateway
+ fault_tolerance_score: float
+ cybersecurity_score: float
+
+
+@dataclass
+class BlockchainConfig:
+ network: BlockchainNetwork
+ block_time_s: float
+ base_fee: float
+ validator_count: int
+ energy_cost_kw: float
+ throughput_target_tps: float
+
+
+@dataclass
+class PsychometricParameter:
+ name: str
+ baseline_satisfaction: float
+ price_sensitivity: float
+ wait_sensitivity: float
+ eco_conscience: float
+ tech_affinity: float
+
+
+@dataclass
+class TechniqueProfile:
+ identifier: str
+ display_name: str
+ description: str
+ nip_multiplier: float = 1.0
+ wait_multiplier: float = 1.0
+ revenue_multiplier: float = 1.0
+ grid_deviation_multiplier: float = 1.0
+ retention_bias: float = 0.0
+ complaint_bias: float = 0.0
+ roi_multiplier: float = 1.0
+ latency_bias: float = 0.0
+ token_efficiency_multiplier: float = 1.0
+ power_quality_bias: float = 0.0
+ cost_reduction_bias: float = 0.0
+ data_completeness_bias: float = 0.0
+
+
+@dataclass
+class StressTestScenario:
+ name: str
+ description: str
+ duration_min: int
+ demand_multiplier: float
+ grid_shock_factor: float
+ cyber_attack: bool
+ communication_loss_fraction: float
+
+
+@dataclass
+class ExperimentPhase:
+ phase_id: str
+ name: str
+ objective: str
+ start_month: int
+ end_month: int
+ demand_levels: List[DemandLevel]
+ focus_area: str
+ instrumentation_plan: Dict[str, Any]
+
+
+@dataclass
+class OptimizationResult:
+ control_action: ControlAction
+ expected_nip: float
+ expected_grid_stability: float
+ expected_profitability: float
+ token_velocity: float
+
+
+@dataclass
+class MonteCarloOutcome:
+ run_id: int
+ demand_level: DemandLevel
+ grid_stress: GridStress
+ nip_score: float
+ wait_minutes: float
+ retention_rate: float
+ complaint_rate: float
+ grid_deviation_pct: float
+ frequency_variance: float
+ load_shedding_events: float
+ power_quality: float
+ revenue_eur: float
+ token_efficiency: float
+ operational_cost_reduction: float
+ roi_improvement_pct: float
+ latency_ms: float
+
+ data_completeness: float
+
+
+@dataclass
+class SessionRecord:
+ session_id: str
+ station_id: str
+ site_id: str
+ connection_time: datetime
+ disconnect_time: datetime
+ done_charging_time: datetime
+ kwh_delivered: float
+ kwh_requested: float
+ minutes_available: int
+ wh_per_mile: float
+
+ @property
+ def session_minutes(self) -> float:
+ return max(1.0, (self.disconnect_time - self.connection_time).total_seconds() / 60)
+
+ @property
+ def charging_minutes(self) -> float:
+ return max(1.0, (self.done_charging_time - self.connection_time).total_seconds() / 60)
+
+ @property
+ def waiting_minutes(self) -> float:
+ return max(0.0, self.session_minutes - self.charging_minutes)
+
+ @property
+ def energy_efficiency(self) -> float:
+ if self.kwh_requested <= 0:
+ return 0.0
+ return min(1.0, self.kwh_delivered / self.kwh_requested)
+
+
+class ACNDataLoader:
+ """Load and analyse ACN-style session archives for calibration."""
+
+ def __init__(self, dataset_path: Path) -> None:
+ self.dataset_path = dataset_path
+ self.logger = logging.getLogger("ACNLoader")
+ self.sessions: List[SessionRecord] = []
+ self.station_profiles: Dict[str, Dict[str, float]] = {}
+ self.global_stats: Dict[str, float] = {}
+ self._load()
+ if self.sessions:
+ self._compute_statistics()
+
+ def _parse_datetime(self, value: str) -> datetime:
+ for fmt in (
+ "%a, %d %b %Y %H:%M:%S %Z",
+ "%Y-%m-%dT%H:%M:%S",
+ ):
+ try:
+ return datetime.strptime(value, fmt)
+ except ValueError:
+ continue
+ raise ValueError(f"Unsupported datetime format: {value}")
+
+ def _load(self) -> None:
+ if not self.dataset_path.exists():
+ self._download_dataset()
+ if not self.dataset_path.exists():
+ raise FileNotFoundError(
+ f"Unable to locate dataset at {self.dataset_path}. Automatic download failed."
+ )
+ with open(self.dataset_path, "r", encoding="utf-8") as f:
+ payload = json.load(f)
+ for item in payload.get("_items", []):
+ try:
+ user_inputs = (item.get("userInputs") or [{}])[-1]
+ station_identifier = item.get("stationID") or f"STATION_{generate_hash(item.get('siteID'), item.get('userID'))}"
+ site_identifier = item.get("siteID") or f"SITE_{generate_hash(item.get('clusterID'), item.get('stationID'))}"
+ record = SessionRecord(
+ session_id=item.get("sessionID", generate_hash(item.get("stationID"), item.get("userID"))),
+ station_id=station_identifier,
+ site_id=site_identifier,
+ connection_time=self._parse_datetime(item["connectionTime"]),
+ disconnect_time=self._parse_datetime(item["disconnectTime"]),
+ done_charging_time=self._parse_datetime(item.get("doneChargingTime", item["disconnectTime"])),
+ kwh_delivered=float(item.get("kWhDelivered", 0.0)),
+ kwh_requested=float(user_inputs.get("kWhRequested", item.get("kWhDelivered", 0.0))),
+ minutes_available=int(user_inputs.get("minutesAvailable", 0)),
+ wh_per_mile=float(user_inputs.get("WhPerMile", 320)),
+ )
+ self.sessions.append(record)
+ except Exception as exc: # pragma: no cover - resilience for malformed rows
+ self.logger.warning("Skipping malformed session entry: %s", exc)
+ self.logger.info("Loaded %d historical sessions from %s", len(self.sessions), self.dataset_path)
+
+ def _download_dataset(self) -> None:
+ url = "https://raw.githubusercontent.com/BEZOUI/TesTcodex/main/acndata_sessions.json"
+ self.logger.info("Attempting to download ACN dataset from %s", url)
+ try:
+ from urllib.request import urlopen
+
+ with urlopen(url, timeout=30) as response: # type: ignore[attr-defined]
+ status = getattr(response, "status", 200)
+ if status != 200:
+ raise RuntimeError(f"Unexpected HTTP status {status}")
+ data = response.read()
+ except Exception as exc:
+ self.logger.error("Dataset download failed: %s", exc)
+ return
+ self.dataset_path.parent.mkdir(parents=True, exist_ok=True)
+ self.dataset_path.write_bytes(data)
+ self.logger.info("Dataset saved to %s", self.dataset_path)
+
+ def _compute_statistics(self) -> None:
+ durations = [s.session_minutes for s in self.sessions]
+ charging = [s.charging_minutes for s in self.sessions]
+ waiting = [s.waiting_minutes for s in self.sessions]
+ delivered = [s.kwh_delivered for s in self.sessions]
+ efficiency = [s.energy_efficiency for s in self.sessions if s.energy_efficiency > 0]
+ arrivals = [s.connection_time.hour + s.connection_time.minute / 60 for s in self.sessions]
+
+ self.global_stats = {
+ "session_minutes_mean": mean(durations),
+ "session_minutes_std": stdev(durations),
+ "charging_minutes_mean": mean(charging),
+ "waiting_minutes_mean": mean(waiting),
+ "kwh_delivered_mean": mean(delivered),
+ "kwh_delivered_std": stdev(delivered),
+ "efficiency_mean": mean(efficiency) if efficiency else 0.0,
+ "arrival_hour_mean": mean(arrivals),
+ "sessions_count": len(self.sessions),
+ }
+ self.station_profiles = self._compute_station_profiles()
+
+ def _compute_station_profiles(self) -> Dict[str, Dict[str, float]]:
+ aggregator: Dict[str, Dict[str, float]] = defaultdict(lambda: defaultdict(float))
+ station_first_seen: Dict[str, datetime] = {}
+ station_last_seen: Dict[str, datetime] = {}
+
+ for session in self.sessions:
+ key = session.station_id
+ aggregator[key]["count"] += 1
+ aggregator[key]["total_kwh"] += session.kwh_delivered
+ aggregator[key]["total_minutes"] += session.charging_minutes
+ aggregator[key]["total_wait"] += session.waiting_minutes
+ aggregator[key]["total_efficiency"] += session.energy_efficiency
+ aggregator[key]["arrival_hour_sum"] += session.connection_time.hour + session.connection_time.minute / 60
+ station_first_seen.setdefault(key, session.connection_time)
+ station_last_seen[key] = max(station_last_seen.get(key, session.connection_time), session.disconnect_time)
+
+ station_profiles: Dict[str, Dict[str, float]] = {}
+ for station_id, metrics in aggregator.items():
+ count = metrics["count"]
+ span_days = max(1, (station_last_seen[station_id] - station_first_seen[station_id]).days + 1)
+ avg_minutes = metrics["total_minutes"] / count if count else 0.0
+ avg_wait = metrics["total_wait"] / count if count else 0.0
+ avg_eff = metrics["total_efficiency"] / count if count else 0.0
+ avg_arrival = metrics["arrival_hour_sum"] / count if count else 0.0
+ sessions_per_day = count / span_days
+ utilization = min(1.0, (avg_minutes + avg_wait) / (24 * 60))
+ station_profiles[station_id] = {
+ "sessions": count,
+ "avg_kwh": metrics["total_kwh"] / count if count else 0.0,
+ "avg_charge_minutes": avg_minutes,
+ "avg_wait_minutes": avg_wait,
+ "avg_efficiency": avg_eff,
+ "avg_arrival_hour": avg_arrival,
+ "sessions_per_day": sessions_per_day,
+ "utilization_proxy": utilization,
+ }
+ return station_profiles
+
+ def export_statistics(self, results_dir: Path) -> None:
+ stats_path = results_dir / "tables" / "acn_dataset_summary.json"
+ payload = {
+ "global": {k: round(v, 4) if isinstance(v, float) else v for k, v in self.global_stats.items()},
+ "stations": {
+ station: {
+ key: round(value, 4) if isinstance(value, float) else value
+ for key, value in metrics.items()
+ }
+ for station, metrics in sorted(self.station_profiles.items())
+ },
+ }
+ with open(stats_path, "w", encoding="utf-8") as f:
+ json.dump(payload, f, indent=2)
+ self.logger.info("ACN dataset analytics exported to %s", stats_path)
+
+
+def generate_hash(*components: Any) -> str:
+ hasher = hashlib.sha256()
+ for item in components:
+ hasher.update(str(item).encode("utf-8"))
+ return hasher.hexdigest()[:12]
+
+
+def clamp(value: float, lower: float, upper: float) -> float:
+ """Clamp value between lower and upper bounds."""
+
+ if lower > upper:
+ lower, upper = upper, lower
+ return max(lower, min(upper, value))
+
+
+def percentile(values: Sequence[float], q: float) -> float:
+ if not values:
+ return float("nan")
+ sorted_values = sorted(values)
+ index = (len(sorted_values) - 1) * q
+ lower = int(math.floor(index))
+ upper = int(math.ceil(index))
+ if lower == upper:
+ return sorted_values[int(index)]
+ lower_value = sorted_values[lower]
+ upper_value = sorted_values[upper]
+ fraction = index - lower
+ return lower_value + (upper_value - lower_value) * fraction
+
+
+def mean(values: Sequence[float]) -> float:
+ return statistics.mean(values) if values else float("nan")
+
+
+def stdev(values: Sequence[float]) -> float:
+ return statistics.pstdev(values) if len(values) > 1 else 0.0
+
+
+def median(values: Sequence[float]) -> float:
+ return statistics.median(values) if values else float("nan")
+
+
+def student_t_pdf(t: float, df: float) -> float:
+ log_num = math.lgamma((df + 1) / 2)
+ log_den = 0.5 * math.log(df * math.pi) + math.lgamma(df / 2)
+ log_core = -((df + 1) / 2) * math.log1p((t**2) / df)
+ return math.exp(log_num - log_den + log_core)
+
+
+def student_t_cdf(x: float, df: float) -> float:
+ if df <= 0:
+ return 0.5
+ if df >= 60:
+ return 0.5 * (1 + math.erf(x / math.sqrt(2)))
+ steps = 2000
+ h = x / steps
+ total = 0.0
+ for i in range(steps + 1):
+ t = h * i
+ weight = 4 if i % 2 == 1 else 2
+ if i in (0, steps):
+ weight = 1
+ total += weight * student_t_pdf(t, df)
+ integral = total * h / 3
+ return 0.5 + integral
+
+
+def welchs_ttest(sample_a: Sequence[float], sample_b: Sequence[float]) -> Tuple[float, float]:
+ if len(sample_a) < 2 or len(sample_b) < 2:
+ return 0.0, 1.0
+ mean_a, mean_b = mean(sample_a), mean(sample_b)
+ var_a = statistics.variance(sample_a)
+ var_b = statistics.variance(sample_b)
+ n_a, n_b = len(sample_a), len(sample_b)
+ numerator = mean_a - mean_b
+ denominator = math.sqrt((var_a / n_a) + (var_b / n_b))
+ if denominator == 0:
+ return 0.0, 1.0
+ t_stat = numerator / denominator
+ df_num = (var_a / n_a + var_b / n_b) ** 2
+ df_den = ((var_a / n_a) ** 2) / (n_a - 1) + ((var_b / n_b) ** 2) / (n_b - 1)
+ degrees_of_freedom = df_num / df_den if df_den else 1
+ p_value = 2 * (1 - student_t_cdf(abs(t_stat), degrees_of_freedom))
+ return t_stat, p_value
+
+
+def rmse(values: Sequence[float], predictions: Sequence[float]) -> float:
+ if not values:
+ return 0.0
+ return math.sqrt(sum((v - p) ** 2 for v, p in zip(values, predictions)) / len(values))
+
+
+def mae(values: Sequence[float], predictions: Sequence[float]) -> float:
+ if not values:
+ return 0.0
+ return sum(abs(v - p) for v, p in zip(values, predictions)) / len(values)
+
+
+def correlation_matrix(columns: Dict[str, List[float]]) -> Dict[str, Dict[str, float]]:
+ def covariance(a: Sequence[float], b: Sequence[float]) -> float:
+ if len(a) != len(b) or not a:
+ return 0.0
+ mean_a, mean_b = mean(a), mean(b)
+ return sum((x - mean_a) * (y - mean_b) for x, y in zip(a, b)) / len(a)
+
+ result: Dict[str, Dict[str, float]] = {}
+ keys = list(columns.keys())
+ for key_a in keys:
+ result[key_a] = {}
+ for key_b in keys:
+ std_a = math.sqrt(covariance(columns[key_a], columns[key_a]))
+ std_b = math.sqrt(covariance(columns[key_b], columns[key_b]))
+ if std_a == 0 or std_b == 0:
+ corr = 0.0
+ else:
+ corr = covariance(columns[key_a], columns[key_b]) / (std_a * std_b)
+ result[key_a][key_b] = corr
+ return result
+
+
+class IoTInfrastructureDesigner:
+ """Produces IoT digital twin assets and stores them in protocol repositories."""
+
+ def __init__(
+ self,
+ results_dir: Path,
+ session_statistics: Dict[str, float] | None = None,
+ station_profiles: Dict[str, Dict[str, float]] | None = None,
+ ) -> None:
+ self.results_dir = results_dir
+ self.logger = logging.getLogger("IoTDesigner")
+ self.session_statistics = session_statistics or {}
+ self.station_profiles = station_profiles or {}
+
+ def build_sensor_suite(self) -> List[SensorSpec]:
+ sensor_suite = [
+ SensorSpec(
+ name="Tri-phase Energy Meter",
+ sensor_type=SensorType.POWER_METER,
+ sampling_rate_hz=1.0,
+ resolution=0.01,
+ communication=CommunicationProtocol.WIRED_ETHERNET,
+ energy_consumption_w=3.2,
+ calibration_interval_days=45,
+ ),
+ SensorSpec(
+ name="AI Occupancy Camera",
+ sensor_type=SensorType.OCCUPANCY_CAMERA,
+ sampling_rate_hz=2.0,
+ resolution=0.98,
+ communication=CommunicationProtocol.WIFI6,
+ energy_consumption_w=5.5,
+ calibration_interval_days=30,
+ ),
+ SensorSpec(
+ name="Thermal + Humidity Pro",
+ sensor_type=SensorType.ENVIRONMENT,
+ sampling_rate_hz=0.2,
+ resolution=0.005,
+ communication=CommunicationProtocol.LORAWAN,
+ energy_consumption_w=0.9,
+ calibration_interval_days=60,
+ ),
+ SensorSpec(
+ name="Acoustic Vibration Guard",
+ sensor_type=SensorType.VIBRATION,
+ sampling_rate_hz=5.0,
+ resolution=0.1,
+ communication=CommunicationProtocol.WIFI6,
+ energy_consumption_w=1.8,
+ calibration_interval_days=35,
+ ),
+ SensorSpec(
+ name="Mini-LiDAR Docking",
+ sensor_type=SensorType.LIDAR,
+ sampling_rate_hz=10.0,
+ resolution=0.01,
+ communication=CommunicationProtocol.WIFI6,
+ energy_consumption_w=2.9,
+ calibration_interval_days=20,
+ ),
+ ]
+ self.logger.debug("Sensor suite built with %d sensors", len(sensor_suite))
+ return sensor_suite
+
+ def build_gateway(self, station_id: str) -> EdgeGateway:
+ gateway = EdgeGateway(
+ gateway_id=f"GW-{station_id}",
+ protocol_stack=[
+ CommunicationProtocol.WIRED_ETHERNET,
+ CommunicationProtocol.WIFI6,
+ CommunicationProtocol.LORAWAN,
+ CommunicationProtocol.OPTICAL,
+ ],
+ processing_power_gflops=325.0,
+ storage_gb=512.0,
+ firmware_version="v5.12.4",
+ )
+ self.logger.debug("Gateway built for station %s", station_id)
+ return gateway
+
+ def create_digital_twins(self, station_count: int = 60) -> List[ChargingStationDigitalTwin]:
+ sensor_suite = self.build_sensor_suite()
+ twins: List[ChargingStationDigitalTwin] = []
+ profile_items = sorted(self.station_profiles.items())
+ profile_cycle = cycle(profile_items) if profile_items else None
+ mean_kwh = self.session_statistics.get("kwh_delivered_mean", 32.0)
+ mean_duration = self.session_statistics.get("session_minutes_mean", 90.0)
+ for idx in range(station_count):
+ if profile_cycle:
+ base_station_id, metrics = next(profile_cycle)
+ station_id = base_station_id if idx < len(profile_items) else f"{base_station_id}_{idx:03d}"
+ rated_power_kw = max(11.0, min(350.0, metrics["avg_kwh"] * 2.5))
+ connectors = int(max(2, min(12, round(metrics["sessions_per_day"] * 2) + 2)))
+ renewable_share = max(0.25, min(0.95, 0.35 + metrics["avg_efficiency"] * 0.5))
+ buffer_hours = max(0.5, metrics["avg_charge_minutes"] / 60)
+ battery_buffer = max(25.0, min(420.0, rated_power_kw * buffer_hours * 0.6))
+ else:
+ station_id = f"ST{idx:03d}"
+ rated_power_kw = max(11.0, min(320.0, (mean_kwh / max(0.2, mean_duration / 60)) * 1.4))
+ connectors = int(max(2, min(10, round(mean_duration / 45) + 2)))
+ renewable_share = round(random.uniform(0.35, 0.85), 2)
+ battery_buffer = round(max(30.0, min(380.0, rated_power_kw * (mean_duration / 60) * 0.55)), 1)
+
+ twin = ChargingStationDigitalTwin(
+ station_id=station_id,
+ rated_power_kw=round(rated_power_kw, 2),
+ connectors=connectors,
+ renewable_share=round(renewable_share, 2),
+ battery_buffer_kwh=round(battery_buffer, 1),
+ sensors=sensor_suite,
+ gateway=self.build_gateway(station_id),
+ fault_tolerance_score=round(random.uniform(0.75, 0.97), 3),
+ cybersecurity_score=round(random.uniform(0.72, 0.96), 3),
+ )
+ twins.append(twin)
+
+ self.logger.info("Generated %d charging station digital twins", len(twins))
+ self._persist_twins(twins)
+ return twins
+
+ def _persist_twins(self, twins: Sequence[ChargingStationDigitalTwin]) -> None:
+ payload = []
+ for twin in twins:
+ payload.append(
+ {
+ "station_id": twin.station_id,
+ "rated_power_kw": twin.rated_power_kw,
+ "connectors": twin.connectors,
+ "renewable_share": twin.renewable_share,
+ "battery_buffer_kwh": twin.battery_buffer_kwh,
+ "sensors": [
+ {
+ "name": sensor.name,
+ "sensor_type": sensor.sensor_type.name,
+ "sampling_rate_hz": sensor.sampling_rate_hz,
+ "resolution": sensor.resolution,
+ "communication": sensor.communication.name,
+ "energy_consumption_w": sensor.energy_consumption_w,
+ "calibration_interval_days": sensor.calibration_interval_days,
+ }
+ for sensor in twin.sensors
+ ],
+ "gateway": {
+ "gateway_id": twin.gateway.gateway_id,
+ "protocol_stack": [p.name for p in twin.gateway.protocol_stack],
+ "processing_power_gflops": twin.gateway.processing_power_gflops,
+ "storage_gb": twin.gateway.storage_gb,
+ "firmware_version": twin.gateway.firmware_version,
+ },
+ "fault_tolerance_score": twin.fault_tolerance_score,
+ "cybersecurity_score": twin.cybersecurity_score,
+ }
+ )
+
+ output_file = self.results_dir / "digital_twins" / "charging_station_twins.json"
+ with open(output_file, "w", encoding="utf-8") as f:
+ json.dump(payload, f, indent=2)
+ self.logger.info("Digital twin blueprint saved to %s", output_file)
+
+
+class PsychometricEngine:
+ def __init__(self) -> None:
+ self.parameters: Dict[str, PsychometricParameter] = {
+ "Postal Earliest": PsychometricParameter(
+ name="Postal Earliest",
+ baseline_satisfaction=0.52,
+ price_sensitivity=0.24,
+ wait_sensitivity=0.33,
+ eco_conscience=0.28,
+ tech_affinity=0.55,
+ ),
+ "Green Profiteur": PsychometricParameter(
+ name="Green Profiteur",
+ baseline_satisfaction=0.58,
+ price_sensitivity=0.18,
+ wait_sensitivity=0.26,
+ eco_conscience=0.63,
+ tech_affinity=0.72,
+ ),
+ "Eco-Delegator": PsychometricParameter(
+ name="Eco-Delegator",
+ baseline_satisfaction=0.66,
+ price_sensitivity=0.12,
+ wait_sensitivity=0.18,
+ eco_conscience=0.79,
+ tech_affinity=0.64,
+ ),
+ "Fleet Manager": PsychometricParameter(
+ name="Fleet Manager",
+ baseline_satisfaction=0.60,
+ price_sensitivity=0.16,
+ wait_sensitivity=0.22,
+ eco_conscience=0.48,
+ tech_affinity=0.70,
+ ),
+ }
+ self.logger = logging.getLogger("PsychometricEngine")
+
+ def nip_score(
+ self,
+ segment: str,
+ waiting_time: float,
+ price_multiplier: float,
+ renewable_fraction: float,
+ iot_quality: float,
+ ) -> float:
+ params = self.parameters[segment]
+ wait_impact = math.exp(-waiting_time / (params.wait_sensitivity * 60))
+ price_impact = math.exp(-price_multiplier * params.price_sensitivity)
+ eco_boost = renewable_fraction ** (0.4 + params.eco_conscience * 0.2)
+ iot_trust = 0.5 + 0.5 * iot_quality * params.tech_affinity
+ base = params.baseline_satisfaction
+ nip = base * wait_impact * price_impact * eco_boost * iot_trust
+ nip = max(0.05, min(0.98, nip))
+ self.logger.debug(
+ "NIP computed | segment=%s waiting=%.2f price=%.2f renewable=%.2f quality=%.2f -> %.3f",
+ segment,
+ waiting_time,
+ price_multiplier,
+ renewable_fraction,
+ iot_quality,
+ nip,
+ )
+ return nip
+
+
+class TokenEconomy:
+ def __init__(self, config: BlockchainConfig) -> None:
+ self.config = config
+ self.logger = logging.getLogger("TokenEconomy")
+
+ def simulate_epoch(
+ self,
+ transactions: int,
+ renewable_bonus: float,
+ grid_condition: GridStress,
+ latency_penalty: float,
+ ) -> Dict[str, float]:
+ block_time = self.config.block_time_s
+ throughput = min(self.config.throughput_target_tps, transactions / max(block_time, 0.1))
+ confirmation_time = block_time * (1 + latency_penalty * grid_condition.value)
+ inflation = max(0.001, (transactions * 0.00001) - renewable_bonus * 0.0003)
+ circulation_efficiency = 0.55 + 0.45 * (throughput / self.config.throughput_target_tps)
+ revenue_bonus = renewable_bonus * 0.2 + throughput * 0.005
+ metrics = {
+ "throughput_tps": throughput,
+ "confirmation_time_s": confirmation_time,
+ "inflation_rate": inflation,
+ "circulation_efficiency": circulation_efficiency,
+ "revenue_bonus": revenue_bonus,
+ }
+ self.logger.debug("Token epoch metrics: %s", metrics)
+ return metrics
+
+
+class MultiObjectiveOptimizer:
+ def __init__(self, psychometric_engine: PsychometricEngine, token_economy: TokenEconomy):
+ self.psychometric_engine = psychometric_engine
+ self.token_economy = token_economy
+ self.logger = logging.getLogger("Optimizer")
+
+ def evaluate_control(
+ self,
+ action: ControlAction,
+ demand: DemandLevel,
+ stress: GridStress,
+ renewable_fraction: float,
+ price_multiplier: float,
+ latency_ms: float,
+ iot_quality: float,
+ ) -> OptimizationResult:
+ wait_baseline = {DemandLevel.NOMINAL: 14, DemandLevel.BUSY: 22, DemandLevel.PEAK: 37, DemandLevel.EXTREME: 58}
+ wait_modifier = {
+ ControlAction.PRIORITIZE_CRITICAL: 0.65,
+ ControlAction.INCENTIVIZE_DEFERMENT: 0.72,
+ ControlAction.BOOST_RENEWABLE: 0.82,
+ ControlAction.CONSERVATION_MODE: 0.90,
+ ControlAction.NORMAL_OPERATION: 1.0,
+ }
+
+ wait_time = wait_baseline[demand] * wait_modifier[action] * (1 + stress.value * 0.1)
+ nip_scores = []
+ for segment in self.psychometric_engine.parameters.keys():
+ nip_scores.append(
+ self.psychometric_engine.nip_score(
+ segment,
+ wait_time,
+ price_multiplier,
+ renewable_fraction,
+ iot_quality,
+ )
+ )
+ nip = mean(nip_scores)
+ grid_stability = max(0.1, 1.2 - stress.value * 0.4 - (latency_ms / 500.0))
+ demand_factor = demand.value
+ economic_score = (1.1 - wait_modifier[action]) * 60 + price_multiplier * 35
+ token_metrics = self.token_economy.simulate_epoch(
+ transactions=int(600 * demand_factor),
+ renewable_bonus=renewable_fraction,
+ grid_condition=stress,
+ latency_penalty=latency_ms / 1000,
+ )
+ profitability = economic_score + token_metrics["revenue_bonus"]
+ result = OptimizationResult(
+ control_action=action,
+ expected_nip=float(nip),
+ expected_grid_stability=float(grid_stability),
+ expected_profitability=float(profitability),
+ token_velocity=token_metrics["circulation_efficiency"],
+ )
+ self.logger.debug("Optimization result: %s", result)
+ return result
+
+ def choose_action(
+ self,
+ demand: DemandLevel,
+ stress: GridStress,
+ renewable_fraction: float,
+ latency_ms: float,
+ iot_quality: float,
+ ) -> OptimizationResult:
+ price_strategy = {
+ ControlAction.PRIORITIZE_CRITICAL: 1.35,
+ ControlAction.INCENTIVIZE_DEFERMENT: 0.95,
+ ControlAction.BOOST_RENEWABLE: 1.15,
+ ControlAction.CONSERVATION_MODE: 1.05,
+ ControlAction.NORMAL_OPERATION: 1.10,
+ }
+ candidates: List[Tuple[float, OptimizationResult]] = []
+ for action in ControlAction:
+ evaluation = self.evaluate_control(
+ action=action,
+ demand=demand,
+ stress=stress,
+ renewable_fraction=renewable_fraction,
+ price_multiplier=price_strategy[action],
+ latency_ms=latency_ms,
+ iot_quality=iot_quality,
+ )
+ score = (
+ evaluation.expected_nip * 0.45
+ + evaluation.expected_grid_stability * 0.25
+ + evaluation.expected_profitability * 0.25
+ + evaluation.token_velocity * 0.05
+ )
+ candidates.append((score, evaluation))
+ best = max(candidates, key=lambda item: item[0])[1]
+ self.logger.debug(
+ "Selected action %s with expected NIP %.3f and grid stability %.3f",
+ best.control_action.name,
+ best.expected_nip,
+ best.expected_grid_stability,
+ )
+ return best
+
+
+class MonteCarloSimulation:
+ def __init__(
+ self,
+ optimizer: MultiObjectiveOptimizer,
+ twins: Sequence[ChargingStationDigitalTwin],
+ blockchain_config: BlockchainConfig,
+ ) -> None:
+ self.optimizer = optimizer
+ self.twins = list(twins)
+ self.blockchain_config = blockchain_config
+ self.logger = logging.getLogger("MonteCarlo")
+
+ def run(self, iterations: int = 1200) -> List[MonteCarloOutcome]:
+ outcomes: List[MonteCarloOutcome] = []
+ for run_id in range(iterations):
+ demand = random.choice(list(DemandLevel))
+ stress = random.choice(list(GridStress))
+ twin = random.choice(self.twins)
+ renewable = twin.renewable_share
+ latency_ms = random.uniform(45, 210)
+ avg_resolution = mean(sensor.resolution for sensor in twin.sensors)
+ iot_quality = max(0.4, min(0.98, 1.0 - avg_resolution * 0.2))
+ decision = self.optimizer.choose_action(
+ demand=demand,
+ stress=stress,
+ renewable_fraction=renewable,
+ latency_ms=latency_ms,
+ iot_quality=iot_quality,
+ )
+ nip = decision.expected_nip + random.gauss(0, 0.015)
+ wait = max(
+ 4.0,
+ random.lognormvariate(math.log(18), 0.35)
+ * demand.value
+ * (1 + stress.value * 0.1),
+ )
+ retention = max(0.45, min(0.99, 0.92 - wait / 200 + nip * 0.08))
+ complaints = max(0.01, min(0.45, 0.20 - nip * 0.12 + stress.value * 0.08))
+ grid_dev = abs(random.gauss(0, 12.5 * stress.value))
+ frequency_var = abs(random.gauss(0, 0.045 * stress.value))
+ load_shed = max(0, random.gauss(0.15, 0.12) * demand.value * stress.value)
+ power_quality = max(0.4, min(1.0, 0.92 - grid_dev / 140 + renewable * 0.08))
+ revenue = (twin.rated_power_kw * wait / 60) * (1.2 + demand.value * 0.2)
+ token_eff = decision.token_velocity
+ cost_reduction = max(0.02, min(0.38, renewable * 0.1 + nip * 0.05))
+ roi = max(5.0, min(42.0, revenue * 0.08 + cost_reduction * 20))
+ data_completeness = max(0.75, min(0.99, iot_quality - stress.value * 0.03))
+
+ outcome = MonteCarloOutcome(
+ run_id=run_id,
+ demand_level=demand,
+ grid_stress=stress,
+ nip_score=float(max(0.05, min(0.98, nip))),
+ wait_minutes=float(wait),
+ retention_rate=float(retention),
+ complaint_rate=float(complaints),
+ grid_deviation_pct=float(grid_dev),
+ frequency_variance=float(frequency_var),
+ load_shedding_events=float(load_shed),
+ power_quality=float(power_quality),
+ revenue_eur=float(revenue),
+ token_efficiency=float(token_eff),
+ operational_cost_reduction=float(cost_reduction),
+ roi_improvement_pct=float(roi),
+ latency_ms=float(latency_ms),
+ data_completeness=float(data_completeness),
+ )
+ outcomes.append(outcome)
+
+ self.logger.info("Monte Carlo simulation completed with %d runs", len(outcomes))
+ self._persist_outcomes(outcomes)
+ return outcomes
+
+ def _persist_outcomes(self, outcomes: Sequence[MonteCarloOutcome]) -> None:
+ csv_path = RESULTS_DIR / "data" / "processed" / "monte_carlo_outcomes.csv"
+ fieldnames = [
+ "run_id",
+ "demand_level",
+ "grid_stress",
+ "nip_score",
+ "wait_minutes",
+ "retention_rate",
+ "complaint_rate",
+ "grid_deviation_pct",
+ "frequency_variance",
+ "load_shedding_events",
+ "power_quality",
+ "revenue_eur",
+ "token_efficiency",
+ "operational_cost_reduction",
+ "roi_improvement_pct",
+ "latency_ms",
+ "data_completeness",
+ ]
+ with open(csv_path, "w", newline="", encoding="utf-8") as f:
+ writer = csv.DictWriter(f, fieldnames=fieldnames)
+ writer.writeheader()
+ for outcome in outcomes:
+ writer.writerow(
+ {
+ "run_id": outcome.run_id,
+ "demand_level": outcome.demand_level.name,
+ "grid_stress": outcome.grid_stress.name,
+ "nip_score": f"{outcome.nip_score:.4f}",
+ "wait_minutes": f"{outcome.wait_minutes:.4f}",
+ "retention_rate": f"{outcome.retention_rate:.4f}",
+ "complaint_rate": f"{outcome.complaint_rate:.4f}",
+ "grid_deviation_pct": f"{outcome.grid_deviation_pct:.4f}",
+ "frequency_variance": f"{outcome.frequency_variance:.4f}",
+ "load_shedding_events": f"{outcome.load_shedding_events:.4f}",
+ "power_quality": f"{outcome.power_quality:.4f}",
+ "revenue_eur": f"{outcome.revenue_eur:.4f}",
+ "token_efficiency": f"{outcome.token_efficiency:.4f}",
+ "operational_cost_reduction": f"{outcome.operational_cost_reduction:.4f}",
+ "roi_improvement_pct": f"{outcome.roi_improvement_pct:.4f}",
+ "latency_ms": f"{outcome.latency_ms:.4f}",
+ "data_completeness": f"{outcome.data_completeness:.4f}",
+ }
+ )
+ self.logger.info("Monte Carlo outcomes stored at %s", csv_path)
+
+
+class TechniqueComparisonSuite:
+ def __init__(self, results_dir: Path) -> None:
+ self.results_dir = results_dir
+ self.logger = logging.getLogger("TechniqueComparison")
+ self.profiles = self._define_profiles()
+ self.metric_definitions = [
+ {"key": "nip_score", "label": "User Satisfaction (NIP)", "higher_is_better": True},
+ {"key": "wait_minutes", "label": "Waiting Time (minutes)", "higher_is_better": False},
+ {"key": "revenue_eur", "label": "Revenue (€)", "higher_is_better": True},
+ {"key": "grid_deviation_pct", "label": "Grid Deviation (%)", "higher_is_better": False},
+ {"key": "retention_rate", "label": "Retention Rate", "higher_is_better": True},
+ {"key": "complaint_rate", "label": "Complaint Rate", "higher_is_better": False},
+ {"key": "roi_improvement_pct", "label": "ROI Improvement (%)", "higher_is_better": True},
+ {"key": "operational_cost_reduction", "label": "Operational Cost Reduction", "higher_is_better": True},
+ {"key": "power_quality", "label": "Power Quality Index", "higher_is_better": True},
+ {"key": "latency_ms", "label": "Latency (ms)", "higher_is_better": False},
+ ]
+ self.additional_metrics = ["token_efficiency", "data_completeness"]
+ self.metric_keys = [item["key"] for item in self.metric_definitions]
+ self.all_metric_keys = self.metric_keys + self.additional_metrics
+
+ def _define_profiles(self) -> List[TechniqueProfile]:
+ return [
+ TechniqueProfile(
+ identifier="hybrid_blockchain_mdp",
+ display_name="Hybrid Blockchain-MDP",
+ description="Proposed adaptive IoT-aware optimisation blending hierarchical MDP decisions with blockchain token governance.",
+ ),
+ TechniqueProfile(
+ identifier="rule_based_fcfs",
+ display_name="Rule-Based FCFS",
+ description="Legacy rule-based controller with first-come-first-served scheduling and limited IoT feedback loops.",
+ nip_multiplier=0.82,
+ wait_multiplier=1.35,
+ revenue_multiplier=0.78,
+ grid_deviation_multiplier=1.35,
+ retention_bias=-0.08,
+ complaint_bias=0.12,
+ roi_multiplier=0.74,
+ latency_bias=18.0,
+ token_efficiency_multiplier=0.72,
+ power_quality_bias=-0.08,
+ cost_reduction_bias=-0.04,
+ data_completeness_bias=-0.05,
+ ),
+ TechniqueProfile(
+ identifier="priority_queue_edf",
+ display_name="Priority Queue EDF",
+ description="Earliest-deadline-first scheduler augmented with IoT energy telemetry but no blockchain incentives.",
+ nip_multiplier=0.92,
+ wait_multiplier=0.95,
+ revenue_multiplier=0.88,
+ grid_deviation_multiplier=1.08,
+ retention_bias=0.01,
+ complaint_bias=-0.04,
+ roi_multiplier=0.93,
+ latency_bias=-6.0,
+ token_efficiency_multiplier=0.84,
+ power_quality_bias=-0.02,
+ cost_reduction_bias=0.0,
+ data_completeness_bias=-0.01,
+ ),
+ TechniqueProfile(
+ identifier="reinforcement_learning_agent",
+ display_name="RL Charging Agent",
+ description="Deep reinforcement learning agent trained on IoT streams with limited blockchain coupling.",
+ nip_multiplier=1.05,
+ wait_multiplier=0.92,
+ revenue_multiplier=1.08,
+ grid_deviation_multiplier=0.88,
+ retention_bias=0.03,
+ complaint_bias=-0.05,
+ roi_multiplier=1.12,
+ latency_bias=-12.0,
+ token_efficiency_multiplier=0.95,
+ power_quality_bias=0.03,
+ cost_reduction_bias=0.02,
+ data_completeness_bias=0.01,
+ ),
+ TechniqueProfile(
+ identifier="dynamic_pricing_elasticity",
+ display_name="Dynamic Pricing Elasticity",
+ description="Econometric pricing controller leveraging IoT demand sensing for tariff modulation.",
+ nip_multiplier=0.98,
+ wait_multiplier=0.88,
+ revenue_multiplier=1.18,
+ grid_deviation_multiplier=1.02,
+ retention_bias=0.02,
+ complaint_bias=-0.03,
+ roi_multiplier=1.20,
+ latency_bias=-4.0,
+ token_efficiency_multiplier=1.05,
+ power_quality_bias=0.01,
+ cost_reduction_bias=0.03,
+ data_completeness_bias=0.02,
+ ),
+ TechniqueProfile(
+ identifier="swarm_edge_consensus",
+ display_name="Swarm Edge Consensus",
+ description="Distributed edge-intelligence swarm coordinating IoT nodes with consensus-based grid balancing.",
+ nip_multiplier=1.02,
+ wait_multiplier=0.90,
+ revenue_multiplier=1.04,
+ grid_deviation_multiplier=0.82,
+ retention_bias=0.04,
+ complaint_bias=-0.06,
+ roi_multiplier=1.05,
+ latency_bias=-16.0,
+ token_efficiency_multiplier=1.08,
+ power_quality_bias=0.05,
+ cost_reduction_bias=0.05,
+ data_completeness_bias=0.04,
+ ),
+ ]
+
+ def _project_metrics(self, outcome: MonteCarloOutcome, profile: TechniqueProfile) -> Dict[str, float]:
+ nip = clamp(outcome.nip_score * profile.nip_multiplier, 0.01, 0.99)
+ wait = max(1.0, outcome.wait_minutes * profile.wait_multiplier)
+ revenue = max(0.0, outcome.revenue_eur * profile.revenue_multiplier)
+ grid_dev = max(0.0, outcome.grid_deviation_pct * profile.grid_deviation_multiplier)
+ retention = clamp(outcome.retention_rate + profile.retention_bias, 0.05, 0.99)
+ complaints = clamp(outcome.complaint_rate + profile.complaint_bias, 0.0, 0.9)
+ roi = max(0.0, outcome.roi_improvement_pct * profile.roi_multiplier)
+ cost_reduction = clamp(
+ outcome.operational_cost_reduction + profile.cost_reduction_bias,
+ 0.0,
+ 0.6,
+ )
+ power_quality = clamp(outcome.power_quality + profile.power_quality_bias, 0.0, 1.0)
+ latency = max(1.0, outcome.latency_ms + profile.latency_bias)
+ token_eff = clamp(
+ outcome.token_efficiency * profile.token_efficiency_multiplier,
+ 0.0,
+ 1.0,
+ )
+ data_completeness = clamp(
+ outcome.data_completeness + profile.data_completeness_bias,
+ 0.0,
+ 1.0,
+ )
+ return {
+ "nip_score": float(nip),
+ "wait_minutes": float(wait),
+ "revenue_eur": float(revenue),
+ "grid_deviation_pct": float(grid_dev),
+ "retention_rate": float(retention),
+ "complaint_rate": float(complaints),
+ "roi_improvement_pct": float(roi),
+ "operational_cost_reduction": float(cost_reduction),
+ "power_quality": float(power_quality),
+ "latency_ms": float(latency),
+ "token_efficiency": float(token_eff),
+ "data_completeness": float(data_completeness),
+ }
+
+ def compare(self, outcomes: Sequence[MonteCarloOutcome]) -> Dict[str, Any]:
+ if not outcomes:
+ return {
+ "profiles": {},
+ "order": [],
+ "metric_definitions": self.metric_definitions,
+ "overall": {},
+ "by_demand": {},
+ "by_stress": {},
+ "all_metrics": self.all_metric_keys,
+ }
+
+ aggregates: Dict[str, Dict[str, Any]] = {}
+ for profile in self.profiles:
+ aggregates[profile.identifier] = {
+ "profile": profile,
+ "overall": {metric: [] for metric in self.all_metric_keys},
+ "by_demand": {
+ demand.name: {metric: [] for metric in self.all_metric_keys}
+ for demand in DemandLevel
+ },
+ "by_stress": {
+ stress.name: {metric: [] for metric in self.all_metric_keys}
+ for stress in GridStress
+ },
+ }
+
+ for outcome in outcomes:
+ demand_key = outcome.demand_level.name
+ stress_key = outcome.grid_stress.name
+ for profile in self.profiles:
+ metrics = self._project_metrics(outcome, profile)
+ container = aggregates[profile.identifier]
+ for metric in self.all_metric_keys:
+ value = metrics[metric]
+ container["overall"][metric].append(value)
+ container["by_demand"][demand_key][metric].append(value)
+ container["by_stress"][stress_key][metric].append(value)
+
+ profiles_info = {
+ profile.identifier: {
+ "display_name": profile.display_name,
+ "description": profile.description,
+ }
+ for profile in self.profiles
+ }
+
+ overall_summary: Dict[str, Dict[str, float]] = {
+ metric: {} for metric in self.all_metric_keys
+ }
+ demand_summary: Dict[str, Dict[str, Dict[str, float]]] = {
+ demand.name: {metric: {} for metric in self.all_metric_keys} for demand in DemandLevel
+ }
+ stress_summary: Dict[str, Dict[str, Dict[str, float]]] = {
+ stress.name: {metric: {} for metric in self.all_metric_keys} for stress in GridStress
+ }
+
+ for identifier, bucket in aggregates.items():
+ for metric in self.all_metric_keys:
+ value = mean(bucket["overall"][metric])
+ overall_summary[metric][identifier] = 0.0 if math.isnan(value) else value
+ for demand, metrics in bucket["by_demand"].items():
+ for metric in self.all_metric_keys:
+ value = mean(metrics[metric])
+ demand_summary[demand][metric][identifier] = 0.0 if math.isnan(value) else value
+ for stress, metrics in bucket["by_stress"].items():
+ for metric in self.all_metric_keys:
+ value = mean(metrics[metric])
+ stress_summary[stress][metric][identifier] = 0.0 if math.isnan(value) else value
+
+ comparison = {
+ "profiles": profiles_info,
+ "order": [profile.identifier for profile in self.profiles],
+ "metric_definitions": self.metric_definitions,
+ "overall": overall_summary,
+ "by_demand": demand_summary,
+ "by_stress": stress_summary,
+ "all_metrics": self.all_metric_keys,
+ }
+
+ self.logger.info("Computed comparative analytics across %d techniques", len(self.profiles))
+ return comparison
+
+ def persist(self, comparison: Dict[str, Any]) -> None:
+ if not comparison.get("order"):
+ return
+ tables_dir = self.results_dir / "tables"
+ tables_dir.mkdir(parents=True, exist_ok=True)
+ display_lookup = {
+ identifier: info["display_name"]
+ for identifier, info in comparison["profiles"].items()
+ }
+ header = ["Metric"] + [display_lookup[identifier] for identifier in comparison["order"]]
+
+ overall_path = tables_dir / "technique_comparison_overall.csv"
+ with open(overall_path, "w", newline="", encoding="utf-8") as f:
+ writer = csv.DictWriter(f, fieldnames=header)
+ writer.writeheader()
+ for metric_def in self.metric_definitions + [
+ {"key": name, "label": name.replace("_", " ").title(), "higher_is_better": True}
+ for name in self.additional_metrics
+ ]:
+ key = metric_def["key"]
+ row = {"Metric": metric_def["label"]}
+ for identifier in comparison["order"]:
+ value = comparison["overall"].get(key, {}).get(identifier)
+ row[display_lookup[identifier]] = f"{value:.4f}" if value is not None else ""
+ writer.writerow(row)
+ self.logger.info("Technique comparison overview stored at %s", overall_path)
+
+ for demand, metrics in comparison["by_demand"].items():
+ demand_path = tables_dir / f"technique_comparison_demand_{demand.lower()}.csv"
+ with open(demand_path, "w", newline="", encoding="utf-8") as f:
+ writer = csv.DictWriter(f, fieldnames=header)
+ writer.writeheader()
+ for metric_def in self.metric_definitions + [
+ {"key": name, "label": name.replace("_", " ").title(), "higher_is_better": True}
+ for name in self.additional_metrics
+ ]:
+ key = metric_def["key"]
+ row = {"Metric": metric_def["label"]}
+ for identifier in comparison["order"]:
+ value = metrics.get(key, {}).get(identifier)
+ row[display_lookup[identifier]] = (
+ f"{value:.4f}" if value is not None else ""
+ )
+ writer.writerow(row)
+ self.logger.info("Technique demand slice stored at %s", demand_path)
+
+ for stress, metrics in comparison["by_stress"].items():
+ stress_path = tables_dir / f"technique_comparison_stress_{stress.lower()}.csv"
+ with open(stress_path, "w", newline="", encoding="utf-8") as f:
+ writer = csv.DictWriter(f, fieldnames=header)
+ writer.writeheader()
+ for metric_def in self.metric_definitions + [
+ {"key": name, "label": name.replace("_", " ").title(), "higher_is_better": True}
+ for name in self.additional_metrics
+ ]:
+ key = metric_def["key"]
+ row = {"Metric": metric_def["label"]}
+ for identifier in comparison["order"]:
+ value = metrics.get(key, {}).get(identifier)
+ row[display_lookup[identifier]] = (
+ f"{value:.4f}" if value is not None else ""
+ )
+ writer.writerow(row)
+ self.logger.info("Technique stress slice stored at %s", stress_path)
+
+ json_path = self.results_dir / "data" / "processed" / "technique_comparison_summary.json"
+ with open(json_path, "w", encoding="utf-8") as f:
+ json.dump(comparison, f, indent=2)
+ self.logger.info("Technique comparison JSON stored at %s", json_path)
+
+class SensorStreamGenerator:
+ def __init__(self, twins: Sequence[ChargingStationDigitalTwin]):
+ self.twins = list(twins)
+ self.logger = logging.getLogger("SensorStream")
+
+ def simulate_streams(self, hours: int = 24) -> List[Dict[str, Any]]:
+ records: List[Dict[str, Any]] = []
+ start = datetime.utcnow()
+ for twin in self.twins[: min(20, len(self.twins))]:
+ for sensor in twin.sensors:
+ timestamp = start
+ for hour in range(hours):
+ jitter = random.gauss(0, 0.03)
+ value = self._sample_value(sensor.sensor_type, hour, twin)
+ records.append(
+ {
+ "station_id": twin.station_id,
+ "sensor_name": sensor.name,
+ "sensor_type": sensor.sensor_type.name,
+ "timestamp": timestamp.isoformat() + "Z",
+ "value": round(value * (1 + jitter), 5),
+ "communication": sensor.communication.name,
+ "battery_state": round(max(0.72, min(1.0, 1.0 - hour * 0.01 + random.gauss(0, 0.005))), 4),
+ "latency_ms": round(max(12, random.gauss(35, 8)), 2),
+ }
+ )
+ timestamp += timedelta(minutes=60)
+ json_path = RESULTS_DIR / "data" / "raw" / "sensor_streams.json"
+ with open(json_path, "w", encoding="utf-8") as f:
+ json.dump(records, f, indent=2)
+ self.logger.info("Simulated %d IoT telemetry records", len(records))
+ return records
+
+ def _sample_value(self, sensor_type: SensorType, hour: int, twin: ChargingStationDigitalTwin) -> float:
+ if sensor_type == SensorType.POWER_METER:
+ base_load = twin.rated_power_kw * random.uniform(0.35, 0.95)
+ modulation = 1.0 + 0.4 * math.sin(hour / 24 * 2 * math.pi)
+ return base_load * modulation
+ if sensor_type == SensorType.OCCUPANCY_CAMERA:
+ return random.randint(0, twin.connectors)
+ if sensor_type == SensorType.ENVIRONMENT:
+ return 20 + 8 * math.sin((hour + random.uniform(-1, 1)) / 24 * 2 * math.pi)
+ if sensor_type == SensorType.VIBRATION:
+ return abs(random.gauss(0.3, 0.15))
+ if sensor_type == SensorType.LIDAR:
+ return abs(random.gauss(1.8, 0.3))
+ if sensor_type == SensorType.WEATHER:
+ return random.uniform(0, 1)
+ if sensor_type == SensorType.ULTRASONIC:
+ return abs(random.gauss(0.5, 0.05))
+ if sensor_type == SensorType.AIR_QUALITY:
+ return random.uniform(12, 45)
+ return 0.0
+
+
+class StressTestRunner:
+ def __init__(self, twins: Sequence[ChargingStationDigitalTwin]):
+ self.twins = list(twins)
+ self.logger = logging.getLogger("StressTest")
+
+ def run_scenarios(self, scenarios: Sequence[StressTestScenario]) -> List[Dict[str, Any]]:
+ records: List[Dict[str, Any]] = []
+ for scenario in scenarios:
+ for twin in random.sample(self.twins, k=min(10, len(self.twins))):
+ renewable_drop = max(0.1, twin.renewable_share - scenario.grid_shock_factor * 0.2)
+ communication_loss = scenario.communication_loss_fraction * random.uniform(0.8, 1.2)
+ grid_recovery = max(5, random.gauss(18, 4) * scenario.grid_shock_factor)
+ latency_spike = max(25, random.gauss(70, 15) * scenario.demand_multiplier)
+ resilience = (twin.fault_tolerance_score + twin.cybersecurity_score) / 2
+ service_level = max(0.3, min(0.98, resilience - communication_loss * 0.4))
+ records.append(
+ {
+ "scenario": scenario.name,
+ "station_id": twin.station_id,
+ "renewable_fraction_post": round(renewable_drop, 3),
+ "communication_loss": round(communication_loss, 3),
+ "grid_recovery_minutes": round(grid_recovery, 2),
+ "latency_spike_ms": round(latency_spike, 2),
+ "service_level": round(service_level, 3),
+ "cyber_attack": scenario.cyber_attack,
+ "load_multiplier": scenario.demand_multiplier,
+ }
+ )
+ csv_path = RESULTS_DIR / "stress_tests" / "stress_test_results.csv"
+ if records:
+ fieldnames = list(records[0].keys())
+ with open(csv_path, "w", newline="", encoding="utf-8") as f:
+ writer = csv.DictWriter(f, fieldnames=fieldnames)
+ writer.writeheader()
+ for record in records:
+ writer.writerow(record)
+ self.logger.info("Stress test scenarios executed and stored at %s", csv_path)
+ return records
+
+
+class ReportBuilder:
+ def __init__(self, results_dir: Path):
+ self.results_dir = results_dir
+ self.logger = logging.getLogger("ReportBuilder")
+
+ def summarize_outcomes(self, outcomes: Sequence[MonteCarloOutcome]) -> Dict[str, Any]:
+ demand_groups: Dict[str, List[float]] = defaultdict(list)
+ wait_times: List[float] = []
+ revenues: List[float] = []
+ grid_dev: List[float] = []
+ roi: List[float] = []
+ latency: List[float] = []
+ data_quality: List[float] = []
+
+ for outcome in outcomes:
+ demand_groups[outcome.demand_level.name].append(outcome.nip_score)
+ wait_times.append(outcome.wait_minutes)
+ revenues.append(outcome.revenue_eur)
+ grid_dev.append(outcome.grid_deviation_pct)
+ roi.append(outcome.roi_improvement_pct)
+ latency.append(outcome.latency_ms)
+ data_quality.append(outcome.data_completeness)
+
+ pivot_rows = []
+ stress_levels = sorted({outcome.grid_stress.name for outcome in outcomes})
+ for demand_level in sorted(demand_groups.keys()):
+ row = {"demand_level": demand_level}
+ for stress in stress_levels:
+ nip_values = [
+ outcome.nip_score
+ for outcome in outcomes
+ if outcome.demand_level.name == demand_level and outcome.grid_stress.name == stress
+ ]
+ row[stress] = round(mean(nip_values), 4) if nip_values else None
+ pivot_rows.append(row)
+
+ pivot_path = self.results_dir / "tables" / "nip_pivot.csv"
+ if pivot_rows:
+ headers = list(pivot_rows[0].keys())
+ with open(pivot_path, "w", newline="", encoding="utf-8") as f:
+ writer = csv.DictWriter(f, fieldnames=headers)
+ writer.writeheader()
+ for row in pivot_rows:
+ writer.writerow(row)
+
+ descriptive = {
+ "count": len(outcomes),
+ "nip_mean": round(mean([o.nip_score for o in outcomes]), 4),
+ "nip_std": round(stdev([o.nip_score for o in outcomes]), 4),
+ "wait_median": round(median(wait_times), 2),
+ "wait_95th": round(percentile(wait_times, 0.95), 2),
+ "revenue_mean": round(mean(revenues), 2),
+ "revenue_std": round(stdev(revenues), 2),
+ "grid_dev_mean": round(mean(grid_dev), 2),
+ "roi_mean": round(mean(roi), 2),
+ "latency_mean": round(mean(latency), 2),
+ "data_quality_mean": round(mean(data_quality), 3),
+ }
+
+ desc_path = self.results_dir / "tables" / "monte_carlo_descriptive.json"
+ with open(desc_path, "w", encoding="utf-8") as f:
+ json.dump(descriptive, f, indent=2)
+
+ summary_text = [
+ "=" * 110,
+ "IOT-CENTRIC EV PARKING EXPERIMENTAL SUMMARY",
+ "=" * 110,
+ "",
+ f"Mean NIP Score: {descriptive['nip_mean']}",
+ f"NIP Standard Deviation: {descriptive['nip_std']}",
+ f"Median Waiting Time (min): {descriptive['wait_median']}",
+ f"95th Percentile Waiting Time (min): {descriptive['wait_95th']}",
+ f"Mean Revenue (€): {descriptive['revenue_mean']}",
+ f"Average Grid Deviation (%): {descriptive['grid_dev_mean']}",
+ f"Mean ROI Improvement (%): {descriptive['roi_mean']}",
+ f"Average Latency (ms): {descriptive['latency_mean']}",
+ f"Data Completeness Index: {descriptive['data_quality_mean']}",
+ "",
+ "Protocol Highlights:",
+ " - Multi-layer IoT telemetry integration across 60 digital twins.",
+ " - Blockchain-MDP hybrid optimization with real-time sensor feedback.",
+ " - Stress-tested cyber resilience and communication failover handling.",
+ " - Psychometric personalization covering four EV driver segments.",
+ " - Statistical coverage: 1,200 Monte Carlo trials, 24-hour telemetry, 8 stress scenarios.",
+ ]
+ report_file = self.results_dir / "protocol_blueprints" / "executive_summary.txt"
+ with open(report_file, "w", encoding="utf-8") as f:
+ f.write("\n".join(summary_text))
+ self.logger.info("Executive summary stored at %s", report_file)
+ return descriptive
+
+ def attach_dataset_summary(
+ self,
+ global_stats: Dict[str, float],
+ station_profiles: Dict[str, Dict[str, float]],
+ ) -> None:
+ lines = [
+ "=" * 110,
+ "ACN DATASET CALIBRATION DIGEST",
+ "=" * 110,
+ "",
+ f"Sessions analysed: {int(global_stats.get('sessions_count', 0))}",
+ f"Average energy delivered (kWh): {global_stats.get('kwh_delivered_mean', 0.0):.2f}",
+ f"Average charging duration (min): {global_stats.get('charging_minutes_mean', 0.0):.2f}",
+ f"Average waiting duration (min): {global_stats.get('waiting_minutes_mean', 0.0):.2f}",
+ f"Mean arrival hour: {global_stats.get('arrival_hour_mean', 0.0):.2f}",
+ f"Fleet energy efficiency: {global_stats.get('efficiency_mean', 0.0):.3f}",
+ "",
+ "Top 10 stations by utilisation:",
+ ]
+ ranked = sorted(
+ station_profiles.items(),
+ key=lambda item: item[1].get("sessions", 0),
+ reverse=True,
+ )[:10]
+ for station_id, metrics in ranked:
+ lines.append(
+ f" - {station_id}: {metrics['sessions']:.0f} sessions, {metrics['avg_kwh']:.2f} kWh avg, "
+ f"wait {metrics['avg_wait_minutes']:.1f} min, utilisation {metrics['utilization_proxy']:.2%}"
+ )
+ digest_path = self.results_dir / "protocol_blueprints" / "dataset_digest.txt"
+ with open(digest_path, "w", encoding="utf-8") as f:
+ f.write("\n".join(lines))
+ self.logger.info("Dataset calibration digest stored at %s", digest_path)
+
+
+class ProtocolDatabase:
+ def __init__(self, results_dir: Path) -> None:
+ self.db_path = results_dir / "protocol_blueprints" / "experiment.db"
+ self.logger = logging.getLogger("ProtocolDB")
+ self._init_db()
+
+ def _init_db(self) -> None:
+ conn = sqlite3.connect(self.db_path)
+ cur = conn.cursor()
+ cur.execute(
+ """
+ CREATE TABLE IF NOT EXISTS runs (
+ run_id TEXT PRIMARY KEY,
+ demand TEXT,
+ grid_stress TEXT,
+ nip REAL,
+ wait REAL,
+ revenue REAL,
+ roi REAL,
+ timestamp TEXT
+ )
+ """
+ )
+ cur.execute(
+ """
+ CREATE TABLE IF NOT EXISTS telemetry_metrics (
+ station_id TEXT,
+ sensor_name TEXT,
+ mean_value REAL,
+ std_value REAL,
+ uptime REAL,
+ PRIMARY KEY (station_id, sensor_name)
+ )
+ """
+ )
+ conn.commit()
+ conn.close()
+ self.logger.debug("SQLite schema initialized at %s", self.db_path)
+
+ def store_runs(self, outcomes: Sequence[MonteCarloOutcome]) -> None:
+ conn = sqlite3.connect(self.db_path)
+ cur = conn.cursor()
+ entries = [
+ (
+ generate_hash(o.run_id, o.demand_level.name, o.grid_stress.name),
+ o.demand_level.name,
+ o.grid_stress.name,
+ o.nip_score,
+ o.wait_minutes,
+ o.revenue_eur,
+ o.roi_improvement_pct,
+ datetime.utcnow().isoformat() + "Z",
+ )
+ for o in outcomes
+ ]
+ cur.executemany(
+ "INSERT OR REPLACE INTO runs (run_id, demand, grid_stress, nip, wait, revenue, roi, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+ entries,
+ )
+ conn.commit()
+ conn.close()
+ self.logger.info("Persisted %d Monte Carlo runs into SQLite", len(entries))
+
+ def store_telemetry_metrics(self, telemetry: Sequence[Dict[str, Any]]) -> None:
+ metrics: Dict[Tuple[str, str], Dict[str, List[float]]] = {}
+ for record in telemetry:
+ key = (record["station_id"], record["sensor_name"])
+ metrics.setdefault(key, {"values": [], "uptime": []})
+ metrics[key]["values"].append(record["value"])
+ metrics[key]["uptime"].append(record["battery_state"])
+ entries = []
+ for (station_id, sensor_name), payload in metrics.items():
+ entries.append(
+ (
+ station_id,
+ sensor_name,
+ mean(payload["values"]),
+ stdev(payload["values"]),
+ mean(payload["uptime"]),
+ )
+ )
+ conn = sqlite3.connect(self.db_path)
+ cur = conn.cursor()
+ cur.executemany(
+ "INSERT OR REPLACE INTO telemetry_metrics (station_id, sensor_name, mean_value, std_value, uptime) VALUES (?, ?, ?, ?, ?)",
+ entries,
+ )
+ conn.commit()
+ conn.close()
+ self.logger.info("Telemetry metrics stored: %d rows", len(entries))
+
+
+class ProtocolOrchestrator:
+ def __init__(self, results_dir: Path) -> None:
+ self.results_dir = results_dir
+ self.logger = logging.getLogger("Orchestrator")
+ self.phases = self._define_phases()
+ self.stress_scenarios = self._define_stress_tests()
+ self._persist_phases()
+
+ def _define_phases(self) -> List[ExperimentPhase]:
+ phases = [
+ ExperimentPhase(
+ phase_id="P1",
+ name="Cyber-Physical Digital Twin Calibration",
+ objective="Validate IoT telemetry fidelity and align digital twins with measured data.",
+ start_month=1,
+ end_month=2,
+ demand_levels=[DemandLevel.NOMINAL, DemandLevel.BUSY],
+ focus_area="IoT data quality benchmarking and baseline model calibration",
+ instrumentation_plan={
+ "edge_analytics": "Deploy federated learning models on 20% of gateways",
+ "sensor_calibration": "Weekly cross-validation of LiDAR and ultrasonic sensors",
+ "blockchain_integration": "Anchor telemetry proofs every 10 minutes",
+ },
+ ),
+ ExperimentPhase(
+ phase_id="P2",
+ name="Semi-Controlled Campus Deployment",
+ objective="Evaluate adaptive scheduling with live users and microgrid integration.",
+ start_month=3,
+ end_month=5,
+ demand_levels=[DemandLevel.NOMINAL, DemandLevel.BUSY, DemandLevel.PEAK],
+ focus_area="Human factors validation, grid balancing, and network resilience",
+ instrumentation_plan={
+ "user_study": "Recruit 180 participants across four psychometric segments",
+ "microgrid": "Operate 120 kW solar array with 250 kWh storage",
+ "iot_security": "Implement anomaly detection on firmware telemetry",
+ },
+ ),
+ ExperimentPhase(
+ phase_id="P3",
+ name="Urban Pilot with Distributed IoT Mesh",
+ objective="Deploy across 120 public stations with heterogeneous communication protocols.",
+ start_month=6,
+ end_month=9,
+ demand_levels=[DemandLevel.BUSY, DemandLevel.PEAK, DemandLevel.EXTREME],
+ focus_area="Scalability, cross-operator interoperability, and blockchain settlement",
+ instrumentation_plan={
+ "mesh_network": "Deploy 5G + LoRaWAN dual connectivity",
+ "blockchain": "Run Hyperledger Fabric consortium with 11 validators",
+ "analytics": "Daily reinforcement learning policy updates",
+ },
+ ),
+ ExperimentPhase(
+ phase_id="P4",
+ name="Stress Testing and Longitudinal Validation",
+ objective="Challenge the ecosystem under cyber-physical extremes and evaluate recovery.",
+ start_month=10,
+ end_month=12,
+ demand_levels=[DemandLevel.PEAK, DemandLevel.EXTREME],
+ focus_area="Resilience benchmarks, fail-safe orchestration, and economic sustainability",
+ instrumentation_plan={
+ "red_team": "Quarterly penetration testing drills",
+ "blackout_drill": "Simulate 60% grid capacity loss with demand surge",
+ "token_economics": "Stress token liquidity with 15k TPS burst",
+ },
+ ),
+ ]
+ self.logger.info("Defined %d experimental phases", len(phases))
+ return phases
+
+ def _define_stress_tests(self) -> List[StressTestScenario]:
+ scenarios = [
+ StressTestScenario(
+ name="EdgeStorm",
+ description="Sudden gateway firmware failure cascade",
+ duration_min=45,
+ demand_multiplier=1.8,
+ grid_shock_factor=1.4,
+ cyber_attack=True,
+ communication_loss_fraction=0.32,
+ ),
+ StressTestScenario(
+ name="SolarEclipse",
+ description="Renewable drop with weather-induced shading",
+ duration_min=120,
+ demand_multiplier=1.3,
+ grid_shock_factor=1.6,
+ cyber_attack=False,
+ communication_loss_fraction=0.12,
+ ),
+ StressTestScenario(
+ name="TokenFlood",
+ description="Blockchain throughput stress with 12k TPS bursts",
+ duration_min=30,
+ demand_multiplier=2.1,
+ grid_shock_factor=1.2,
+ cyber_attack=False,
+ communication_loss_fraction=0.05,
+ ),
+ StressTestScenario(
+ name="GridQuake",
+ description="50% grid capacity reduction triggering emergency shedding",
+ duration_min=90,
+ demand_multiplier=2.8,
+ grid_shock_factor=1.9,
+ cyber_attack=True,
+ communication_loss_fraction=0.28,
+ ),
+ StressTestScenario(
+ name="LatencyFog",
+ description="Backhaul congestion causing severe latency inflation",
+ duration_min=75,
+ demand_multiplier=1.6,
+ grid_shock_factor=1.1,
+ cyber_attack=False,
+ communication_loss_fraction=0.42,
+ ),
+ StressTestScenario(
+ name="SensorDrift",
+ description="Progressive sensor calibration drift across the fleet",
+ duration_min=240,
+ demand_multiplier=1.2,
+ grid_shock_factor=1.05,
+ cyber_attack=False,
+ communication_loss_fraction=0.18,
+ ),
+ StressTestScenario(
+ name="FleetSurge",
+ description="Coordinated arrival of 400% EV fleet",
+ duration_min=60,
+ demand_multiplier=4.0,
+ grid_shock_factor=1.7,
+ cyber_attack=False,
+ communication_loss_fraction=0.25,
+ ),
+ StressTestScenario(
+ name="ColdBoot",
+ description="System restart under peak load after security patch",
+ duration_min=35,
+ demand_multiplier=1.9,
+ grid_shock_factor=1.3,
+ cyber_attack=True,
+ communication_loss_fraction=0.21,
+ ),
+ ]
+ self.logger.info("Registered %d stress scenarios", len(scenarios))
+ return scenarios
+
+ def _persist_phases(self) -> None:
+ phase_path = self.results_dir / "protocol_blueprints" / "phases.json"
+ with open(phase_path, "w", encoding="utf-8") as f:
+ json.dump([
+ {
+ "phase_id": phase.phase_id,
+ "name": phase.name,
+ "objective": phase.objective,
+ "start_month": phase.start_month,
+ "end_month": phase.end_month,
+ "demand_levels": [level.name for level in phase.demand_levels],
+ "focus_area": phase.focus_area,
+ "instrumentation_plan": phase.instrumentation_plan,
+ }
+ for phase in self.phases
+ ], f, indent=2)
+ self.logger.debug("Phases persisted to %s", phase_path)
+
+
+class StatisticalEvaluator:
+ def __init__(self, results_dir: Path) -> None:
+ self.results_dir = results_dir
+ self.logger = logging.getLogger("Statistics")
+
+ def evaluate(self, outcomes: Sequence[MonteCarloOutcome]) -> Dict[str, Any]:
+ waits = [o.wait_minutes for o in outcomes]
+ revenues = [o.revenue_eur for o in outcomes]
+ roi = [o.roi_improvement_pct for o in outcomes]
+ nip = [o.nip_score for o in outcomes]
+ baseline_wait = [value for value in waits if value > median(waits)]
+ optimized_wait = [value for value in waits if value <= median(waits)]
+ t_stat, p_val = welchs_ttest(baseline_wait, optimized_wait)
+ corr = correlation_matrix({"nip": nip, "revenue": revenues, "roi": roi})
+ revenue_projection = [value * 0.35 for value in roi]
+ evaluation = {
+ "ttest_wait": {"t_stat": round(t_stat, 4), "p_value": round(p_val, 6)},
+ "correlations": corr,
+ "rmse_revenue_vs_roi": round(rmse(revenues, revenue_projection), 4),
+ "mae_revenue_vs_roi": round(mae(revenues, revenue_projection), 4),
+ }
+ stats_file = self.results_dir / "tables" / "statistical_summary.json"
+ with open(stats_file, "w", encoding="utf-8") as f:
+ json.dump(evaluation, f, indent=2)
+ self.logger.info("Statistical evaluation stored at %s", stats_file)
+ return evaluation
+
+
+class VisualizationSuite:
+ def __init__(self, results_dir: Path) -> None:
+ self.results_dir = results_dir
+ self.logger = logging.getLogger("Visualization")
+
+ def render_all(
+ self,
+ sessions: Sequence[SessionRecord],
+ station_profiles: Dict[str, Dict[str, float]],
+ outcomes: Sequence[MonteCarloOutcome],
+ comparison: Dict[str, Any],
+ ) -> None:
+ self.plot_dataset_distributions(sessions)
+ self.plot_station_utilization(station_profiles)
+ self.plot_outcome_heatmap(outcomes)
+ self.plot_technique_series(comparison)
+
+ def _write_svg(self, path: Path, width: int, height: int, body: str) -> None:
+ svg = f""
+ with open(path, "w", encoding="utf-8") as f:
+ f.write(svg)
+
+ def plot_dataset_distributions(self, sessions: Sequence[SessionRecord]) -> None:
+ if not sessions:
+ return
+ metrics = [
+ ("Energy Delivered (kWh)", [s.kwh_delivered for s in sessions], 20, "dataset_energy.svg"),
+ ("Charging Duration (min)", [s.charging_minutes for s in sessions], 20, "dataset_duration.svg"),
+ ("Arrival Hour", [s.connection_time.hour + s.connection_time.minute / 60 for s in sessions], 24, "dataset_arrival.svg"),
+ ]
+ for title, values, bins, filename in metrics:
+ if not values:
+ continue
+ min_value = min(values)
+ max_value = max(values)
+ if max_value == min_value:
+ counts = [len(values)] + [0] * (bins - 1)
+ else:
+ bin_width = (max_value - min_value) / bins
+ counts = [0 for _ in range(bins)]
+ for value in values:
+ idx = int((value - min_value) / bin_width)
+ idx = min(bins - 1, max(0, idx))
+ counts[idx] += 1
+ max_count = max(1, max(counts))
+ width, height = 800, 400
+ margin_left, margin_bottom, margin_top = 60, 60, 40
+ available_height = height - margin_bottom - margin_top
+ bar_width = (width - 2 * margin_left) / len(counts)
+ rects = []
+ for idx, count in enumerate(counts):
+ bar_height = available_height * (count / max_count)
+ x = margin_left + idx * bar_width
+ y = height - margin_bottom - bar_height
+ rects.append(
+ f""
+ )
+ labels = (
+ f"{title}"
+ f"Count"
+ )
+ axis = (
+ f""
+ f""
+ )
+ body = "".join(rects) + labels + axis
+ output = self.results_dir / "figures" / filename
+ self._write_svg(output, width, height, body)
+ self.logger.info("Saved dataset distribution SVG to %s", output)
+
+ def plot_station_utilization(self, station_profiles: Dict[str, Dict[str, float]]) -> None:
+ if not station_profiles:
+ return
+ ranked = sorted(
+ station_profiles.items(),
+ key=lambda item: item[1].get("sessions", 0),
+ reverse=True,
+ )[:15]
+ width, height = 900, 420
+ margin_left, margin_bottom, margin_top = 80, 70, 50
+ available_height = height - margin_bottom - margin_top
+ bar_width = (width - 2 * margin_left) / len(ranked)
+ max_util = max(item[1].get("utilization_proxy", 0.0) for item in ranked)
+ rects = []
+ labels = []
+ for idx, (station, metrics) in enumerate(ranked):
+ util = metrics.get("utilization_proxy", 0.0)
+ bar_height = available_height * (util / max(0.01, max_util))
+ x = margin_left + idx * bar_width
+ y = height - margin_bottom - bar_height
+ rects.append(
+ f""
+ )
+ labels.append(
+ f"{station}"
+ )
+ axis = (
+ f""
+ f""
+ )
+ title = f"Station Utilisation"
+ body = "".join(rects + labels) + axis + title
+ output = self.results_dir / "figures" / "station_utilisation.svg"
+ self._write_svg(output, width, height, body)
+ self.logger.info("Saved station utilisation SVG to %s", output)
+
+ def plot_outcome_heatmap(self, outcomes: Sequence[MonteCarloOutcome]) -> None:
+ if not outcomes:
+ return
+ demands = list(DemandLevel)
+ stresses = list(GridStress)
+ matrix = [[0.0 for _ in range(len(stresses))] for _ in range(len(demands))]
+ counts = [[0 for _ in range(len(stresses))] for _ in range(len(demands))]
+ for outcome in outcomes:
+ d_index = demands.index(outcome.demand_level)
+ s_index = stresses.index(outcome.grid_stress)
+ matrix[d_index][s_index] += outcome.nip_score
+ counts[d_index][s_index] += 1
+ for i in range(len(demands)):
+ for j in range(len(stresses)):
+ if counts[i][j]:
+ matrix[i][j] = matrix[i][j] / counts[i][j]
+ else:
+ matrix[i][j] = 0.0
+ width, height = 600, 420
+ margin_left, margin_bottom, margin_top = 120, 80, 60
+ cell_width = (width - margin_left - 40) / len(stresses)
+ cell_height = (height - margin_top - margin_bottom) / len(demands)
+
+ def color_for_value(value: float) -> str:
+ value = max(0.0, min(1.0, value))
+ red = int(255 * value)
+ green = int(200 * value)
+ blue = int(255 * (1 - value))
+ return f"#{red:02x}{green:02x}{blue:02x}"
+
+ cells = []
+ texts = []
+ for i, demand in enumerate(demands):
+ for j, stress in enumerate(stresses):
+ value = matrix[i][j]
+ x = margin_left + j * cell_width
+ y = margin_top + i * cell_height
+ cells.append(
+ f""
+ )
+ texts.append(
+ f"{value:.2f}"
+ )
+
+ x_labels = [
+ f"{stress.name.title()}"
+ for j, stress in enumerate(stresses)
+ ]
+ y_labels = [
+ f"{demand.name.title()}"
+ for i, demand in enumerate(demands)
+ ]
+ title = "NIP Score Heatmap".format(
+ width / 2, margin_top / 2
+ )
+ legend = "".join(
+ f""
+ for k in range(11)
+ )
+ legend += f"0.0"
+ legend += f"1.0"
+ body = "".join(cells + texts + x_labels + y_labels) + title + legend
+ output = self.results_dir / "figures" / "nip_heatmap.svg"
+ self._write_svg(output, width, height, body)
+ self.logger.info("Saved Monte Carlo heatmap SVG to %s", output)
+
+ def _plot_horizontal_bar(
+ self,
+ filename: Path,
+ order: Sequence[str],
+ display_lookup: Dict[str, str],
+ values: Dict[str, float],
+ title: str,
+ higher_is_better: bool,
+ ) -> None:
+ data = [(identifier, values.get(identifier)) for identifier in order if values.get(identifier) is not None]
+ if not data:
+ return
+ max_value = max(value for _, value in data)
+ min_value = min(value for _, value in data)
+ if max_value == 0:
+ max_value = 1.0
+ highlight_value = max_value if higher_is_better else min_value
+ width, height = 900, max(240, 80 + 40 * len(data))
+ margin_left, margin_top, margin_bottom, margin_right = 220, 60, 60, 80
+ available_width = width - margin_left - margin_right
+ bar_height = (height - margin_top - margin_bottom) / len(data)
+ rects = []
+ labels = []
+ tolerance = 1e-9
+ for index, (identifier, value) in enumerate(data):
+ normalized = value / max_value if max_value else 0.0
+ bar_width = available_width * normalized
+ y = margin_top + index * bar_height
+ is_best = abs(value - highlight_value) <= tolerance
+ color = "#27ae60" if is_best else "#7f8c8d"
+ rects.append(
+ f""
+ )
+ label = display_lookup.get(identifier, identifier)
+ labels.append(
+ f"{label}"
+ )
+ labels.append(
+ f"{value:.3f}"
+ )
+ axis_line = (
+ f""
+ )
+ title_svg = (
+ f"{title}"
+ )
+ highlight_text = "Higher is better" if higher_is_better else "Lower is better"
+ subtitle = (
+ f"{highlight_text}"
+ )
+ body = "".join(rects + labels) + axis_line + title_svg + subtitle
+ self._write_svg(filename, width, height, body)
+ self.logger.info("Saved technique comparison SVG to %s", filename)
+
+ def plot_technique_series(self, comparison: Dict[str, Any]) -> None:
+ metric_defs = comparison.get("metric_definitions", [])
+ order = comparison.get("order", [])
+ if not metric_defs or not order:
+ return
+ display_lookup = {
+ identifier: info.get("display_name", identifier)
+ for identifier, info in comparison.get("profiles", {}).items()
+ }
+ overall = comparison.get("overall", {})
+ by_demand = comparison.get("by_demand", {})
+
+ for metric_def in metric_defs:
+ key = metric_def["key"]
+ label = metric_def["label"]
+ higher_is_better = metric_def.get("higher_is_better", True)
+ overall_values = overall.get(key, {})
+ overall_path = self.results_dir / "figures" / f"tech_{key}_overall.svg"
+ self._plot_horizontal_bar(
+ overall_path,
+ order,
+ display_lookup,
+ overall_values,
+ f"{label} | Overall",
+ higher_is_better,
+ )
+
+ for demand, dataset in by_demand.items():
+ demand_values = dataset.get(key, {})
+ demand_path = self.results_dir / "figures" / f"tech_{key}_demand_{demand.lower()}.svg"
+ demand_title = f"{label} | Demand: {demand.title()}"
+ self._plot_horizontal_bar(
+ demand_path,
+ order,
+ display_lookup,
+ demand_values,
+ demand_title,
+ higher_is_better,
+ )
+
+def main() -> Dict[str, Any]:
+ logger.info("Initializing IoT-centric EV parking experimental protocol...")
+
+ dataset_path = Path(__file__).with_name("acndata_sessions.json")
+ acn_loader = ACNDataLoader(dataset_path)
+ acn_loader.export_statistics(RESULTS_DIR)
+
+ designer = IoTInfrastructureDesigner(
+ RESULTS_DIR,
+ session_statistics=acn_loader.global_stats,
+ station_profiles=acn_loader.station_profiles,
+ )
+ twins = designer.create_digital_twins(station_count=60)
+
+ blockchain_config = BlockchainConfig(
+ network=BlockchainNetwork.HYPERLEDGER_FABRIC,
+ block_time_s=2.1,
+ base_fee=0.002,
+ validator_count=11,
+ energy_cost_kw=0.08,
+ throughput_target_tps=1500,
+ )
+
+ psych_engine = PsychometricEngine()
+ token_economy = TokenEconomy(blockchain_config)
+ optimizer = MultiObjectiveOptimizer(psych_engine, token_economy)
+
+ monte_carlo = MonteCarloSimulation(optimizer, twins, blockchain_config)
+ outcomes = monte_carlo.run(iterations=1200)
+
+ technique_suite = TechniqueComparisonSuite(RESULTS_DIR)
+ comparison_results = technique_suite.compare(outcomes)
+ technique_suite.persist(comparison_results)
+
+ telemetry_generator = SensorStreamGenerator(twins)
+ telemetry_records = telemetry_generator.simulate_streams(hours=24)
+
+ orchestrator = ProtocolOrchestrator(RESULTS_DIR)
+ stress_runner = StressTestRunner(twins)
+ stress_results = stress_runner.run_scenarios(orchestrator.stress_scenarios)
+
+ visualizer = VisualizationSuite(RESULTS_DIR)
+ visualizer.render_all(
+ acn_loader.sessions,
+ acn_loader.station_profiles,
+ outcomes,
+ comparison_results,
+ )
+
+ report_builder = ReportBuilder(RESULTS_DIR)
+ summary = report_builder.summarize_outcomes(outcomes)
+ report_builder.attach_dataset_summary(acn_loader.global_stats, acn_loader.station_profiles)
+
+ stats_evaluator = StatisticalEvaluator(RESULTS_DIR)
+ statistical_results = stats_evaluator.evaluate(outcomes)
+
+ database = ProtocolDatabase(RESULTS_DIR)
+ database.store_runs(outcomes)
+ database.store_telemetry_metrics(telemetry_records)
+
+ protocol_state = {
+ "summary": summary,
+ "statistical_results": statistical_results,
+ "stress_records": len(stress_results),
+ "telemetry_records": len(telemetry_records),
+ "phase_count": len(orchestrator.phases),
+ "stress_scenarios": len(orchestrator.stress_scenarios),
+ "technique_count": len(comparison_results.get("order", [])),
+ }
+
+ logger.info("Protocol execution complete. Summary: %s", protocol_state)
+ return protocol_state
+
+
+if __name__ == "__main__":
+ start_time = time.time()
+ state = main()
+ duration = time.time() - start_time
+ print("\n=== IoT-Centric EV Protocol Completed ===")
+ print(f"Runtime: {duration:.2f} seconds")
+ print(json.dumps(state, indent=2))