We've updated the ServiceNow Community Code of Conduct, adding guidelines around AI usage, professionalism, and content violations. Read more

After creating a change request, we need to automate the Risk assessment.

PradeepP5795880
Kilo Contributor

After creating a change request, we need to automate the Risk assessment. We created an Action that contains a script where we pass input variables such as the Change Sys ID. We have attached the script is provided below. The record is being created in the Assessment instance and is linked to the Change Request. However, when we open the Change Request and click on Risk Assessment, the questions are not getting updated.

(function execute(inputs, outputs) {
    // -------------------- Config --------------------
    // If you know the Metric Type sys_id, put it here for best performance.
    var METRIC_TYPE_ID = ''; // e.g., 'b1c2d3e4f5...'
    var METRIC_TYPE_NAMES = ['Change Risk Assessment', 'Change risk assessment', 'Change Risk'];

    // -------------------- Helpers --------------------
    function pick() {
        for (var i = 0; i < arguments.length; i++) {
            var v = arguments[i];
            if (v !== undefined && v !== null && ('' + v).trim() !== '') return v;
        }
        return '';
    }
    function normStr(s) { return (s + '').trim().toLowerCase(); }
    function nowIso() { return new Date().toISOString(); }

    function safeInt(n, dflt) {
        var x = parseInt(n, 10);
        return isNaN(x) ? dflt : x;
    }

    function getMetricTypeSysId() {
        if (METRIC_TYPE_ID && ('' + METRIC_TYPE_ID).length === 32) return METRIC_TYPE_ID;
        for (var i = 0; i < METRIC_TYPE_NAMES.length; i++) {
            var mt = new GlideRecord('asmt_metric_type');
            mt.addActiveQuery();
            mt.addQuery('name', METRIC_TYPE_NAMES[i]);
            mt.setLimit(1);
            mt.query();
            if (mt.next()) return String(mt.getUniqueValue());
        }
        return '';
    }

    // -------------------- Inputs --------------------
    var changeSysId       = String(pick(inputs.changeSysId, inputs.changesysid));
    var affectsCritical   = String(pick(inputs.affectsCritical, inputs.affectscritical,   'No'));
    var complexity        = String(pick(inputs.complexity,     inputs.complexity,        'Low'));
    // Accept both spellings "backout" and "backup"
    var backoutDifficulty = String(pick(inputs.backoutDifficulty, inputs.backoutdifficulty, inputs.backupDifficulty, 'Low'));
    var redundancyPlan    = String(pick(inputs.redundancyPlan, inputs.redundancyplan,     'Yes'));
    var verifyDifficulty  = String(pick(inputs.verifyDifficulty, inputs.verifydifficulty,  'Low'));
    var soxRelated        = String(pick(inputs.soxRelated,      inputs.soxrelated,        'No'));
    // Initialize outputs to safe defaults
    outputs.assessmentinstance = '';
    outputs.riskscore = '';
    outputs.risklevel = '';
    // IMPORTANT: If your Action variable "answersapplied" is JSON type, assign an object.
    // If it is String type, assign JSON.stringify(obj) instead (see later).
    var answersObj = null;

    if (!changeSysId) {
        gs.error('[AutoRisk] No Change Request sys_id provided.');
        // Still return a minimal answers payload for transparency
        answersObj = {
            error: 'No changeSysId',
            inputs: { affectsCritical: affectsCritical, complexity: complexity, backoutDifficulty: backoutDifficulty, redundancyPlan: redundancyPlan, verifyDifficulty: verifyDifficulty, soxRelated: soxRelated },
            runAt: nowIso()
        };
        outputs.answersapplied = answersObj; // If String type, wrap with JSON.stringify(answersObj)
        return;
    }

    // -------------------- Risk score calculation (independent of assessment) --------------------
    function weightYesNo(v, yesPts, noPts) {
        return normStr(v) === 'yes' ? yesPts : noPts;
    }
    function weightLMH(v, lowPts, medPts, highPts) {
        var k = normStr(v);
        if (k === 'low' || k === 'not complex' || k === 'easy') return lowPts;
        if (k === 'somewhat complex' || k === 'somewhat' || k === 'medium' || k === 'moderate') return medPts;
        if (k === 'high' || k === 'severe' || k === 'hard' || k === 'complex' || k === 'highly complex') return highPts;
        return 0;
    }

    var calcScore = 0;
    // 1) Affects critical services?
    calcScore += weightYesNo(affectsCritical, 25, 0);
    // 2) Complexity
    calcScore += weightLMH(complexity, 5, 10, 20);
    // 3) Backout difficulty
    calcScore += weightLMH(backoutDifficulty, 5, 10, 20);
    // 4) Redundancy plan (Yes reduces risk)
    calcScore += (normStr(redundancyPlan) === 'yes') ? 0 : 15;
    // 5) Verify/test difficulty
    calcScore += weightLMH(verifyDifficulty, 5, 10, 20);
    // 6) SOX related?
    calcScore += weightYesNo(soxRelated, 20, 0);

    var calcLevel = 'Low';
    if (calcScore >= 70) calcLevel = 'Critical';
    else if (calcScore >= 45) calcLevel = 'High';
    else if (calcScore >= 20) calcLevel = 'Moderate';

    // Pre-fill outputs from our calculation (do NOT rely on assessment instance fields)
    outputs.riskscore = String(calcScore);
    outputs.risklevel = calcLevel;

    // Also prepare our answers object (for the JSON output)
    answersObj = {
        changeSysId: changeSysId,
        inputs: {
            affectsCritical: affectsCritical,
            complexity: complexity,
            backoutDifficulty: backoutDifficulty,
            redundancyPlan: redundancyPlan,
            verifyDifficulty: verifyDifficulty,
            soxRelated: soxRelated
        },
        calculation: {
            score: calcScore,
            level: calcLevel
        },
        runAt: nowIso()
    };

    // -------------------- Assessment: find or create instance --------------------
    var metricTypeSysId = getMetricTypeSysId();
    if (!metricTypeSysId) {
        gs.error("[AutoRisk] Metric type not found. Looked for: " + METRIC_TYPE_NAMES.join(', '));
        // Still return a useful payload
        outputs.answersapplied = answersObj; // If String type, use JSON.stringify(answersObj)
        return;
    }

    function findExistingInstance(changeId, mtSysId) {
        var ai = new GlideRecord('asmt_assessment_instance');
        ai.addQuery('metric_type', mtSysId);
        ai.addQuery('trigger_id', changeId); // link to the record being assessed
        ai.orderByDesc('sys_created_on');
        ai.setLimit(1);
        ai.query();
        return ai.next() ? String(ai.getUniqueValue()) : '';
    }

    function createInstanceViaAPI(mtSysId, changeId) {
        try {
            // Preferred signature: createAssessments(metricTypeSysId, triggerRecordSysId)
            if (typeof sn_assessment !== 'undefined' && sn_assessment.AssessmentCreation) {
                var ids = new sn_assessment.AssessmentCreation().createAssessments(mtSysId, changeId);
                if (ids && ids.length > 0) return String(ids[0]);
            }
        } catch (e) {
            gs.warn('[AutoRisk] AssessmentCreation API path failed: ' + e.message);
        }
        return '';
    }

    function createInstanceManually(mtSysId, changeId) {
        var ai = new GlideRecord('asmt_assessment_instance');
        ai.initialize();
        ai.metric_type = mtSysId;
        ai.state = 'ready';
        // IMPORTANT links
        if (ai.isValidField('trigger_table')) ai.trigger_table = 'change_request';
        if (ai.isValidField('trigger_id'))    ai.trigger_id    = changeId;
        // Optional assignment
        var ch = new GlideRecord('change_request');
        if (ch.get(changeId)) {
            if (ai.isValidField('user') && ch.assigned_to) ai.user = ch.assigned_to;
            if (ai.isValidField('short_description')) ai.short_description = 'Change Risk Assessment: ' + (ch.number || changeId);
        }
        return ai.insert() || '';
    }

    var instanceSysId = findExistingInstance(changeSysId, metricTypeSysId);
    if (!instanceSysId) instanceSysId = createInstanceViaAPI(metricTypeSysId, changeSysId);
    if (!instanceSysId) instanceSysId = createInstanceManually(metricTypeSysId, changeSysId);

    if (!instanceSysId) {
        gs.error('[AutoRisk] Failed to create/fetch assessment instance.');
        outputs.answersapplied = answersObj; // If String type, use JSON.stringify(answersObj)
        return;
    }

    outputs.assessmentinstance = instanceSysId;
    gs.info('[AutoRisk] Assessment instance = ' + instanceSysId);

    // -------------------- Ensure instance questions exist --------------------
    function countInstanceQuestions(instId) {
        var ga = new GlideAggregate('asmt_assessment_instance_question');
        ga.addQuery('instance', instId);
        ga.addAggregate('COUNT');
        ga.query();
        return ga.next() ? safeInt(ga.getAggregate('COUNT'), 0) : 0;
    }

    function createQuestionsViaAPI(instId) {
        try {
            if (typeof sn_assessment !== 'undefined' && sn_assessment.AssessmentCreation) {
                var ai = new GlideRecord('asmt_assessment_instance');
                if (ai.get(instId)) {
                    new sn_assessment.AssessmentCreation().createInstanceQuestions(ai);
                    return true;
                }
            }
        } catch (e) {
            gs.warn('[AutoRisk] createInstanceQuestions API failed: ' + e.message);
        }
        return false;
    }

    function createQuestionsManually(instId, mtSysId) {
        var made = 0;
        var m = new GlideRecord('asmt_metric');
        m.addActiveQuery();
        m.addQuery('metric_type', mtSysId);
        m.orderBy('order');
        m.query();
        while (m.next()) {
            var iq = new GlideRecord('asmt_assessment_instance_question');
            iq.initialize();
            iq.instance = instId;
            iq.metric   = m.getUniqueValue();
            // Stamp source to point back to the change (questions grid shows these)
            if (iq.isValidField('source_table')) iq.source_table = 'change_request';
            if (iq.isValidField('source_id'))    iq.source_id    = changeSysId;
            else if (iq.isValidField('source'))  iq.source       = changeSysId;
            iq.insert();
            made++;
        }
       
        return made;
    }

    var qBefore = countInstanceQuestions(instanceSysId);
    if (qBefore === 0) {
        var apiMade = createQuestionsViaAPI(instanceSysId);
        var qAfter = countInstanceQuestions(instanceSysId);
        if (!apiMade || qAfter === 0) {
            var manual = createQuestionsManually(instanceSysId, metricTypeSysId);
            gs.info('[AutoRisk] Manually created ' + manual + ' question row(s).');
        } else {
            gs.info('[AutoRisk] Questions created via API: ' + qAfter);
        }
    } else {
        gs.info('[AutoRisk] Instance already has ' + qBefore + ' question(s).');
    }

    // -------------------- Map our inputs to metrics and write results --------------------
    // We will search metrics by "contains" on their label/question
    var metrics = []; // {sys_id, key, label}
    var mm = new GlideRecord('asmt_metric');
    mm.addQuery('metric_type', metricTypeSysId);
    mm.addActiveQuery();
    mm.query();
    while (mm.next()) {
        var name = String(mm.getValue('name') || mm.getDisplayValue('name') || '');
        var label = name;
        if (mm.isValidField('question'))
            label = String(mm.getValue('question') || mm.getDisplayValue('question') || name);
        else if (mm.isValidField('label'))
            label = String(mm.getValue('label') || mm.getDisplayValue('label') || name);
        metrics.push({ sys_id: String(mm.getUniqueValue()), key: normStr(label || name), label: label });
    }

    function findMetricByContains(parts) {
        var wants = [].concat(parts || []).map(normStr);
        for (var i = 0; i < metrics.length; i++) {
            for (var j = 0; j < wants.length; j++) {
                if (metrics[i].key.indexOf(wants[j]) > -1) return metrics[i];
            }
        }
        return null;
    }

    // Map your answers to the likely metric labels (adjust patterns to your metric wording)
    var mapping = [
        { want: affectsCritical,   find: ['affect', 'critical', 'business service', 'critical ci'] },
        { want: complexity,        find: ['complex', 'complexity'] },
        { want: backoutDifficulty, find: ['backout', 'back out', 'revert'] },
        { want: redundancyPlan,    find: ['redundancy plan', 'rollback plan', 'backout plan'] },
        { want: verifyDifficulty,  find: ['verify successful', 'verify the change', 'verification'] },
        { want: soxRelated,        find: ['sox'] }
    ];

    // Value mapping for numeric weight stored with each metric result (optional)
    function mapWeight(label) {
        var L = (label || '').trim();
        var weights = {
            'No': 0, 'Some (LOCAL)': 5, 'Yes (GLOBAL)': 10,
            'Not complex': 0, 'Somewhat complex': 5, 'Highly complex': 10,
            'Easy': 0, 'Somewhat Complex': 5, 'Complex': 10,
            'Yes': 0, 'Some redundancy': 5, 'Moderate': 5, 'Hard': 10
        };
        return weights.hasOwnProperty(L) ? weights[L] : 0;
    }

    function upsertMetricResult(instanceId, metricSysId, displayValue, numericValue) {

        // var results = new GlideRecord('asmt_metric_result');
        // results.addEncodedQuery('instance.task_id=' + inputs.changeSysId);
        // results.query();
        // results.actual_value = 0;
        // results.updateMultiple();
        // var r = new GlideRecord('asmt_metric_result');
        // r.addQuery('instance', instanceId);
        // r.addQuery('metric', metricSysId);
        // r.setLimit(1);
        // r.query();
        // var exists = r.next();
        // if (!exists) { r.initialize(); r.instance = instanceId; r.metric = metricSysId; }

        // if (r.isValidField('display_value')) r.display_value = String(displayValue || '');
        // if (r.isValidField('value'))         r.value         = String(numericValue != null ? numericValue : '');
        // if (r.isValidField('answer_text'))   r.answer_text   = String(displayValue || '');
        // if (r.isValidField('answer_value'))  r.answer_value  = String(numericValue != null ? numericValue : '');
        // return exists ? r.update() : r.insert();
    }
    var a = new GlideRecord('asmt_assessment_instance_question');
    a.addQuery('instance', instanceSysId);
    a.query();

    while (a.next()){
        var results = new GlideRecord('asmt_metric_result');
        results.initialize();
        results.instance = a.instance;
        results.metric = a.metric;
        results.actual_value = 0;
        results.source_id = a.source_id;
        results.insert();
    }

    var answered = 0;
    for (var k = 0; k < mapping.length; k++) {
        var map = mapping[k];
        if (!map.want) continue;
        var metric = findMetricByContains(map.find);
        if (!metric) {
            gs.warn('[AutoRisk] Metric not found for: ' + JSON.stringify(map.find));
            continue;
        }
        var weight = mapWeight(map.want);
        upsertMetricResult(instanceSysId, metric.sys_id, map.want, weight);
        answered++;
    }
    gs.info('[AutoRisk] Total metrics answered: ' + answered);

    // -------------------- Submit / complete the assessment --------------------
    var submitted = false;
    try {
        if (typeof sn_assessment !== 'undefined' && sn_assessment.AssessmentEvaluation) {
            new sn_assessment.AssessmentEvaluation().submit(instanceSysId);
            submitted = true;
            gs.info('[AutoRisk] Submitted via AssessmentEvaluation API.');
        }
    } catch (e) {
        gs.warn('[AutoRisk] AssessmentEvaluation API failed: ' + e.message);
    }
    if (!submitted) {
        var inst = new GlideRecord('asmt_assessment_instance');
        if (inst.get(instanceSysId)) {
            if (inst.isValidField('state'))     inst.state = 'complete';
            if (inst.isValidField('submitted')) inst.submitted = true;
            if (inst.isValidField('taken'))     inst.taken = true;
            inst.update();
            gs.info('[AutoRisk] Marked assessment instance complete (fallback).');
        }
    }

    // -------------------- Finalize outputs --------------------
    outputs.assessmentinstance = instanceSysId;     // String
    outputs.riskscore = String(calcScore);          // our calculated score (always populated)
    outputs.risklevel = String(calcLevel);          // our calculated level (always populated)

    // If your output variable "answersapplied" is JSON type:
    outputs.answersapplied = answersObj;

    // If instead your Action defined "answersapplied" as String, comment the previous line and use:
    // outputs.answersapplied = JSON.stringify(answersObj);

})(inputs, outputs);




