Risk Assessment Summarization

SkillDev tools

Summarize risk assessments with scoring, trends, heat map data, and mitigation recommendations covering inherent vs residual risk analysis

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Risk Assessment Summarization skill

What this skill tells your AI

The instructions your AI receives, as published by happy-technologies-llc/happy-platform-skills in skills/grc/risk-assessment-summarization/SKILL.md and read by ahel’s review.

Overview

This skill produces comprehensive summaries of risk assessments in ServiceNow GRC, including:

  • Aggregating risk scores across profiles, categories, and business units
  • Comparing inherent risk versus residual risk to measure control effectiveness
  • Generating heat map data (likelihood x impact matrices) for visual reporting
  • Identifying risk trends over time (increasing, stable, decreasing)
  • Recommending mitigation strategies based on treatment plans and residual exposure
  • Linking risk assessments to underlying risk events and control effectiveness

When to use:

  • Preparing risk committee or board-level risk reports
  • After completing a risk assessment cycle and needing consolidated results
  • When comparing risk posture across business units or time periods
  • During strategic planning to understand enterprise risk exposure
  • When evaluating effectiveness of risk mitigation investments

Prerequisites

  • Roles: sn_risk.manager, sn_grc.manager, or admin
  • Plugins: com.sn_risk, com.sn_grc
  • Access: Read access to sn_grc_risk, sn_risk_definition, sn_risk_event, sn_grc_profile tables
  • Knowledge: Understanding of risk scoring methodology (likelihood x impact) and organizational risk appetite

Key Risk Tables

TablePurposeKey Fields
sn_grc_riskRisk register recordsnumber, short_description, risk_score, inherent_risk, residual_risk, state, treatment, likelihood, impact, profile, category, owner
sn_risk_definitionRisk framework definitionsnumber, short_description, risk_criteria, scoring_method, appetite
sn_risk_eventActual risk events/incidentsnumber, short_description, event_date, impact_amount, risk, category
sn_grc_profileEntity profiles for risk scopingnumber, short_description, profile_type, applies_to
sn_grc_itemGRC content itemsnumber, short_description, item_type, profile

Procedure

Step 1: Retrieve Active Risk Records

Query all active risks with scoring data.

Using MCP (Claude Code/Desktop):

Tool: SN-Query-Table
Parameters:
  table_name: sn_grc_risk
  query: active=true^ORDERBYDESCrisk_score
  fields: sys_id,number,short_description,risk_score,inherent_risk,residual_risk,state,treatment,likelihood,impact,profile,category,owner,sys_created_on,sys_updated_on
  limit: 100

Using REST API:

GET /api/now/table/sn_grc_risk?sysparm_query=active=true^ORDERBYDESCrisk_score&sysparm_fields=sys_id,number,short_description,risk_score,inherent_risk,residual_risk,state,treatment,likelihood,impact,profile,category,owner&sysparm_limit=100

Step 2: Retrieve Risk Framework Definitions

Understand the scoring methodology and risk appetite thresholds.

Using MCP:

Tool: SN-Query-Table
Parameters:
  table_name: sn_risk_definition
  query: active=true
  fields: sys_id,number,short_description,risk_criteria,scoring_method,appetite,description
  limit: 20

Using REST API:

GET /api/now/table/sn_risk_definition?sysparm_query=active=true&sysparm_fields=sys_id,number,short_description,risk_criteria,scoring_method,appetite,description&sysparm_limit=20

Step 3: Generate Risk Assessment Summary with Inherent vs Residual Analysis

