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.

Weighted risk score formula Likelihood times impact gives a base score, multiplied by a category weight to produce a weighted score that sorts into a ranked register. Weighted Risk Score Likelihood Impact Category weight Weighted score 1–5 1–5 x0.5–x2.0 ranked register × ×
Likelihood times impact gives a base score; a category weight scales it into the weighted score that ranks the register.

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'])

The reason to weight rather than simply multiply is that the two axes are not commensurable. A failure that is nearly certain but costs almost nothing and one that is unlikely but would take the checkout offline can produce identical products, and treating them as equivalent sends the team to fix the wrong one first. Weighting lets you state, explicitly and reviewably, that impact matters more than likelihood on this migration — which is almost always true, because a low-likelihood catastrophe still ends the project.

Why an unweighted product misranks risks Two failure modes with identical unweighted products — a near-certain cosmetic issue and an unlikely checkout outage — separate correctly once impact is weighted more heavily than likelihood. Same product, very different problems Failure mode Likelihood Impact Product Weighted Alt text missing on archive images 5 2 10 6.5 — monitor Checkout POST loses its body on 301 2 5 10 14 — fix first Canonical drift on paginated series 4 3 12 10 — after the above Weighting impact at roughly twice likelihood separates the middle row from the first, which an unweighted product cannot do. State the weights explicitly in the model so the ordering can be challenged on its assumptions rather than its arithmetic.
The unweighted column ranks a cosmetic certainty alongside a revenue-path catastrophe; the weighted one puts them where the team's time should go.

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.

One property worth designing for deliberately: the model should be stable under small input changes. If nudging a likelihood from 3 to 4 reshuffles the top of the queue, the ordering is being driven by arithmetic noise rather than by real differences, and people will quickly stop trusting it. Test the model by perturbing a few scores by one point and checking that the top band stays recognisably the same — if it does not, the weights are too aggressive or the scale is too coarse for the number of items you are ranking.

Sanity checks on a weighted scoring model before it drives decisions Four checks — stability under small changes, traceability of every score to evidence, absence of unowned rows, and agreement between the top band and the rollback thresholds. Test the model before you let it set priorities Stable a one-point change does not reshuffle the top band Traceable every score cites the count or revenue figure it came from Owned no row above the line lacks a name and a due date Consistent the top band and the rollback thresholds describe the same risks A model failing any of these produces a ranking people quietly override, which is worse than having no ranking at all.
The last check is the one most often missed: if something ranks high enough to delay a launch, it should also be something that would abort one.

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.

How do I choose the weights themselves? Start from the ratio the business would recognise if stated in plain language — usually something close to “a catastrophic outcome matters about twice as much as a certain one” — and then test it against a handful of cases where everyone already agrees on the answer. If the model ranks those correctly, the weights are serviceable; if it does not, adjust and re-test rather than adjusting the individual scores to compensate. Tuning scores to force a desired ordering is the failure mode that turns a model into an elaborate way of writing down a pre-existing opinion.

Should the weights differ between migrations? Slightly, and for a specific reason: what counts as catastrophic changes with the estate. A transactional site weights impact heavily because a broken checkout is unrecoverable revenue; a reference site with no conversion path may reasonably weight likelihood higher, since its exposure is gradual ranking loss rather than a sudden outage. Set the weights once at the start of each migration, record the reasoning, and leave them alone thereafter — weights adjusted mid-project are indistinguishable from moving the goalposts.

Related

← Back to Risk Assessment Frameworks