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

PradeepP5795880
Giga Explorer

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);




0 REPLIES 0