|
3 | 3 | var moment = require('moment'); |
4 | 4 | var find_options = require('./query'); |
5 | 5 |
|
6 | | -function storage (collection, ctx) { |
| 6 | +/** |
| 7 | + * Truncate OpenAPS prediction arrays to a bounded length. |
| 8 | + * |
| 9 | + * This helper limits the size of `openaps.suggested.predBGs` and |
| 10 | + * `openaps.enacted.predBGs` arrays to avoid unbounded growth of |
| 11 | + * `devicestatus` documents, which helps keep MongoDB documents |
| 12 | + * under the 16 MB size limit. |
| 13 | + * |
| 14 | + * The function only operates on the following prediction series, |
| 15 | + * when present: |
| 16 | + * - `IOB` (insulin-on-board-based predictions) |
| 17 | + * - `COB` (carbs-on-board-based predictions) |
| 18 | + * - `UAM` (unannounced-meal-based predictions) |
| 19 | + * - `ZT` (zero-temp-based predictions) |
| 20 | + * |
| 21 | + * The `maxSize` argument defines the maximum number of prediction |
| 22 | + * points retained per series. When not explicitly configured by |
| 23 | + * `env.predictionsMaxSize`, this is typically set to 288, which |
| 24 | + * corresponds to 24 hours of 5-minute prediction intervals |
| 25 | + * (24 * 60 / 5) and provides a practical upper bound to keep |
| 26 | + * documents reasonably small for MongoDB storage. |
| 27 | + * |
| 28 | + * If `maxSize` is falsy or not a positive number, the input |
| 29 | + * object is returned unmodified. |
| 30 | + * |
| 31 | + * @param {Object} obj - The devicestatus-like object potentially containing |
| 32 | + * `openaps.suggested.predBGs` and/or `openaps.enacted.predBGs`. |
| 33 | + * @param {number} maxSize - Maximum allowed length for each prediction array |
| 34 | + * (commonly 288 by default). |
| 35 | + * @returns {Object} The same object reference, possibly with truncated |
| 36 | + * prediction arrays. |
| 37 | + */ |
| 38 | +function truncatePredictions (obj, maxSize) { |
| 39 | + if (!maxSize || maxSize <= 0) return obj; |
| 40 | + |
| 41 | + var predictionTypes = ['IOB', 'COB', 'UAM', 'ZT']; |
| 42 | + |
| 43 | + if (obj && obj.openaps && obj.openaps.suggested && obj.openaps.suggested.predBGs) { |
| 44 | + var suggestedPredBGs = obj.openaps.suggested.predBGs; |
| 45 | + predictionTypes.forEach(function(type) { |
| 46 | + if (Array.isArray(suggestedPredBGs[type]) && suggestedPredBGs[type].length > maxSize) { |
| 47 | + suggestedPredBGs[type] = suggestedPredBGs[type].slice(0, maxSize); |
| 48 | + } |
| 49 | + }); |
| 50 | + } |
7 | 51 |
|
8 | | - function create (statuses, fn) { |
| 52 | + if (obj && obj.openaps && obj.openaps.enacted && obj.openaps.enacted.predBGs) { |
| 53 | + var enactedPredBGs = obj.openaps.enacted.predBGs; |
| 54 | + predictionTypes.forEach(function(type) { |
| 55 | + if (Array.isArray(enactedPredBGs[type]) && enactedPredBGs[type].length > maxSize) { |
| 56 | + enactedPredBGs[type] = enactedPredBGs[type].slice(0, maxSize); |
| 57 | + } |
| 58 | + }); |
| 59 | + } |
9 | 60 |
|
10 | | - if (!Array.isArray(statuses)) { statuses = [statuses]; } |
| 61 | + return obj; |
| 62 | +} |
11 | 63 |
|
12 | | - const r = []; |
13 | | - let errorOccurred = false; |
| 64 | +function storage (env, ctx) { |
14 | 65 |
|
15 | | - for (let i = 0; i < statuses.length; i++) { |
| 66 | + var collection = env.devicestatus_collection; |
| 67 | + var predictionsMaxSize = env.predictionsMaxSize || null; |
| 68 | + |
| 69 | + function create (statuses, fn) { |
16 | 70 |
|
17 | | - const obj = statuses[i]; |
| 71 | + if (!Array.isArray(statuses)) { statuses = [statuses]; } |
18 | 72 |
|
19 | | - if (errorOccurred) return; |
| 73 | + if (statuses.length === 0) { |
| 74 | + return fn(null, []); |
| 75 | + } |
20 | 76 |
|
21 | | - // Normalize all dates to UTC |
22 | | - const d = moment(obj.created_at).isValid() ? moment.parseZone(obj.created_at) : moment(); |
| 77 | + // Prepare all documents before insert |
| 78 | + statuses.forEach(function(obj) { |
| 79 | + var d = moment(obj.created_at).isValid() ? moment.parseZone(obj.created_at) : moment(); |
23 | 80 | obj.created_at = d.toISOString(); |
24 | 81 | obj.utcOffset = d.utcOffset(); |
| 82 | + truncatePredictions(obj, predictionsMaxSize); |
| 83 | + }); |
25 | 84 |
|
26 | | - api().insertOne(obj, function(err, results) { |
27 | | - if (err !== null && err.message) { |
28 | | - console.log('Error inserting the device status object', err.message); |
29 | | - errorOccurred = true; |
30 | | - fn(err.message, null); |
31 | | - return; |
32 | | - } |
33 | | - |
34 | | - if (!err) { |
35 | | - |
36 | | - if (!obj._id) obj._id = results.insertedIds[0]._id; |
37 | | - r.push(obj); |
38 | | - |
39 | | - ctx.bus.emit('data-update', { |
40 | | - type: 'devicestatus' |
41 | | - , op: 'update' |
42 | | - , changes: ctx.ddata.processRawDataForRuntime([obj]) |
43 | | - }); |
44 | | - |
45 | | - // Last object! Return results |
46 | | - if (i == statuses.length - 1) { |
47 | | - fn(null, r); |
48 | | - ctx.bus.emit('data-received'); |
49 | | - } |
50 | | - } |
| 85 | + // Use insertMany for batch insert |
| 86 | + api().insertMany(statuses, { ordered: true }, function(err, insertResult) { |
| 87 | + if (err) { |
| 88 | + console.log('Error inserting device status objects', err.message); |
| 89 | + fn(err.message || err, null); |
| 90 | + return; |
| 91 | + } |
| 92 | + |
| 93 | + // Assign _ids from insertMany result |
| 94 | + if (insertResult && insertResult.insertedIds) { |
| 95 | + Object.keys(insertResult.insertedIds).forEach(function(index) { |
| 96 | + statuses[index]._id = insertResult.insertedIds[index]; |
| 97 | + }); |
| 98 | + } |
| 99 | + |
| 100 | + // Emit data-update for all inserted documents |
| 101 | + ctx.bus.emit('data-update', { |
| 102 | + type: 'devicestatus' |
| 103 | + , op: 'update' |
| 104 | + , changes: ctx.ddata.processRawDataForRuntime(statuses) |
51 | 105 | }); |
52 | | - }; |
| 106 | + |
| 107 | + ctx.bus.emit('data-received'); |
| 108 | + fn(null, statuses); |
| 109 | + }); |
53 | 110 | } |
54 | 111 |
|
55 | 112 | function last (fn) { |
@@ -103,14 +160,14 @@ function storage (collection, ctx) { |
103 | 160 | ctx.bus.emit('data-update', { |
104 | 161 | type: 'devicestatus' |
105 | 162 | , op: 'remove' |
106 | | - , count: stat.result.n |
| 163 | + , count: stat.deletedCount |
107 | 164 | , changes: opts.find._id |
108 | 165 | }); |
109 | 166 |
|
110 | 167 | fn(err, stat); |
111 | 168 | } |
112 | 169 |
|
113 | | - return api().remove( |
| 170 | + return api().deleteMany( |
114 | 171 | query_for(opts), removed); |
115 | 172 | } |
116 | 173 |
|
|
0 commit comments