|
| 1 | +// Background Script: Tag incident resolution outliers by z score |
| 2 | +(function() { |
| 3 | + var TABLE = 'incident'; |
| 4 | + var DAYS = 30; |
| 5 | + var Z_THRESHOLD = 2.5; |
| 6 | + var FLAG_FIELD = 'u_outlier'; // create this boolean field or change action to add work_notes - potential to change to tag as well if one exists |
| 7 | + |
| 8 | + // Build look-back cutoff |
| 9 | + var cutoff = new GlideDateTime(); |
| 10 | + cutoff.addDaysUTC(-DAYS); |
| 11 | + |
| 12 | + // First pass: mean and std dev of resolution minutes |
| 13 | + // Compute duration per record as closed_at - opened_at in minutes |
| 14 | + var minutes = []; |
| 15 | + var gr = new GlideRecord(TABLE); |
| 16 | + gr.addQuery('closed_at', '>=', cutoff); |
| 17 | + gr.addQuery('state', '>=', 6); // resolved or closed |
| 18 | + gr.addNotNullQuery('opened_at'); |
| 19 | + gr.addNotNullQuery('closed_at'); |
| 20 | + gr.query(); |
| 21 | + while (gr.next()) { |
| 22 | + var opened = String(gr.getValue('opened_at')); |
| 23 | + var closed = String(gr.getValue('closed_at')); |
| 24 | + var mins = gs.dateDiff(opened, closed, true) / 60; |
| 25 | + minutes.push({ id: gr.getUniqueValue(), mins: mins }); |
| 26 | + } |
| 27 | + if (!minutes.length) { |
| 28 | + gs.info('No records in window. Exiting.'); |
| 29 | + return; |
| 30 | + } |
| 31 | + |
| 32 | + var sum = minutes.reduce(function(a, x) { return a + x.mins; }, 0); |
| 33 | + var mean = sum / minutes.length; |
| 34 | + |
| 35 | + var variance = minutes.reduce(function(a, x) { |
| 36 | + var d = x.mins - mean; return a + d * d; |
| 37 | + }, 0) / minutes.length; |
| 38 | + var std = Math.sqrt(variance); |
| 39 | + |
| 40 | + // Second pass: tag outliers |
| 41 | + var tagged = 0; |
| 42 | + minutes.forEach(function(row) { |
| 43 | + var z = std > 0 ? (row.mins - mean) / std : 0; |
| 44 | + if (z >= Z_THRESHOLD) { |
| 45 | + var r = new GlideRecord(TABLE); |
| 46 | + if (r.get(row.id)) { |
| 47 | + if (r.isValidField(FLAG_FIELD)) { |
| 48 | + r[FLAG_FIELD] = true; |
| 49 | + r.update(); |
| 50 | + } else { |
| 51 | + r.work_notes = 'Marked outlier by automation. z=' + z.toFixed(2) + ', mean=' + Math.round(mean) + 'm, std=' + Math.round(std) + 'm'; |
| 52 | + r.update(); |
| 53 | + } |
| 54 | + tagged++; |
| 55 | + } |
| 56 | + } |
| 57 | + }); |
| 58 | + |
| 59 | + gs.info('Outlier tagging complete. Window=' + DAYS + 'd, N=' + minutes.length + ', mean=' + Math.round(mean) + 'm, std=' + Math.round(std) + 'm, tagged=' + tagged); |
| 60 | +})(); |
0 commit comments