Risk Assessment Summarization
SkillDev toolsSummarize 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.
No other account needed.
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, oradmin - 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
| Table | Purpose | Key Fields |
|---|---|---|
sn_grc_risk | Risk register records | number, short_description, risk_score, inherent_risk, residual_risk, state, treatment, likelihood, impact, profile, category, owner |
sn_risk_definition | Risk framework definitions | number, short_description, risk_criteria, scoring_method, appetite |
sn_risk_event | Actual risk events/incidents | number, short_description, event_date, impact_amount, risk, category |
sn_grc_profile | Entity profiles for risk scoping | number, short_description, profile_type, applies_to |
sn_grc_item | GRC content items | number, 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
| Operation | MCP Tool | REST Endpoint |
|---|---|---|
| Query Risks | SN-Query-Table | GET /api/now/table/sn_grc_risk |
| Query Risk Definitions | SN-Query-Table | GET /api/now/table/sn_risk_definition |
| Query Risk Events | SN-Query-Table | GET /api/now/table/sn_risk_event |
| Search Risks | SN-Natural-Language-Search | N/A |
| Complex Analysis | SN-Execute-Background-Script | POST /api/now/table/sys_trigger |
| Schema Discovery | SN-Discover-Table-Schema | GET /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 issuesgrc/issue-summarization- Summarize issues arising from risk assessmentsgrc/control-objective-management- Manage controls that mitigate assessed risksgrc/regulatory-alert-analysis- Assess regulatory changes impacting risk landscapesecurity/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