4
\$\begingroup\$

I've written a script to calculate the user's scores as a part of a larger survey application written in React/Redux. The questions are all based on the Likert Scale, i.e. "How much do you agree with this statement?" from 1 - 5, with 5 being the most. However, the answers are presented and stored as words.

The questions are sorted into various categories, and within each category the question values are added up and averaged.

So the script first sorts the questions into the categories, then maps the answers to the number values associated with them so they can be averaged. Each average score is converted to a verbal value of High, Medium, or Low, which is then used to get a message that corresponds with that score.

The only caveat is that for the final category, it's scored a bit differently.

The reason I'm posting this here is because I feel my code is quite clunky and could be a lot cleaner. I especially don't like the way that I have hard-coded in the names of the categories in the various functions. That seems very hard to maintain or update!

Any help on making this cleaner and more flexible would be greatly appreciated!

I've included at the top sample data so you can get it to work without any import statements or anything like that.

const results = {
  "results": [
    {
      "id": 1,
      "category": "TM",
      "messages": [
        {
          "score": "High",
          "message": "You are a team player! You enjoy collaborating with others to create better outcomes together."
        },
        {
          "score": "Medium",
          "message": "You enjoy working with others, but also appreciate working solo to accomplish your goals."
        },
        {
          "score": "Low",
          "message": "You work best on your own to accomplish your goals and find that others slow you down"
        }
      ]
    },
    {
      "id": 2,
      "category": "CM",
      "messages": [
        {
          "score": "High",
          "message": "You approach conflict head on and try to bring everyone onto the same page. When someone needs a mediator, they call you!"
        },
        {
          "score": "Medium",
          "message": "You work to resolve conflict if pushed, but avoid it if you can. "
        },
        {
          "score": "Low",
          "message": "You prefer to keep to yourself and avoid conflict."
        }
      ]
    },
    {
      "id": 3,
      "category": "CO",
      "messages": [
        {
          "score": "High",
          "message": "You enjoy helping others to learn and develop."
        },
        {
          "score": "Medium",
          "message": "You support others when needed, but don’t offer assistance without prompting."
        },
        {
          "score": "Low",
          "message": "You aren’t really the mentoring type. You prefer to concentrate on yourself and your own accomplishments."
        }
      ]
    },
    {
      "id": 4,
      "category": "SL",
      "messages": [
        {
          "score": "High",
          "message": "You are in tune with others and understand how they feel."
        },
        {
          "score": "Medium",
          "message": "At times you understand others’ emotions, but sometimes you aren’t sure how others feel."
        },
        {
          "score": "Low",
          "message": "Sometimes you struggle to understand how others’ feel."
        }
      ]
    },
    {
      "id": 5,
      "category": "AO",
      "messages": [
        {
          "score": "High",
          "message": "You are always trying to be the best you can be!"
        },
        {
          "score": "Medium",
          "message": "You set goals when needed, but don’t feel the need to always try to improve."
        },
        {
          "score": "Low",
          "message": "You feel comfortable with yourself and don’t feel the need to constantly strive for more."
        }
      ]
    },
    {
      "id": 6,
      "category": "AD",
      "messages": [
        {
          "score": "High",
          "message": "You enjoy change and can go with the flow with ease!"
        },
        {
          "score": "Medium",
          "message": "You can adapt when changes are needed, but also enjoy when things stay the same and you can get into a routine."
        },
        {
          "score": "Low",
          "message": "You are not a fan of change, and prefer to stick with a steady routine."
        }
      ]
    },
    {
      "id": 7,
      "category": "GR",
      "messages": [
        {
          "score": "High",
          "message": "You push through to accomplish your goals, no matter what life throws at you! You are well-suited to work on long-term goals."
        },
        {
          "score": "Medium",
          "message": "You see things through if you can, but sometimes a difficult situation leads you to change your goals."
        },
        {
          "score": "Low",
          "message": "Sometimes you prefer short-term goals and can be easily distracted."
        }
      ]
    },
    {
      "id": 8,
      "category": "PR",
      "messages": [
        {
          "score": "High",
          "message": ""
        },
        {
          "score": "Medium",
          "message": ""
        },
        {
          "score": "Low",
          "message": ""
        }
      ]
    },
    {
      "id": 9,
      "category": "SA",
      "messages": [
        {
          "score": "High",
          "message": "You are in touch with your thoughts and emotions."
        },
        {
          "score": "Medium",
          "message": "You usually have a good handle on how you are feeling and what’s going through your mind."
        },
        {
          "score": "Low",
          "message": "You find that you aren’t really in touch with how you feel at times."
        }
      ]
    },
    {
      "id": 10,
      "category": "SC",
      "messages": [
        {
          "score": "High",
          "message": "You are as cool as a cucumber! You are able to control yourself even in difficult times."
        },
        {
          "score": "Medium",
          "message": "You find that you often can maintain your composure, but sometimes your emotions get the best of you."
        },
        {
          "score": "Low",
          "message": "You find that at times you are impulsive and react before you consider the consequences."
        }
      ]
    },
    {
      "id": 11,
      "category": "PA",
      "messages": [
        {
          "score": "High",
          "message": "You recognize that a positive attitude brings success and happiness! You see the good first and are always looking toward the future."
        },
        {
          "score": "Medium",
          "message": "You are cautiously optimistic about the future."
        },
        {
          "score": "Low",
          "message": "You remember the past as some of the best times of your life, and aren’t sure if things will ever be that good again."
        }
      ]
    },
    {
      "id": 12,
      "category": "IM",
      "messages": [
        {
          "score": "High",
          "message": "You are a visionary and encourage others to be the best they can be."
        },
        {
          "score": "Medium",
          "message": "You have the ability to motivate others through your vision, but at times prefer to just focus on what needs to be done."
        },
        {
          "score": "Low",
          "message": "You are task-oriented and focus on what needs to be done, instead of using vision to inspire others to follow."
        }
      ]
    },
    {
      "id": 12,
      "category": "LP",
      "messages": [
        {
          "score": "Transformation Leader",
          "message": "You see the opportunity to motivate and engage others through establishing a shared vision for the future. You trust others and understand that people are motivated intrinsically and your job is to support and encourage them, and they will seek to accomplish shared goals. "
        },
        {
          "score": "Transactional Leadership",
          "message": "You believe people need to be closely monitored and guided to succeed. You believe in the use of incentives to motivate others. You may be better suited to individual contributor than a leadership role, as you like to work independently."
        }
      ]
    }
  ]
}

