Skip to content

Commit 88dc5ec

Browse files
committed
new feature for change data cal
1 parent 93891d6 commit 88dc5ec

File tree

4 files changed

+129
-0
lines changed

4 files changed

+129
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Similarity Calculator for ServiceNow Change Requests
2+
3+
## Overview
4+
This utility provides manual similarity scoring between ServiceNow change request records using text analysis, without requiring machine learning. It helps developers and admins find similar changes by comparing descriptions and calculating similarity scores programmatically.
5+
6+
## How It Works
7+
1. Extracts keywords from change request descriptions
8+
2. Compares keyword overlap between change requests
9+
3. Calculates a similarity score (0-100%)
10+
4. Finds and ranks similar change requests based on score
11+
12+
## Features
13+
- Compare change request descriptions using keyword matching
14+
- Calculate similarity scores between change requests
15+
- Find and rank similar changes programmatically
16+
- No ML or Predictive Intelligence required
17+
18+
## Use Cases
19+
- Manual clustering of change requests
20+
- Identifying duplicate or related changes
21+
- Data quality analysis before ML model training
22+
- Change impact analysis and triage
23+
24+
## Setup Requirements
25+
- ServiceNow instance with access to the `change_request` table
26+
- Script execution permissions (Background Script or Script Include)
27+
- No external dependencies
28+
29+
## Customization
30+
- Adjust keyword extraction logic for your environment
31+
- Change scoring algorithm to use TF-IDF, cosine similarity, etc.
32+
- Filter by assignment group, category, or other fields
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// ========================================
2+
// Similarity Calculator for ServiceNow Changes
3+
// ========================================
4+
// Purpose: Manually score similarity between change requests using text analysis
5+
// No ML required
6+
// ========================================
7+
8+
(function similarityChangeCalculator() {
9+
// --- CONFIG ---
10+
var config = {
11+
table: 'change_request',
12+
baseChangeSysId: 'YOU ID HERE', // Set to the sys_id of the change to compare
13+
fields: ['short_description', 'description'],
14+
maxResults: 50,
15+
minSimilarity: 0 // Minimum similarity % to report
16+
};
17+
18+
// --- Helper: Extract keywords from text ---
19+
function extractKeywords(text) {
20+
if (!text) return [];
21+
// Simple keyword extraction: split, lowercase, remove stopwords
22+
var stopwords = ['the','and','a','an','to','of','in','for','on','with','at','by','from','is','it','this','that','as','are','was','were','be','has','have','had','but','or','not','can','will','do','does','did','if','so','then','than','too','very','just','also','into','out','up','down','over','under','again','more','less','most','least','such','no','yes','you','your','our','their','my','me','i'];
23+
var words = text.toLowerCase().replace(/[^a-z0-9 ]/g, ' ').split(/\s+/);
24+
var keywords = [];
25+
for (var i = 0; i < words.length; i++) {
26+
var word = words[i];
27+
if (word && stopwords.indexOf(word) === -1 && word.length > 2) {
28+
keywords.push(word);
29+
}
30+
}
31+
return keywords;
32+
}
33+
34+
// --- Helper: Calculate similarity score ---
35+
function calcSimilarity(keywordsA, keywordsB) {
36+
if (!keywordsA.length || !keywordsB.length) return 0;
37+
var mapA = {};
38+
var mapB = {};
39+
for (var i = 0; i < keywordsA.length; i++) {
40+
mapA[keywordsA[i]] = true;
41+
}
42+
for (var j = 0; j < keywordsB.length; j++) {
43+
mapB[keywordsB[j]] = true;
44+
}
45+
var intersection = 0;
46+
var unionMap = {};
47+
for (var k in mapA) {
48+
unionMap[k] = true;
49+
if (mapB[k]) intersection++;
50+
}
51+
for (var l in mapB) {
52+
unionMap[l] = true;
53+
}
54+
var union = Object.keys(unionMap).length;
55+
return union ? (intersection / union * 100) : 0;
56+
}
57+
58+
// --- Get base change request ---
59+
var baseGr = new GlideRecord(config.table);
60+
if (!baseGr.get(config.baseChangeSysId)) {
61+
gs.error('Base change request not found: ' + config.baseChangeSysId);
62+
return;
63+
}
64+
var baseText = config.fields.map(function(f) { return baseGr.getValue(f); }).join(' ');
65+
var baseKeywords = extractKeywords(baseText);
66+
67+
// --- Find candidate change requests ---
68+
var gr = new GlideRecord(config.table);
69+
gr.addQuery('active', true);
70+
gr.addQuery('sys_id', '!=', config.baseChangeSysId);
71+
gr.setLimit(config.maxResults);
72+
gr.query();
73+
74+
var results = [];
75+
while (gr.next()) {
76+
var compareText = config.fields.map(function(f) { return gr.getValue(f); }).join(' ');
77+
var compareKeywords = extractKeywords(compareText);
78+
var score = calcSimilarity(baseKeywords, compareKeywords);
79+
results.push({
80+
sys_id: gr.getUniqueValue(),
81+
number: gr.getValue('number'),
82+
short_description: gr.getValue('short_description'),
83+
similarity: score
84+
});
85+
}
86+
87+
// --- Sort and print results ---
88+
results.sort(function(a, b) { return b.similarity - a.similarity; });
89+
gs.info('=== Similarity Results ===');
90+
for (var i = 0; i < results.length; i++) {
91+
var r = results[i];
92+
gs.info((i+1) + '. ' + r.number + ' [' + r.sys_id + '] (' + r.similarity.toFixed(1) + '%) - ' + r.short_description);
93+
}
94+
if (results.length === 0) {
95+
gs.info('No similar change requests found above threshold.');
96+
}
97+
})();
File renamed without changes.

0 commit comments

Comments
 (0)