event.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. 'use strict';
  2. const logger = require('../logger');
  3. const nantum = require('../modules/nantum');
  4. const { vtnId } = require('../config');
  5. function calculateEventStatus(notificationTime, startTime, endTime) {
  6. const nowMillis = new Date().getTime();
  7. if (nowMillis < new Date(startTime).getTime()) {
  8. return 'far';
  9. }
  10. if (nowMillis > new Date(endTime).getTime()) {
  11. return 'completed';
  12. }
  13. return 'active';
  14. }
  15. function calculateDurationSeconds(startTime, endTime) {
  16. return Math.round(
  17. (new Date(endTime).getTime() - new Date(startTime).getTime()) / 1000,
  18. );
  19. }
  20. function calculateDuration(startTime, endTime) {
  21. return `PT${calculateDurationSeconds(startTime, endTime)}S`;
  22. }
  23. function calculateNotificationDuration(notificationTime, startTime) {
  24. if (!notificationTime) {
  25. return 'PT0S';
  26. }
  27. return calculateDuration(notificationTime, startTime);
  28. }
  29. function calculateEventIntervals(eventInfoValues, eventDurationSeconds) {
  30. //TODO: this is likely incorrect. Get more details on the event_info_value data model.
  31. let result = [];
  32. for (let i = 0; i < eventInfoValues.length; i++) {
  33. const eventInfoValue = eventInfoValues[i];
  34. const nextOffset =
  35. i === eventInfoValues.length - 1
  36. ? eventDurationSeconds - eventInfoValue.timeOffset
  37. : eventInfoValues[i + 1].timeOffset;
  38. result.push({
  39. signalPayloads: [eventInfoValue.value],
  40. duration: `PT${nextOffset}S`,
  41. uid: `${i + 1}`,
  42. });
  43. }
  44. return result;
  45. }
  46. function calculateEventSignals(eventInstances, eventDurationSeconds) {
  47. return eventInstances.map(eventInstance => {
  48. return {
  49. signalName: eventInstance.event_type_id,
  50. signalId: '112233445566',
  51. signalType: 'level',
  52. intervals: calculateEventIntervals(
  53. eventInstance.event_info_values,
  54. eventDurationSeconds,
  55. ),
  56. };
  57. });
  58. }
  59. function convertToOadrEvents(nantumEvent) {
  60. if (!nantumEvent.dr_event_data) {
  61. // no event
  62. return [];
  63. }
  64. const nowMillis = new Date().getTime();
  65. if (
  66. nowMillis < new Date(nantumEvent.dr_event_data.notification_time).getTime()
  67. ) {
  68. return []; // not in the notification period yet
  69. }
  70. return [
  71. {
  72. eventDescriptor: {
  73. eventId: nantumEvent.event_identifier,
  74. modificationNumber: nantumEvent.event_mod_number,
  75. marketContext: 'http://MarketContext1',
  76. createdDateTime: '2020-04-14T16:06:39.000Z',
  77. eventStatus: calculateEventStatus(
  78. nantumEvent.dr_event_data.notification_time,
  79. nantumEvent.dr_event_data.start_time,
  80. nantumEvent.dr_event_data.end_time,
  81. ),
  82. testEvent: nantumEvent.test_event,
  83. priority: 0,
  84. },
  85. activePeriod: {
  86. startDate: nantumEvent.dr_event_data.start_time,
  87. duration: calculateDuration(
  88. nantumEvent.dr_event_data.start_time,
  89. nantumEvent.dr_event_data.end_time,
  90. ),
  91. notificationDuration: calculateNotificationDuration(
  92. nantumEvent.dr_event_data.notification_time,
  93. nantumEvent.dr_event_data.start_time,
  94. ),
  95. },
  96. signals: {
  97. event: calculateEventSignals(
  98. nantumEvent.dr_event_data.event_instance,
  99. calculateDurationSeconds(
  100. nantumEvent.dr_event_data.start_time,
  101. nantumEvent.dr_event_data.end_time,
  102. ),
  103. ),
  104. },
  105. target: {
  106. venId: [nantumEvent.client_id],
  107. },
  108. responseRequired: 'always',
  109. },
  110. ];
  111. }
  112. async function retrieveEvents(
  113. oadrRequestEvent,
  114. clientCertificateCn,
  115. clientCertificateFingerprint,
  116. ) {
  117. logger.info(
  118. 'retrieveEvents',
  119. oadrRequestEvent,
  120. clientCertificateCn,
  121. clientCertificateFingerprint,
  122. );
  123. const requestVenId = oadrRequestEvent.venId;
  124. if (!requestVenId) {
  125. const error = new Error('No VenID in request');
  126. error.responseCode = 452;
  127. throw error;
  128. }
  129. if (requestVenId !== clientCertificateFingerprint) {
  130. // as per certification item #512, venId MUST be case-sensitive
  131. const error = new Error('VenID does not match certificate');
  132. error.responseCode = 452;
  133. throw error;
  134. }
  135. if (!clientCertificateCn) {
  136. const error = new Error('Could not determine CN from client certificate');
  137. error.responseCode = 452;
  138. throw error;
  139. }
  140. const event = await nantum.fetchEvent(requestVenId);
  141. return {
  142. responseCode: '200',
  143. responseDescription: 'OK',
  144. responseRequestId: oadrRequestEvent.requestId || '',
  145. requestId: oadrRequestEvent.requestId || '',
  146. vtnId: vtnId,
  147. events: convertToOadrEvents(event),
  148. };
  149. }
  150. async function updateOptType(
  151. oadrCreatedEvent,
  152. clientCertificateCn,
  153. clientCertificateFingerprint,
  154. ) {
  155. logger.info(
  156. 'updateOptType',
  157. oadrCreatedEvent,
  158. clientCertificateCn,
  159. clientCertificateFingerprint,
  160. );
  161. const requestVenId = oadrCreatedEvent.venId;
  162. validateVenId(requestVenId, clientCertificateFingerprint, true);
  163. let opted = await nantum.fetchOpted(requestVenId);
  164. //TODO: more validation: VEN may opt into an event that doesn't exist. VEN may opt into an old version of an event
  165. // (modificationNumber doesn't match). May opt into a completed event. Indicate error(s) to client.
  166. for (const eventResponse of oadrCreatedEvent.eventResponses) {
  167. // remove existing opts for this eventId
  168. opted = [
  169. ...opted.filter(optedItem => optedItem.eventId !== eventResponse.eventId),
  170. ];
  171. opted.push({
  172. eventId: eventResponse.eventId,
  173. modificationNumber: eventResponse.modificationNumber,
  174. optType: eventResponse.optType,
  175. });
  176. }
  177. await nantum.updateOpted(requestVenId, opted);
  178. return {
  179. responseCode: '200',
  180. responseDescription: 'OK',
  181. venId: clientCertificateFingerprint,
  182. };
  183. }
  184. async function filterOutAcknowledgedEvents(venId, events) {
  185. const opted = (await nantum.fetchOpted(venId)) || [];
  186. return events.filter(
  187. event =>
  188. opted.filter(
  189. optedItem =>
  190. optedItem.eventId === event.eventDescriptor.eventId &&
  191. optedItem.modificationNumber ===
  192. event.eventDescriptor.modificationNumber,
  193. ).length === 0,
  194. );
  195. }
  196. async function pollForEvents(
  197. oadrPoll,
  198. clientCertificateCn,
  199. clientCertificateFingerprint,
  200. ) {
  201. logger.info(
  202. 'pollForEvents',
  203. oadrPoll,
  204. clientCertificateCn,
  205. clientCertificateFingerprint,
  206. );
  207. const requestVenId = oadrPoll.venId;
  208. validateVenId(requestVenId, clientCertificateFingerprint, true);
  209. const event = await nantum.fetchEvent(requestVenId);
  210. const filteredEvents = await filterOutAcknowledgedEvents(
  211. requestVenId,
  212. convertToOadrEvents(event),
  213. );
  214. if (filteredEvents.length > 0) {
  215. return {
  216. responseCode: '200',
  217. responseDescription: 'OK',
  218. responseRequestId: '', // required field, but empty is allowed as per spec
  219. requestId: '',
  220. vtnId: vtnId,
  221. events: filteredEvents,
  222. };
  223. }
  224. return undefined;
  225. }
  226. function validateVenId(requestVenId, clientCertificateFingerprint, required) {
  227. if (requestVenId === clientCertificateFingerprint) {
  228. return;
  229. }
  230. if (!required && requestVenId == null) {
  231. return;
  232. }
  233. if (required && requestVenId == null) {
  234. const error = new Error('VenID is missing');
  235. error.responseCode = 452;
  236. throw error;
  237. }
  238. const error = new Error('VenID is invalid');
  239. error.responseCode = 452;
  240. throw error;
  241. }
  242. module.exports = {
  243. pollForEvents,
  244. retrieveEvents,
  245. updateOptType,
  246. };