const testAnswers = {
  values: {
    TM1: "Consistently",
    TM2: "Consistently",
    TM3: "Consistently",
    TM4: "Sometimes",
    CM1: "Never",
    CM2: "Rarely",
    CM3: "Consistently",
    CM4: "Consistently",
    CO1: "Rarely",
    CO2: "Never",
    CO3: "Never",
    CO4: "Never",
    SL1: "Often",
    SL2: "Never",
    SL3: "Consistently",
    SL4: "Sometimes",
    AO1: "Consistently",
    AO2: "Sometimes",
    AO3: "Often",
    AO4: "Sometimes",
    AD1: "Never",
    AD2: "Consistently",
    AD3: "Often",
    AD4: "Sometimes",
    GR1: "Never",
    GR2: "Consistently",
    GR3: "Often",
    GR4: "Sometimes",
    PR1: "Never",
    PR2: "Consistently",
    PR3: "Often",
    SA1: "Never",
    SA2: "Consistently",
    SA3: "Often",
    SA4: "Sometimes",
    SC1: "Never",
    SC2: "Consistently",
    SC3: "Often",
    SC4: "Sometimes",
    PA1: "Never",
    PA2: "Consistently",
    PA3: "Often",
    PA4: "Sometimes",
    IM1: "Never",
    IM2: "Consistently",
    IM3: "Often",
    IM4: "Sometimes",
    LL: "Leadership",
    LP1: "Consistently",
    LP2: "Consistently",
    LP3: "Often",
    LP4: "Consistently",
  }
}


