@statengine/siamese 0.0.18 → 0.0.19
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/.babelrc +5 -5
- package/.eslintrc +18 -18
- package/LICENSE +674 -674
- package/README.md +18 -18
- package/lib/helpers.js +3 -3
- package/package.json +42 -42
- package/src/helpers.js +49 -49
- package/src/index.js +358 -358
- package/src/priorityDispatchCodes.js +985 -985
- package/test/test.helpers.js +487 -487
- package/test/test.js +114 -114
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 };
|