Nuclear power plants and energy SaaS companies operate under some of the most stringent regulatory regimes in the world — NRC 10 CFR Part 50 technical specification surveillance intervals measured in days, NERC CIP Bulk Electric System (BES) cyber security patch windows with defined timelines, and 10 CFR 50.72 operating event notifications that must reach the NRC Emergency Notification System (ENS) within 1–24 hours.
Most nuclear and energy operations teams still track these compliance obligations in spreadsheets and share-drive documents. n8n changes that: self-hosted, git-versionable, deployable inside OT/IT separated network zones — critical when BES Cyber System data, plant performance records, and reactor operating data must never cross to a third-party cloud iPaaS under NERC CIP CIP-003 through CIP-011.
Here are 5 n8n workflows for nuclear energy and power SaaS teams, complete with import-ready JSON.
1. NRC 10 CFR Part 50 Technical Specification Surveillance Tracker
The problem: Nuclear plants must complete surveillance tests on safety systems at defined intervals per the plant's Technical Specifications (TS). A missed surveillance creates a Limiting Condition for Operation (LCO) entry and a potential reportability determination under 10 CFR 50.72 or 50.73. Tracking dozens of surveillance frequencies (monthly, quarterly, 18-month) in spreadsheets is error-prone.
The workflow: Runs weekdays at 6AM. Reads a surveillance_requirements Sheets table (surveillance_id, tech_spec_section, frequency, next_due_date, responsible_engineer). Classifies by days remaining: OVERDUE (LCO entry flag), CRITICAL (≤3 days), URGENT (≤7), WARNING (≤14), NOTICE (≤30). Routes OVERDUE/CRITICAL to Slack #nuclear-ops-critical with LCO language, emails the responsible engineer for all tiers.
{"name": "NRC 10 CFR Part 50 Technical Specification Surveillance Tracker", "nodes": [{"name": "Daily 6AM", "type": "n8n-nodes-base.scheduleTrigger", "parameters": {"rule": {"interval": [{"field": "cronExpression", "expression": "0 6 * * 1-5"}]}}, "position": [250, 300]}, {"name": "Read Surveillance Requirements", "type": "n8n-nodes-base.googleSheets", "parameters": {"operation": "readRows", "sheetId": "{{SURVEILLANCE_SHEET_ID}}", "options": {}}, "position": [450, 300]}, {"name": "Classify Urgency", "type": "n8n-nodes-base.code", "parameters": {"jsCode": "const items = [];\nconst now = new Date();\nfor (const item of $input.all()) {\n const s = item.json;\n const due = new Date(s.next_due_date);\n const daysLeft = Math.floor((due - now) / 86400000);\n let tier = null;\n if (daysLeft < 0) tier = 'OVERDUE';\n else if (daysLeft <= 3) tier = 'CRITICAL';\n else if (daysLeft <= 7) tier = 'URGENT';\n else if (daysLeft <= 14) tier = 'WARNING';\n else if (daysLeft <= 30) tier = 'NOTICE';\n if (tier) {\n items.push({ json: { ...s, days_left: daysLeft, tier, nrc_lcoo_entry_required: tier === 'OVERDUE' || tier === 'CRITICAL', alert_ts: now.toISOString() } });\n }\n}\nreturn items;"}, "position": [650, 300]}, {"name": "Route by Tier", "type": "n8n-nodes-base.switch", "parameters": {"dataType": "string", "value1": "={{$json.tier}}", "rules": {"rules": [{"value2": "OVERDUE", "output": 0}, {"value2": "CRITICAL", "output": 1}, {"value2": "URGENT", "output": 2}, {"value2": "WARNING", "output": 3}, {"value2": "NOTICE", "output": 4}]}}, "position": [850, 300]}, {"name": "Slack OVERDUE", "type": "n8n-nodes-base.slack", "parameters": {"channel": "#nuclear-ops-critical", "text": ":rotating_light: OVERDUE \u2014 {{$json.surveillance_id}} ({{$json.tech_spec_section}}) overdue {{$json.days_left}} days. LCO entry required. Owner: {{$json.responsible_engineer}}."}, "position": [1050, 100]}, {"name": "Slack CRITICAL", "type": "n8n-nodes-base.slack", "parameters": {"channel": "#nuclear-ops-critical", "text": ":red_circle: CRITICAL \u2014 {{$json.surveillance_id}} ({{$json.tech_spec_section}}) due in {{$json.days_left}} days. LCO entry may be required. Owner: {{$json.responsible_engineer}}."}, "position": [1050, 250]}, {"name": "Slack URGENT", "type": "n8n-nodes-base.slack", "parameters": {"channel": "#nuclear-compliance", "text": ":large_orange_circle: URGENT \u2014 {{$json.surveillance_id}} due in {{$json.days_left}} days ({{$json.next_due_date}}). Tech Spec: {{$json.tech_spec_section}}."}, "position": [1050, 400]}, {"name": "Gmail Engineer", "type": "n8n-nodes-base.gmail", "parameters": {"to": "={{$json.engineer_email}}", "subject": "[{{$json.tier}}] Surveillance {{$json.surveillance_id}} \u2014 {{$json.days_left}} days remaining ({{$json.tech_spec_section}})", "message": "Technical Specification surveillance deadline approaching:\n\nSurveillance ID: {{$json.surveillance_id}}\nTech Spec Section: {{$json.tech_spec_section}}\nDescription: {{$json.surveillance_description}}\nFrequency: {{$json.frequency}}\nNext Due: {{$json.next_due_date}}\nDays Remaining: {{$json.days_left}}\nStatus: {{$json.tier}}\nLCO Entry Required: {{$json.nrc_lcoo_entry_required}}\n\nPlease schedule surveillance and update the tracking sheet upon completion."}, "position": [1050, 550]}], "connections": {"Daily 6AM": {"main": [[{"node": "Read Surveillance Requirements", "type": "main", "index": 0}]]}, "Read Surveillance Requirements": {"main": [[{"node": "Classify Urgency", "type": "main", "index": 0}]]}, "Classify Urgency": {"main": [[{"node": "Route by Tier", "type": "main", "index": 0}]]}, "Route by Tier": {"main": [[{"node": "Slack OVERDUE", "type": "main", "index": 0}], [{"node": "Slack CRITICAL", "type": "main", "index": 0}], [{"node": "Slack URGENT", "type": "main", "index": 0}], [{"node": "Gmail Engineer", "type": "main", "index": 0}], [{"node": "Gmail Engineer", "type": "main", "index": 0}]]}}}
What to populate: TS 3.3.1 RPS actuation logic monthly tests, TS 3.5.1 ECCS injection quarterly, TS 3.6.1 containment integrity 18-month, TS 3.8.1 AC electrical power sources, TS 4.0.5 inservice test program milestones.
2. NERC CIP Compliance Deadline & Patch Window Monitor
The problem: NERC CIP standards (CIP-007 R2 — Systems Security Management, CIP-010 R1 — Configuration Change Management) define patch application windows: 35 days for critical security patches on BES Cyber Systems, 180 days for lower categories. Missing a patch window is a reportable NERC violation with penalties up to $1M/day.
The workflow: Runs weekdays at 7AM. Reads a nerc_cip_deadlines table with requirement_id, cip_standard, deadline_date, bes_cyber_system (boolean), responsible_team. Classifies urgency. For BES Cyber System items (highest criticality), routes to a separate Slack #nerc-cip-critical channel. Logs every alert to a CIP audit register for annual NERC spot-check responses.
{"name": "NERC CIP Compliance Deadline & Patch Window Monitor", "nodes": [{"name": "Daily 7AM Weekdays", "type": "n8n-nodes-base.scheduleTrigger", "parameters": {"rule": {"interval": [{"field": "cronExpression", "expression": "0 7 * * 1-5"}]}}, "position": [250, 300]}, {"name": "Read CIP Deadlines", "type": "n8n-nodes-base.googleSheets", "parameters": {"operation": "readRows", "sheetId": "{{NERC_CIP_SHEET_ID}}", "options": {}}, "position": [450, 300]}, {"name": "Classify CIP Urgency", "type": "n8n-nodes-base.code", "parameters": {"jsCode": "const items = [];\nconst now = new Date();\nfor (const item of $input.all()) {\n const d = item.json;\n const due = new Date(d.deadline_date);\n const daysLeft = Math.floor((due - now) / 86400000);\n let tier = null;\n if (daysLeft < 0) tier = 'OVERDUE';\n else if (daysLeft <= 7) tier = 'CRITICAL';\n else if (daysLeft <= 14) tier = 'URGENT';\n else if (daysLeft <= 30) tier = 'WARNING';\n else if (daysLeft <= 60) tier = 'NOTICE';\n if (tier) {\n const isBes = d.bes_cyber_system === 'true' || d.bes_cyber_system === true;\n items.push({ json: { ...d, days_left: daysLeft, tier, bes_cyber_system: isBes, nerc_violation_risk: tier === 'OVERDUE', alert_ts: now.toISOString() } });\n }\n}\nreturn items;"}, "position": [650, 300]}, {"name": "If BES Critical", "type": "n8n-nodes-base.if", "parameters": {"conditions": {"boolean": [{"value1": "={{$json.bes_cyber_system}}", "value2": true}]}}, "position": [850, 300]}, {"name": "Slack CIP Critical BES", "type": "n8n-nodes-base.slack", "parameters": {"channel": "#nerc-cip-critical", "text": ":rotating_light: {{$json.tier}} \u2014 BES Cyber System \u2014 {{$json.requirement_id}} ({{$json.cip_standard}}) due {{$json.deadline_date}} ({{$json.days_left}}d). NERC violation risk: {{$json.nerc_violation_risk}}. Owner: {{$json.responsible_team}}."}, "position": [1050, 200]}, {"name": "Slack CIP Standard", "type": "n8n-nodes-base.slack", "parameters": {"channel": "#nerc-cip-compliance", "text": "{{$json.tier}} \u2014 {{$json.requirement_id}} ({{$json.cip_standard}}) due in {{$json.days_left}} days. Owner: {{$json.responsible_team}}."}, "position": [1050, 400]}, {"name": "Gmail CIP Owner", "type": "n8n-nodes-base.gmail", "parameters": {"to": "={{$json.owner_email}}", "subject": "[NERC CIP {{$json.tier}}] {{$json.requirement_id}} \u2014 {{$json.days_left}} days to {{$json.deadline_date}}", "message": "NERC CIP compliance deadline approaching:\n\nRequirement: {{$json.requirement_id}}\nCIP Standard: {{$json.cip_standard}}\nDeadline: {{$json.deadline_date}}\nDays Remaining: {{$json.days_left}}\nBES Cyber System: {{$json.bes_cyber_system}}\nNERC Violation Risk: {{$json.nerc_violation_risk}}\nOwner: {{$json.responsible_team}}\n\nPlease confirm compliance status and update the CIP tracking register."}, "position": [1050, 560]}, {"name": "Log to CIP Register", "type": "n8n-nodes-base.googleSheets", "parameters": {"operation": "appendRow", "sheetId": "{{CIP_AUDIT_LOG_SHEET_ID}}", "options": {}, "fieldsUi": {"values": [{"fieldId": "requirement_id", "fieldValue": "={{$json.requirement_id}}"}, {"fieldId": "tier", "fieldValue": "={{$json.tier}}"}, {"fieldId": "days_left", "fieldValue": "={{$json.days_left}}"}, {"fieldId": "bes_cyber_system", "fieldValue": "={{$json.bes_cyber_system}}"}, {"fieldId": "alert_ts", "fieldValue": "={{$json.alert_ts}}"}]}}, "position": [1050, 700]}], "connections": {"Daily 7AM Weekdays": {"main": [[{"node": "Read CIP Deadlines", "type": "main", "index": 0}]]}, "Read CIP Deadlines": {"main": [[{"node": "Classify CIP Urgency", "type": "main", "index": 0}]]}, "Classify CIP Urgency": {"main": [[{"node": "If BES Critical", "type": "main", "index": 0}]]}, "If BES Critical": {"main": [[{"node": "Slack CIP Critical BES", "type": "main", "index": 0}], [{"node": "Slack CIP Standard", "type": "main", "index": 0}]]}, "Slack CIP Critical BES": {"main": [[{"node": "Gmail CIP Owner", "type": "main", "index": 0}]]}, "Slack CIP Standard": {"main": [[{"node": "Gmail CIP Owner", "type": "main", "index": 0}]]}, "Gmail CIP Owner": {"main": [[{"node": "Log to CIP Register", "type": "main", "index": 0}]]}}}
CIP standards to track: CIP-007 R2 security patch deadlines (35/180 days), CIP-010 R1 configuration baseline quarterly reviews, CIP-005 R1 Electronic Security Perimeter annual reviews, CIP-004 R4 access authorization quarterly, CIP-011 Information Protection annual plan reviews.
3. NRC 10 CFR 50.72 / 50.73 Operating Event Alert & ENS Notification Prep
The problem: 10 CFR 50.72 requires licensees to notify the NRC Operations Center (ENS call) within 1 hour for emergency/alert class events, 8 hours for other conditions requiring notification, and 24 hours for selected informational items. Delays and missed calls trigger NRC inspection follow-up. Paper-based shift logs miss the notification clock.
The workflow: Triggered by webhook from a plant event reporting app or control room DCS integration. Classifies by event_type: EMERGENCY_CLASS (1h ENS), ALERT (8h ENS), UNUSUAL_EVENT (24h ENS), NON_EMERGENCY_REPORTABLE (no ENS but 30-day LER required), CORRECTIVE_ACTION. For NRC-notify events: fires Slack #nuclear-ops-emergency immediately with calculated deadline, emails the shift supervisor with ENS phone number, logs to the event registry.
{"name": "NRC 10 CFR 50.72 / 50.73 Operating Event Alert & ENS Notification Prep", "nodes": [{"name": "Webhook Operating Event", "type": "n8n-nodes-base.webhook", "parameters": {"path": "nuclear-event", "httpMethod": "POST", "responseMode": "responseNode"}, "position": [250, 300]}, {"name": "Respond 200 OK", "type": "n8n-nodes-base.respondToWebhook", "parameters": {"responseCode": 200, "responseBody": "{\"status\":\"received\"}"}, "position": [450, 150]}, {"name": "Classify Event", "type": "n8n-nodes-base.code", "parameters": {"jsCode": "const evt = $input.first().json;\nconst type = evt.event_type || 'OTHER';\nconst classMap = {\n 'EMERGENCY_CLASS': { level: 1, cfr_72: true, cfr_73: true, ens_hours: 1, notify_nrc: true },\n 'ALERT': { level: 2, cfr_72: true, cfr_73: true, ens_hours: 8, notify_nrc: true },\n 'UNUSUAL_EVENT': { level: 3, cfr_72: true, cfr_73: false, ens_hours: 24, notify_nrc: true },\n 'NON_EMERGENCY_REPORTABLE': { level: 4, cfr_72: false, cfr_73: true, ens_hours: 0, notify_nrc: false },\n 'CORRECTIVE_ACTION': { level: 5, cfr_72: false, cfr_73: false, ens_hours: 0, notify_nrc: false }\n};\nconst cls = classMap[type] || classMap['CORRECTIVE_ACTION'];\nconst ensDeadline = cls.ens_hours > 0 ? new Date(Date.now() + cls.ens_hours * 3600000).toISOString() : null;\nreturn [{ json: { ...evt, classification: cls, event_type: type, ens_deadline: ensDeadline, event_ts: new Date().toISOString() } }];"}, "position": [450, 350]}, {"name": "If NRC Notify Required", "type": "n8n-nodes-base.if", "parameters": {"conditions": {"boolean": [{"value1": "={{$json.classification.notify_nrc}}", "value2": true}]}}, "position": [650, 350]}, {"name": "Slack Nuclear Ops Emergency", "type": "n8n-nodes-base.slack", "parameters": {"channel": "#nuclear-ops-emergency", "text": ":sos: {{$json.event_type}} \u2014 {{$json.plant_id}} ({{$json.event_ts}})\nDescription: {{$json.description}}\n10 CFR 50.72 required: {{$json.classification.cfr_72}}\n10 CFR 50.73 LER required: {{$json.classification.cfr_73}}\nENS notification deadline: {{$json.ens_deadline}}\nShift supervisor: {{$json.shift_supervisor}}"}, "position": [850, 200]}, {"name": "Gmail Shift Supervisor", "type": "n8n-nodes-base.gmail", "parameters": {"to": "={{$json.shift_supervisor_email}}", "subject": "[{{$json.event_type}}] NRC ENS Notification Required \u2014 {{$json.plant_id}} \u2014 Deadline: {{$json.ens_deadline}}", "message": "Operating event classified requiring NRC notification:\n\nPlant: {{$json.plant_id}}\nEvent Type: {{$json.event_type}}\nTimestamp: {{$json.event_ts}}\nDescription: {{$json.description}}\n\n10 CFR 50.72 (immediate notification): {{$json.classification.cfr_72}}\n10 CFR 50.73 (LER \u2014 30-day report): {{$json.classification.cfr_73}}\nENS Call Deadline: {{$json.ens_deadline}}\n\nPlease initiate ENS notification protocol and contact the NRC Operations Center at 301-816-5100."}, "position": [850, 400]}, {"name": "Log Event Registry", "type": "n8n-nodes-base.googleSheets", "parameters": {"operation": "appendRow", "sheetId": "{{EVENT_REGISTRY_SHEET_ID}}", "options": {}, "fieldsUi": {"values": [{"fieldId": "event_ts", "fieldValue": "={{$json.event_ts}}"}, {"fieldId": "plant_id", "fieldValue": "={{$json.plant_id}}"}, {"fieldId": "event_type", "fieldValue": "={{$json.event_type}}"}, {"fieldId": "cfr_72", "fieldValue": "={{$json.classification.cfr_72}}"}, {"fieldId": "cfr_73", "fieldValue": "={{$json.classification.cfr_73}}"}, {"fieldId": "ens_deadline", "fieldValue": "={{$json.ens_deadline}}"}]}}, "position": [850, 600]}], "connections": {"Webhook Operating Event": {"main": [[{"node": "Respond 200 OK", "type": "main", "index": 0}, {"node": "Classify Event", "type": "main", "index": 0}]]}, "Classify Event": {"main": [[{"node": "If NRC Notify Required", "type": "main", "index": 0}]]}, "If NRC Notify Required": {"main": [[{"node": "Slack Nuclear Ops Emergency", "type": "main", "index": 0}], [{"node": "Log Event Registry", "type": "main", "index": 0}]]}, "Slack Nuclear Ops Emergency": {"main": [[{"node": "Gmail Shift Supervisor", "type": "main", "index": 0}]]}, "Gmail Shift Supervisor": {"main": [[{"node": "Log Event Registry", "type": "main", "index": 0}]]}}}
Critical note: This workflow automates the notification clock and routing. The actual ENS call must be made by a qualified shift supervisor — this workflow ensures the clock starts and the right person is notified within minutes, not discovered during the next shift handoff.
4. FERC / DOE Regulatory Filing Deadline Tracker
The problem: FERC-regulated utilities and nuclear plant owners manage recurring filing obligations: FERC Form 1 (annual), EQR (quarterly), NERC-FERC joint reliability standard compliance filings, DOE-923 fuel and generation data (annual), seasonal peak forecast submissions, transmission planning data requests. Missing a filing can mean FERC show-cause orders.
The workflow: Runs weekdays at 8AM. Reads a ferc_regulatory_deadlines table (filing_name, regulation_reference, filing_deadline, responsible_party, owner_email). Classifies urgency. OVERDUE/CRITICAL route to Slack #regulatory-critical with penalty language. All tiers get a personalized email to the responsible party. Every alert logs to a regulatory compliance register.
{"name": "FERC / DOE Regulatory Filing Deadline Tracker", "nodes": [{"name": "Daily 8AM Weekdays", "type": "n8n-nodes-base.scheduleTrigger", "parameters": {"rule": {"interval": [{"field": "cronExpression", "expression": "0 8 * * 1-5"}]}}, "position": [250, 300]}, {"name": "Read FERC Deadlines", "type": "n8n-nodes-base.googleSheets", "parameters": {"operation": "readRows", "sheetId": "{{FERC_DEADLINES_SHEET_ID}}", "options": {}}, "position": [450, 300]}, {"name": "Classify Filing Urgency", "type": "n8n-nodes-base.code", "parameters": {"jsCode": "const items = [];\nconst now = new Date();\nfor (const item of $input.all()) {\n const f = item.json;\n const due = new Date(f.filing_deadline);\n const daysLeft = Math.floor((due - now) / 86400000);\n let tier = null;\n if (daysLeft < 0) tier = 'OVERDUE';\n else if (daysLeft <= 7) tier = 'CRITICAL';\n else if (daysLeft <= 14) tier = 'URGENT';\n else if (daysLeft <= 30) tier = 'WARNING';\n else if (daysLeft <= 60) tier = 'NOTICE';\n if (tier) {\n const isPenaltyRisk = tier === 'OVERDUE' || tier === 'CRITICAL';\n items.push({ json: { ...f, days_left: daysLeft, tier, penalty_risk: isPenaltyRisk, alert_ts: now.toISOString() } });\n }\n}\nreturn items;"}, "position": [650, 300]}, {"name": "Route by Urgency", "type": "n8n-nodes-base.switch", "parameters": {"dataType": "string", "value1": "={{$json.tier}}", "rules": {"rules": [{"value2": "OVERDUE", "output": 0}, {"value2": "CRITICAL", "output": 1}, {"value2": "URGENT", "output": 2}, {"value2": "WARNING", "output": 3}, {"value2": "NOTICE", "output": 4}]}}, "position": [850, 300]}, {"name": "Slack FERC Overdue", "type": "n8n-nodes-base.slack", "parameters": {"channel": "#regulatory-critical", "text": ":rotating_light: OVERDUE \u2014 {{$json.filing_name}} ({{$json.regulation_reference}}) was due {{$json.filing_deadline}}. FERC penalty risk. Owner: {{$json.responsible_party}}. Escalating to VP Regulatory."}, "position": [1050, 100]}, {"name": "Slack FERC Critical", "type": "n8n-nodes-base.slack", "parameters": {"channel": "#regulatory-critical", "text": ":red_circle: CRITICAL \u2014 {{$json.filing_name}} ({{$json.regulation_reference}}) due in {{$json.days_left}} days. Owner: {{$json.responsible_party}}."}, "position": [1050, 250]}, {"name": "Gmail Regulatory Team", "type": "n8n-nodes-base.gmail", "parameters": {"to": "={{$json.owner_email}}", "subject": "[FERC {{$json.tier}}] {{$json.filing_name}} \u2014 {{$json.days_left}} days to {{$json.filing_deadline}}", "message": "FERC/DOE regulatory filing deadline:\n\nFiling: {{$json.filing_name}}\nRegulation: {{$json.regulation_reference}}\nDeadline: {{$json.filing_deadline}}\nDays Remaining: {{$json.days_left}}\nPenalty Risk: {{$json.penalty_risk}}\nOwner: {{$json.responsible_party}}\n\nPlease confirm filing status and update the regulatory tracking register."}, "position": [1050, 450]}, {"name": "Log to Regulatory Register", "type": "n8n-nodes-base.googleSheets", "parameters": {"operation": "appendRow", "sheetId": "{{REGULATORY_LOG_SHEET_ID}}", "options": {}, "fieldsUi": {"values": [{"fieldId": "filing_name", "fieldValue": "={{$json.filing_name}}"}, {"fieldId": "tier", "fieldValue": "={{$json.tier}}"}, {"fieldId": "days_left", "fieldValue": "={{$json.days_left}}"}, {"fieldId": "penalty_risk", "fieldValue": "={{$json.penalty_risk}}"}, {"fieldId": "alert_ts", "fieldValue": "={{$json.alert_ts}}"}]}}, "position": [1050, 600]}], "connections": {"Daily 8AM Weekdays": {"main": [[{"node": "Read FERC Deadlines", "type": "main", "index": 0}]]}, "Read FERC Deadlines": {"main": [[{"node": "Classify Filing Urgency", "type": "main", "index": 0}]]}, "Classify Filing Urgency": {"main": [[{"node": "Route by Urgency", "type": "main", "index": 0}]]}, "Route by Urgency": {"main": [[{"node": "Slack FERC Overdue", "type": "main", "index": 0}], [{"node": "Slack FERC Critical", "type": "main", "index": 0}], [{"node": "Gmail Regulatory Team", "type": "main", "index": 0}], [{"node": "Gmail Regulatory Team", "type": "main", "index": 0}], [{"node": "Gmail Regulatory Team", "type": "main", "index": 0}]]}, "Slack FERC Overdue": {"main": [[{"node": "Log to Regulatory Register", "type": "main", "index": 0}]]}, "Slack FERC Critical": {"main": [[{"node": "Log to Regulatory Register", "type": "main", "index": 0}]]}, "Gmail Regulatory Team": {"main": [[{"node": "Log to Regulatory Register", "type": "main", "index": 0}]]}}}
Key filings to track: FERC Form 1 (March 31), EQR submission (15 days after quarter end), NERC TADS (June 1), DOE-923 (March 31), DOE-860 (February 1), FERC eTariff compliance filings, transmission planning study deadlines, state PUC annual reports.
5. Weekly Nuclear Plant Reliability & Safety KPI Dashboard
The problem: Plant managers, VP Nuclear, and board-level leadership need a weekly snapshot of plant reliability and safety performance: capacity factor, Equivalent Forced Outage Rate (EFOR), safety system actuations, open corrective actions, NRC inspection findings. Compiling it from OSIsoft PI historian and CMMS exports takes 45 minutes every Monday morning.
The workflow: Runs every Monday at 7AM. Reads plant performance data from Google Sheets (or a data warehouse). Calculates 7-day averages: capacity factor (with week-over-week delta), EFOR, cumulative safety system actuations, open corrective actions, NRC findings. Sends an HTML table to the plant manager and a one-liner to Slack #nuclear-ops.
{"name": "Weekly Nuclear Plant Reliability & Safety KPI Dashboard", "nodes": [{"name": "Monday 7AM", "type": "n8n-nodes-base.scheduleTrigger", "parameters": {"rule": {"interval": [{"field": "cronExpression", "expression": "0 7 * * 1"}]}}, "position": [250, 300]}, {"name": "Read Plant Performance Data", "type": "n8n-nodes-base.googleSheets", "parameters": {"operation": "readRows", "sheetId": "{{PLANT_PERFORMANCE_SHEET_ID}}", "options": {}}, "position": [450, 300]}, {"name": "Build Nuclear KPI Report", "type": "n8n-nodes-base.code", "parameters": {"jsCode": "const rows = $input.all().map(i => i.json);\nconst latest = rows.slice(-7); // Last 7 days\nconst prevWeek = rows.slice(-14, -7);\nconst avg = (arr, key) => arr.length ? (arr.reduce((s, r) => s + parseFloat(r[key] || 0), 0) / arr.length).toFixed(2) : 0;\nconst capacityFactor = avg(latest, 'capacity_factor_pct');\nconst efor = avg(latest, 'equivalent_forced_outage_rate_pct');\nconst prevCapacity = avg(prevWeek, 'capacity_factor_pct');\nconst capWoW = prevCapacity > 0 ? (((parseFloat(capacityFactor) - parseFloat(prevCapacity)) / parseFloat(prevCapacity)) * 100).toFixed(1) : 0;\nconst safetyActuations = latest.reduce((s, r) => s + parseInt(r.safety_system_actuations || 0), 0);\nconst correctiveActions = latest.reduce((s, r) => s + parseInt(r.corrective_actions_open || 0), 0);\nconst nrcInspectionFindings = latest.reduce((s, r) => s + parseInt(r.nrc_inspection_findings || 0), 0);\nconst html = '<h2>Weekly Nuclear Plant Reliability & Safety KPI</h2>' +\n '<table border=1 cellpadding=4><tr><th>Metric</th><th>This Week</th><th>WoW %</th></tr>' +\n '<tr><td>Capacity Factor</td><td>' + capacityFactor + '%</td><td>' + capWoW + '%</td></tr>' +\n '<tr><td>EFOR</td><td>' + efor + '%</td><td>\u2014</td></tr>' +\n '<tr><td>Safety System Actuations</td><td>' + safetyActuations + '</td><td>\u2014</td></tr>' +\n '<tr><td>Open Corrective Actions</td><td>' + correctiveActions + '</td><td>\u2014</td></tr>' +\n '<tr><td>NRC Inspection Findings</td><td>' + nrcInspectionFindings + '</td><td>\u2014</td></tr>' +\n '</table>';\nreturn [{ json: { capacity_factor: capacityFactor, efor, cap_wow: capWoW, safety_actuations: safetyActuations, corrective_actions: correctiveActions, nrc_findings: nrcInspectionFindings, html_report: html, report_week: new Date().toISOString().split('T')[0] } }];"}, "position": [650, 300]}, {"name": "Gmail Plant Manager", "type": "n8n-nodes-base.gmail", "parameters": {"to": "plant-manager@company.com", "subject": "Weekly Nuclear Plant KPI Dashboard \u2014 Week of {{$json.report_week}}", "message": "={{$json.html_report}}", "options": {"appendAttribution": false}}, "position": [850, 250]}, {"name": "Slack Nuclear Ops", "type": "n8n-nodes-base.slack", "parameters": {"channel": "#nuclear-ops", "text": "Weekly KPI: Capacity factor {{$json.capacity_factor}}% ({{$json.cap_wow}}% WoW) | EFOR {{$json.efor}}% | Safety actuations: {{$json.safety_actuations}} | Open CAs: {{$json.corrective_actions}} | NRC findings: {{$json.nrc_findings}}"}, "position": [850, 400]}], "connections": {"Monday 7AM": {"main": [[{"node": "Read Plant Performance Data", "type": "main", "index": 0}]]}, "Read Plant Performance Data": {"main": [[{"node": "Build Nuclear KPI Report", "type": "main", "index": 0}]]}, "Build Nuclear KPI Report": {"main": [[{"node": "Gmail Plant Manager", "type": "main", "index": 0}, {"node": "Slack Nuclear Ops", "type": "main", "index": 0}]]}}}
Why self-hosted n8n for nuclear and energy?
| Concern | Zapier/Make | Self-hosted n8n |
|---|---|---|
| BES Cyber System data egress | Routes through US/EU cloud — NERC CIP violation | Stays in your OT/IT-separated network |
| NRC operating data | Third-party processor under 10 CFR 50 App. B | Zero external data transfer |
| NERC CIP-007 R2 patch tracking | Not deployable inside BES Electronic Security Perimeter | Deployable inside ESP boundary |
| Audit trail for NERC spot-checks | No git-versioned workflow history | Every change is a git commit (CIP-010 evidence) |
| Air-gapped plant networks | Requires internet connectivity | Runs completely offline on internal infrastructure |
| Cost at 50k ops/mo | $50–200/mo | $0 (self-hosted) |
Get the full FlowKit n8n template library
These 5 workflows are examples of what you can build with n8n. If you want pre-built, ready-to-import templates for the most common automation use cases — email auto-response, lead capture, invoice generation, AI customer support, price monitoring, daily reports — check out the FlowKit n8n Template Store:
stripeai.gumroad.com — Individual templates $12–$29, full bundle $97.
All templates include the workflow JSON, setup guide, and example data — import to your n8n instance and customize in minutes.
Top comments (0)