Skip to content

Commit bc2d789

Browse files
authored
Background Script: Tag incident resolution outliers using z score (#2466)
1 parent 45f8be4 commit bc2d789

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-0
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Tag incident resolution outliers by z score
2+
3+
## What this solves
4+
Average resolution time hides long-tail outliers. This script calculates mean and standard deviation of resolution minutes and tags incidents whose z score exceeds a threshold, helping teams investigate anomalies.
5+
6+
## Where to use
7+
Run as a Background Script or convert into a Scheduled Job for periodic tagging.
8+
9+
## How it works
10+
- Uses `GlideAggregate` to compute count, mean, and approximate variance
11+
- Calculates z score per resolved incident
12+
- Sets a flag field or work note on outliers above a configurable z threshold
13+
14+
## Configure
15+
- `DAYS`: look-back window
16+
- `Z_THRESHOLD`: default 2.5
17+
- `FLAG_FIELD`: field to set, for example a custom boolean `u_outlier`
18+
19+
## References
20+
- GlideAggregate API
21+
https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideAggregate/concept/c_GlideAggregateAPI.html
22+
- GlideRecord API
23+
https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideRecord/concept/c_GlideRecordAPI.html
24+
- GlideDateTime API
25+
https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideDateTime/concept/c_GlideDateTimeAPI.html
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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

Comments
 (0)