const categorizeAnswers = answers =>{
  const valueNames = Object.keys(answers.values);
  const getAnswers = category => valueNames.filter(value => value.includes(category));

  const answersTM = getAnswers("TM").map((key) => answers.values[key]);
  const answersCM = getAnswers("CM").map((key) => answers.values[key]);
  const answersCO = getAnswers("CO").map((key) => answers.values[key]);
  const answersSL = getAnswers("SL").map((key) => answers.values[key]);
  const answersAO = getAnswers("AO").map((key) => answers.values[key]);
  const answersAD = getAnswers("AD").map((key) => answers.values[key]);
  const answersGR = getAnswers("GR").map((key) => answers.values[key]);
  const answersPR = getAnswers("PR").map((key) => answers.values[key]);
  const answersSA = getAnswers("SA").map((key) => answers.values[key]);
  const answersSC = getAnswers("SC").map((key) => answers.values[key]);
  const answersPA = getAnswers("PA").map((key) => answers.values[key]);
  const answersIM = getAnswers("IM").map((key) => answers.values[key]);
  const answersLP = getAnswers("LP").map((key) => answers.values[key]);

  return categorizedAnswers = [
    answersTM,
    answersCM,
    answersCO,
    answersSL,
    answersAO,
    answersAD,
    answersGR,
    answersPR,
    answersSA,
    answersSC,
    answersPA,
    answersIM,
    answersLP
  ]
}

const convertAnswersToNumbers = answers => {
  const answerValues = {
    Consistently: 5,
    Often: 4,
    Sometimes: 3,
    Rarely: 2,
    Never: 1
  }

  /* TODO: Reverse value of answer if question.reversed === true */

  const numberAnswers = answers.map(answer => answer.map(resp => answerValues[resp]));
  return numberAnswers;
}

const getAverage = array => {
  let num = 0, length = array.length;
  if (!length) return 0;
  for (let i = 0; i < length; i++) {
    num += parseInt(array[i], 10);
  }
  return num/length;
}

const calculateScore = (array, getAverage) => {
  let score;
  let average = getAverage(array);

  if (average >= 4) {
    score = 'High';
  } else if (average <= 2) {
    score = 'Low';
  } else {
    score = 'Medium';
  }

  return score;
}

const calculateLeadershipScore = (array, getAverage) => {
  let score;
  let average = getAverage(array);

  if ((testAnswers.values['LL'] === 'Leadership' || testAnswers.values['LL'] === 'I don\'t know') && average >= 3.5) {
    score = 'Transformation Leader';
  } else {
    score = 'Transactional Leadership';
  }
  return score;
}

const getMessage = (results, score, category) => {
  let message;
  results.results.forEach(object => {
    if (object.category === category) {
      object.messages.forEach(element => {
        if (element.score === score) {
          message = element.message
        }
      })
    }
  });
  return message;
}

const getResults = (answers, categorizeAnswers, convertAnswersToNumbers) => {
  const categorizedAnswers = categorizeAnswers(answers);
  const numberAnswers = convertAnswersToNumbers(categorizedAnswers);

  const scores = [];
  const getScores = numberAnswers => {
    for (let i = 0; i < numberAnswers.length -1; i++) {
      let score = calculateScore(numberAnswers[i], getAverage);
      scores.push(score);
    }
    let score = calculateLeadershipScore(numberAnswers[12], getAverage);
    scores.push(score);
  }

  getScores(numberAnswers);

  const messageTM = getMessage(results, scores[0], "TM");
  const messageCM = getMessage(results, scores[1], "CM");
  const messageCO = getMessage(results, scores[2], "CO");
  const messageSL = getMessage(results, scores[3], "SL");
  const messageAO = getMessage(results, scores[4], "AO");
  const messageAD = getMessage(results, scores[5], "AD");
  const messageGR = getMessage(results, scores[6], "GR");
  /* const messagePR = getMessage(results, scores[7], "PR"); -- This answer set is not being evaluated at this time */
  const messageSA = getMessage(results, scores[8], "SA");
  const messageSC = getMessage(results, scores[9], "SC");
  const messagePA = getMessage(results, scores[10], "PA");
  const messageIM = getMessage(results, scores[11], "IM");
  const messageLP = getMessage(results, scores[12], "LP");

  const messages = {
    messageTM,
    messageCM,
    messageCO,
    messageSL,
    messageAO,
    messageAD,
    messageGR,
    messageSA,
    messageSC,
    messagePA,
    messageIM,
    messageLP
  }
  console.log(messages);
  return messages;
}

