From 8fa9da775cfc129966b9f47355e2a930ba2665ca Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 24 Oct 2025 15:47:41 -0700 Subject: [PATCH 1/3] Normalize README casing across all folders --- .../Auto-Populate Short Discription/Readme.md | 36 ------- .../validate phone number/Readme.md | 39 ------- .../Readme.md | 100 ------------------ .../CMDB/CMDB Utility Scripts/ReadME.md | 18 ---- .../updateMultipleRecords/README.md | 19 +++- .../updateMultipleRecords/readme.md | 15 --- .../{readme.md => README.md} | 0 7 files changed, 15 insertions(+), 212 deletions(-) delete mode 100644 Client-Side Components/Client Scripts/Auto-Populate Short Discription/Readme.md delete mode 100644 Client-Side Components/Client Scripts/validate phone number/Readme.md delete mode 100644 Server-Side Components/Background Scripts/Convert Incident Records to JSON/Readme.md delete mode 100644 Specialized Areas/CMDB/CMDB Utility Scripts/ReadME.md delete mode 100644 Specialized Areas/Fix scripts/updateMultipleRecords/readme.md rename Specialized Areas/ITOM/Track Discovery Status/{readme.md => README.md} (100%) diff --git a/Client-Side Components/Client Scripts/Auto-Populate Short Discription/Readme.md b/Client-Side Components/Client Scripts/Auto-Populate Short Discription/Readme.md deleted file mode 100644 index 4dab2bdbd8..0000000000 --- a/Client-Side Components/Client Scripts/Auto-Populate Short Discription/Readme.md +++ /dev/null @@ -1,36 +0,0 @@ -# Auto-Populate Short Description (Client Script) -## Overview - -This client script automatically updates the Short Description field in ServiceNow whenever a user selects a category on the form. It improves data consistency, saves time, and ensures that short descriptions remain meaningful and standardized. - -## How It Works - -When a user selects a category such as Hardware, Software, or Network, the script automatically prefixes the existing short description with a corresponding label (for example, “Hardware Issue –”). -This makes incident records easier to identify and improves the quality of data entry. - -## Configuration Steps - -1. Log in to your ServiceNow instance with admin or developer access. -2. Navigate to System Definition → Client Scripts. -3. Create a new Client Script with the following details: - -``` -Table: Incident -Type: onChange -Field name: category -Copy and paste the provided script into the Script field. -Save the record and make sure the script is active. -``` - -## Testing - -1. Open any existing or new Incident record. -2. Select a category such as Hardware or Software. -3. The Short Description field will automatically update with the corresponding prefix. - -## Benefits - -Speeds up data entry for users. -Maintains consistency in short descriptions. -Reduces manual effort and human errors. -Enhances clarity in incident listings and reports. diff --git a/Client-Side Components/Client Scripts/validate phone number/Readme.md b/Client-Side Components/Client Scripts/validate phone number/Readme.md deleted file mode 100644 index 5f47923199..0000000000 --- a/Client-Side Components/Client Scripts/validate phone number/Readme.md +++ /dev/null @@ -1,39 +0,0 @@ -Phone Number Validation — Client Script -Overview - -This Client Script validates that users enter their phone numbers in the strict format: (123) 456-7890. - -It is triggered whenever the Phone field changes on a sys_user record. If the input does not match the required format, the script: - -Displays an inline error message directly below the field. - -Clears the invalid input so the user can re-enter the correct value. - -This script is designed to be dynamic, simple, and user-friendly. - -Features - -Ensures phone numbers follow the exact format (123) 456-7890. - -Provides immediate feedback via field-level error messages. - -Clears invalid entries automatically to prevent submission errors. - -Works on Classic UI forms and provides clear messaging to the user. - -Usage Instructions -1. Create the Client Script - -Navigate to System Definition → Client Scripts. - -Click New to create a client script. - -2. Configure the Script - -Name: Phone Number Validation - -Table: sys_user - -Type: onChange - -Field: phone diff --git a/Server-Side Components/Background Scripts/Convert Incident Records to JSON/Readme.md b/Server-Side Components/Background Scripts/Convert Incident Records to JSON/Readme.md deleted file mode 100644 index 565466e9ba..0000000000 --- a/Server-Side Components/Background Scripts/Convert Incident Records to JSON/Readme.md +++ /dev/null @@ -1,100 +0,0 @@ -# Active Incidents JSON Export – ServiceNow - -This repository contains **two approaches** to fetch active incidents from ServiceNow and convert them to JSON. Both use `GlideRecord` but differ in flexibility and readability. - ---- - -## 1. Simple Approach - -This method fetches a **fixed set of fields** and converts them directly to JSON. - -```javascript -var incidents = []; -var gr = new GlideRecord('incident'); -gr.addQuery('active', true); -gr.query(); -while (gr.next()) { - incidents.push({ - number: gr.number.toString(), - short_description: gr.short_description.toString(), - state: gr.state.toString(), - assigned_to: gr.assigned_to.getDisplayValue('name'), - created_on: gr.sys_created_on.getDisplayValue() - }); -} - -var jsonOutput = JSON.stringify(incidents); -gs.info(jsonOutput); -``` - -### ✅ Pros -- Simple and easy to implement -- Works fine for a fixed set of fields -- Direct JSON output - -### ❌ Cons -- Fields are hard-coded → not reusable for other tables -- Reference fields handling is not dynamic -- Numeric state values are not human-readable - ---- - -## 2. Flexible & Dynamic Approach - -This method allows dynamic fields, handles reference fields, and adds human-readable state labels. - -```javascript -var tableName = 'incident'; -var fieldsToInclude = ['number', 'short_description', 'state', 'assigned_to', 'sys_created_on']; -var incidentList = []; - -var gr = new GlideRecord(tableName); -gr.addQuery('active', true); -gr.query(); - -while (gr.next()) { - var incidentObj = {}; - - fieldsToInclude.forEach(function(field) { - if (gr.isValidField(field) && gr[field].getRefRecord) { - incidentObj[field] = gr[field].getDisplayValue(); - } else if (gr.isValidField(field)) { - incidentObj[field] = gr[field].toString(); - } else { - incidentObj[field] = null; - } - }); - - // Map numeric state to human-readable label - var stateMap = { - '1': 'New', - '2': 'In Progress', - '3': 'On Hold', - '6': 'Resolved', - '7': 'Closed' - }; - incidentObj.state_label = stateMap[incidentObj.state] || 'Unknown'; - - incidentList.push(incidentObj); -} - -var jsonOutput = JSON.stringify(incidentList, null, 2); -gs.info("Here's your JSON for active incidents:\n" + jsonOutput); -``` - -### ✅ Pros -- Dynamic → easily reusable for any table and fields -- Handles reference fields gracefully -- Adds human-readable state labels -- JSON is formatted for readability -- Checks `isValidField` to prevent errors - -### ❌ Cons -- Slightly more complex than the simple version -- Requires manual mapping for fields like state labels - - -## Summary - -- **Simple Approach**: Best for quick tasks with fixed fields -- **Flexible Approach**: Best for reusable scripts, handling dynamic tables, reference fields, and human-readable output \ No newline at end of file diff --git a/Specialized Areas/CMDB/CMDB Utility Scripts/ReadME.md b/Specialized Areas/CMDB/CMDB Utility Scripts/ReadME.md deleted file mode 100644 index 072ea0e347..0000000000 --- a/Specialized Areas/CMDB/CMDB Utility Scripts/ReadME.md +++ /dev/null @@ -1,18 +0,0 @@ -This script is used in ServiceNow to automatically fill in the missing manufacturer information for Configuration Items (CIs) in the CMDB (Configuration Management Database). - -1. Predefined Mapping: -The script starts with a list of known model names and their corresponding manufacturer names.For example, a model called ThinkPad T14 is made by Lenovo, and MacBook Pro 16 is made by Apple -2. Look Up Manufacturer: - * It defines a function that looks up the manufacturer’s record in the core_company table (based on the name) and gets its unique ID (sys_id). -3. Find CIs Missing a Manufacturer: - * The script goes through all CIs in the cmdb_ci table where the manufacturer field is empty. -4. Update Missing Manufacturer: - * For each of those CIs: - * It checks the model name. - * If the model is in the predefined mapping: - * It looks up the correct manufacturer in the core_company table. - * It updates the CI record by setting the manufacturer field with the correct sys_id. - * It also logs that the update was successful. - * If the manufacturer is not found in the system, it logs a warning. -5. Final Log: - * After going through all matching CIs, it logs how many records were successfully updated. diff --git a/Specialized Areas/Fix scripts/updateMultipleRecords/README.md b/Specialized Areas/Fix scripts/updateMultipleRecords/README.md index 24cd1cd207..3d914a4e98 100644 --- a/Specialized Areas/Fix scripts/updateMultipleRecords/README.md +++ b/Specialized Areas/Fix scripts/updateMultipleRecords/README.md @@ -1,4 +1,15 @@ -//This Fix scripts is to clean up multiple record errors -// Navigate to Scripts-Background -// Past the script and update the place holder variable value: table name, field, and value etc. -// Also advisable to validate the row count the //gr.getRowCount() and remove from codebase. +## update Multiple Records +These 2 scripts are used to update Multiple records but in different ways +### update_multiple_records.script file +In this script it uses `updateMultiple()` Function +1. In this script it uses `updateMultiple()` Function +2. Automatically updates system fields like sys_updated_by and sys_updated_on. +3. Cannot skip system fields using autoSysFields(false). +4. Always triggers workflows, business rules, and script actions. +5. Cannot disable per record. + +### update_multiple_records_v2.script file +In this script it uses `update()` Function +1. update() is a GlideRecord method used to update a single record in the database. It is commonly used inside a loop to update multiple records individually. +2. Can skip updating system fields using autoSysFields(false). +3. Can skip workflows/business rules using setWorkflow(false). \ No newline at end of file diff --git a/Specialized Areas/Fix scripts/updateMultipleRecords/readme.md b/Specialized Areas/Fix scripts/updateMultipleRecords/readme.md deleted file mode 100644 index 3d914a4e98..0000000000 --- a/Specialized Areas/Fix scripts/updateMultipleRecords/readme.md +++ /dev/null @@ -1,15 +0,0 @@ -## update Multiple Records -These 2 scripts are used to update Multiple records but in different ways -### update_multiple_records.script file -In this script it uses `updateMultiple()` Function -1. In this script it uses `updateMultiple()` Function -2. Automatically updates system fields like sys_updated_by and sys_updated_on. -3. Cannot skip system fields using autoSysFields(false). -4. Always triggers workflows, business rules, and script actions. -5. Cannot disable per record. - -### update_multiple_records_v2.script file -In this script it uses `update()` Function -1. update() is a GlideRecord method used to update a single record in the database. It is commonly used inside a loop to update multiple records individually. -2. Can skip updating system fields using autoSysFields(false). -3. Can skip workflows/business rules using setWorkflow(false). \ No newline at end of file diff --git a/Specialized Areas/ITOM/Track Discovery Status/readme.md b/Specialized Areas/ITOM/Track Discovery Status/README.md similarity index 100% rename from Specialized Areas/ITOM/Track Discovery Status/readme.md rename to Specialized Areas/ITOM/Track Discovery Status/README.md From dbc1d1039aaefc808a369eda325096b75e51b4f0 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 24 Oct 2025 15:49:37 -0700 Subject: [PATCH 2/3] Normalize casing for final README in Track Discovery Status --- .../ITOM/Track Discovery Status/ReadME.md | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 Specialized Areas/ITOM/Track Discovery Status/ReadME.md diff --git a/Specialized Areas/ITOM/Track Discovery Status/ReadME.md b/Specialized Areas/ITOM/Track Discovery Status/ReadME.md deleted file mode 100644 index bfde3dbbc8..0000000000 --- a/Specialized Areas/ITOM/Track Discovery Status/ReadME.md +++ /dev/null @@ -1,10 +0,0 @@ -Defines a list of IP addresses to check (hardcoded in the script). -Example IPs: 192.168.1.35, 192.168.1.27, 192.168.1.15. -For each IP address in the list: -It looks up the CI (Configuration Item) in the cmdb_ci_computer table with a matching IP. -If a CI is found: -It checks the discovery_device_history table to see if that CI was discovered by ServiceNow Discovery. -If discovery history exists, it finds the related Discovery Status record (discovery_status table) and gets its number (like DIS123456). -If no CI or discovery record is found, it notes the reason in the result. -Compiles all results (IP + discovery status or error message) into a list. -Prints the results in a clear JSON format in the system logs, making it easy to read and review. From 178486ee792f34b414233b43dcd23590a5c28e34 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 25 Oct 2025 05:40:09 -0700 Subject: [PATCH 3/3] added script for change request data --- .../Change Request/README.md | 37 ++ ...ze_change_request_data_training_quality.js | 571 ++++++++++++++++++ .../{ => Incident Request}/README.md | 0 .../analyze_incident_data_training_quality.js | 2 +- 4 files changed, 609 insertions(+), 1 deletion(-) create mode 100644 Specialized Areas/Predictive Intelligence/Training Data Preparer/Change Request/README.md create mode 100644 Specialized Areas/Predictive Intelligence/Training Data Preparer/Change Request/analyze_change_request_data_training_quality.js rename Specialized Areas/Predictive Intelligence/Training Data Preparer/{ => Incident Request}/README.md (100%) rename Specialized Areas/Predictive Intelligence/Training Data Preparer/{ => Incident Request}/analyze_incident_data_training_quality.js (99%) diff --git a/Specialized Areas/Predictive Intelligence/Training Data Preparer/Change Request/README.md b/Specialized Areas/Predictive Intelligence/Training Data Preparer/Change Request/README.md new file mode 100644 index 0000000000..1f85cf396d --- /dev/null +++ b/Specialized Areas/Predictive Intelligence/Training Data Preparer/Change Request/README.md @@ -0,0 +1,37 @@ +# Training Data Quality Analyzer for ServiceNow Predictive Intelligence (Change Requests) + +## Overview +This script analyzes the quality of change request data in ServiceNow to determine readiness for Predictive Intelligence (PI) model training. It provides detailed statistics and quality metrics to help ServiceNow developers and admins identify and address data issues before starting ML training jobs. + +## Purpose +- Assess completeness and quality of key fields in change request records +- Identify common data issues that could impact PI model performance +- Provide actionable insights for improving training data + +## Features +- Checks completeness of important fields (e.g., short_description, description, category, risk, assignment_group, implementation_plan, test_plan, backout_plan, close_notes) +- Analyzes text quality for description, implementation/test/backout plans, and close notes +- Evaluates category diversity and closure times +- Calculates an overall data quality score +- Outputs results to the ServiceNow system logs + +## Setup Requirements +1. **ServiceNow Instance** with Predictive Intelligence plugin enabled +2. **Script Execution Permissions**: Run as a background script or Script Include with access to the `change_request` table +3. **No external dependencies**: Uses only standard ServiceNow APIs (GlideRecord, GlideAggregate, GlideDateTime) +4. **Sufficient Data Volume**: At least 50 closed change requests recommended for meaningful analysis + +## How It Works +1. **Field Existence Check**: Dynamically verifies that each key field exists on the change_request table or its parent tables +2. **Statistics Gathering**: Collects counts for total, closed, and recent change requests +3. **Completeness Analysis**: Calculates the percentage of records with each key field filled +4. **Text Quality Analysis**: Measures average length and quality of description, implementation/test/backout plans, and close notes +5. **Category Distribution**: Reports on the spread and diversity of change request categories +6. **Closure Time Analysis**: Evaluates how quickly change requests are closed +7. **Quality Scoring**: Combines all metrics into a single overall score +8. **Log Output**: Prints all results and warnings to the ServiceNow logs for review + +## Customization +- Adjust the `keyFields` array in the config section to match your organization's data requirements +- Modify thresholds for text length, closure time, and completeness as needed +- Increase `sampleSize` for more detailed analysis if you have a large dataset diff --git a/Specialized Areas/Predictive Intelligence/Training Data Preparer/Change Request/analyze_change_request_data_training_quality.js b/Specialized Areas/Predictive Intelligence/Training Data Preparer/Change Request/analyze_change_request_data_training_quality.js new file mode 100644 index 0000000000..a2cc8fdaef --- /dev/null +++ b/Specialized Areas/Predictive Intelligence/Training Data Preparer/Change Request/analyze_change_request_data_training_quality.js @@ -0,0 +1,571 @@ +// ======================================== +// PI Training Data Quality Analyzer +// ======================================== +// Purpose: Analyze change_request data quality for Predictive Intelligence training +// Use Case: Identify data quality issues before training ML models +// No Training Required: Analyzes existing data without ML +// ======================================== + +(function analyzeChangeRequestTrainingDataQuality() { + // Print all fields that exist on the change_request table and its parents + function printAllFields(tableName) { + var tables = [tableName]; + var currentTable = tableName; + while (currentTable) { + var tableRec = new GlideRecord('sys_db_object'); + tableRec.addQuery('name', currentTable); + tableRec.query(); + if (tableRec.next()) { + var parentSysId = tableRec.getValue('super_class'); + if (parentSysId && parentSysId != '') { + var parentRec = new GlideRecord('sys_db_object'); + if (parentRec.get(parentSysId)) { + var parentName = parentRec.getValue('name'); + tables.push(parentName); + currentTable = parentName; + } else { + currentTable = null; + } + } else { + currentTable = null; + } + } else { + currentTable = null; + } + } + var field = new GlideRecord('sys_dictionary'); + field.addQuery('name', 'IN', tables.join(',')); + field.query(); + + } + + printAllFields('change_request'); + // Helper: check if field exists in table hierarchy + function fieldExists(tableName, fieldName) { + var tables = [tableName]; + var currentTable = tableName; + while (currentTable) { + var tableRec = new GlideRecord('sys_db_object'); + tableRec.addQuery('name', currentTable); + tableRec.query(); + if (tableRec.next()) { + var parentSysId = tableRec.getValue('super_class'); + if (parentSysId && parentSysId != '') { + var parentRec = new GlideRecord('sys_db_object'); + if (parentRec.get(parentSysId)) { + var parentName = parentRec.getValue('name'); + tables.push(parentName); + currentTable = parentName; + } else { + currentTable = null; + } + } else { + currentTable = null; + } + } else { + currentTable = null; + } + } + var field = new GlideRecord('sys_dictionary'); + field.addQuery('element', fieldName); + field.addQuery('name', 'IN', tables.join(',')); + field.query(); + return field.hasNext(); + } + + // ============================================ + // CONFIGURATION + // ============================================ + var config = { + table: 'change_request', + + // Fields to analyze for completeness + keyFields: [ + 'short_description', + 'description', + 'category', + 'risk', + 'assignment_group', + 'implementation_plan', + 'test_plan', + 'backout_plan', + 'close_notes' + ], + + // Quality thresholds + thresholds: { + minDescriptionLength: 20, // Characters + minCloseNotesLength: 50, // Characters + minImplementationPlanLength: 30, // Characters + minTestPlanLength: 30, // Characters + minBackoutPlanLength: 30, // Characters + minResolutionTime: 10, // Minutes + maxAge: 365, // Days - only analyze recent data + targetCompleteness: 80 // Percent of fields filled + }, + + // States to analyze + states: { + closed: 3 + }, + + sampleSize: 500 // Max records to analyze in detail + }; + + gs.info('========================================'); + gs.info('PI Change Request Training Data Quality Analysis'); + gs.info('========================================'); + gs.info('Table: ' + config.table); + gs.info('Sample Size: Up to ' + config.sampleSize + ' records'); + gs.info(''); + + // ============================================ + // STEP 1: Overall Data Statistics + // ============================================ + gs.info('=== STEP 1: Overall Statistics ==='); + gs.info(''); + + var stats = getOverallStats(); + + gs.info('Total Change Requests:'); + gs.info(' All States: ' + stats.total); + gs.info(' Closed: ' + stats.closed); + gs.info(' Last 90 Days: ' + stats.recent90); + gs.info(' Last 365 Days: ' + stats.recent365); + gs.info(''); + + if (stats.closed < 50) { + gs.warn('⚠️ Low number of closed change requests - need at least 50 for training'); + gs.info('Current: ' + stats.closed); + } else { + gs.info('✅ Sufficient closed change requests for training'); + } + + // ============================================ + // STEP 2: Field Completeness Analysis + // ============================================ + gs.info(''); + gs.info('=== STEP 2: Field Completeness Analysis ==='); + gs.info('Analyzing closed change requests from last ' + config.thresholds.maxAge + ' days'); + gs.info(''); + + var completeness = analyzeFieldCompleteness(); + + gs.info('Field Completeness Scores:'); + gs.info(''); + + for (var field in completeness) { + var pct = completeness[field].percentage; + var icon = pct >= 80 ? '✅' : pct >= 50 ? '⚠️' : '❌'; + + gs.info(icon + ' ' + field + ': ' + pct.toFixed(1) + '%'); + gs.info(' Filled: ' + completeness[field].filled + ' / ' + completeness[field].total); + + if (pct < 50) { + gs.info(' ⚠️ LOW - This field may not be useful for training'); + } + gs.info(''); + } + + // ============================================ + // STEP 3: Text Quality Analysis + // ============================================ + gs.info(''); + gs.info('=== STEP 3: Text Quality Analysis ==='); + gs.info('Analyzing text field content quality'); + gs.info(''); + + var textQuality = analyzeTextQuality(); + + gs.info('Description Quality:'); + gs.info(' Average Length: ' + textQuality.description.avgLength.toFixed(0) + ' characters'); + gs.info(' Too Short (<20 chars): ' + textQuality.description.tooShort + ' (' + + (textQuality.description.tooShortPct).toFixed(1) + '%)'); + gs.info(' Good Quality: ' + textQuality.description.goodQuality + ' (' + + (textQuality.description.goodQualityPct).toFixed(1) + '%)'); + gs.info(''); + + gs.info('Close Notes Quality:'); + gs.info(' Average Length: ' + textQuality.closeNotes.avgLength.toFixed(0) + ' characters'); + gs.info(' Too Short (<50 chars): ' + textQuality.closeNotes.tooShort + ' (' + + (textQuality.closeNotes.tooShortPct).toFixed(1) + '%)'); + gs.info(' Good Quality: ' + textQuality.closeNotes.goodQuality + ' (' + + (textQuality.closeNotes.goodQualityPct).toFixed(1) + '%)'); + gs.info(''); + + // Implementation/Test/Backout Plan Quality + gs.info('Implementation Plan Quality:'); + gs.info(' Average Length: ' + textQuality.implementationPlan.avgLength.toFixed(0) + ' characters'); + gs.info(' Too Short (<30 chars): ' + textQuality.implementationPlan.tooShort + ' (' + + (textQuality.implementationPlan.tooShortPct).toFixed(1) + '%)'); + gs.info(' Good Quality: ' + textQuality.implementationPlan.goodQuality + ' (' + + (textQuality.implementationPlan.goodQualityPct).toFixed(1) + '%)'); + gs.info(''); + gs.info('Test Plan Quality:'); + gs.info(' Average Length: ' + textQuality.testPlan.avgLength.toFixed(0) + ' characters'); + gs.info(' Too Short (<30 chars): ' + textQuality.testPlan.tooShort + ' (' + + (textQuality.testPlan.tooShortPct).toFixed(1) + '%)'); + gs.info(' Good Quality: ' + textQuality.testPlan.goodQuality + ' (' + + (textQuality.testPlan.goodQualityPct).toFixed(1) + '%)'); + gs.info(''); + gs.info('Backout Plan Quality:'); + gs.info(' Average Length: ' + textQuality.backoutPlan.avgLength.toFixed(0) + ' characters'); + gs.info(' Too Short (<30 chars): ' + textQuality.backoutPlan.tooShort + ' (' + + (textQuality.backoutPlan.tooShortPct).toFixed(1) + '%)'); + gs.info(' Good Quality: ' + textQuality.backoutPlan.goodQuality + ' (' + + (textQuality.backoutPlan.goodQualityPct).toFixed(1) + '%)'); + gs.info(''); + + // ============================================ + // STEP 4: Category Distribution + // ============================================ + gs.info(''); + gs.info('=== STEP 4: Category Distribution ==='); + gs.info('Analyzing change request category spread'); + gs.info(''); + + var categoryDist = analyzeCategoryDistribution(); + + gs.info('Top 10 Categories:'); + for (var i = 0; i < Math.min(10, categoryDist.length); i++) { + var cat = categoryDist[i]; + gs.info(' ' + (i+1) + '. ' + (cat.category || '(empty)') + ': ' + cat.count + ' change requests'); + } + gs.info(''); + + if (categoryDist.length < 5) { + gs.warn('⚠️ Low category diversity - model may not generalize well'); + } else { + gs.info('✅ Good category diversity for training'); + } + + // ============================================ + // STEP 5: Resolution Time Analysis + // ============================================ + gs.info(''); + gs.info('=== STEP 5: Resolution Time Analysis ==='); + gs.info(''); + + var timeAnalysis = analyzeResolutionTimes(); + + gs.info('Resolution Times:'); + gs.info(' Average: ' + timeAnalysis.avgMinutes.toFixed(0) + ' minutes'); + gs.info(' Median: ' + timeAnalysis.medianMinutes.toFixed(0) + ' minutes'); + gs.info(' Too Quick (<10 min): ' + timeAnalysis.tooQuick + ' (' + + (timeAnalysis.tooQuickPct).toFixed(1) + '%)'); + gs.info(''); + + if (timeAnalysis.tooQuickPct > 30) { + gs.warn('⚠️ Many change requests closed very quickly'); + gs.info(' These may be duplicates or low-quality data'); + gs.info(' Consider filtering: end_date > start_date + 10 minutes'); + } + + // ============================================ + // STEP 6: Overall Quality Score + // ============================================ + gs.info(''); + gs.info('=== STEP 6: Overall Data Quality Score ==='); + gs.info(''); + + var overallScore = calculateOverallScore(completeness, textQuality, timeAnalysis); + + var scoreIcon = overallScore >= 80 ? '✅' : overallScore >= 60 ? '⚠️' : '❌'; + gs.info(scoreIcon + ' Overall Quality Score: ' + overallScore.toFixed(0) + '/100'); + gs.info(''); + + if (overallScore >= 80) { + gs.info('✅ EXCELLENT - Data is ready for high-quality training'); + } else if (overallScore >= 60) { + gs.info('⚠️ FAIR - Data can be used but consider improvements'); + } else { + gs.info('❌ POOR - Significant data quality issues exist'); + } + + // ============================================ + // STEP 7: Recommendations + // ============================================ + gs.info(''); + gs.info('========================================'); + gs.info('Analysis Complete'); + gs.info('========================================'); + + // ============================================ + // HELPER FUNCTIONS + // ============================================ + function getOverallStats() { + var result = { + total: 0, + closed: 0, + recent90: 0, + recent365: 0 + }; + // Total change requests + var totalGr = new GlideAggregate(config.table); + totalGr.addAggregate('COUNT'); + totalGr.query(); + if (totalGr.next()) { + result.total = parseInt(totalGr.getAggregate('COUNT')); + } + // Closed + var closedGr = new GlideAggregate(config.table); + closedGr.addQuery('state', config.states.closed); + closedGr.addAggregate('COUNT'); + closedGr.query(); + if (closedGr.next()) { + result.closed = parseInt(closedGr.getAggregate('COUNT')); + } + // Recent 90 days + var recent90Gr = new GlideAggregate(config.table); + recent90Gr.addQuery('state', config.states.closed); + recent90Gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(90)'); + recent90Gr.addAggregate('COUNT'); + recent90Gr.query(); + if (recent90Gr.next()) { + result.recent90 = parseInt(recent90Gr.getAggregate('COUNT')); + } + // Recent 365 days + var recent365Gr = new GlideAggregate(config.table); + recent365Gr.addQuery('state', config.states.closed); + recent365Gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(365)'); + recent365Gr.addAggregate('COUNT'); + recent365Gr.query(); + if (recent365Gr.next()) { + result.recent365 = parseInt(recent365Gr.getAggregate('COUNT')); + } + return result; + } + + function analyzeFieldCompleteness() { + var results = {}; + var totalGr = new GlideAggregate(config.table); + totalGr.addQuery('state', config.states.closed); + totalGr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')'); + totalGr.addAggregate('COUNT'); + totalGr.query(); + var total = 0; + if (totalGr.next()) { + total = parseInt(totalGr.getAggregate('COUNT')); + } + for (var f = 0; f < config.keyFields.length; f++) { + var fieldName = config.keyFields[f]; + if (!fieldExists(config.table, fieldName)) { + gs.warn('Field does not exist: ' + fieldName + ' - skipping completeness analysis for this field'); + continue; + } + var filledGr = new GlideAggregate(config.table); + filledGr.addQuery('state', config.states.closed); + filledGr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')'); + filledGr.addQuery(fieldName, '!=', ''); + filledGr.addNotNullQuery(fieldName); + filledGr.addAggregate('COUNT'); + filledGr.query(); + var filled = 0; + if (filledGr.next()) { + filled = parseInt(filledGr.getAggregate('COUNT')); + } + results[fieldName] = { + total: total, + filled: filled, + percentage: total > 0 ? (filled / total * 100) : 0 + }; + } + return results; + } + + function analyzeTextQuality() { + var gr = new GlideRecord(config.table); + gr.addQuery('state', config.states.closed); + gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')'); + gr.setLimit(config.sampleSize); + gr.query(); + var descStats = { totalLength: 0, count: 0, tooShort: 0, goodQuality: 0 }; + var closeNotesStats = { totalLength: 0, count: 0, tooShort: 0, goodQuality: 0 }; + var implementationPlanStats = { totalLength: 0, count: 0, tooShort: 0, goodQuality: 0 }; + var testPlanStats = { totalLength: 0, count: 0, tooShort: 0, goodQuality: 0 }; + var backoutPlanStats = { totalLength: 0, count: 0, tooShort: 0, goodQuality: 0 }; + while (gr.next()) { + var desc = gr.getValue('description') || ''; + if (desc) { + descStats.count++; + descStats.totalLength += desc.length; + if (desc.length < config.thresholds.minDescriptionLength) { + descStats.tooShort++; + } else { + descStats.goodQuality++; + } + } + var closeNotes = gr.getValue('close_notes') || ''; + if (closeNotes) { + closeNotesStats.count++; + closeNotesStats.totalLength += closeNotes.length; + if (closeNotes.length < config.thresholds.minCloseNotesLength) { + closeNotesStats.tooShort++; + } else { + closeNotesStats.goodQuality++; + } + } + var implementationPlan = gr.getValue('implementation_plan') || ''; + if (implementationPlan) { + implementationPlanStats.count++; + implementationPlanStats.totalLength += implementationPlan.length; + if (implementationPlan.length < config.thresholds.minImplementationPlanLength) { + implementationPlanStats.tooShort++; + } else { + implementationPlanStats.goodQuality++; + } + } + var testPlan = gr.getValue('test_plan') || ''; + if (testPlan) { + testPlanStats.count++; + testPlanStats.totalLength += testPlan.length; + if (testPlan.length < config.thresholds.minTestPlanLength) { + testPlanStats.tooShort++; + } else { + testPlanStats.goodQuality++; + } + } + var backoutPlan = gr.getValue('backout_plan') || ''; + if (backoutPlan) { + backoutPlanStats.count++; + backoutPlanStats.totalLength += backoutPlan.length; + if (backoutPlan.length < config.thresholds.minBackoutPlanLength) { + backoutPlanStats.tooShort++; + } else { + backoutPlanStats.goodQuality++; + } + } + } + return { + description: { + avgLength: descStats.count > 0 ? descStats.totalLength / descStats.count : 0, + tooShort: descStats.tooShort, + tooShortPct: descStats.count > 0 ? (descStats.tooShort / descStats.count * 100) : 0, + goodQuality: descStats.goodQuality, + goodQualityPct: descStats.count > 0 ? (descStats.goodQuality / descStats.count * 100) : 0 + }, + closeNotes: { + avgLength: closeNotesStats.count > 0 ? closeNotesStats.totalLength / closeNotesStats.count : 0, + tooShort: closeNotesStats.tooShort, + tooShortPct: closeNotesStats.count > 0 ? (closeNotesStats.tooShort / closeNotesStats.count * 100) : 0, + goodQuality: closeNotesStats.goodQuality, + goodQualityPct: closeNotesStats.count > 0 ? (closeNotesStats.goodQuality / closeNotesStats.count * 100) : 0 + }, + implementationPlan: { + avgLength: implementationPlanStats.count > 0 ? implementationPlanStats.totalLength / implementationPlanStats.count : 0, + tooShort: implementationPlanStats.tooShort, + tooShortPct: implementationPlanStats.count > 0 ? (implementationPlanStats.tooShort / implementationPlanStats.count * 100) : 0, + goodQuality: implementationPlanStats.goodQuality, + goodQualityPct: implementationPlanStats.count > 0 ? (implementationPlanStats.goodQuality / implementationPlanStats.count * 100) : 0 + }, + testPlan: { + avgLength: testPlanStats.count > 0 ? testPlanStats.totalLength / testPlanStats.count : 0, + tooShort: testPlanStats.tooShort, + tooShortPct: testPlanStats.count > 0 ? (testPlanStats.tooShort / testPlanStats.count * 100) : 0, + goodQuality: testPlanStats.goodQuality, + goodQualityPct: testPlanStats.count > 0 ? (testPlanStats.goodQuality / testPlanStats.count * 100) : 0 + }, + backoutPlan: { + avgLength: backoutPlanStats.count > 0 ? backoutPlanStats.totalLength / backoutPlanStats.count : 0, + tooShort: backoutPlanStats.tooShort, + tooShortPct: backoutPlanStats.count > 0 ? (backoutPlanStats.tooShort / backoutPlanStats.count * 100) : 0, + goodQuality: backoutPlanStats.goodQuality, + goodQualityPct: backoutPlanStats.count > 0 ? (backoutPlanStats.goodQuality / backoutPlanStats.count * 100) : 0 + } + }; + } + + function analyzeCategoryDistribution() { + var catGr = new GlideAggregate(config.table); + catGr.addQuery('state', config.states.closed); + catGr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')'); + catGr.groupBy('category'); + catGr.addAggregate('COUNT'); + catGr.orderByAggregate('COUNT'); + catGr.query(); + var categories = []; + while (catGr.next()) { + categories.push({ + category: catGr.getValue('category'), + count: parseInt(catGr.getAggregate('COUNT')) + }); + } + categories.sort(function(a, b) { return b.count - a.count; }); + return categories; + } + + function analyzeResolutionTimes() { + var gr = new GlideRecord(config.table); + gr.addQuery('state', config.states.closed); + gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')'); + gr.addNotNullQuery('start_date'); + gr.addNotNullQuery('end_date'); + gr.setLimit(config.sampleSize); + gr.query(); + var times = []; + var tooQuick = 0; + while (gr.next()) { + var start = new GlideDateTime(gr.getValue('start_date')); + var end = new GlideDateTime(gr.getValue('end_date')); + var diff = GlideDateTime.subtract(start, end); + var minutes = diff.getNumericValue() / 1000 / 60; + if (minutes > 0) { + times.push(minutes); + if (minutes < config.thresholds.minResolutionTime) { + tooQuick++; + } + } + } + times.sort(function(a, b) { return a - b; }); + var avgMinutes = 0; + if (times.length > 0) { + var sum = 0; + for (var t = 0; t < times.length; t++) { + sum += times[t]; + } + avgMinutes = sum / times.length; + } + var medianMinutes = 0; + if (times.length > 0) { + var midIdx = Math.floor(times.length / 2); + medianMinutes = times[midIdx]; + } + return { + avgMinutes: avgMinutes, + medianMinutes: medianMinutes, + tooQuick: tooQuick, + tooQuickPct: times.length > 0 ? (tooQuick / times.length * 100) : 0, + sampleSize: times.length + }; + } + + function calculateOverallScore(completeness, textQuality, timeAnalysis) { + var score = 0; + var weights = { + completeness: 40, + textQuality: 40, + timeQuality: 20 + }; + var compTotal = 0; + var compCount = 0; + for (var field in completeness) { + compTotal += completeness[field].percentage; + compCount++; + } + var compScore = compCount > 0 ? (compTotal / compCount) : 0; + score += (compScore / 100) * weights.completeness; + var textScore = ( + textQuality.description.goodQualityPct + + textQuality.closeNotes.goodQualityPct + + textQuality.implementationPlan.goodQualityPct + + textQuality.testPlan.goodQualityPct + + textQuality.backoutPlan.goodQualityPct + ) / 5; + score += (textScore / 100) * weights.textQuality; + var timeScore = 100 - timeAnalysis.tooQuickPct; + score += (timeScore / 100) * weights.timeQuality; + return score; + } + + +})(); diff --git a/Specialized Areas/Predictive Intelligence/Training Data Preparer/README.md b/Specialized Areas/Predictive Intelligence/Training Data Preparer/Incident Request/README.md similarity index 100% rename from Specialized Areas/Predictive Intelligence/Training Data Preparer/README.md rename to Specialized Areas/Predictive Intelligence/Training Data Preparer/Incident Request/README.md diff --git a/Specialized Areas/Predictive Intelligence/Training Data Preparer/analyze_incident_data_training_quality.js b/Specialized Areas/Predictive Intelligence/Training Data Preparer/Incident Request/analyze_incident_data_training_quality.js similarity index 99% rename from Specialized Areas/Predictive Intelligence/Training Data Preparer/analyze_incident_data_training_quality.js rename to Specialized Areas/Predictive Intelligence/Training Data Preparer/Incident Request/analyze_incident_data_training_quality.js index c34d6440d2..c1f0cd3e05 100644 --- a/Specialized Areas/Predictive Intelligence/Training Data Preparer/analyze_incident_data_training_quality.js +++ b/Specialized Areas/Predictive Intelligence/Training Data Preparer/Incident Request/analyze_incident_data_training_quality.js @@ -36,7 +36,7 @@ var field = new GlideRecord('sys_dictionary'); field.addQuery('name', 'IN', tables.join(',')); field.query(); - // Removed printout of all available fields + } printAllFields('incident');