Produce aggregated risk metrics and control effectiveness:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    var summary = {
      generated_date: new GlideDateTime().toString(),
      total_risks: 0,
      by_treatment: {},
      by_category: {},
      inherent_vs_residual: {
        avg_inherent: 0,
        avg_residual: 0,
        control_effectiveness_pct: 0,
        risks_above_appetite: 0
      },
      severity_distribution: { critical: 0, high: 0, medium: 0, low: 0 },
      top_risks: []
    };

    var inherentSum = 0, residualSum = 0, count = 0;
    var riskAppetite = 50; // Default; adjust per organization

    var gr = new GlideRecord('sn_grc_risk');
    gr.addQuery('active', true);
    gr.orderByDesc('risk_score');
    gr.query();

    while (gr.next()) {
      summary.total_risks++;
      count++;

      var inherent = parseInt(gr.inherent_risk.toString()) || 0;
      var residual = parseInt(gr.residual_risk.toString()) || 0;
      var score = parseInt(gr.risk_score.toString()) || 0;

      inherentSum += inherent;
      residualSum += residual;

      if (residual > riskAppetite) summary.inherent_vs_residual.risks_above_appetite++;

      // Severity distribution
      if (score >= 80) summary.severity_distribution.critical++;
      else if (score >= 60) summary.severity_distribution.high++;
      else if (score >= 30) summary.severity_distribution.medium++;
      else summary.severity_distribution.low++;

      // Treatment breakdown
      var treat = gr.treatment.getDisplayValue() || 'Not Assigned';
      summary.by_treatment[treat] = (summary.by_treatment[treat] || 0) + 1;

      // Category breakdown
      var cat = gr.category.getDisplayValue() || 'Uncategorized';
      summary.by_category[cat] = (summary.by_category[cat] || 0) + 1;

      // Top 10 risks
      if (summary.top_risks.length < 10) {
        summary.top_risks.push({
          number: gr.number.toString(),
          title: gr.short_description.toString(),
          risk_score: score,
          inherent: inherent,
          residual: residual,
          treatment: gr.treatment.getDisplayValue(),
          owner: gr.owner.getDisplayValue()
        });
      }
    }

    if (count > 0) {
      summary.inherent_vs_residual.avg_inherent = Math.round(inherentSum / count);
      summary.inherent_vs_residual.avg_residual = Math.round(residualSum / count);
      if (inherentSum > 0) {
        summary.inherent_vs_residual.control_effectiveness_pct =
          Math.round(((inherentSum - residualSum) / inherentSum) * 100);
      }
    }

    gs.info('RISK ASSESSMENT SUMMARY:\n' + JSON.stringify(summary, null, 2));
  description: "GRC: Generate risk assessment summary with inherent vs residual analysis"

Step 4: Generate Heat Map Data (Likelihood x Impact Matrix)

Build a likelihood-impact matrix for heat map visualization:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Heat map: 5x5 matrix (likelihood x impact)
    var heatMap = {};
    for (var l = 1; l <= 5; l++) {
      for (var i = 1; i <= 5; i++) {
        heatMap[l + 'x' + i] = { likelihood: l, impact: i, count: 0, risks: [] };
      }
    }

    var gr = new GlideRecord('sn_grc_risk');
    gr.addQuery('active', true);
    gr.query();

    while (gr.next()) {
      var likelihood = parseInt(gr.likelihood.toString()) || 0;
      var impact = parseInt(gr.impact.toString()) || 0;

      // Normalize to 1-5 scale if needed
      if (likelihood > 5) likelihood = Math.ceil(likelihood / 20);
      if (impact > 5) impact = Math.ceil(impact / 20);
      if (likelihood < 1) likelihood = 1;
      if (impact < 1) impact = 1;

      var key = likelihood + 'x' + impact;
      if (heatMap[key]) {
        heatMap[key].count++;
        if (heatMap[key].risks.length < 3) {
          heatMap[key].risks.push(gr.number.toString() + ': ' + gr.short_description.toString());
        }
      }
    }

    // Format for output
    var matrix = [];
    for (var cell in heatMap) {
      if (heatMap[cell].count > 0) {
        matrix.push(heatMap[cell]);
      }
    }
    matrix.sort(function(a, b) { return (b.likelihood * b.impact) - (a.likelihood * a.impact); });

    gs.info('RISK HEAT MAP DATA:\n' + JSON.stringify(matrix, null, 2));
    gs.info('\nHEAT MAP LEGEND:');
    gs.info('  Critical (Red): Likelihood >= 4 AND Impact >= 4');
    gs.info('  High (Orange): Score >= 12');
    gs.info('  Medium (Yellow): Score >= 6');
    gs.info('  Low (Green): Score < 6');
  description: "GRC: Generate risk heat map data (likelihood x impact matrix)"

Step 5: Analyze Risk Trends Over Time