getResults(testAnswers, categorizeAnswers, convertAnswersToNumbers);
\$\endgroup\$
3
\$\begingroup\$

DRY out the code.

Wow, that must have taken some time to type in. There is so much redundant data and unneeded processing.

One of programming's golden rules is Don't Repeat Yourself

Your code is 13K+ in size; over half of that is redundant, or completely unused data.

And the process of scoring is just a spaghetti of calls, loops, temp arrays, unneeded lines, and variables.

If you have groups of values, such as:

SA1 : "blah",
SA2 : "blah",
SA3 : "blah",

Store them in an array:

SA : ["blah","blah","blah"],

If you are populating an array:

var aA = getVal("A");
var aB = getVal("B");
var aC = getVal("C");

var foo = [
   aA,
   aB,
   aC,
];

Put them in the array directly; don't use temp variables.

var foo = [
    getVal("A"),
    getVal("B"),
    getVal("C")
]

Or use a loop:

var foo = "ABC".split("").map(getVal);

Simplify

Your code gets a set of answers, a few for each category, scores them and calculates a mean; it then uses that mean to get the message. That is a very simple process and should be done one at a time for each category, rather than getting the score for all, then the mean, then finding the message for all.

This simplifies the process, as you are not storing intermediate results and you don't need to pass data around that only has a temporary life.

A rewrite

Warning: don't copy and paste this code, as I did not make sure that I correctly duplicated the data.

The answers have the score, mean, and message added to them. All data is referenced via the category; you don't need to search.

After the function has run you can access the details:

testAnswers.TM.score;   // total score
testAnswers.TM.mean ;    // average
testAnswers.TM.message;  // result text
testAnswers.TM.answers;  // answers given

The code:

