skills/grc/esg-document-intelligence/SKILL.md
Extract and analyze ESG data from utility invoices and sustainability reports, tracking carbon emissions, energy consumption, waste metrics, and disclosure compliance across ESG frameworks
npx skillsauth add happy-technologies-llc/happy-servicenow-skills esg-document-intelligenceInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill extracts and analyzes Environmental, Social, and Governance (ESG) data from documents and ServiceNow records. It covers:
When to use:
sn_esg.admin, sn_esg.manager, sn_esg.data_entry, or admincom.sn_esg (ESG Management)| Table | Purpose | Key Fields |
|-------|---------|------------|
| sn_esg_metric | ESG performance metrics | number, metric_name, category, value, unit, period, location, scope, framework, data_source |
| sn_esg_disclosure | Framework disclosure responses | number, framework, disclosure_id, response, status, evidence, reporting_period, reviewer |
| sn_esg_framework | ESG framework definitions | name, version, category, standard_body, active, disclosure_count |
| sn_esg_data_point | Raw data points from sources | metric, value, date, source_document, location, verified, notes |
| cmn_location | Facility/location records | name, city, state, country, building, latitude, longitude |
Retrieve current ESG metrics to understand the baseline.
Using MCP (Claude Code/Desktop):
Tool: SN-Query-Table
Parameters:
table_name: sn_esg_metric
query: active=true^ORDERBYcategory
fields: sys_id,number,metric_name,category,value,unit,period,location,scope,framework,data_source,last_updated
limit: 100
Using REST API:
GET /api/now/table/sn_esg_metric?sysparm_query=active=true^ORDERBYcategory&sysparm_fields=sys_id,number,metric_name,category,value,unit,period,location,scope,framework,data_source,last_updated&sysparm_limit=100&sysparm_display_value=true
Process utility invoice data to extract environmental metrics.
Retrieve invoice attachments:
Tool: SN-Query-Table
Parameters:
table_name: sys_attachment
query: table_nameLIKEesg^ORtable_nameLIKEutility^ORtable_nameLIKEinvoice^content_typeLIKEpdf
fields: sys_id,file_name,table_name,table_sys_id,content_type,size_bytes,sys_created_on
limit: 50
order_by_desc: sys_created_on
Parse and record extracted energy data:
Tool: SN-Create-Record
Parameters:
table_name: sn_esg_data_point
fields:
metric: [energy_metric_sys_id]
value: 45230
date: 2026-02-28
source_document: "February 2026 Electric Utility Invoice - Building A"
location: [location_sys_id]
verified: false
notes: "Extracted from utility invoice. 45,230 kWh electricity consumption."
Compute Scope 1, 2, and 3 emissions from source data:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// EPA emission factors (2025 US average)
var EMISSION_FACTORS = {
electricity_kwh: 0.000386, // metric tons CO2e per kWh (US avg grid)
natural_gas_therm: 0.005302, // metric tons CO2e per therm
diesel_gallon: 0.010180, // metric tons CO2e per gallon
gasoline_gallon: 0.008887, // metric tons CO2e per gallon
propane_gallon: 0.005740 // metric tons CO2e per gallon
};
var period = '2026-Q1';
var emissions = {
period: period,
scope1: { total: 0, sources: {} },
scope2: { total: 0, sources: {} },
scope3: { total: 0, sources: {} },
total_co2e: 0,
unit: 'metric tons CO2e'
};
// Query energy data points for the period
var dp = new GlideRecord('sn_esg_data_point');
dp.addQuery('date', '>=', '2026-01-01');
dp.addQuery('date', '<=', '2026-03-31');
dp.query();
while (dp.next()) {
var metricRec = new GlideRecord('sn_esg_metric');
metricRec.get(dp.metric.toString());
var metricName = metricRec.metric_name.toString().toLowerCase();
var value = parseFloat(dp.value.toString()) || 0;
var scope = metricRec.scope.toString();
var co2e = 0;
if (metricName.match(/electricity/)) co2e = value * EMISSION_FACTORS.electricity_kwh;
else if (metricName.match(/natural gas/)) co2e = value * EMISSION_FACTORS.natural_gas_therm;
else if (metricName.match(/diesel/)) co2e = value * EMISSION_FACTORS.diesel_gallon;
else if (metricName.match(/gasoline/)) co2e = value * EMISSION_FACTORS.gasoline_gallon;
if (scope == '1') {
emissions.scope1.total += co2e;
emissions.scope1.sources[metricName] = (emissions.scope1.sources[metricName] || 0) + co2e;
} else if (scope == '2') {
emissions.scope2.total += co2e;
emissions.scope2.sources[metricName] = (emissions.scope2.sources[metricName] || 0) + co2e;
} else {
emissions.scope3.total += co2e;
emissions.scope3.sources[metricName] = (emissions.scope3.sources[metricName] || 0) + co2e;
}
}
emissions.scope1.total = Math.round(emissions.scope1.total * 100) / 100;
emissions.scope2.total = Math.round(emissions.scope2.total * 100) / 100;
emissions.scope3.total = Math.round(emissions.scope3.total * 100) / 100;
emissions.total_co2e = emissions.scope1.total + emissions.scope2.total + emissions.scope3.total;
gs.info('EMISSIONS CALCULATION:\n' + JSON.stringify(emissions, null, 2));
description: "ESG: Calculate GHG emissions by scope from energy data points"
Check which framework disclosures are complete vs. gaps:
Tool: SN-Execute-Background-Script
Parameters:
script: |
var frameworks = ['GRI', 'SASB', 'TCFD', 'CDP'];
var completeness = { reporting_period: '2025', frameworks: {} };
for (var f = 0; f < frameworks.length; f++) {
var fw = frameworks[f];
completeness.frameworks[fw] = { total: 0, complete: 0, in_progress: 0, not_started: 0, gaps: [] };
var disc = new GlideRecord('sn_esg_disclosure');
disc.addQuery('framework.name', fw);
disc.addQuery('reporting_period', '2025');
disc.query();
while (disc.next()) {
completeness.frameworks[fw].total++;
var status = disc.status.toString().toLowerCase();
if (status == 'complete' || status == 'submitted') {
completeness.frameworks[fw].complete++;
} else if (status == 'in_progress' || status == 'draft') {
completeness.frameworks[fw].in_progress++;
} else {
completeness.frameworks[fw].not_started++;
completeness.frameworks[fw].gaps.push({
disclosure_id: disc.disclosure_id.toString(),
description: disc.short_description.toString()
});
}
}
}
gs.info('DISCLOSURE COMPLETENESS:\n' + JSON.stringify(completeness, null, 2));
description: "ESG: Assess disclosure completeness across ESG frameworks"
Compile trend data for executive reporting:
Tool: SN-Execute-Background-Script
Parameters:
script: |
var dashboard = {
generated_date: new GlideDateTime().toString(),
environmental: {
total_emissions_ytd: 0,
emissions_vs_target: '',
energy_consumption_kwh: 0,
renewable_percentage: 0,
waste_diverted_percentage: 0,
water_usage_gallons: 0
},
yoy_comparison: {},
by_location: {},
data_quality: { verified: 0, unverified: 0, missing: 0 }
};
// Aggregate YTD metrics by category
var categories = {
'emissions': 'total_emissions_ytd',
'energy': 'energy_consumption_kwh',
'water': 'water_usage_gallons'
};
var metric = new GlideRecord('sn_esg_metric');
metric.addQuery('category', 'environmental');
metric.addQuery('active', true);
metric.query();
while (metric.next()) {
var loc = metric.location.getDisplayValue() || 'Unknown';
if (!dashboard.by_location[loc]) dashboard.by_location[loc] = { emissions: 0, energy: 0 };
var val = parseFloat(metric.value.toString()) || 0;
var mName = metric.metric_name.toString().toLowerCase();
if (mName.match(/emission|co2|ghg/)) {
dashboard.environmental.total_emissions_ytd += val;
dashboard.by_location[loc].emissions += val;
}
if (mName.match(/energy|electricity|kwh/)) {
dashboard.environmental.energy_consumption_kwh += val;
dashboard.by_location[loc].energy += val;
}
}
// Data quality check
var dp = new GlideAggregate('sn_esg_data_point');
dp.addQuery('verified', true);
dp.addAggregate('COUNT');
dp.query();
if (dp.next()) dashboard.data_quality.verified = parseInt(dp.getAggregate('COUNT'));
var dpUnverified = new GlideAggregate('sn_esg_data_point');
dpUnverified.addQuery('verified', false);
dpUnverified.addAggregate('COUNT');
dpUnverified.query();
if (dpUnverified.next()) dashboard.data_quality.unverified = parseInt(dpUnverified.getAggregate('COUNT'));
gs.info('ESG DASHBOARD DATA:\n' + JSON.stringify(dashboard, null, 2));
description: "ESG: Generate environmental performance dashboard data"
Record newly extracted metrics back to the ESG module:
Tool: SN-Update-Record
Parameters:
table_name: sn_esg_metric
sys_id: [metric_sys_id]
data:
value: 1245.6
last_updated: 2026-03-19
data_source: "Utility invoices Q1 2026 - Buildings A, B, C"
work_notes: "Updated from Q1 utility invoice extraction. Previous value: 1180.2. Increase of 5.5% due to expanded Building C operations."
| Operation | MCP Tool | REST Endpoint | |-----------|----------|---------------| | Query Metrics | SN-Query-Table | GET /api/now/table/sn_esg_metric | | Query Disclosures | SN-Query-Table | GET /api/now/table/sn_esg_disclosure | | Query Frameworks | SN-Query-Table | GET /api/now/table/sn_esg_framework | | Create Data Points | SN-Create-Record | POST /api/now/table/sn_esg_data_point | | Update Metrics | SN-Update-Record | PATCH /api/now/table/sn_esg_metric | | Emissions Calculation | SN-Execute-Background-Script | POST /api/now/table/sys_trigger | | Search Documents | SN-Natural-Language-Search | N/A |
Symptom: Query against sn_esg_metric returns a table-not-found error
Cause: The ESG Management plugin may not be activated, or the table name may differ by version
Solution: Query sys_db_object with nameLIKEesg to find available ESG tables. Check if com.sn_esg plugin is active.
Symptom: Calculated emissions seem unreasonably high or low Cause: Unit mismatch between input data and emission factor (e.g., MWh vs. kWh, therms vs. MMBtu) Solution: Verify the unit field on both the metric and data point records. Convert units before applying emission factors.
Symptom: No disclosure records found for GRI, SASB, or TCFD Cause: Framework disclosure templates must be loaded via the ESG module setup; they are not auto-populated Solution: Navigate to ESG Management > Frameworks in the ServiceNow UI and import the relevant framework templates.
Scenario: Sustainability analyst processes monthly electricity and gas invoices
Tool: SN-Create-Record
Parameters:
table_name: sn_esg_data_point
fields:
metric: [electricity_metric_sys_id]
value: 125400
date: 2026-02-28
source_document: "ConEd Invoice #INV-2026-0228 - HQ Building"
location: [hq_location_sys_id]
verified: false
notes: "125,400 kWh. Rate: $0.14/kWh. Total: $17,556. Demand charge: 450 kW peak."
Result: Data point recorded, Scope 2 emissions auto-calculated at 48.4 metric tons CO2e.
Scenario: ESG manager needs to identify gaps in CDP Climate Change questionnaire
Tool: SN-Query-Table
Parameters:
table_name: sn_esg_disclosure
query: framework.nameLIKECDP^reporting_period=2025^status!=complete
fields: disclosure_id,short_description,status,response,evidence
limit: 100
Result: 14 of 82 CDP disclosures incomplete. Critical gaps: Scope 3 Category 6 (Business Travel), Scope 3 Category 7 (Employee Commuting), Climate-related targets validation.
grc/issue-summarization - GRC issue analysis for ESG compliance findingsgrc/risk-assessment-summarization - Climate risk assessment analysisgrc/regulatory-alert-analysis - ESG regulatory change trackingdocument/document-extraction - Extract data from utility invoices and reportsreporting/executive-dashboard - Build ESG executive dashboardstesting
Manage supplier onboarding, qualification, performance monitoring, and offboarding with auditable lifecycle controls
tools
Identify emerging risks, prioritize intake signals, and route candidates into formal GRC risk assessment workflows
documentation
Screen inbound documents for completeness, policy risk, and routing readiness before extraction or case workflows
testing
Generate concise task summaries with status, timeline, blockers, SLA risk, and recommended next actions