Compare current risk posture to historical baseline:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    var periods = [
      { label: 'Current', days: 0 },
      { label: '30 Days Ago', days: 30 },
      { label: '60 Days Ago', days: 60 },
      { label: '90 Days Ago', days: 90 }
    ];

    var trends = [];
    for (var p = 0; p < periods.length; p++) {
      var period = periods[p];
      var totalScore = 0, count = 0, critical = 0;

      if (period.days === 0) {
        var gr = new GlideRecord('sn_grc_risk');
        gr.addQuery('active', true);
        gr.query();
        while (gr.next()) {
          var s = parseInt(gr.risk_score.toString()) || 0;
          totalScore += s;
          count++;
          if (s >= 80) critical++;
        }
      } else {
        // Use risk events as proxy for historical risk levels
        var ev = new GlideAggregate('sn_risk_event');
        ev.addQuery('sys_created_on', '>=', gs.daysAgo(period.days));
        ev.addQuery('sys_created_on', '<', gs.daysAgo(period.days - 30));
        ev.addAggregate('COUNT');
        ev.query();
        if (ev.next()) count = parseInt(ev.getAggregate('COUNT'));
      }

      trends.push({
        period: period.label,
        total_risks: count,
        avg_score: count > 0 ? Math.round(totalScore / count) : 0,
        critical_risks: critical
      });
    }

    gs.info('RISK TREND ANALYSIS:\n' + JSON.stringify(trends, null, 2));
  description: "GRC: Analyze risk score trends over time"

Step 6: Generate Mitigation Recommendations

Produce actionable recommendations based on risk treatment and residual exposure:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    var recommendations = [];

    var gr = new GlideRecord('sn_grc_risk');
    gr.addQuery('active', true);
    gr.addQuery('residual_risk', '>', 50);
    gr.orderByDesc('residual_risk');
    gr.setLimit(20);
    gr.query();

    while (gr.next()) {
      var inherent = parseInt(gr.inherent_risk.toString()) || 0;
      var residual = parseInt(gr.residual_risk.toString()) || 0;
      var reduction = inherent > 0 ? Math.round(((inherent - residual) / inherent) * 100) : 0;
      var treatment = gr.treatment.getDisplayValue() || 'Not Assigned';

      var rec = {
        risk_number: gr.number.toString(),
        title: gr.short_description.toString(),
        inherent_risk: inherent,
        residual_risk: residual,
        risk_reduction_pct: reduction,
        current_treatment: treatment,
        recommendations: []
      };

      // Generate recommendations based on treatment and residual level
      if (treatment === 'Accept' && residual >= 70) {
        rec.recommendations.push('Re-evaluate acceptance decision; residual risk exceeds organizational appetite');
        rec.recommendations.push('Conduct cost-benefit analysis for additional controls');
      }
      if (treatment === 'Mitigate' && reduction < 30) {
        rec.recommendations.push('Current controls are insufficient; risk reduction is only ' + reduction + '%');
        rec.recommendations.push('Evaluate additional compensating controls or alternative mitigation strategies');
      }
      if (treatment === 'Transfer' && residual >= 60) {
        rec.recommendations.push('Verify insurance coverage adequacy for residual exposure');
        rec.recommendations.push('Review third-party SLAs and liability clauses');
      }
      if (treatment === 'Not Assigned') {
        rec.recommendations.push('URGENT: Assign risk treatment strategy immediately');
        rec.recommendations.push('Schedule risk owner meeting to determine treatment approach');
      }
      if (residual >= 80) {
        rec.recommendations.push('CRITICAL: Escalate to executive leadership for immediate action');
      }

      // Check for related control failures
      var ctrl = new GlideRecord('sn_compliance_control');
      ctrl.addQuery('profile', gr.profile.toString());
      ctrl.addQuery('state', 'NOT IN', 'compliant,passed');
      ctrl.query();
      if (ctrl.getRowCount() > 0) {
        rec.recommendations.push('Address ' + ctrl.getRowCount() + ' failing controls linked to this risk profile');
      }

      recommendations.push(rec);
    }

    gs.info('MITIGATION RECOMMENDATIONS:\n' + JSON.stringify(recommendations, null, 2));
  description: "GRC: Generate mitigation recommendations for high-residual risks"

Step 7: Query Risk Events for Loss Data Context

Retrieve actual risk events to ground the assessment in real incidents:

Using MCP:

Tool: SN-Query-Table
Parameters:
  table_name: sn_risk_event
  query: sys_created_on>=javascript:gs.daysAgo(365)^ORDERBYDESCimpact_amount
  fields: sys_id,number,short_description,event_date,impact_amount,risk,category,state
  limit: 50

Using REST API:

GET /api/now/table/sn_risk_event?sysparm_query=sys_created_on>=javascript:gs.daysAgo(365)^ORDERBYDESCimpact_amount&sysparm_fields=sys_id,number,short_description,event_date,impact_amount,risk,category,state&sysparm_limit=50

Tool Usage

