fleetmap-reports 2.0.364 → 2.0.365

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetmap-reports",
3
- "version": "2.0.364",
3
+ "version": "2.0.365",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -1,8 +1,9 @@
1
+ const axios = require('axios')
1
2
  const traccarHelper = require('../util/traccar')
2
3
  const { getDriverData, devicesByDriver } = require('../util/driver')
3
4
  const { getCanDriverStyleMessages, parseCanDriverStyleMessage } = require('../util/xpert')
4
5
  const { calculateRPMSections } = require('./performance-report')
5
- const { getTranslations, isClientSide, convertToLocaleString, convertMS, executeServerSide } = require('../util/utils')
6
+ const { getTranslations, isClientSide, convertToLocaleString, convertMS, getServerHost } = require('../util/utils')
6
7
  const jsPDF = require('jspdf')
7
8
  const { headerFromUser, addTable } = require('../util/pdfDocument')
8
9
  const { getStyle } = require('../reportStyle')
@@ -87,25 +88,8 @@ async function create (from, to, userData, traccar) {
87
88
  }
88
89
 
89
90
  if (isClientSide() && (userData.drivers.length > 50 || ((new Date(to).getTime() - new Date(from).getTime()) > (1000 * 60 * 60 * 24 * 15)))) {
90
- const sliced = automaticReports.sliceArray(userData.drivers, 5)
91
- userData.requestToRunServerSide = true
92
- userData.byDriver = true
93
- // Narrow the devices sent per-slice to just the ones those drivers actually used, so a single
94
- // server invocation doesn't have to hold the whole fleet's route data in memory at once (was
95
- // causing Runtime.OutOfMemory on large fleets).
96
- const allDriversData = await executeServerSide('driverranking-report', sliced, from, to, userData, userData.drivers.length, traccar, 5, (driversSlice) => {
97
- const deviceIds = new Set()
98
- driversSlice.forEach(d => {
99
- const ids = deviceIdsByDriver.get(d.uniqueId)
100
- if (ids) {
101
- ids.forEach(id => deviceIds.add(id))
102
- }
103
- })
104
- return { devices: userData.devices.filter(device => deviceIds.has(device.id)) }
105
- })
106
-
107
- allDriversData.sort((a, b) => b.overallScore - a.overallScore)
108
- allDriversData.forEach((d, index) => { d.rank = index + 1 })
91
+ const rawRows = await runServerSideGrid(from, to, userData, deviceIdsByDriver, traccar)
92
+ const allDriversData = scoreAndRankDrivers(mergeRawRows(rawRows), userData)
109
93
 
110
94
  reportData.drivers = allDriversData
111
95
  reportData.target = buildTargets(userData)
@@ -159,7 +143,25 @@ async function create (from, to, userData, traccar) {
159
143
  }
160
144
  }
161
145
 
