fleetmap-reports 2.0.358 → 2.0.359
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/package.json +1 -1
- package/src/partnerReports/driverranking-report.js +39 -7
- package/.claude/worktrees/eager-stonebraker-c08ac7/.eslintrc.js +0 -15
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/enGB.js +0 -309
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/esCL.js +0 -297
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/frFR.json +0 -324
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/index.js +0 -35
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/ptBR.js +0 -301
- package/.claude/worktrees/eager-stonebraker-c08ac7/lang/ptPT.js +0 -310
- package/.claude/worktrees/eager-stonebraker-c08ac7/package-lock.json +0 -11944
- package/.claude/worktrees/eager-stonebraker-c08ac7/package.json +0 -45
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/activity-report.js +0 -822
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/automaticReports.js +0 -34
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/carbone/index.js +0 -13
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/custom/vistawasteActivity-report.js +0 -544
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/events-report.js +0 -360
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/fuel-consumption-report.js +0 -296
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/fuel-drop-report.js +0 -128
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/here.js +0 -172
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/idle-report.js +0 -403
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/index.js +0 -244
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/kms-report.js +0 -549
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/location-report.js +0 -496
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/machines-report.js +0 -278
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/afriquia.js +0 -129
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/dailyuse-report.js +0 -80
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/delayedstart-report.js +0 -223
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/driverranking-report.js +0 -328
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/gpsjump-report.js +0 -61
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/partnerReports/performance-report.js +0 -359
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/refueling-report.js +0 -345
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/reportStyle.js +0 -34
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/sensor-report.js +0 -447
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/speeding-report.js +0 -663
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/stop-report.js +0 -242
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/ActivityReportTemplate.mjml +0 -51
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/EmptyReportTemplate.mjml +0 -25
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/EventsReportTemplate.mjml +0 -75
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/FuelDropReportTemplate.mjml +0 -60
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/IdleReportTemplate.mjml +0 -54
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/KmsReportGroupByDayTemplate.mjml +0 -43
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/KmsReportTemplate.mjml +0 -44
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/LocationReportTemplate.mjml +0 -37
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/RefuelingReportTemplate.mjml +0 -60
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/SensorReportTemplate.mjml +0 -23
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/SpeedingReportTemplate.mjml +0 -80
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/TripReportTemplate.mjml +0 -97
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/ZoneReportTemplate.mjml +0 -52
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/templates/report.mjml +0 -96
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/activity.test.js +0 -99
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/carbone.test.js +0 -20
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/events.test.js +0 -30
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/fuel.test.js +0 -35
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/idle.test.js +0 -16
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/index.js +0 -24
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/index.test.js +0 -203
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/kms.test.js +0 -74
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/performance.test.js +0 -36
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/positions.test.js +0 -67
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/speeding.test.js +0 -120
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/translations.test.js +0 -21
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/trips.test.js +0 -140
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/tests/zones.test.js +0 -162
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/trip-report.js +0 -641
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/device.js +0 -13
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/driver.js +0 -90
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/fuel.js +0 -102
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/geofence.js +0 -52
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/odoo.js +0 -18
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/parallel.js +0 -64
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/pdfDocument.js +0 -140
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/route.js +0 -78
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/timetable.js +0 -30
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/traccar.js +0 -213
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/traccar.worker.js +0 -49
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/trips.js +0 -270
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/utils.js +0 -358
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/worker.js +0 -14
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/util/xpert.js +0 -58
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/word/index.js +0 -95
- package/.claude/worktrees/eager-stonebraker-c08ac7/src/zone-report.js +0 -904
|
@@ -1,663 +0,0 @@
|
|
|
1
|
-
const jsPDF = require('jspdf')
|
|
2
|
-
const {
|
|
3
|
-
convertMS, convertToLocaleString, convertToFeature, convertPositionToFeature, getTranslations, getLanguage,
|
|
4
|
-
getCountry,
|
|
5
|
-
getServerHost
|
|
6
|
-
} = require('./util/utils')
|
|
7
|
-
const { headerFromUser, AmiriRegular } = require('./util/pdfDocument')
|
|
8
|
-
require('jspdf-autotable')
|
|
9
|
-
const { getStyle } = require('./reportStyle')
|
|
10
|
-
const drivers = require('./util/driver')
|
|
11
|
-
const {
|
|
12
|
-
getUserPartner
|
|
13
|
-
} = require('fleetmap-partners')
|
|
14
|
-
const traccarHelper = require('./util/traccar')
|
|
15
|
-
const { devicesToProcess } = require('./util/device')
|
|
16
|
-
const { point } = require('@turf/helpers')
|
|
17
|
-
const distance = require('@turf/distance')
|
|
18
|
-
const { insideGeofence } = require('./util/geofence')
|
|
19
|
-
const booleanPointInPolygon = require('@turf/boolean-point-in-polygon')
|
|
20
|
-
const automaticReports = require('./automaticReports')
|
|
21
|
-
const { default: axios } = require('axios')
|
|
22
|
-
const { getDriverName, getDriverData } = require('./util/driver')
|
|
23
|
-
|
|
24
|
-
const fileName = 'SpeedingReport'
|
|
25
|
-
const eventTypes = ['deviceOverspeed']
|
|
26
|
-
|
|
27
|
-
async function createSpeedingReport (from, to, userData, traccarInstance) {
|
|
28
|
-
const reportData = []
|
|
29
|
-
|
|
30
|
-
if (userData.byDriver) {
|
|
31
|
-
console.log('ByDriver')
|
|
32
|
-
const allData = await createSpeedingReportByDriver(from, to, userData, traccarInstance)
|
|
33
|
-
reportData.push(allData)
|
|
34
|
-
} else if (userData.byGroup) {
|
|
35
|
-
console.log('ByGroup')
|
|
36
|
-
const allData = await createSpeedingReportByGroup(from, to, userData, traccarInstance)
|
|
37
|
-
reportData.push(...allData)
|
|
38
|
-
} else {
|
|
39
|
-
console.log('createSpeedingReportByDevice')
|
|
40
|
-
const allData = await createSpeedingReportByDevice(from, to, userData, traccarInstance)
|
|
41
|
-
reportData.push(allData)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return reportData
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function createSpeedingReportByGroup (from, to, userData, traccarInstance) {
|
|
48
|
-
const reportData = []
|
|
49
|
-
for (const g of userData.groups) {
|
|
50
|
-
const devices = userData.devices.filter(d => d.groupId === g.id)
|
|
51
|
-
console.log(g.name + ' devices:' + devices.length)
|
|
52
|
-
if (devices.length > 0) {
|
|
53
|
-
const groupData = {
|
|
54
|
-
devices: [],
|
|
55
|
-
group: g,
|
|
56
|
-
xpert: devices.filter(d => d.attributes.xpert).length > 0
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const events = await traccarHelper.getEvents(traccarInstance, from, to, devices, eventTypes)
|
|
60
|
-
const routes = await traccarHelper.getRoute(traccarInstance, from, to, devices)
|
|
61
|
-
|
|
62
|
-
devices.sort((a, b) => (a.name > b.name) ? 1 : -1)
|
|
63
|
-
|
|
64
|
-
if (events.length > 0) {
|
|
65
|
-
groupData.devices = processDevices(from, to, devices, events, routes, userData)
|
|
66
|
-
reportData.push(groupData)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const withoutGroupData = await createSpeedingReportByDevice(from, to, userData, traccarInstance)
|
|
72
|
-
reportData.push(withoutGroupData)
|
|
73
|
-
|
|
74
|
-
return reportData
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function processServerSide () {
|
|
78
|
-
return (process.env.PROCESS_HERE_EVENTS && process.env.PROCESS_HERE_EVENTS === 'server')
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function getOverspeedEvents (from, to, userData, devices, allData, traccarInstance) {
|
|
82
|
-
const _sliced = automaticReports.sliceArray(automaticReports.sliceArray(devices, 5), 5)
|
|
83
|
-
let deviceCount = 0
|
|
84
|
-
const overspeedEvents = []
|
|
85
|
-
for (const sliced of _sliced) {
|
|
86
|
-
await Promise.all(sliced.map(async slice => {
|
|
87
|
-
overspeedEvents.push(...await getEvents(traccarInstance, from, to, slice, userData, deviceCount, devices.length, slice.length))
|
|
88
|
-
deviceCount = deviceCount + slice.length
|
|
89
|
-
if (processServerSide()) {
|
|
90
|
-
console.log('LOADING_MESSAGE:' + slice[0].name)
|
|
91
|
-
console.log(`PROGRESS_PERC:${deviceCount / devices.length * 100}`)
|
|
92
|
-
}
|
|
93
|
-
}))
|
|
94
|
-
}
|
|
95
|
-
return overspeedEvents
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async function createSpeedingReportByDevice (from, to, userData, traccarInstance) {
|
|
99
|
-
const devices = devicesToProcess(userData)
|
|
100
|
-
|
|
101
|
-
const allData = {
|
|
102
|
-
devices: [],
|
|
103
|
-
xpert: devices.filter(d => d && d.attributes && d.attributes.xpert).length > 0
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
allData.devices = await getOverspeedEvents(from, to, userData, devices, allData, traccarInstance)
|
|
107
|
-
|
|
108
|
-
return allData
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async function createSpeedingReportByDriver (from, to, userData, traccarInstance) {
|
|
112
|
-
const { devices } = await drivers.devicesByDriver(traccarInstance, from, to, userData.drivers, userData.devices)
|
|
113
|
-
console.log('Devices with driver', devices.length)
|
|
114
|
-
const _drivers = []
|
|
115
|
-
const sliced = automaticReports.sliceArray(devices, 5)
|
|
116
|
-
let deviceCount = 0
|
|
117
|
-
for (const slice of sliced) {
|
|
118
|
-
const eventsByDevice = await getEvents(traccarInstance, from, to, slice, userData, deviceCount, devices.length, slice.length)
|
|
119
|
-
if (processServerSide()) {
|
|
120
|
-
console.log('LOADING_MESSAGE:' + slice[0].name)
|
|
121
|
-
console.log(`PROGRESS_PERC:${deviceCount / devices.length * 100}`)
|
|
122
|
-
}
|
|
123
|
-
if (eventsByDevice && eventsByDevice.length > 0) {
|
|
124
|
-
_drivers.push(...(await processDrivers(from, to, eventsByDevice, userData)))
|
|
125
|
-
}
|
|
126
|
-
deviceCount = deviceCount + slice.length
|
|
127
|
-
}
|
|
128
|
-
return { drivers: _drivers }
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async function getEvents (traccarInstance, from, to, devices, userData, deviceCount, totalDevices, sliceSize) {
|
|
132
|
-
if (processServerSide() && !userData.useVehicleSpeedLimit) {
|
|
133
|
-
const cookie = await axios.get('/pinmeapi/cookie/get', { withCredentials: true }).then(d => d.data)
|
|
134
|
-
const url = `https://${getServerHost()}/pinmeapi/reports/speeding-report/getEvents`
|
|
135
|
-
const maxAttempts = 3
|
|
136
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
137
|
-
try {
|
|
138
|
-
return await axios.post(url, { from, to, devices, userData, deviceCount, totalDevices, sliceSize, cookie }, { withCredentials: true, timeout: 60000 }).then(d => d.data)
|
|
139
|
-
} catch (e) {
|
|
140
|
-
console.log(`getEvents failed for devices [${devices.map(d => d.name).join(', ')}] (attempt ${attempt}/${maxAttempts}): ${e.message}`)
|
|
141
|
-
if (attempt === maxAttempts) {
|
|
142
|
-
return []
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
} else {
|
|
147
|
-
const geofencesFeatures = userData.geofences.filter(g => g.area && g.area.startsWith('POLYGON') &&
|
|
148
|
-
g.attributes.speedLimit).map(g => convertToFeature(g))
|
|
149
|
-
|
|
150
|
-
const allInOne = await traccarHelper.getAllInOne(traccarInstance, from, to, devices, true, false,
|
|
151
|
-
false, false, deviceCount, totalDevices, sliceSize)
|
|
152
|
-
const routes = allInOne.route
|
|
153
|
-
|
|
154
|
-
const events = []
|
|
155
|
-
if (userData.roadSpeedLimits) {
|
|
156
|
-
const hereResults = await getRoadSpeedLimits(devices, routes, userData.maxSpeedThreshold, userData.minimumIdleMinutes)
|
|
157
|
-
console.log('got', hereResults.length, 'from here')
|
|
158
|
-
events.push(...hereResults)
|
|
159
|
-
} else if (!userData.useVehicleSpeedLimit && userData.customSpeed) {
|
|
160
|
-
events.push(...await getCustomSpeedLimitEvents(devices, routes, userData.customSpeed))
|
|
161
|
-
} else {
|
|
162
|
-
const traccarEvents = await traccarHelper.getEvents(traccarInstance, from, to, devices, eventTypes)
|
|
163
|
-
if (geofencesFeatures.length) {
|
|
164
|
-
events.push(...getGeofenceSpeedLimitEvents(geofencesFeatures, routes, devices))
|
|
165
|
-
events.push(...traccarEvents.filter(e => !(e.geofenceId)))
|
|
166
|
-
} else {
|
|
167
|
-
events.push(...traccarEvents)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
if (events.length > 0) {
|
|
171
|
-
return processDevices(from, to, devices, events, routes, userData)
|
|
172
|
-
}
|
|
173
|
-
return []
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function getGeofenceSpeedLimitEvents (geofencesFeatures, routes, devices) {
|
|
178
|
-
const events = []
|
|
179
|
-
|
|
180
|
-
devices.forEach(d => {
|
|
181
|
-
const deviceRoute = routes.filter(p => p.deviceId === d.id)
|
|
182
|
-
const routePoints = deviceRoute.map(p => convertPositionToFeature(p))
|
|
183
|
-
geofencesFeatures.forEach(g => {
|
|
184
|
-
let eventIsActive = false
|
|
185
|
-
routePoints.forEach(p => {
|
|
186
|
-
if (booleanPointInPolygon.default(p, g) && p.properties.position.speed > g.properties.geofence.attributes.speedLimit) {
|
|
187
|
-
if (!eventIsActive) {
|
|
188
|
-
const event = {
|
|
189
|
-
positionId: p.properties.position.id,
|
|
190
|
-
deviceId: d.id,
|
|
191
|
-
geofenceId: g.properties.geofence.id,
|
|
192
|
-
attributes: {
|
|
193
|
-
speedLimit: g.properties.geofence.attributes.speedLimit,
|
|
194
|
-
speed: p.properties.position.speed
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
events.push(event)
|
|
198
|
-
}
|
|
199
|
-
eventIsActive = true
|
|
200
|
-
} else {
|
|
201
|
-
eventIsActive = false
|
|
202
|
-
}
|
|
203
|
-
})
|
|
204
|
-
})
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
return events
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
async function processDrivers (from, to, eventsByDevice, userData) {
|
|
211
|
-
const driversResult = []
|
|
212
|
-
|
|
213
|
-
for (const d of userData.drivers) {
|
|
214
|
-
const driverEvents = (await getDriverData(d, { events: eventsByDevice.map(d => d.alerts).flat() }, userData)).events
|
|
215
|
-
if (driverEvents.length > 0) {
|
|
216
|
-
driverEvents.sort((a, b) => a.positionId - b.positionId)
|
|
217
|
-
driversResult.push({
|
|
218
|
-
driver: d,
|
|
219
|
-
from,
|
|
220
|
-
to,
|
|
221
|
-
alerts: driverEvents,
|
|
222
|
-
maxSpeed: driverEvents.reduce((a, b) => {
|
|
223
|
-
return a > b.attributes.maxSpeed ? a : b.attributes.maxSpeed
|
|
224
|
-
}, 0)
|
|
225
|
-
})
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return driversResult
|
|
230
|
-
}
|
|
231
|
-
const MAX_DEVICE_SPEED = 160
|
|
232
|
-
|
|
233
|
-
function processDevices (from, to, devices, events, routes, userData) {
|
|
234
|
-
const devicesResult = []
|
|
235
|
-
findEventsPosition(from, to, devices, events, routes, userData)
|
|
236
|
-
|
|
237
|
-
for (const d of devices) {
|
|
238
|
-
const deviceEvents = events.filter(e => e.deviceId === d.id && e.position).filter(e => e.position.speed < MAX_DEVICE_SPEED)
|
|
239
|
-
if (deviceEvents.length > 0) {
|
|
240
|
-
deviceEvents.sort((a, b) => a.positionId - b.positionId)
|
|
241
|
-
devicesResult.push({
|
|
242
|
-
device: d,
|
|
243
|
-
from,
|
|
244
|
-
to,
|
|
245
|
-
alerts: deviceEvents,
|
|
246
|
-
maxSpeed: deviceEvents.reduce((a, b) => {
|
|
247
|
-
return a > (b && b.attributes && b.attributes.maxSpeed) ? a : (b && b.attributes && b.attributes.maxSpeed)
|
|
248
|
-
}, 0)
|
|
249
|
-
})
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return devicesResult
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function findEventsPosition (from, to, devices, events, routes, userData) {
|
|
257
|
-
for (const d of devices) {
|
|
258
|
-
const deviceEvents = events.filter(e => e.deviceId === d.id && e.positionId)
|
|
259
|
-
if (deviceEvents.length > 0) {
|
|
260
|
-
const positions = routes.filter(p => p.deviceId === d.id)
|
|
261
|
-
for (const a of deviceEvents) {
|
|
262
|
-
const pIndex = positions.findIndex(p => p.id === a.positionId)
|
|
263
|
-
if (pIndex >= 0) {
|
|
264
|
-
let geofence = null
|
|
265
|
-
if (a.geofenceId) {
|
|
266
|
-
geofence = userData.geofences.find(g => g.id === a.geofenceId)
|
|
267
|
-
if (geofence) {
|
|
268
|
-
a.geofenceName = geofence.name + ' (' + Math.round(a.attributes.speedLimit * 1.85200) + ' Km/h)'
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (!userData.roadSpeedLimits) {
|
|
273
|
-
a.position = positions[pIndex]
|
|
274
|
-
calculateEventData(positions, pIndex, a, d, geofence, userData)
|
|
275
|
-
}
|
|
276
|
-
if (a.position.attributes.driverUniqueId) {
|
|
277
|
-
a.driver = getDriverName(a.position.attributes.driverUniqueId, a.position.fixTime, userData)
|
|
278
|
-
}
|
|
279
|
-
a.deviceName = d.name
|
|
280
|
-
} else {
|
|
281
|
-
console.warn('cant find position for event', a)
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
async function getCustomSpeedLimitEvents (devices, routes, customSpeed) {
|
|
289
|
-
console.log('custom speed limit events')
|
|
290
|
-
const events = []
|
|
291
|
-
const maxSpeed = customSpeed / 1.85200
|
|
292
|
-
|
|
293
|
-
for (const d of devices) {
|
|
294
|
-
const positions = routes.filter(p => p.deviceId === d.id)
|
|
295
|
-
let eventIsActive = false
|
|
296
|
-
|
|
297
|
-
for (const position of positions) {
|
|
298
|
-
if (position.speed > maxSpeed && !eventIsActive) {
|
|
299
|
-
const event = {
|
|
300
|
-
positionId: position && position.id,
|
|
301
|
-
position,
|
|
302
|
-
deviceId: d.id,
|
|
303
|
-
attributes: {
|
|
304
|
-
speedLimit: maxSpeed,
|
|
305
|
-
speed: position.speed
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
events.push(event)
|
|
309
|
-
eventIsActive = true
|
|
310
|
-
} else if (position.speed <= maxSpeed) {
|
|
311
|
-
eventIsActive = false
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return events
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
async function getRoadSpeedLimits (devices, routes, threshold, minimumMinutes = 0) {
|
|
320
|
-
const position = routes.find(p => getCountry(p))
|
|
321
|
-
const country = position && getCountry(position)
|
|
322
|
-
return getOSMSpeedingEvents(devices, routes, threshold, minimumMinutes, country)
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
exports.getRoadSpeedLimits = getRoadSpeedLimits
|
|
326
|
-
|
|
327
|
-
let countSuccess = 0
|
|
328
|
-
let countError = 0
|
|
329
|
-
let lastSuccessDuration
|
|
330
|
-
async function invokeValhalla (route, i, chunk, country, threshold, results, retry = 3) {
|
|
331
|
-
const slice = route.slice(i, i + chunk)
|
|
332
|
-
const url = `http://valhalla-${country}.pinme.io:8002/trace_attributes`
|
|
333
|
-
try {
|
|
334
|
-
const now = new Date()
|
|
335
|
-
const {
|
|
336
|
-
// eslint-disable-next-line camelcase
|
|
337
|
-
matched_points,
|
|
338
|
-
edges
|
|
339
|
-
} = await axios.post(url,
|
|
340
|
-
{
|
|
341
|
-
costing: 'auto',
|
|
342
|
-
shape_match: 'map_snap',
|
|
343
|
-
filters: {
|
|
344
|
-
attributes: [
|
|
345
|
-
'admin.country_code',
|
|
346
|
-
'admin.country_text',
|
|
347
|
-
'admin.state_code',
|
|
348
|
-
'admin.state_text',
|
|
349
|
-
'edge.names',
|
|
350
|
-
'edge.way_id',
|
|
351
|
-
'edge.speed_limit',
|
|
352
|
-
'matched.point',
|
|
353
|
-
'matched.type',
|
|
354
|
-
'matched.edge_index'
|
|
355
|
-
],
|
|
356
|
-
action: 'include'
|
|
357
|
-
},
|
|
358
|
-
shape: slice.map(p => ({
|
|
359
|
-
lon: p.longitude,
|
|
360
|
-
lat: p.latitude
|
|
361
|
-
}))
|
|
362
|
-
}, { timeout: 50000 })
|
|
363
|
-
.then(r => r.data)
|
|
364
|
-
countSuccess++
|
|
365
|
-
lastSuccessDuration = new Date() - now
|
|
366
|
-
// eslint-disable-next-line camelcase
|
|
367
|
-
matched_points.forEach((mp, mIndex) => {
|
|
368
|
-
const edge = edges[mp.edge_index]
|
|
369
|
-
const position = route[mIndex + i]
|
|
370
|
-
if (edge && (edge.speed_limit + (threshold || 0)) < position.speed * 1.852) {
|
|
371
|
-
results.push({ ...mp, ...edge, ...position, position })
|
|
372
|
-
}
|
|
373
|
-
})
|
|
374
|
-
} catch (e) {
|
|
375
|
-
countError++
|
|
376
|
-
if (e.response && e.response.data && e.response.data.error_code === 444) {
|
|
377
|
-
console.warn(e.response.data.error)
|
|
378
|
-
return
|
|
379
|
-
}
|
|
380
|
-
if (--retry) {
|
|
381
|
-
console.warn('retry', retry, e.message, (e.response && e.response.data) || url, 'deviceId',
|
|
382
|
-
slice[0] && slice[0].deviceId, slice[0] && slice[0].address, slice[0] && slice[0].fixTime, country, 'chunk', chunk,
|
|
383
|
-
'success', countSuccess, 'error', countError, lastSuccessDuration)
|
|
384
|
-
return invokeValhalla(route, i, chunk, country, threshold, results, retry)
|
|
385
|
-
} else {
|
|
386
|
-
console.error(e.message, (e.response && e.response.data) || url, 'deviceId',
|
|
387
|
-
slice[0] && slice[0].deviceId, slice[0] && slice[0].address, slice[0] && slice[0].fixTime, country, 'chunk', chunk, 'success', countSuccess, 'error', countError, lastSuccessDuration)
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
async function getOSMSpeedingEvents (devices, routes, threshold, minimumMinutes = 0, country) {
|
|
393
|
-
const chunk = 100
|
|
394
|
-
const events = []
|
|
395
|
-
for (const d of devices) {
|
|
396
|
-
const route = routes.filter(r => r.deviceId === d.id)
|
|
397
|
-
const results = []
|
|
398
|
-
const promises = []
|
|
399
|
-
for (let i = 0; i < route.length; i += chunk) {
|
|
400
|
-
promises.push(invokeValhalla(route, i, chunk, country, threshold, results))
|
|
401
|
-
}
|
|
402
|
-
await Promise.all(promises)
|
|
403
|
-
const reduced = results.reduce((acc, cur, idx, src) => {
|
|
404
|
-
const last = acc.length && acc.slice(-1)[0]
|
|
405
|
-
if (last && new Date(cur.fixTime) - new Date(src[idx - 1].fixTime) < 1000 * 60 * 2) {
|
|
406
|
-
last.eventTime = new Date(cur.fixTime) - new Date(last.fixTime)
|
|
407
|
-
last.attributes.maxSpeed = Math.max(last.attributes.maxSpeed, cur.speed)
|
|
408
|
-
last.distance += distance.default(point([cur.longitude, cur.latitude]), point([src[idx - 1].longitude, src[idx - 1].latitude]))
|
|
409
|
-
} else {
|
|
410
|
-
cur.positionId = cur && cur.id
|
|
411
|
-
cur.roadSpeedLimit = cur.speed_limit
|
|
412
|
-
cur.deviceId = d.id
|
|
413
|
-
cur.attributes = { speedLimit: cur.speed_limit, speed: cur.speed, maxSpeed: cur.speed }
|
|
414
|
-
cur.eventTime = 0
|
|
415
|
-
cur.distance = 0
|
|
416
|
-
acc.push(cur)
|
|
417
|
-
}
|
|
418
|
-
return acc
|
|
419
|
-
}, [])
|
|
420
|
-
events.push(...reduced)
|
|
421
|
-
}
|
|
422
|
-
return events.filter(e => e.eventTime >= minimumMinutes * 60 * 1000)
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function calculateEventData (positions, pIndex, alert, device, geofence) {
|
|
426
|
-
let endEventPosition = alert.position
|
|
427
|
-
let maxSpeed = alert.position.speed
|
|
428
|
-
let dist = 0
|
|
429
|
-
while (pIndex + 1 < positions.length) {
|
|
430
|
-
pIndex++
|
|
431
|
-
dist += distance.default(point([endEventPosition.longitude, endEventPosition.latitude]),
|
|
432
|
-
point([positions[pIndex].longitude, positions[pIndex].latitude]))
|
|
433
|
-
endEventPosition = positions[pIndex]
|
|
434
|
-
if (endEventPosition.speed <= alert.attributes.speedLimit || (geofence && !insideGeofence(endEventPosition, geofence))) {
|
|
435
|
-
alert.eventTime = new Date(endEventPosition.fixTime) - new Date(alert.position.fixTime) + (device.attributes.overspeedMinimalDuration || 0) * 1000
|
|
436
|
-
alert.attributes.maxSpeed = maxSpeed
|
|
437
|
-
alert.distance = dist
|
|
438
|
-
break
|
|
439
|
-
} else {
|
|
440
|
-
if (maxSpeed < endEventPosition.speed) {
|
|
441
|
-
maxSpeed = endEventPosition.speed
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
async function exportSpeedingReportToPDF (userData, reportData) {
|
|
448
|
-
console.log('Export to PDF')
|
|
449
|
-
const lang = getLanguage(userData)
|
|
450
|
-
|
|
451
|
-
const timezone = userData.user.attributes.timezone
|
|
452
|
-
const translations = getTranslations(userData)
|
|
453
|
-
|
|
454
|
-
const overspeedData = userData.byDriver ? reportData.drivers : reportData.devices
|
|
455
|
-
|
|
456
|
-
const headers = [
|
|
457
|
-
translations.report.date,
|
|
458
|
-
translations.report.address,
|
|
459
|
-
translations.report.overspeed,
|
|
460
|
-
translations.report.distance,
|
|
461
|
-
translations.report.maxSpeed,
|
|
462
|
-
translations.report.duration
|
|
463
|
-
]
|
|
464
|
-
|
|
465
|
-
if (userData.roadSpeedLimits) {
|
|
466
|
-
headers.splice(2, 0, translations.report.roadSpeedLimit)
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
if (userData.byDriver) {
|
|
470
|
-
headers.push(translations.report.vehicle)
|
|
471
|
-
} else {
|
|
472
|
-
headers.push(translations.report.driver)
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
if (overspeedData) {
|
|
476
|
-
let first = true
|
|
477
|
-
// eslint-disable-next-line new-cap
|
|
478
|
-
const doc = new jsPDF.jsPDF('l', undefined, undefined, true)
|
|
479
|
-
await headerFromUser(doc, translations.report.titleSpeedingReport, userData.user)
|
|
480
|
-
|
|
481
|
-
overspeedData.forEach(d => {
|
|
482
|
-
try {
|
|
483
|
-
const data = []
|
|
484
|
-
|
|
485
|
-
const name = userData.byDriver ? d.driver.name : deviceName(d.device)
|
|
486
|
-
const group = userData.byDriver ? userData.groups.find(g => g.drivers && g.drivers.includes(d.id)) : userData.groups.find(g => d.device.groupId === g.id)
|
|
487
|
-
|
|
488
|
-
let space = 0
|
|
489
|
-
if (!first) {
|
|
490
|
-
doc.addPage()
|
|
491
|
-
} else {
|
|
492
|
-
first = false
|
|
493
|
-
space = 10
|
|
494
|
-
}
|
|
495
|
-
doc.setFontSize(13)
|
|
496
|
-
doc.text(name, 20, space + 20)
|
|
497
|
-
doc.setFontSize(11)
|
|
498
|
-
doc.text(group ? translations.report.group + ': ' + group.name : '', 150, space + 20)
|
|
499
|
-
doc.text(convertToLocaleString(d.from, lang, timezone, userData.user) + ' - ' + convertToLocaleString(d.to, lang, timezone, userData.user), 20, space + 25)
|
|
500
|
-
if (d.alerts[0] && !userData.byDriver) {
|
|
501
|
-
doc.text(translations.report.speedLimit + ': ' + ((userData.useVehicleSpeedLimit || !userData.customSpeed) ? Math.round(d.alerts[0].attributes.speedLimit * 1.85200) + ' Km/h' : (userData.roadSpeedLimits ? translations.report.roadSpeedLimit : userData.customSpeed + ' Km/h')), 20, space + 30)
|
|
502
|
-
}
|
|
503
|
-
d.alerts.forEach(a => {
|
|
504
|
-
const temp = [
|
|
505
|
-
getAlertDate(a, userData.user),
|
|
506
|
-
a.position.address + (a.geofenceName ? ' - ' + a.geofenceName : ''),
|
|
507
|
-
Math.round(a.attributes.speed * 1.85200),
|
|
508
|
-
a.distance && new Intl.NumberFormat(userData.user.attributes.lang, { maximumSignificantDigits: 1 }).format(a.distance),
|
|
509
|
-
a.attributes.maxSpeed ? Math.round(a.attributes.maxSpeed * 1.85200) : Math.round(a.attributes.speed * 1.85200),
|
|
510
|
-
convertMS(a.eventTime, true),
|
|
511
|
-
userData.byDriver ? a.deviceName : a.driver
|
|
512
|
-
]
|
|
513
|
-
|
|
514
|
-
if (userData.roadSpeedLimits) {
|
|
515
|
-
temp.splice(2, 0, a.roadSpeedLimit)
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
data.push(temp)
|
|
519
|
-
})
|
|
520
|
-
|
|
521
|
-
const footValues = [
|
|
522
|
-
'Total:' + d.alerts.length,
|
|
523
|
-
'',
|
|
524
|
-
'',
|
|
525
|
-
'',
|
|
526
|
-
getMaxSpeed(d.alerts),
|
|
527
|
-
convertMS(d.alerts.reduce((a, b) => a + b.eventTime, 0), true)
|
|
528
|
-
]
|
|
529
|
-
|
|
530
|
-
if (userData.roadSpeedLimits) {
|
|
531
|
-
footValues.splice(2, 0, '')
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
if (timezone === 'Asia/Qatar') {
|
|
535
|
-
doc.addFileToVFS('Amiri-Regular.ttf', AmiriRegular())
|
|
536
|
-
doc.addFont('Amiri-Regular.ttf', 'Amiri', 'normal')
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
const style = getStyle(getUserPartner(userData.user))
|
|
540
|
-
doc.autoTable({
|
|
541
|
-
head: [headers],
|
|
542
|
-
body: data,
|
|
543
|
-
foot: [footValues],
|
|
544
|
-
showFoot: 'lastPage',
|
|
545
|
-
headStyles: {
|
|
546
|
-
fillColor: style.pdfHeaderColor,
|
|
547
|
-
textColor: style.pdfHeaderTextColor,
|
|
548
|
-
fontSize: 10
|
|
549
|
-
},
|
|
550
|
-
bodyStyles: {
|
|
551
|
-
fillColor: style.pdfBodyColor,
|
|
552
|
-
textColor: style.pdfBodyTextColor,
|
|
553
|
-
fontSize: 8,
|
|
554
|
-
font: timezone === 'Asia/Qatar' ? 'Amiri' : ''
|
|
555
|
-
},
|
|
556
|
-
footStyles: {
|
|
557
|
-
fillColor: style.pdfFooterColor,
|
|
558
|
-
textColor: style.pdfFooterTextColor,
|
|
559
|
-
fontSize: 9
|
|
560
|
-
},
|
|
561
|
-
startY: space + 35
|
|
562
|
-
})
|
|
563
|
-
} catch (e) {
|
|
564
|
-
console.error(e, 'moving on...')
|
|
565
|
-
}
|
|
566
|
-
})
|
|
567
|
-
|
|
568
|
-
return doc
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
function exportSpeedingReportToExcel (userData, reportData) {
|
|
573
|
-
console.log('Export to Excel')
|
|
574
|
-
|
|
575
|
-
const lang = getLanguage(userData)
|
|
576
|
-
const translations = getTranslations(userData)
|
|
577
|
-
const overspeedData = userData.byDriver ? reportData.drivers : reportData.devices
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
const settings = {
|
|
581
|
-
sheetName: translations.report.titleSpeedingReport.substring(0, 31), // The name of the sheet
|
|
582
|
-
fileName // The name of the spreadsheet
|
|
583
|
-
}
|
|
584
|
-
const headers = [
|
|
585
|
-
userData.byDriver ? { label: translations.report.driver, value: 'driver' } : { label: translations.report.vehicle, value: 'name' },
|
|
586
|
-
userData.byDriver ? { label: translations.report.group, value: 'groupName' } : { label: translations.report.group, value: 'groupName' },
|
|
587
|
-
{ label: translations.report.date, value: 'fixTime' },
|
|
588
|
-
{ label: translations.report.address, value: 'address' },
|
|
589
|
-
{
|
|
590
|
-
label: userData.roadSpeedLimits ? translations.report.roadSpeedLimit : translations.report.speedLimit,
|
|
591
|
-
value: 'speedLimit'
|
|
592
|
-
},
|
|
593
|
-
{ label: translations.report.overspeed, value: 'speed' },
|
|
594
|
-
{ label: translations.report.distance, value: 'distance' },
|
|
595
|
-
{ label: translations.report.maxSpeed, value: 'maxSpeed' },
|
|
596
|
-
{ label: translations.report.duration, value: 'duration' },
|
|
597
|
-
userData.byDriver ? { label: translations.report.vehicle, value: 'name' } : { label: translations.report.driver, value: 'driver' },
|
|
598
|
-
{ label: 'link', value: 'link', format: 'hyperlink' }
|
|
599
|
-
]
|
|
600
|
-
|
|
601
|
-
if (userData.roadSpeedLimits) {
|
|
602
|
-
headers.push({ label: translations.report.info, value: 'info' })
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
let data = []
|
|
606
|
-
if (overspeedData) {
|
|
607
|
-
overspeedData.forEach(d => {
|
|
608
|
-
data = data.concat([{}])
|
|
609
|
-
data = data.concat(d.alerts.map(a => {
|
|
610
|
-
const group = userData.groups.find(g => g.id === d.device.groupId)
|
|
611
|
-
return {
|
|
612
|
-
groupName: group ? group.name : '',
|
|
613
|
-
driver: userData.byDriver ? d.driver.name : a.driver,
|
|
614
|
-
speed: Math.round(a.attributes.speed * 1.85200),
|
|
615
|
-
distance: a.distance && new Intl.NumberFormat(lang, { maximumSignificantDigits: 1 }).format(a.distance),
|
|
616
|
-
maxSpeed: a.attributes.maxSpeed ? Math.round(a.attributes.maxSpeed * 1.85200) : Math.round(a.attributes.speed * 1.85200),
|
|
617
|
-
duration: convertMS(a.eventTime, true),
|
|
618
|
-
fixTime: getAlertDate(a, userData.user),
|
|
619
|
-
name: userData.byDriver ? a.deviceName : d.device.name,
|
|
620
|
-
address: a.position.address + (a.geofenceName ? ' - ' + a.geofenceName : ''),
|
|
621
|
-
speedLimit: userData.roadSpeedLimits
|
|
622
|
-
? a.roadSpeedLimit
|
|
623
|
-
: userData.useVehicleSpeedLimit
|
|
624
|
-
? Math.round(a.attributes.speedLimit * 1.85200)
|
|
625
|
-
: userData.customSpeed,
|
|
626
|
-
...(userData.roadSpeedLimits ? { info: `https://openstreetmap.org/way/${a.way_id}` } : {}),
|
|
627
|
-
link: `https://${getUserPartner(userData.user).host}/#/map?vehicleName=${userData.byDriver ? a.deviceName : d.device.name}&date=${new Date(a.position.fixTime).toISOString()}`
|
|
628
|
-
}
|
|
629
|
-
}))
|
|
630
|
-
})
|
|
631
|
-
return {
|
|
632
|
-
headers,
|
|
633
|
-
data,
|
|
634
|
-
settings
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
function deviceName (device) {
|
|
640
|
-
return device.name + (device.attributes.license_plate ? ', ' + device.attributes.license_plate : '') + (device.model ? ', ' + device.model : '')
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
function getAlertDate (row, user) {
|
|
644
|
-
return convertToLocaleString(row.position.fixTime, user.attributes.lang, user.attributes.timezone, user)
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
function getMaxSpeed (data) {
|
|
648
|
-
const values = data.map(item => {
|
|
649
|
-
return item.attributes.maxSpeed
|
|
650
|
-
})
|
|
651
|
-
const max = values.reduce(function (a, b) {
|
|
652
|
-
return Math.max(a, b)
|
|
653
|
-
})
|
|
654
|
-
return Math.round(max * 1.85200)
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
exports.createSpeedingReport = createSpeedingReport
|
|
658
|
-
exports.exportSpeedingReportToPDF = exportSpeedingReportToPDF
|
|
659
|
-
exports.exportSpeedingReportToExcel = exportSpeedingReportToExcel
|
|
660
|
-
exports.create = createSpeedingReport
|
|
661
|
-
exports.exportToPDF = exportSpeedingReportToPDF
|
|
662
|
-
exports.exportToExcel = exportSpeedingReportToExcel
|
|
663
|
-
exports.getOverspeedEvents = getOverspeedEvents
|