|
| 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 | +})(); |
0 commit comments