@statengine/siamese 0.0.19 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -1,358 +1,358 @@
1
- import geohash from 'ngeohash';
2
- import moment from 'moment-timezone';
3
- import proj4 from 'proj4';
4
- import _ from 'lodash';
5
- import { validate } from 'jsonschema';
6
- import { ShiftConfiguration, FirecaresLookup } from '@statengine/shiftly';
7
- import { schemas } from '@statengine/schemas';
8
- import fs from 'fs';
9
- import xml2js from 'xml2js';
10
- import Promise from 'bluebird';
11
- import helpers from './helpers';
12
-
13
- export function transformDate(dateString, timeZone, format) {
14
- return moment.tz(dateString, format, timeZone);
15
- }
16
-
17
- export class BaseNormalizer {
18
- // eslint-disable-next-line class-methods-use-this
19
- static get schema() {
20
- throw new Error('Not implemented.');
21
- }
22
-
23
- transformCoordinates(x, y, toProjection = 'EPSG:4326') {
24
- return proj4(proj4(this.projection), proj4(toProjection), [x, y]);
25
- }
26
-
27
- parseDate(incomingDate) {
28
- // Assume dates are already in ISO-8601, just convert them into to the department's TZ.
29
- return moment(incomingDate).tz(this.timeZone);
30
- }
31
-
32
- static latLongToGeohash(x, y, precision = 12) {
33
- if (_.isNil(x) || _.isNil(y)) {
34
- return null;
35
- }
36
- return geohash.encode(y, x, precision);
37
- }
38
-
39
- // eslint-disable-next-line class-methods-use-this
40
- normalize() {
41
- throw new Error('Not implemented.');
42
- }
43
-
44
- validate() {
45
- return validate(this.normalize(), this.constructor.schema);
46
- }
47
- }
48
-
49
-
50
- export default class IncidentNormalizer extends BaseNormalizer {
51
- constructor(payload, {
52
- timeZone = 'US/Eastern',
53
- projection = 'EPSG:4326',
54
- fdId = null,
55
- firecaresId = null,
56
- name = null,
57
- state = null,
58
- shiftConfig = {},
59
- } = {}) {
60
- super();
61
- this.payload = payload;
62
- this.timeZone = timeZone;
63
- this.projection = projection;
64
- this.fdId = fdId;
65
- this.firecaresId = firecaresId;
66
- this.name = name;
67
- this.state = state;
68
- // First use firecaresId to see if shift configuration exists in shiftly
69
- const shiftlyConfig = IncidentNormalizer.lookupShiftlyConfig(this.firecaresId);
70
- if (shiftlyConfig != null) {
71
- this.shiftConfig = shiftlyConfig;
72
- } else {
73
- this.shiftConfig = new ShiftConfiguration(shiftConfig);
74
- }
75
- }
76
-
77
- // eslint-disable-next-line class-methods-use-this
78
- static get schema() {
79
- return schemas.fire;
80
- }
81
-
82
- // eslint-disable-next-line class-methods-use-this
83
- normalize() {
84
- return _.omitBy({
85
- address: this.normalizeAddress(),
86
- description: this.normalizeDescription(),
87
- apparatus: this.normalizeApparatus(),
88
- fire_department: this.normalizeFireDepartment(),
89
- version: this.constructor.schema.version,
90
- }, _.isNil);
91
- }
92
-
93
- // eslint-disable-next-line class-methods-use-this
94
- normalizeAddress() {
95
- throw new Error('Not implemented.');
96
- }
97
-
98
- // eslint-disable-next-line class-methods-use-this
99
- normalizeDescription() {
100
- throw new Error('Not implemented.');
101
- }
102
-
103
- // eslint-disable-next-line class-methods-use-this
104
- normalizeApparatus() {
105
- throw new Error('Not implemented.');
106
- }
107
-
108
- // eslint-disable-next-line class-methods-use-this
109
- combineEntries() {
110
- throw new Error('Not implemented.');
111
- }
112
-
113
- calculateShift(date) {
114
- return this.shiftConfig.calculateShift(date);
115
- }
116
-
117
- static calculateDuration(startTime, endTime) {
118
- return moment.duration(endTime.diff(startTime)).asSeconds();
119
- }
120
-
121
- static calculateUnitStatusExtendedData(unitStatus) {
122
- const times = {};
123
-
124
- const arr = _.values(unitStatus);
125
-
126
- let clearedProperties = [];
127
-
128
- _.map([
129
- 'available',
130
- 'in_quarters',
131
- 'available_radio',
132
- 'available_mobile',
133
- 'cleared'], key => clearedProperties.push(_.get(unitStatus, `${key}`)));
134
-
135
- clearedProperties = _.filter(clearedProperties, prop => !_.isNil(prop));
136
-
137
- let cleared;
138
- if (clearedProperties.length > 0) {
139
- cleared = _.minBy(clearedProperties, o => moment(o.timestamp).valueOf());
140
- } else {
141
- cleared = _.maxBy(arr, o => moment(o.timestamp).valueOf());
142
- }
143
-
144
- let scene_left;
145
- if (unitStatus.transport_started) {
146
- scene_left = unitStatus.transport_started;
147
- } else {
148
- scene_left = cleared;
149
- }
150
-
151
- let scene_arrived;
152
- if (unitStatus.staging) {
153
- scene_arrived = unitStatus.staging;
154
- } else {
155
- scene_arrived = unitStatus.arrived;
156
- }
157
-
158
- const requirements = {
159
- turnout_duration: [unitStatus.enroute, unitStatus.dispatched],
160
- travel_duration: [scene_arrived, unitStatus.enroute],
161
- staging_duration: [unitStatus.arrived, unitStatus.staging],
162
- response_duration: [scene_arrived, unitStatus.dispatched],
163
- on_scene_duration: [scene_left, scene_arrived],
164
- event_duration: [cleared, unitStatus.dispatched],
165
- on_scene_to_patient_contact: [unitStatus.patient_contact, scene_arrived],
166
- on_scene_to_transport_arrived_duration: [unitStatus.transport_arrived, scene_arrived],
167
- patient_contact_to_transport_arrived_duration: [unitStatus.transport_arrived, unitStatus.patient_contact],
168
- transport_duration: [unitStatus.transport_arrived, unitStatus.transport_started],
169
- at_hospital_duration: [cleared, unitStatus.transport_arrived]
170
- };
171
-
172
- Object.entries(requirements).forEach(([key, value]) => {
173
- if (!_.every(value)) {
174
- return;
175
- }
176
- times[key] = IncidentNormalizer.calculateDuration(moment(value[1].timestamp),
177
- moment(value[0].timestamp));
178
- });
179
- return times;
180
- }
181
-
182
- static calculateDescriptionExtendedData(description) {
183
- const times = {};
184
- const requirements = {
185
- psap_answer_time_to_event_creation: [description.event_opened, description.psap_answer_time],
186
- psap_answer_time_to_first_dispatch: [
187
- description.first_unit_dispatched,
188
- description.psap_answer_time,
189
- ],
190
- psap_answer_time_to_first_arrival: [
191
- description.first_unit_arrived,
192
- description.psap_answer_time,
193
- ],
194
- event_duration: [description.event_closed, description.event_opened],
195
- response_duration: [description.first_unit_arrived, description.first_unit_dispatched],
196
- };
197
-
198
- Object.entries(requirements).forEach(([key, value]) => {
199
- if (!_.every(value)) {
200
- return;
201
- }
202
-
203
- times[key] = IncidentNormalizer.calculateDuration(moment(value[1]),
204
- moment(value[0]));
205
- });
206
-
207
- return times;
208
- }
209
-
210
- // eslint-disable-next-line class-methods-use-this
211
- normalizeFireDepartment() {
212
- return {
213
- fd_id: this.fdId,
214
- firecares_id: this.firecaresId,
215
- name: this.name,
216
- state: this.state,
217
- timezone: this.timeZone,
218
- };
219
- }
220
-
221
- static toJSON(string) {
222
- return new Promise((resolve, reject) => {
223
- try {
224
- const js = JSON.parse(string);
225
- resolve(js || string);
226
- } catch (e) {
227
- try {
228
- const parser = new xml2js.Parser({ explicitArray: false });
229
- parser.parseString(string, (err, result) => resolve(result || string));
230
- } catch (err) {
231
- reject(err);
232
- }
233
- }
234
- });
235
- }
236
-
237
- static fromBytes(buffer, encoding = 'utf8', ...args) {
238
- return this.fromString(buffer.toString(encoding), ...args);
239
- }
240
-
241
- static fromString(string, ...args) {
242
- // eslint-disable-next-line consistent-return
243
- return new Promise((resolve, reject) => {
244
- try {
245
- return this.toJSON(string)
246
- .then(res => resolve(new this(res, ...args)));
247
- } catch (e) {
248
- reject(e);
249
- }
250
- });
251
- }
252
-
253
- static fromStrings(strings, ...args) {
254
- // eslint-disable-next-line consistent-return
255
- return Promise.map(strings, this.toJSON)
256
- .then(jsons => new this(this.combineEntries(jsons), ...args));
257
- }
258
-
259
- static fromFile(file, options = { encoding: 'utf-8', flag: 'r' }, ...args) {
260
- return new Promise((resolve, reject) => {
261
- fs.readFile(file, options, (err, data) => {
262
- if (err) { reject(err); }
263
- return this.fromString(data, ...args)
264
- .then(resolve);
265
- });
266
- });
267
- }
268
-
269
- static fromFiles(files, ...args) {
270
- // eslint-disable-next-line
271
- return Promise.map(files, (file) => {
272
- return new Promise((resolve, reject) => {
273
- fs.readFile(file, 'utf-8', (err, data) => {
274
- if (err) { reject(err); }
275
- resolve(data);
276
- });
277
- });
278
- })
279
- .then(jsons => this.fromStrings(jsons, ...args));
280
- }
281
-
282
- static lookupShiftlyConfig(firecaresId) {
283
- const shiftlyFactoryMethod = FirecaresLookup[firecaresId];
284
- let shiftlyConfig = null;
285
- if (shiftlyFactoryMethod != null) {
286
- shiftlyConfig = shiftlyFactoryMethod();
287
- }
288
- return shiftlyConfig;
289
- }
290
- }
291
-
292
- export class VehicleTelemetryNormalizer extends BaseNormalizer {
293
- constructor(payload, {
294
- timeZone = 'US/Eastern',
295
- projection = 'EPSG:4326',
296
- fdId = null,
297
- firecaresId = null,
298
- name = null,
299
- state = null,
300
- } = {}) {
301
- super();
302
- this.payload = payload;
303
- this.timeZone = timeZone;
304
- this.projection = projection;
305
- this.fdId = fdId;
306
- this.firecaresId = firecaresId;
307
- this.name = name;
308
- this.state = state;
309
- }
310
-
311
- // eslint-disable-next-line class-methods-use-this
312
- static get schema() {
313
- return schemas.vehicleTelemetry;
314
- }
315
-
316
- normalize() {
317
- const incident = this.normalizeIncident();
318
- const response = {
319
- version: this.constructor.schema.version,
320
- fire_department: this.normalizeFireDepartment(),
321
- apparatus: this.normalizeApparatus(),
322
- telemetry: this.normalizeTelemetry(),
323
- };
324
-
325
- if (!_.isEmpty(incident.incident_number)) {
326
- response.incident = incident;
327
- }
328
-
329
- return response;
330
- }
331
-
332
- normalizeFireDepartment() {
333
- return {
334
- fd_id: this.fdId,
335
- firecares_id: this.firecaresId,
336
- name: this.name,
337
- state: this.state,
338
- timezone: this.timeZone,
339
- };
340
- }
341
-
342
- // eslint-disable-next-line class-methods-use-this
343
- normalizeApparatus() {
344
- throw new Error('Not implemented.');
345
- }
346
-
347
- // eslint-disable-next-line class-methods-use-this
348
- normalizeIncident() {
349
- throw new Error('Not implemented.');
350
- }
351
-
352
- // eslint-disable-next-line class-methods-use-this
353
- normalizeTelemetry() {
354
- throw new Error('Not implemented.');
355
- }
356
- }
357
-
358
- export { helpers };
1
+ import geohash from 'ngeohash';
2
+ import moment from 'moment-timezone';
3
+ import proj4 from 'proj4';
4
+ import _ from 'lodash';
5
+ import { validate } from 'jsonschema';
6
+ import { ShiftConfiguration, FirecaresLookup } from '@statengine/shiftly';
7
+ import { schemas } from '@statengine/schemas';
8
+ import fs from 'fs';
9
+ import xml2js from 'xml2js';
10
+ import Promise from 'bluebird';
11
+ import helpers from './helpers';
12
+
13
+ export function transformDate(dateString, timeZone, format) {
14
+ return moment.tz(dateString, format, timeZone);
15
+ }
16
+
17
+ export class BaseNormalizer {
18
+ // eslint-disable-next-line class-methods-use-this
19
+ static get schema() {
20
+ throw new Error('Not implemented.');
21
+ }
22
+
23
+ transformCoordinates(x, y, toProjection = 'EPSG:4326') {
24
+ return proj4(proj4(this.projection), proj4(toProjection), [x, y]);
25
+ }
26
+
27
+ parseDate(incomingDate) {
28
+ // Assume dates are already in ISO-8601, just convert them into to the department's TZ.
29
+ return moment(incomingDate).tz(this.timeZone);
30
+ }
31
+
32
+ static latLongToGeohash(x, y, precision = 12) {
33
+ if (_.isNil(x) || _.isNil(y)) {
34
+ return null;
35
+ }
36
+ return geohash.encode(y, x, precision);
37
+ }
38
+
39
+ // eslint-disable-next-line class-methods-use-this
40
+ normalize() {
41
+ throw new Error('Not implemented.');
42
+ }
43
+
44
+ validate() {
45
+ return validate(this.normalize(), this.constructor.schema);
46
+ }
47
+ }
48
+
49
+
50
+ export default class IncidentNormalizer extends BaseNormalizer {
51
+ constructor(payload, {
52
+ timeZone = 'US/Eastern',
53
+ projection = 'EPSG:4326',
54
+ fdId = null,
55
+ firecaresId = null,
56
+ name = null,
57
+ state = null,
58
+ shiftConfig = {},
59
+ } = {}) {
60
+ super();
61
+ this.payload = payload;
62
+ this.timeZone = timeZone;
63
+ this.projection = projection;
64
+ this.fdId = fdId;
65
+ this.firecaresId = firecaresId;
66
+ this.name = name;
67
+ this.state = state;
68
+ // First use firecaresId to see if shift configuration exists in shiftly
69
+ const shiftlyConfig = IncidentNormalizer.lookupShiftlyConfig(this.firecaresId);
70
+ if (shiftlyConfig != null) {
71
+ this.shiftConfig = shiftlyConfig;
72
+ } else {
73
+ this.shiftConfig = new ShiftConfiguration(shiftConfig);
74
+ }
75
+ }
76
+
77
+ // eslint-disable-next-line class-methods-use-this
78
+ static get schema() {
79
+ return schemas.fire;
80
+ }
81
+
82
+ // eslint-disable-next-line class-methods-use-this
83
+ normalize() {
84
+ return _.omitBy({
85
+ address: this.normalizeAddress(),
86
+ description: this.normalizeDescription(),
87
+ apparatus: this.normalizeApparatus(),
88
+ fire_department: this.normalizeFireDepartment(),
89
+ version: this.constructor.schema.version,
90
+ }, _.isNil);
91
+ }
92
+
93
+ // eslint-disable-next-line class-methods-use-this
94
+ normalizeAddress() {
95
+ throw new Error('Not implemented.');
96
+ }
97
+
98
+ // eslint-disable-next-line class-methods-use-this
99
+ normalizeDescription() {
100
+ throw new Error('Not implemented.');
101
+ }
102
+
103
+ // eslint-disable-next-line class-methods-use-this
104
+ normalizeApparatus() {
105
+ throw new Error('Not implemented.');
106
+ }
107
+
108
+ // eslint-disable-next-line class-methods-use-this
109
+ combineEntries() {
110
+ throw new Error('Not implemented.');
111
+ }
112
+
113
+ calculateShift(date) {
114
+ return this.shiftConfig.calculateShift(date);
115
+ }
116
+
117
+ static calculateDuration(startTime, endTime) {
118
+ return moment.duration(endTime.diff(startTime)).asSeconds();
119
+ }
120
+
121
+ static calculateUnitStatusExtendedData(unitStatus) {
122
+ const times = {};
123
+
124
+ const arr = _.values(unitStatus);
125
+
126
+ let clearedProperties = [];
127
+
128
+ _.map([
129
+ 'available',
130
+ 'in_quarters',
131
+ 'available_radio',
132
+ 'available_mobile',
133
+ 'cleared'], key => clearedProperties.push(_.get(unitStatus, `${key}`)));
134
+
135
+ clearedProperties = _.filter(clearedProperties, prop => !_.isNil(prop));
136
+
137
+ let cleared;
138
+ if (clearedProperties.length > 0) {
139
+ cleared = _.minBy(clearedProperties, o => moment(o.timestamp).valueOf());
140
+ } else {
141
+ cleared = _.maxBy(arr, o => moment(o.timestamp).valueOf());
142
+ }
143
+
144
+ let scene_left;
145
+ if (unitStatus.transport_started) {
146
+ scene_left = unitStatus.transport_started;
147
+ } else {
148
+ scene_left = cleared;
149
+ }
150
+
151
+ let scene_arrived;
152
+ if (unitStatus.staging) {
153
+ scene_arrived = unitStatus.staging;
154
+ } else {
155
+ scene_arrived = unitStatus.arrived;
156
+ }
157
+
158
+ const requirements = {
159
+ turnout_duration: [unitStatus.enroute, unitStatus.dispatched],
160
+ travel_duration: [scene_arrived, unitStatus.enroute],
161
+ staging_duration: [unitStatus.arrived, unitStatus.staging],
162
+ response_duration: [scene_arrived, unitStatus.dispatched],
163
+ on_scene_duration: [scene_left, scene_arrived],
164
+ event_duration: [cleared, unitStatus.dispatched],
165
+ on_scene_to_patient_contact: [unitStatus.patient_contact, scene_arrived],
166
+ on_scene_to_transport_arrived_duration: [unitStatus.transport_arrived, scene_arrived],
167
+ patient_contact_to_transport_arrived_duration: [unitStatus.transport_arrived, unitStatus.patient_contact],
168
+ transport_duration: [unitStatus.transport_arrived, unitStatus.transport_started],
169
+ at_hospital_duration: [cleared, unitStatus.transport_arrived]
170
+ };
171
+
172
+ Object.entries(requirements).forEach(([key, value]) => {
173
+ if (!_.every(value)) {
174
+ return;
175
+ }
176
+ times[key] = IncidentNormalizer.calculateDuration(moment(value[1].timestamp),
177
+ moment(value[0].timestamp));
178
+ });
179
+ return times;
180
+ }
181
+
182
+ static calculateDescriptionExtendedData(description) {
183
+ const times = {};
184
+ const requirements = {
185
+ psap_answer_time_to_event_creation: [description.event_opened, description.psap_answer_time],
186
+ psap_answer_time_to_first_dispatch: [
187
+ description.first_unit_dispatched,
188
+ description.psap_answer_time,
189
+ ],
190
+ psap_answer_time_to_first_arrival: [
191
+ description.first_unit_arrived,
192
+ description.psap_answer_time,
193
+ ],
194
+ event_duration: [description.event_closed, description.event_opened],
195
+ response_duration: [description.first_unit_arrived, description.first_unit_dispatched],
196
+ };
197
+
198
+ Object.entries(requirements).forEach(([key, value]) => {
199
+ if (!_.every(value)) {
200
+ return;
201
+ }
202
+
203
+ times[key] = IncidentNormalizer.calculateDuration(moment(value[1]),
204
+ moment(value[0]));
205
+ });
206
+
207
+ return times;
208
+ }
209
+
210
+ // eslint-disable-next-line class-methods-use-this
211
+ normalizeFireDepartment() {
212
+ return {
213
+ fd_id: this.fdId,
214
+ firecares_id: this.firecaresId,
215
+ name: this.name,
216
+ state: this.state,
217
+ timezone: this.timeZone,
218
+ };
219
+ }
220
+
221
+ static toJSON(string) {
222
+ return new Promise((resolve, reject) => {
223
+ try {
224
+ const js = JSON.parse(string);
225
+ resolve(js || string);
226
+ } catch (e) {
227
+ try {
228
+ const parser = new xml2js.Parser({ explicitArray: false });
229
+ parser.parseString(string, (err, result) => resolve(result || string));
230
+ } catch (err) {
231
+ reject(err);
232
+ }
233
+ }
234
+ });
235
+ }
236
+
237
+ static fromBytes(buffer, encoding = 'utf8', ...args) {
238
+ return this.fromString(buffer.toString(encoding), ...args);
239
+ }
240
+
241
+ static fromString(string, ...args) {
242
+ // eslint-disable-next-line consistent-return
243
+ return new Promise((resolve, reject) => {
244
+ try {
245
+ return this.toJSON(string)
246
+ .then(res => resolve(new this(res, ...args)));
247
+ } catch (e) {
248
+ reject(e);
249
+ }
250
+ });
251
+ }
252
+
253
+ static fromStrings(strings, ...args) {
254
+ // eslint-disable-next-line consistent-return
255
+ return Promise.map(strings, this.toJSON)
256
+ .then(jsons => new this(this.combineEntries(jsons), ...args));
257
+ }
258
+
259
+ static fromFile(file, options = { encoding: 'utf-8', flag: 'r' }, ...args) {
260
+ return new Promise((resolve, reject) => {
261
+ fs.readFile(file, options, (err, data) => {
262
+ if (err) { reject(err); }
263
+ return this.fromString(data, ...args)
264
+ .then(resolve);
265
+ });
266
+ });
267
+ }
268
+
269
+ static fromFiles(files, ...args) {
270
+ // eslint-disable-next-line
271
+ return Promise.map(files, (file) => {
272
+ return new Promise((resolve, reject) => {
273
+ fs.readFile(file, 'utf-8', (err, data) => {
274
+ if (err) { reject(err); }
275
+ resolve(data);
276
+ });
277
+ });
278
+ })
279
+ .then(jsons => this.fromStrings(jsons, ...args));
280
+ }
281
+
282
+ static lookupShiftlyConfig(firecaresId) {
283
+ const shiftlyFactoryMethod = FirecaresLookup[firecaresId];
284
+ let shiftlyConfig = null;
285
+ if (shiftlyFactoryMethod != null) {
286
+ shiftlyConfig = shiftlyFactoryMethod();
287
+ }
288
+ return shiftlyConfig;
289
+ }
290
+ }
291
+
292
+ export class VehicleTelemetryNormalizer extends BaseNormalizer {
293
+ constructor(payload, {
294
+ timeZone = 'US/Eastern',
295
+ projection = 'EPSG:4326',
296
+ fdId = null,
297
+ firecaresId = null,
298
+ name = null,
299
+ state = null,
300
+ } = {}) {
301
+ super();
302
+ this.payload = payload;
303
+ this.timeZone = timeZone;
304
+ this.projection = projection;
305
+ this.fdId = fdId;
306
+ this.firecaresId = firecaresId;
307
+ this.name = name;
308
+ this.state = state;
309
+ }
310
+
311
+ // eslint-disable-next-line class-methods-use-this
312
+ static get schema() {
313
+ return schemas.vehicleTelemetry;
314
+ }
315
+
316
+ normalize() {
317
+ const incident = this.normalizeIncident();
318
+ const response = {
319
+ version: this.constructor.schema.version,
320
+ fire_department: this.normalizeFireDepartment(),
321
+ apparatus: this.normalizeApparatus(),
322
+ telemetry: this.normalizeTelemetry(),
323
+ };
324
+
325
+ if (!_.isEmpty(incident.incident_number)) {
326
+ response.incident = incident;
327
+ }
328
+
329
+ return response;
330
+ }
331
+
332
+ normalizeFireDepartment() {
333
+ return {
334
+ fd_id: this.fdId,
335
+ firecares_id: this.firecaresId,
336
+ name: this.name,
337
+ state: this.state,
338
+ timezone: this.timeZone,
339
+ };
340
+ }
341
+
342
+ // eslint-disable-next-line class-methods-use-this
343
+ normalizeApparatus() {
344
+ throw new Error('Not implemented.');
345
+ }
346
+
347
+ // eslint-disable-next-line class-methods-use-this
348
+ normalizeIncident() {
349
+ throw new Error('Not implemented.');
350
+ }
351
+
352
+ // eslint-disable-next-line class-methods-use-this
353
+ normalizeTelemetry() {
354
+ throw new Error('Not implemented.');
355
+ }
356
+ }
357
+
358
+ export { helpers };