0

I'm writing a simple application that takes in the likelihood of a risk occurring, and the severity of its outcome, and translates that into a rating of how bad the risk is based on a risk matrix.

My current solution is to translate the likelihood and consequence into 1-5, multiply them together, and then use the result in a bunch of if/else statements to get the risk rating. My problem is that the ranges overlap and I have to deal with those cases individually, which I feel is a bit of a cop-out.

var likelihoods = {         
    "Rare" : 1,
    "Unlikely" : 2,
    "Possible" : 3,
    "Likely" : 4,
    "Almost Certain" : 5,
};

var consequences = { 
    "Minor" : 1,
    "Moderate" : 2,
    "Major" : 3,
    "Severe" : 4,
    "Extreme" : 5,
};

var likelihood = likelihoods[item.likelihood];  
var consequence = consequences[item.consequence];  

var riskVal= likelihood * consequence;

if (likelihood == 2 && consequence == 2) { riskRating = 1 }
else if (likelihood == 1 && consequence == 5) { riskRating = 3 }
else if (likelihood == 2 && consequence == 5) { riskRating = 4 }

else if (riskVal <= 3) { riskRating = 1 } //Low risk 
else if (riskVal <= 6) { riskRating = 2 } //Medium risk
else if (riskVal <= 12) { riskRating = 3 } //High risk
else { riskRating = 4 } //Critical risk

What's a cleaner way to do this?

Jesse Bell
  • 23
  • 3

1 Answers1

2

I think it might be best to make this as self-documenting as possible. So actually including the table in the source code strikes me as extremely helpful. To do this, I would write a (hopefully generic) function that parses such a table and returns a function that accepts row and column and returns the matching value. Perhaps something like this:

const lookup = (tbl) => {
  const allRows = tbl.split('\n')
        .filter(row => row.length && !row.startsWith('|---'))
  const colNames = allRows[0].split(/\|/).map(s => s.trim())
        .filter(s => s.length > 0)
  const rows = allRows.slice(1).map(r => r.split(/\|/)
        .map(s => s.trim()).filter(s => s.length > 1))
  const table = rows.reduce((table, row) => {
    table[row[0]] = row.slice(1).reduce((r, col, idx) => {
      r[colNames[idx]] = col;
      return r
    }, {})
    return table
  }, {});
  return (row, col) => (table[row] || {})[col]
}

var riskVal = lookup(`
                   | Minor  | Moderate | Major    | Severe   | Extreme  |
  |----------------|--------|----------|----------|----------|----------| 
  | Rare           | Low    | Low      | Low      | Medium   | High     | 
  | Unlikely       | Low    | Low      | Medium   | High     | High     |
  | Possible       | Low    | Medium   | High     | High     | Critical |  
  | Likely         | Medium | High     | High     | Critical | Critical |   
  | Almost Certain | Medium | High     | Critical | Critical | Critical |
`)

console.log(riskVal('Possible', 'Severe')) //=> 'High'
console.log(riskVal('Almost Certain', 'Major')) //=> 'Critical
console.log(riskVal('Unlikely', 'Extreme')) //=> 'High
console.log(riskVal('Rare', 'Minor')) //=> 'Low'
console.log(riskVal('Possible', 'Moderate')) //=> 'Medium'

Obviously if you want the numeric values for input or for output, you could just switch the table, although you might have to parse some strings into numbers as you go (or stringify some numbers.)

Note that lookup is generic. If you structure your table as shown, it doesn't matter what's in the row or column headers, the returned function should yield the appropriate cell value. There can be no duplicated row or column headers, of course, but that seems a reasonable restriction.

Also note that I translated your code back into a table quickly. There might be an error or two in there. Don't count on this specific table to be accurate.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103