OperationMCP ToolREST Endpoint
Query RisksSN-Query-TableGET /api/now/table/sn_grc_risk
Query Risk DefinitionsSN-Query-TableGET /api/now/table/sn_risk_definition
Query Risk EventsSN-Query-TableGET /api/now/table/sn_risk_event
Search RisksSN-Natural-Language-SearchN/A
Complex AnalysisSN-Execute-Background-ScriptPOST /api/now/table/sys_trigger
Schema DiscoverySN-Discover-Table-SchemaGET /api/now/table/sys_dictionary

Best Practices

  • Inherent vs Residual: Always present both inherent and residual risk to demonstrate control value
  • Risk Appetite Context: Frame all scores against the organization's defined risk appetite thresholds
  • Heat Map Clarity: Use standardized 5x5 matrices with consistent color coding across reports
  • Trend Baselines: Establish quarterly baselines for meaningful trend comparison
  • Treatment Validation: Verify that risk treatment strategies are actively implemented, not just documented
  • Loss Data Integration: Incorporate actual risk event data to validate assessment accuracy
  • Owner Accountability: Include risk owner names in all summaries for clear accountability

Troubleshooting

Risk Scores Show as Zero

Symptom: All risk_score, inherent_risk, or residual_risk fields return 0 or empty Cause: Risk scoring may use calculated fields populated by assessment workflows, or custom scoring fields Solution:

Tool: SN-Discover-Table-Schema
Parameters:
  table_name: sn_grc_risk

Check for alternative scoring fields like calculated_risk, risk_score_calculated, or assessment_score. Also verify that risk assessments have been completed.

Likelihood and Impact Not Populated

Symptom: Heat map generation fails due to empty likelihood/impact values Cause: Risk records may use qualitative ratings (High/Medium/Low) instead of numeric scores Solution: Map qualitative values to numeric scale: Critical=5, High=4, Medium=3, Low=2, Very Low=1. Check the sys_choice table for field value definitions.

Risk Events Table Empty

Symptom: No records returned from sn_risk_event Cause: The Risk Events module may not be configured, or events are tracked in a custom table Solution: Check if com.sn_risk_advanced plugin is activated. Some organizations track loss events in custom tables or external systems.

Examples

Example 1: Quarterly Enterprise Risk Summary

Scenario: CRO needs a consolidated risk posture report for the board

Tool: SN-Query-Table
Parameters:
  table_name: sn_grc_risk
  query: active=true^risk_score>=70^ORDERBYcategory
  limit: 50

Generated Summary:

  • Total Active Risks: 156
  • Risk Distribution: Critical: 12 | High: 34 | Medium: 72 | Low: 38
  • Average Inherent Risk: 62/100 | Average Residual Risk:** 38/100
  • Control Effectiveness: 39% average risk reduction through controls
  • Above Appetite: 18 risks exceed organizational risk appetite (threshold: 50)
  • Top Category: Information Security (42 risks, 27%)
  • Trend: Risk count increased 8% from last quarter; average residual decreased 4%
  • Key Recommendation: 5 risks with "Accept" treatment exceed appetite -- re-evaluate acceptance decisions

Example 2: Heat Map Report for Risk Committee

Scenario: Risk committee requests a visual risk landscape

Heat Map Output (top-right = highest concern):

Impact →    1     2     3     4     5
Likelihood
    5      [ 0]  [ 2]  [ 5]  [ 8]  [ 3]  ← Highest concern row
    4      [ 1]  [ 4]  [12]  [15]  [ 6]
    3      [ 3]  [ 8]  [18]  [10]  [ 4]
    2      [ 5]  [12]  [ 9]  [ 6]  [ 2]
    1      [ 8]  [ 7]  [ 5]  [ 2]  [ 1]
  • Red Zone (L>=4, I>=4): 32 risks requiring immediate executive attention
  • Amber Zone (Score>=12): 47 risks under active mitigation
  • Green Zone (Score<6): 21 risks within acceptable tolerance

Related Skills

  • grc/issue-action-plan - Create action plans for risks that have materialized as issues
  • grc/issue-summarization - Summarize issues arising from risk assessments
  • grc/control-objective-management - Manage controls that mitigate assessed risks
  • grc/regulatory-alert-analysis - Assess regulatory changes impacting risk landscape
  • security/audit-compliance - Audit controls supporting risk mitigation

References

Signals

GitHub stars
37
Forks
13
Last commit
Jul 2026
Advanced
Catalog kind
skill
Gateway key
risk-assessment-summarization
Source
github.com/happy-technologies-llc/happy-platform-skills