2 REPLIES 2

Matthew_13
Mega Sage

Hi Buddy,

You’re successfully creating the assessment instance, but the answers never show because they’re not actually being written in the way ServiceNow expects for Risk Assessments.

Two things in your script are causing it:

  1. The function that should save answers (upsertMetricResult) is commented out
    So when the script runs, it calculates values… but never persists them to the assessment.

  2. You’re inserting empty metric results with actual_value = 0 for every question
    That creates rows, but they’re treated as unanswered/neutral by the Risk UI, so when you open Risk Assessment from the Change, nothing appears populated.

In ServiceNow, the Risk Assessment UI only reflects answers when:

  • an asmt_metric_result exists for the metric

  • AND it contains the value tied to the metric’s configured choices (not a custom weight you calculate)

  • AND the instance question is linked correctly

Right now:

  • instances are created ✔

  • questions are created ✔

  • resuults are inserted (they’re blank and never updated)

What you should change:

• Remove the loop inserting default metric results with actual_value = 0
• Implement upsertMetricResult() so it:

  • finds the metric result for the instance + metric

  • writes the selected choice label

  • writes the numeric value tied to that metric choice from the metric definition, not your own scoring table

Also: the risk score shown in Change is calculated from the metric definitions and their weights. If you inject your own numeric scoring, the platform won’t reconcile it with the assessment and the UI won’t reflect answers correctly.

Bottom line:
You’re automating instance creation correctly. The break is at the “answer persistence” layer — ServiceNow isn’t seeing valid metric results tied to metric definitions, so the Risk Assessment link opens an empty questionnaire.

 

@PradeepP5795880 - If this help you answer, Please mark Accepted Solution.

MJG

PradeepP5795880
Kilo Contributor

Can anyone please help me on this