SLA Risk Prediction and Auto-Escalation System
A ServiceNow-based orchestration layer built on top of a Python/FastMCP risk scoring service
1. Overview
This document describes an intern project built around predicting and acting on SLA breach risk for ServiceNow
incidents. A trained ML model, served through a FastMCP layer, reads incident data from ServiceNow, predicts
the likelihood of an SLA breach, and writes that prediction back into ServiceNow. From there, ServiceNow itself
takes over: it validates the incoming prediction, decides whether the incident needs to be escalated, routes it to
the right team if so, updates the incident, notifies the relevant people, and surfaces the risk on the incident form
for agents to see.
The main focus of this document, and of the work behind it, is the ServiceNow side of this system — the tables,
Business Rule, Flow, UI Policy, and Client Scripts that turn a raw prediction into a routed, auditable, agent-visible
action. The scoring model itself is referenced only where it feeds into this ServiceNow logic, since that is the
boundary most of the actual configuration work sits behind.
The goal was not to build a production-grade enterprise platform, but to get hands-on experience with core
ServiceNow development tools — custom tables, a Business Rule, Flow Designer, Client Scripts, and a UI Policy
— while wiring them into a real prediction feed rather than working with dummy data.
2. Overall Flow
At a high level, a risk score enters ServiceNow through a single custom table, and everything after that point is
handled natively within the platform. The diagram below shows the full path, from the prediction being written
in, through validation and decision-making, to the action an agent eventually sees on the incident form.
The one point where the system reaches outside ServiceNow is the initial read of incident data and the write-
back of the predicted score, both done through ServiceNow's REST Table API against the u_sla_risk_score table.
Once that record lands, the rest of the diagram — validation, branching, routing, updating, notifying, and logging
— runs entirely inside ServiceNow.
3. u_sla_risk_score — the entry point table
This table is where a prediction becomes visible to ServiceNow. It was designed to be simple enough to write to
reliably from outside, while carrying enough information for ServiceNow to act on a score without needing to
look anywhere else.
Field Type Purpose
u_incident Reference (Incident) Which incident this score belongs to
Field Type Purpose
u_risk_score Decimal (0–100) Predicted probability of SLA breach
u_risk_band Choice (High / Medium / Simplified risk tier used for branching logic
Low)
u_explanation String Short text reasoning returned by the model
u_scored_at Date/Time When the score was generated
u_escalated Boolean Whether this score has already triggered an
escalation
u_escalated_at Date/Time When the escalation happened, if it did
To make the rest of this document concrete, the same three sample incidents are used throughout as worked
examples: INC0010045 (Hardware, moderate risk), INC0010046 (Network, high risk, a category with an existing
routing rule), and INC0010052 (Facilities, high risk, a category with no routing rule, to show the fallback path).
Worked example — record written for INC0010046
The scoring service inserts a row like this once it has evaluated the incident:
u_incident INC0010046 (sys_id of the incident)
u_risk_score 88.00
u_risk_band High
u_explanation Recurring network timeout pattern, unassigned for
46 minutes
u_scored_at 2026-07-18 09:12:03
u_escalated false
This is the record that everything from Section 4 onward reacts to — first the Business Rule checks it is well-
formed, then the Flow decides what to do with it.
Since this table is written to programmatically rather than through a form filled in by a person, it was important
to check incoming data before trusting it. A before-insert/update Business Rule was added on u_sla_risk_score
to catch bad or malformed writes early, rather than letting incorrect data flow into the rest of the system.
The rule checks, in order:
• The risk score is a valid number between 0 and 100.
• The incident reference is present and actually resolves to a real incident record.
• The risk band is one of the three expected values (High, Medium, Low).
• Whether this looks like a duplicate write for the same incident within a short window, which is logged as a
warning rather than blocked.
• The scored-at timestamp is filled in automatically if the external service did not set it.
Any record that fails the score-range, incident-reference, or risk-band checks is rejected outright using
setAbortAction, so it never reaches the table in an invalid state. This was tested by submitting an out-of-range
score (150) and confirming the insert was blocked, and by submitting a valid record and confirming it saved
correctly.
Worked example — valid record passes through
The INC0010046 record from Section 3 passes every check: 88.00 is within 0–100, the incident reference
resolves to a real incident, and “High” is a recognised risk band. The Business Rule lets the insert proceed and,
since u_scored_at was already set by the scoring service, leaves it unchanged.
Worked example — invalid record is rejected
If a malformed write were attempted instead, for example:
u_incident INC0010046 (sys_id of the incident)
u_risk_score 140.00
u_risk_band High
the Business Rule's bounds check catches 140.00 as outside the 0–100 range on the first condition, calls
setAbortAction(true), and the insert is blocked before it reaches the table.
5. Orchestration — Flow Designer
This is the central piece of the build. A Flow Designer flow, named SLA Risk Score Escalation Orchestrator, is
triggered whenever a u_sla_risk_score record is created or updated, with a condition that it has not already
been escalated, so the flow does not repeatedly act on a record it has already handled.
The diagram below breaks the flow down step by step, in the same order the actions sit in the flow's canvas,
including both the low/medium-risk path and the full high-risk escalation path with its category-matched and
fallback branches.
5.1 Trigger and initial lookup
The trigger fires on the u_sla_risk_score table for both create and update, filtered to records where u_escalated
is still false. The first action inside the flow looks up the related Incident record by matching its Sys ID against the
u_incident reference field on the triggering record, so every later step has direct access to the incident's own
fields, such as category.
5.2 Risk band branch
An If step checks whether Risk Band is High. When it is not, the flow takes the simpler path: it creates a single
record in u_sla_escalation_log noting that no action was taken, and the flow ends there. This keeps low- and
medium-risk scores fully auditable without doing anything to the incident itself.
Worked example — INC0010045 — Medium risk, no action
INC0010045 (Hardware) comes in with u_risk_score = 35.00 and u_risk_band = Medium. The If step evaluates to
No, so the flow skips straight to logging and stops. The incident itself is left completely untouched — no
reassignment, no urgency change, no notification. The resulting log entry looks like this:
u_incident INC0010045
u_risk_score_record (sys_id of this u_sla_risk_score record)
u_action_taken No Action - Below Threshold
u_risk_score_at_action 35.00
u_flow_outcome Success
5.3 Category-based routing
When the risk band is High, the flow looks up u_sla_routing_rule, matching the incident's category against the
table and requiring the row to be marked Active. A second If step checks whether a matching Assignment Group
came back. If nothing matched, a fallback lookup runs against the same table for the row where Category is
“Default”, so a High-risk incident is never left without an owner just because its category has no explicit
mapping. Both paths converge on the same assignment group value before continuing.
Worked example — INC0010046 — category match found
INC0010046's incident record has Category = Network. The lookup against u_sla_routing_rule finds an Active
row where Category = Network, returning Assignment Group = Network Support. Since this is not empty, the
second If step evaluates No, and the flow moves straight to Step 5 using Network Support as the assignment
group.
Worked example — INC0010052 — no category match, fallback used
INC0010052's incident record has Category = Facilities, a category that has no row in u_sla_routing_rule. The
first lookup returns no Assignment Group, so the second If step evaluates Yes, and the fallback lookup runs
instead, matching Category = “Default” and returning Assignment Group = Incident Management. The flow then
continues to Step 5 using Incident Management as the assignment group, exactly as it would for a mapped
category — the incident is never left unrouted just because Facilities has no explicit rule.
5.4 Applying the escalation
• Updates the incident: sets Assignment Group to the resolved value, raises Urgency, and adds a work note
explaining the automated action and the risk score that triggered it.
• Updates the originating u_sla_risk_score record: sets u_escalated to true and stamps u_escalated_at with
the current time, so the trigger condition prevents this record from firing the flow again.
• Sends a notification about the escalation.
• Creates a record in u_sla_escalation_log capturing the action taken, the assignment group used, the risk
score at the time, and the outcome of the flow run.
Worked example — INC0010046 — before and after
Continuing the same example, using Network Support as the resolved assignment group from Step 3:
Assignment Group (before) Service Desk
Assignment Group (after) Network Support
Urgency (before) 2 - Medium
Urgency (after) 1 - High
Work notes (added) Automated AIOps Platform: High risk threshold
breached (Risk Score: 88.00). Auto-routed based on
category mapping.
The originating u_sla_risk_score record is then updated:
u_escalated true
u_escalated_at 2026-07-18 09:12:05
And the escalation is logged as a new row in u_sla_escalation_log:
u_incident INC0010046
u_action_taken Reassigned + Notified
u_previous_assignment_group Service Desk
u_new_assignment_group Network Support
u_risk_score_at_action 88.00
u_flow_outcome Success
INC0010052 goes through the same three updates, the only difference being that Incident Management is used
as the assignment group throughout, since that is what the fallback lookup returned.
5.6 Routing table
u_sla_routing_rule is a small lookup table with a category field, an assignment group reference, an active flag,
and an optional priority override. It was populated with a handful of real categories (Database, Network,
Hardware, Software) mapped to corresponding groups, plus a Default row used as the fallback when a category
has no explicit mapping.
Worked example — relevant rows for the examples above
Category Assignment Group Active
Network Network Support true
Hardware Hardware true
Default Incident Management true
This is exactly why INC0010046 (Network) resolved to Network Support directly, while INC0010052 (Facilities,
not shown here) fell through to the Default row and resolved to Incident Management instead.
5.7 Escalation log
u_sla_escalation_log exists purely for traceability — every time the flow runs, whether it escalates or not, a row
is written recording the incident, the score at the time, the action taken, the assignment group before and after,
and the outcome. This was included so that automated actions taken by the flow are not invisible after the fact.
5.8 Testing
The flow was tested for both branches: a Low-risk score correctly produced a “no action” log entry with no
changes to the incident, and a High-risk score for a mapped category correctly reassigned the incident, raised
urgency, sent a notification, marked the score as escalated, and logged the outcome. The fallback path was also
tested using a category with no matching routing rule, which correctly routed to the default group instead of
failing.
6. Form-level behaviour — Client Scripts and UI Policy
The last part of the project focused on making the risk information visible and enforceable directly on the
incident form, rather than only living in the background tables.
6.1 UI Policy
A UI Policy was added on the Incident table that makes a new Escalation Reason field mandatory specifically
when someone tries to move a high-urgency incident (i.e. one the flow has escalated) into a Resolved or Closed
state. This was done so that an agent cannot silently close an auto-escalated incident without recording why,
without adding friction to normal, non-escalated tickets.
Worked example — INC0010046 vs INC0010045 at close time
INC0010046 is now sitting at Urgency = 1 - High, following the escalation in Section 5.4. If an agent tries to set its
state to Resolved without filling in Escalation Reason, the UI Policy blocks the save and marks the field
mandatory. INC0010045, on the other hand, was never escalated and stayed at its original Medium urgency, so
the same UI Policy condition never matches, and it can be resolved normally with no extra field required.
6.2 Client Scripts
Two small Client Scripts were added on the Incident form. An onLoad script checks the incident's urgency and
displays an on-screen banner if the incident is currently marked High, indicating it was auto-escalated and
should be reviewed before closing. A matching onChange script does the same thing live, if urgency is changed
while the form is open, without needing a page refresh.
Worked example — what the agent actually sees
Opening INC0010046 after the flow has run shows the “HIGH RISK” banner immediately, since
g_form.getValue('urgency') reads ‘1’ on load. Opening INC0010045 shows no banner at all, since its urgency was
never changed from the original value. If an agent were to manually raise INC0010045's urgency to High for an
unrelated reason, the onChange script would show the same banner immediately, even though this incident was
never touched by the Flow.
6.3 Testing
The banner was confirmed to appear correctly for an escalated (High urgency) incident and to stay hidden for a
normal incident, both on page load and when urgency was changed manually. The UI Policy was confirmed to
block closing a High-urgency incident without an Escalation Reason, and to allow the save once the field was
filled in.
7. Summary
From the point a risk prediction is written into u_sla_risk_score, the rest of the system runs natively inside
ServiceNow: validating the data, deciding whether and how to escalate, routing to the right team, notifying,
logging the decision, and surfacing it on the incident form. This is handled through a Business Rule, a Flow, a
routing lookup table, an audit log table, a UI Policy, and two Client Scripts, all built and tested as part of this
project.
The focus throughout was on learning how to design and wire together these ServiceNow-native pieces around
a real, live-feeding prediction source, rather than on the prediction model itself.