const results = {
    PR: { messages: ["", "", "" ] },
    TM: { messages: [
        "You are a team player! You enjoy collaborating with others to create better outcomes together.",
        "You enjoy working with others, but also appreciate working solo to accomplish your goals.",
        "You work best on your own to accomplish your goals and find that others slow you down",
    ] },
    CM: { messages: [
        "You approach conflict head on and try to bring everyone onto the same page. When someone needs a mediator, they call you!",
        "You work to resolve conflict if pushed, but avoid it if you can. ",
        "You prefer to keep to yourself and avoid conflict.",
    ] },
    CO: { messages: [
        "You enjoy helping others to learn and develop.",
        "You support others when needed, but don’t offer assistance without prompting.",
        "You aren’t really the mentoring type. You prefer to concentrate on yourself and your own accomplishments.",
    ] },
    SL: { messages: [
        "You are in tune with others and understand how they feel.",
        "At times you understand others’ emotions, but sometimes you aren’t sure how others feel.",
        "Sometimes you struggle to understand how others’ feel.",
    ] },
    AO: { messages: [
        "You are always trying to be the best you can be!",
        "You set goals when needed, but don’t feel the need to always try to improve.",
        "You feel comfortable with yourself and don’t feel the need to constantly strive for more.",
    ] },
    AD: { messages: [
        "You enjoy change and can go with the flow with ease!",
        "You can adapt when changes are needed, but also enjoy when things stay the same and you can get into a routine.",
        "You are not a fan of change, and prefer to stick with a steady routine.",
    ] },
    GR: { messages: [
        "You push through to accomplish your goals, no matter what life throws at you! You are well-suited to work on long-term goals.",
        "You see things through if you can, but sometimes a difficult situation leads you to change your goals.",
        "Sometimes you prefer short-term goals and can be easily distracted.",
    ] },
    SA: { messages: [
        "You are in touch with your thoughts and emotions.",
        "You usually have a good handle on how you are feeling and what’s going through your mind.",
        "You find that you aren’t really in touch with how you feel at times.",
    ] },
    SC: { messages: [
        "You are as cool as a cucumber! You are able to control yourself even in difficult times.",
        "You find that you often can maintain your composure, but sometimes your emotions get the best of you.",
        "You find that at times you are impulsive and react before you consider the consequences.",
    ] },
    PA: { messages: [
        "You recognize that a positive attitude brings success and happiness! You see the good first and are always looking toward the future.",
        "You are cautiously optimistic about the future.",
        "You remember the past as some of the best times of your life, and aren’t sure if things will ever be that good again.",
    ] },
    IM: { messages: [
        "You are a visionary and encourage others to be the best they can be.",
        "You have the ability to motivate others through your vision, but at times prefer to just focus on what needs to be done.",
        "You are task-oriented and focus on what needs to be done, instead of using vision to inspire others to follow.",
    ] },
    LP: {  messages: [
        "Transformation Leader; You see the opportunity to motivate and engage others through establishing a shared vision for the future. You trust others and understand that people are motivated intrinsically and your job is to support and encourage them, and they will seek to accomplish shared goals. ",
        "Transactional Leadership; You believe people need to be closely monitored and guided to succeed. You believe in the use of incentives to motivate others. You may be better suited to individual contributor than a leadership role, as you like to work independently.",
    ] }
}
const resultScoring = {
    LP(answer){
        if( answer.answers.includes("Leadership") || answer.answers.includes("I don't know") ) && answer.mean >= 3.5){
            answer.message = results[cat].messages[0];    
        } else {
            answer.message = results[cat].messages[1];  
        }
    },
    default(answer, cat){
        var messageIndex = 1
        if (answer.mean >= 4) { messageIndex = 0 }
        else if (answer.mean <= 2) { messageIndex = 2 }
        answer.message = results[cat].messages[messageIndex];        
    }
};

function getResults(answers) {
    const answerValues = { Consistently: 5, Often: 4,  Sometimes: 3,  Rarely: 2, Never: 1 };   
    const messageArray = [];
    for (const cat of Object.keys(answers)) {
        let score = 0;
        const answer = answers[cat];
        for (const answer of answer.answers) { 
            score += answerValues[answer] !== undefined ? answerValues[answer] : 0;
        }
        answer.score = score;
        answer.mean = score / answer.answers.length;
        if (resultScoring[cat] === undefined) { resultScoring.default(answer, cat) }
        else { resultScoring[cat](answer) }
        messageArray.push(answer.message);
    }
    return messageArray;
}



const testAnswers = {
    TM: {answers : ["Consistently", "Consistently", "Consistently", "Sometimes"]},
    CO: {answers : ["Rarely", "Never", "Never", "Never"]},
    CM: {answers : ["Never", "Rarely", "Consistently", "Consistently"]},
    SL: {answers : ["Often", "Never", "Consistently", "Sometimes"]},
    AD: {answers : ["Never", "Consistently", "Often", "Sometimes"]},
    AO: {answers : ["Consistently", "Sometimes", "Often", "Sometimes"]},
    GR: {answers : ["Never", "Consistently", "Often", "Sometimes"]},
    PA: {answers : ["Never", "Consistently", "Often", "Sometimes"]},
    LP: {answers : ["Consistently", "Consistently", "Often", "Consistently"]},
    IM: {answers : ["Never", "Consistently", "Often", "Sometimes"]},
    SA: {answers : ["Never", "Consistently", "Often", "Sometimes"]},
    SC: {answers : ["Never", "Consistently", "Often", "Sometimes"]},
    PR: {answers : ["Never", "Consistently", "Often"]},
    LL: {answers : ["Leadership"]}
};
scoreAnswers(testAnswers);

One last thing

The returning messages???

const messages = {
  messageTM,
  messageCM,
  ...
}

These are strings. Don't you mean to return an array??

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.