Scoring Migration Risk with a Weighted Impact Model
Problem Statement
A flat risk list treats “the favicon 404s” and “all product URLs lose their redirects” as equals, which is useless for deciding what to fix first. A weighted impact model scores each risk as likelihood times impact, then multiplies by a category weight so revenue-affecting failures outrank cosmetic ones with the same raw score. The output is a ranked, defensible mitigation order rather than a wall of equally-shouting bullet points, and a single comparable number per risk that you can sort, filter, and put in front of leadership. Crucially, the model makes the prioritisation transparent: anyone can see why a risk sits where it does, because the likelihood, impact, and weight that produced its score are all on the row. This page is part of Risk Assessment Frameworks; start there for the wider risk methodology.
When to Use This Approach
- The risk register is long enough that “fix everything” is not a realistic order of operations.
- Different risks hit different parts of the business (revenue, SEO, compliance, brand) and deserve different weights.
- Stakeholders need a transparent, repeatable reason why item A is mitigated before item B.
- You want a single number per risk that can be sorted, filtered, and reported to leadership.
- The same model will be reused across migrations, so subjectivity must be constrained by a rubric.
Step-by-Step Instructions
1. Define Likelihood and Impact Scales
Ambiguous scores are worthless, so anchor each 1–5 level to a concrete definition. Write a rubric so two assessors give the same score for the same risk.
# scales.yml — anchored 1–5 definitions (no vague "medium")
likelihood:
1: "Rare: strong controls, never seen on similar migrations"
3: "Possible: plausible without specific mitigation"
5: "Almost certain: known recurring failure mode"
impact:
1: "Negligible: cosmetic, no traffic or revenue effect"
3: "Serious: measurable traffic/revenue dip, recoverable"
5: "Critical: major revenue loss or extended outage"
2. Assign Category Weights
Not all risk categories matter equally. Give each category a weight that reflects its business stake, so a revenue risk outranks an equally-likely cosmetic one. Set the weights once with the business owners and freeze them for the migration, so individual scores cannot be quietly inflated by re-weighting mid-assessment. Keep the spread meaningful but not extreme — a 4x gap between the highest and lowest weight is usually enough to separate categories without letting one factor swamp likelihood and impact entirely.
# weights.yml — multipliers applied to the base score by category
weights:
revenue: 2.0 # direct order/lead impact
seo_indexation: 1.6 # organic visibility
performance: 1.2 # Core Web Vitals / UX
compliance: 1.4 # legal/accessibility
cosmetic: 0.5 # branding, non-functional
3. Compute the Weighted Score
Multiply likelihood by impact for the base score, then by the category weight. Storing all three inputs keeps the score auditable rather than a black box: when a stakeholder challenges a ranking, you can point at the exact likelihood, impact, and weight that produced it and adjust the disputed input rather than the conclusion. Keep the calculation in a single script driven from a flat input file so the whole register can be recomputed in seconds whenever a score, a weight, or a mitigation changes — a model that is painful to re-run is a model that quietly goes stale.
# Weighted score = likelihood * impact * category_weight
import yaml, csv
weights = yaml.safe_load(open('weights.yml'))['weights']
out = []
for r in csv.DictReader(open('risks.csv')): # cols: id,risk,category,likelihood,impact
base = int(r['likelihood']) * int(r['impact'])
r['weighted'] = round(base * weights[r['category']], 1)
out.append(r)
out.sort(key=lambda x: x['weighted'], reverse=True) # highest risk first
4. Banding and Mitigation Order
Translate the weighted score into action bands so the register drives behaviour, not just a spreadsheet column. The top band blocks go-live until mitigated, the next band must be addressed before launch, and the lower bands are monitored or formally accepted. Bands matter because a raw number invites endless debate about whether 14.0 is “really worse” than 13.0, whereas a banded label converts the score into a decision. Attach an owner and a target date to every risk above the lowest band, so the register becomes a live mitigation plan rather than a static assessment that ages on a shelf.
# Map weighted score to an action band
def band(score):
if score >= 30: return 'CRITICAL — blocks go-live' # must mitigate before cutover
if score >= 15: return 'HIGH — mitigate pre-launch'
if score >= 6: return 'MEDIUM — monitor with plan'
return 'LOW — accept / log'
for r in out: r['band'] = band(r['weighted'])
Worked Example
A migration of oldshop.example.com has a register of 40 risks. “Product URL redirects fail” scores likelihood 3, impact 5 — a base of 15 — and sits in the revenue category, so the 2.0 weight lifts it to 30.0: CRITICAL, blocking go-live until the redirect map is signed off. “Footer copyright year is wrong” scores likelihood 5, impact 1 — base 5 — in the cosmetic category with a 0.5 weight, landing at 2.5: LOW, logged and accepted.
Without weighting, the footer risk (base 5) would have sat near a base-6 medium item and competed for attention; with weighting it correctly drops to the bottom of the register. At the other end, a performance risk — “new image pipeline may regress LCP” at likelihood 3, impact 3, base 9, performance weight 1.2 — lands at 10.8, HIGH, and is scheduled for pre-launch mitigation rather than ignored. The model thus separates three risks that all looked “medium-ish” on a flat list into clearly different actions.
The ranked register sorts the redirect risk to the top and drives the mitigation order, and because the redirect item is CRITICAL it becomes an explicit blocker on the go/no-go gate. It builds directly on the migration risk matrix, feeds the go/no-go gate communicated through Stakeholder Communication Plans, and rolls up into the Pre-Migration Auditing & Risk Assessment record.
Verification
Confirm the model is consistent and the ranking behaves.
# Every risk must have a valid category that exists in weights.yml
python -c "import csv,yaml;w=yaml.safe_load(open('weights.yml'))['weights'];\
print([r['category'] for r in csv.DictReader(open('risks.csv')) if r['category'] not in w])"
# Confirm the register is sorted descending by weighted score
python -c "import csv;v=[float(r['weighted']) for r in csv.DictReader(open('scored.csv'))];print(v==sorted(v,reverse=True))"
# Confirm at least one CRITICAL blocks go-live, or justify why none exist
grep -c 'CRITICAL' scored.csv
Watch for these failures: unanchored 1–5 scales so scores drift between assessors; weights that sum-normalise away the intended emphasis; and a register that is scored but never sorted, so the ranking exists only in theory. Re-score after each mitigation so the register reflects residual risk, not the original worst case — a risk that has been mitigated from impact 5 to impact 2 should visibly drop down the order. And resist the urge to invent extra precision: a 1–5 scale times a 1–5 scale gives plenty of separation, and adding decimal scoring only manufactures false confidence without changing the action the band dictates.
FAQ
Why multiply by a category weight instead of just using likelihood times impact? Because a likelihood-times-impact score treats a revenue outage and a cosmetic glitch identically when their raw scores match. The category weight encodes business priority, so two risks with the same base score separate correctly and the register ranks by what the organisation actually cares about.
How do I keep scoring objective across assessors? Anchor every level of the likelihood and impact scales to a concrete definition and have two people score independently, reconciling any gap of more than one level. The rubric, not the assessor’s mood, should determine the number, which is what makes the model repeatable across migrations.
What score should block go-live? Set an explicit critical threshold — here, a weighted score of 30 or above — and treat it as a hard gate that must be mitigated or formally accepted before cutover. Tying the top band to the go/no-go decision is what turns the model from a report into a control.
Related
- Creating a Migration Risk Matrix for Enterprise Sites
- Writing a Migration Go-Live Runbook
- Risk Assessment Frameworks
← Back to Risk Assessment Frameworks