162
- const allDriversData = (Array.from(driversData.values())).filter(d => d.distance > 0)
146
+ const rawRows = Array.from(driversData.values())
147
+
148
+ // When invoked as one cell of the client's (driverSlice x dateChunk) grid, return the raw
149
+ // accumulated totals for just this chunk - the client sums them across chunks for the same
150
+ // driver before computing scores, since scores are relative to the whole requested period.
151
+ if (userData.rawMode) {
152
+ reportData.drivers = rawRows
153
+ return reportData
154
+ }
155
+
156
+ reportData.drivers = scoreAndRankDrivers(rawRows, userData)
157
+ reportData.target = buildTargets(userData)
158
+ reportData.coefficient = buildCoefficients()
159
+
160
+ return reportData
161
+ }
162
+
163
+ function scoreAndRankDrivers (rawRows, userData) {
164
+ const allDriversData = rawRows.filter(d => d.distance > 0)
163
165
  allDriversData.forEach(d => {
164
166
  d.avgFuelConsumption = getCanAvgConsumption(d.distance, d.spentFuel).byKms
165
167
 
@@ -185,12 +187,86 @@ async function create (from, to, userData, traccar) {
185
187
  })
186
188
  allDriversData.sort((a, b) => b.overallScore - a.overallScore)
187
189
  allDriversData.forEach((d, index) => { d.rank = index + 1 })
190
+ return allDriversData
191
+ }
188
192
 
189
- reportData.drivers.push(...allDriversData)
190
- reportData.target = buildTargets(userData)
191
- reportData.coefficient = buildCoefficients()
193
+ const mergedFields = ['distance', 'highEngineRPM', 'hardBraking', 'hardAcceleration', 'hardCornering',
194
+ 'overspeed', 'spentFuel', 'geofenceAlarm', 'continuesDrivingAlarm', 'reverseAlarm', 'otherAlarm']
195
+
196
+ // Sums each driver's raw totals across every (driverSlice x dateChunk) grid cell they appeared in
197
+ function mergeRawRows (rawRows) {
198
+ const merged = new Map()
199
+ rawRows.forEach(row => {
200
+ let acc = merged.get(row.driverId)
201
+ if (!acc) {
202
+ acc = { driverId: row.driverId, name: row.name }
203
+ mergedFields.forEach(field => { acc[field] = 0 })
204
+ merged.set(row.driverId, acc)
205
+ }
206
+ mergedFields.forEach(field => { acc[field] += row[field] || 0 })
207
+ })
208
+ return Array.from(merged.values())
209
+ }
192
210
 
193
- return reportData
211
+ // Fans the report out across a grid of (driverSlice x dateChunk) requests to the backend, so no
212
+ // single Lambda invocation has to hold more than a handful of drivers' worth of one week of route
213
+ // data in memory at once, and progress feedback arrives once per grid cell rather than once per
214
+ // driver-slice (which could take minutes on its own with a long date range).
215
+ async function runServerSideGrid (from, to, userData, deviceIdsByDriver, traccar) {
216
+ const driverSlices = automaticReports.sliceArray(userData.drivers, 5)
217
+ const dateChunks = splitDateRange(from, to, 7)
218
+
219
+ const cells = []
220
+ driverSlices.forEach(driverSlice => {
221
+ dateChunks.forEach(dateChunk => cells.push({ driverSlice, dateChunk }))
222
+ })
223
+
224
+ const cookie = await axios.get('/pinmeapi/cookie/get', { withCredentials: true }).then(d => d.data)
225
+ const url = `https://${getServerHost()}/pinmeapi/reports/driverranking-report`
226
+ const rawRows = []
227
+ let completed = 0
228
+ const maxConcurrent = 10
229
+
230
+ for (let i = 0; i < cells.length; i += maxConcurrent) {
231
+ const batch = cells.slice(i, i + maxConcurrent)
232
+ await Promise.all(batch.map(async ({ driverSlice, dateChunk }) => {
233
+ const deviceIds = new Set()
234
+ driverSlice.forEach(d => {
235
+ const ids = deviceIdsByDriver.get(d.uniqueId)
236
+ if (ids) {
237
+ ids.forEach(id => deviceIds.add(id))
238
+ }
239
+ })
240
+ const slicedDevices = userData.devices.filter(device => deviceIds.has(device.id))
241
+
242
+ try {
243
+ const response = await axios.post(url, {
244
+ from: dateChunk.from,
245
+ to: dateChunk.to,
246
+ userData: {
247
+ ...userData,
248
+ drivers: driverSlice,
249
+ devices: slicedDevices,
250
+ geofences: [],
251
+ requestToRunServerSide: true,
252
+ rawMode: true
253
+ },
254
+ cookie
255
+ }, { withCredentials: true })
256
+ const result = response.data[0]
257
+ if (result) {
258
+ rawRows.push(...(result.drivers || []))
259
+ }
260
+ } catch (e) {
261
+ console.error(e)
262
+ }
263
+ completed++
264
+ console.log('LOADING_MESSAGE:' + driverSlice[0].name)
265
+ console.log(`PROGRESS_PERC:${(completed / cells.length) * 100}`)
266
+ }))
267
+ }
268
+
269
+ return rawRows
194
270
  }
195
271
 
196
272
  function buildTargets (userData) {
@@ -241,6 +317,7 @@ async function processDevice (driversData, device, userData, allInOne, alarmEven
241
317
  let driverData = driversData.get(d.id)
242
318
  if (!driverData) {
243
319
  driverData = {
320
+ driverId: d.id,
244
321
  name: d.name,
245
322
  distance: 0,
246
323
  highEngineRPM: 0,