develop-candymat #7
|
@ -1,7 +1,8 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true
|
||||
node: true,
|
||||
jest: true
|
||||
},
|
||||
'extends': [
|
||||
'plugin:vue/recommended',
|
||||
|
|
|
@ -20,7 +20,7 @@ module.exports = {
|
|||
'jest-serializer-vue'
|
||||
],
|
||||
testMatch: [
|
||||
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
|
||||
'**/*.test.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
|
||||
],
|
||||
testURL: 'http://localhost/'
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<v-progress
|
||||
class="result-percentage"
|
||||
:value="party.score"
|
||||
:max="totalScoredPoints"
|
||||
:max="totalMaxPoints"
|
||||
/>
|
||||
</router-link>
|
||||
|
||||
|
@ -99,17 +99,9 @@
|
|||
<script>
|
||||
import { IPDATA_URL } from '@/config/api'
|
||||
import { getTranslatedUrl, getUserLanguage } from '@/i18n/helper'
|
||||
import {
|
||||
MAX_POINTS,
|
||||
BASE_POINTS,
|
||||
MIN_POINTS,
|
||||
EMPHASIS_POINTS,
|
||||
getScoringGrid
|
||||
} from '@/app/euromat/scoring'
|
||||
import { getPartiesWithScores, getTotalMaxPoints } from '@/app/euromat/scoring'
|
||||
import { parties } from '@/data'
|
||||
|
||||
const addUp = (a, b) => a + b
|
||||
|
||||
export default {
|
||||
name: 'Results',
|
||||
|
||||
|
@ -126,11 +118,8 @@
|
|||
return {
|
||||
userCountry: getUserLanguage().country,
|
||||
scoringGrid: [],
|
||||
answers: [],
|
||||
emphasized: [],
|
||||
scores: [],
|
||||
parties,
|
||||
totalScoredPoints: 0
|
||||
parties: [],
|
||||
totalMaxPoints: 0
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -180,18 +169,15 @@
|
|||
console.warn('Unable to fetch geo location:', error)
|
||||
}
|
||||
|
||||
this.emphasized = emphasized
|
||||
this.answers = answers
|
||||
|
||||
this.scoringGrid = getScoringGrid(this.answers, this.emphasized)
|
||||
this.scores = this.getScorePoints(this.scoringGrid)
|
||||
this.parties = this.parties
|
||||
.map(this.getScorePerParty)
|
||||
const partiesWithScores = getPartiesWithScores(answers, emphasized, parties)
|
||||
this.parties = partiesWithScores.map(party => ({
|
||||
token: party.token,
|
||||
score: party.score,
|
||||
nationalParty: party['national_parties'][this.userCountry]
|
||||
}))
|
||||
.sort((a, b) => a.score - b.score)
|
||||
.reverse()
|
||||
this.totalScoredPoints = this.scores
|
||||
.map(s => s.highestScore)
|
||||
.reduce(addUp, 0)
|
||||
this.totalMaxPoints = getTotalMaxPoints(answers, emphasized)
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -224,69 +210,7 @@
|
|||
}
|
||||
},
|
||||
getScorePercentage (score) {
|
||||
return (score / this.totalScoredPoints * 100).toFixed(2)
|
||||
},
|
||||
evalPoints (party, user, emphasis) {
|
||||
let score = 0
|
||||
|
||||
if (user.position === party.position) {
|
||||
score = MAX_POINTS
|
||||
} else if (
|
||||
(user.position === 'positive' && party.position === 'neutral') ||
|
||||
(user.position === 'neutral' && party.position === 'positive') ||
|
||||
(user.position === 'negative' && party.position === 'neutral')
|
||||
) {
|
||||
score = BASE_POINTS
|
||||
} else if (
|
||||
(user.position === 'positive' && party.position === 'negative') ||
|
||||
(user.position === 'neutral' && party.position === 'negative') ||
|
||||
(user.position === 'negative' && party.position === 'positive')
|
||||
) {
|
||||
score = MIN_POINTS
|
||||
}
|
||||
|
||||
return {
|
||||
party: party.party,
|
||||
score: emphasis ? score * EMPHASIS_POINTS : score
|
||||
}
|
||||
},
|
||||
getHighestScore (scores) {
|
||||
const highestScore = Math.max(...scores.map(s => s.score))
|
||||
|
||||
if (!highestScore) {
|
||||
return MIN_POINTS
|
||||
}
|
||||
|
||||
return highestScore === 1
|
||||
? MAX_POINTS
|
||||
: highestScore
|
||||
},
|
||||
getScorePoints (grid) {
|
||||
// 1. Iterate over scoringGrid
|
||||
// 2. Get user and party positions of each thesis
|
||||
// 3. Evaluate points based on calculation model for each party
|
||||
// 4. Count the highest score per thesis
|
||||
// 5. Return a new object for each thesis row with results
|
||||
return grid.map(row => {
|
||||
const partiesFromRow = row.positions.filter(p => p.type === 'party')
|
||||
const user = row.positions[row.positions.length - 1]
|
||||
const scores = partiesFromRow.map(party => this.evalPoints(party, user, row.emphasis))
|
||||
const highestScore = this.getHighestScore(scores)
|
||||
return {
|
||||
thesis: row.thesis,
|
||||
highestScore,
|
||||
scores
|
||||
}
|
||||
})
|
||||
},
|
||||
getScorePerParty (party) {
|
||||
return {
|
||||
token: party.token,
|
||||
score: this.scores
|
||||
.map(t => t.scores.find(s => s.party === party.id).score)
|
||||
.reduce(addUp, 0),
|
||||
nationalParty: party['national_parties'][this.userCountry]
|
||||
}
|
||||
return (score / this.totalMaxPoints * 100).toFixed(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,60 @@
|
|||
import { parties } from '@/data'
|
||||
|
||||
export const MAX_POINTS = 2
|
||||
export const BASE_POINTS = 1
|
||||
export const MIN_POINTS = 0
|
||||
export const EMPHASIS_POINTS = 2
|
||||
|
||||
export function getPartyPositions (thesis) {
|
||||
return parties.map(party => {
|
||||
const position = party.positions.find(p => p.thesis === thesis)
|
||||
export function getPartiesWithScores (answers, emphasized, partiesPositions) {
|
||||
const scorePointsGrid = getScorePointsGrid(answers, emphasized, partiesPositions)
|
||||
|
||||
return partiesPositions.map(party => ({
|
||||
...party,
|
||||
score: getTotalScorePerParty(party, scorePointsGrid)
|
||||
}))
|
||||
}
|
||||
|
||||
export function getTotalMaxPoints (userAnswers, userEmphasized) {
|
||||
return userAnswers.map(answer => {
|
||||
const emphasis = userEmphasized.filter(e => e.thesis === answer.thesis).length >= 1
|
||||
return getMaxScorePerThesis(answer.position, emphasis)
|
||||
}).reduce((total, maxScorePerRow) => total + maxScorePerRow)
|
||||
}
|
||||
|
||||
function getScorePointsGrid (userAnswers, userEmphasized, partiesPositions) {
|
||||
// 1. Iterate over scoringGrid
|
||||
// 2. Get user and party positions of each thesis
|
||||
// 3. Evaluate points based on calculation model for each party
|
||||
// 4. Get the maximum score per thesis
|
||||
// 5. Return a new object for each thesis row with results
|
||||
const scoringGrid = getScoringGrid(userAnswers, userEmphasized, partiesPositions)
|
||||
|
||||
return scoringGrid.map(row => {
|
||||
const partiesFromRow = row.positions.filter(p => p.type === 'party')
|
||||
const userPosition = getUserPosition(row)
|
||||
const scores = partiesFromRow.map(party => ({
|
||||
party: party.party,
|
||||
score: evalPointsPerThesisPerParty(party.position, userPosition, row.emphasis)
|
||||
}))
|
||||
return {
|
||||
type: 'party',
|
||||
party: party.id,
|
||||
position: (position && position.position) || {}
|
||||
thesis: row.thesis,
|
||||
scores
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getTotalScorePerParty (party, scorePointsGrid) {
|
||||
return scorePointsGrid
|
||||
.map(thesis => thesis.scores.find(scores => scores.party === party.id).score)
|
||||
.reduce((total, score) => total + score, 0)
|
||||
}
|
||||
|
||||
function getMaxScorePerThesis (userPosition, emphasis) {
|
||||
return userPosition === 'skipped' ? MIN_POINTS : emphasis ? MAX_POINTS * EMPHASIS_POINTS : MAX_POINTS
|
||||
}
|
||||
|
||||
function getUserPosition (row) {
|
||||
return row.positions.find(p => p.type === 'user').position
|
||||
}
|
||||
|
||||
// Grid example:
|
||||
// [
|
||||
// {
|
||||
|
@ -33,15 +72,48 @@ export function getPartyPositions (thesis) {
|
|||
// },
|
||||
// ...
|
||||
// ]
|
||||
export function getScoringGrid (userAnswers, emphasizedTheses) {
|
||||
function getScoringGrid (userAnswers, emphasizedTheses, parties) {
|
||||
return userAnswers.map(answer => (
|
||||
{
|
||||
thesis: answer.thesis,
|
||||
emphasis: emphasizedTheses.filter(e => e.thesis === answer.thesis).length >= 1,
|
||||
positions: [
|
||||
...getPartyPositions(answer.thesis),
|
||||
...getPartyPositions(answer.thesis, parties),
|
||||
...[{ type: 'user', position: answer.position }]
|
||||
]
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
function getPartyPositions (thesis, parties) {
|
||||
return parties.map(party => {
|
||||
const position = party.positions.find(p => p.thesis === thesis)
|
||||
return {
|
||||
type: 'party',
|
||||
party: party.id,
|
||||
position: (position && position.position) || {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function evalPointsPerThesisPerParty (partyPosition, userPosition, emphasis) {
|
||||
let score = 0
|
||||
|
||||
if (userPosition === partyPosition) {
|
||||
score = MAX_POINTS
|
||||
} else if (
|
||||
(userPosition === 'positive' && partyPosition === 'neutral') ||
|
||||
(userPosition === 'neutral' && partyPosition === 'positive') ||
|
||||
(userPosition === 'neutral' && partyPosition === 'negative') ||
|
||||
(userPosition === 'negative' && partyPosition === 'neutral')
|
||||
) {
|
||||
score = BASE_POINTS
|
||||
} else if (
|
||||
(userPosition === 'positive' && partyPosition === 'negative') ||
|
||||
(userPosition === 'negative' && partyPosition === 'positive')
|
||||
) {
|
||||
score = MIN_POINTS
|
||||
}
|
||||
|
||||
return emphasis ? score * EMPHASIS_POINTS : score
|
||||
}
|
||||
|
|
109
src/app/euromat/scoring.test.js
Normal file
109
src/app/euromat/scoring.test.js
Normal file
|
@ -0,0 +1,109 @@
|
|||
import { evalPointsPerThesisPerParty, getPartiesWithScores, getTotalMaxPoints } from '@/app/euromat/scoring'
|
||||
|
||||
const positionDef = ['positive', 'neutral', 'negative', 'skipped']
|
||||
// See offficial Rechenmodell of bpb from 2019
|
||||
const rechenmodellGrid = [
|
||||
[0, 0, 0, 0, 0, false],
|
||||
[0, 0, 2, 0, 1, false],
|
||||
[0, 2, 2, 0, 0, false],
|
||||
[0, 0, 0, 0, 2, false],
|
||||
[1, 1, 0, 2, 2, true],
|
||||
[0, 0, 2, 0, 0, false],
|
||||
[0, 2, 2, 1, 0, false],
|
||||
[0, 0, 0, 2, 2, false],
|
||||
[2, 0, 2, 2, 1, false],
|
||||
[1, 2, 2, 2, 1, false],
|
||||
[2, 2, 0, 0, 2, false],
|
||||
[0, 2, 2, 2, 2, false],
|
||||
[0, 2, 0, 0, 3, false],
|
||||
[0, 2, 1, 2, 2, false],
|
||||
[2, 0, 0, 1, 2, false],
|
||||
[2, 0, 0, 0, 2, true],
|
||||
[2, 0, 0, 2, 3, false],
|
||||
[0, 0, 0, 0, 2, false],
|
||||
[2, 0, 0, 0, 1, false],
|
||||
[2, 0, 0, 0, 1, false],
|
||||
[0, 0, 2, 2, 2, true],
|
||||
[2, 2, 1, 2, 2, false],
|
||||
[2, 2, 0, 2, 2, false],
|
||||
[2, 2, 2, 0, 2, false],
|
||||
[0, 2, 0, 2, 1, false],
|
||||
[1, 2, 2, 0, 0, false],
|
||||
[2, 0, 2, 0, 0, false],
|
||||
[2, 0, 0, 2, 2, false],
|
||||
[0, 2, 0, 2, 1, false],
|
||||
[1, 2, 0, 2, 2, false],
|
||||
[0, 1, 2, 0, 0, false],
|
||||
[1, 2, 2, 0, 1, false],
|
||||
[2, 0, 2, 2, 1, false],
|
||||
[0, 2, 2, 2, 2, false],
|
||||
[0, 2, 0, 0, 0, false],
|
||||
[0, 2, 0, 0, 1, false],
|
||||
[2, 2, 2, 2, 0, false],
|
||||
[2, 0, 0, 2, 0, false]
|
||||
]
|
||||
|
||||
const rechenmodellExpectedTotalScorePerParty = [44, 37, 28, 50]
|
||||
const rechenmodellExpectedMaxScore = 78
|
||||
|
||||
const testParties = [1, 2, 3, 4].map(partyId => ({
|
||||
id: partyId,
|
||||
token: 'idontcare',
|
||||
positions: rechenmodellGrid.map((thesis, index) => ({
|
||||
thesis: index,
|
||||
position: positionDef[thesis[partyId - 1]]
|
||||
}))
|
||||
}))
|
||||
|
||||
const testAnswers = rechenmodellGrid.map((thesis, index) => ({
|
||||
position: positionDef[thesis[4]],
|
||||
thesis: index
|
||||
}))
|
||||
|
||||
const testEmphasis = rechenmodellGrid
|
||||
.map((thesis, index) => ({
|
||||
...thesis,
|
||||
thesis: index
|
||||
}))
|
||||
.filter((thesis, index) => thesis[5])
|
||||
.map(thesis => ({ thesis: thesis.thesis }))
|
||||
|
||||
describe('The getPartiesWithScores function', () => {
|
||||
it('returns the correct total scores according to the Rechenmodell example of bpb', () => {
|
||||
const resultPartiesWithScores = getPartiesWithScores(testAnswers, testEmphasis, testParties)
|
||||
|
||||
expect(resultPartiesWithScores.map(party => party.score)).toEqual(rechenmodellExpectedTotalScorePerParty)
|
||||
})
|
||||
})
|
||||
|
||||
describe('The getTotalMaxPoints function', () => {
|
||||
it('returns the correct maximum score points according to the Rechenmodell example of bpb', () => {
|
||||
const resultTotalMaxPoints = getTotalMaxPoints(testAnswers, testEmphasis)
|
||||
|
||||
expect(resultTotalMaxPoints).toEqual(rechenmodellExpectedMaxScore)
|
||||
})
|
||||
})
|
||||
|
||||
describe('The evalPointsPerThesisPerParty fucntion', () => {
|
||||
test.each`
|
||||
partyPosition | userPosition | expectedScore | expectedScoreWithEmphasis
|
||||
${'negative'} | ${'negative'} | ${2} | ${4}
|
||||
${'negative'} | ${'neutral'} | ${1} | ${2}
|
||||
${'negative'} | ${'positive'} | ${0} | ${0}
|
||||
${'neutral'} | ${'negative'} | ${1} | ${2}
|
||||
${'neutral'} | ${'neutral'} | ${2} | ${4}
|
||||
${'neutral'} | ${'positive'} | ${1} | ${2}
|
||||
${'positive'} | ${'negative'} | ${0} | ${0}
|
||||
${'positive'} | ${'neutral'} | ${1} | ${2}
|
||||
${'positive'} | ${'positive'} | ${2} | ${4}
|
||||
${'positive'} | ${'skipped'} | ${0} | ${0}
|
||||
`('returns the correct score (according to the Rechenmodell of the bpb' +
|
||||
' if the party position is $partyPosition and the user\'s position is $userPosition',
|
||||
({ partyPosition, userPosition, expectedScore, expectedScoreWithEmphasis }) => {
|
||||
const resultScore = evalPointsPerThesisPerParty(partyPosition, userPosition, false)
|
||||
const resultScoreWithEmphasis = evalPointsPerThesisPerParty(partyPosition, userPosition, true)
|
||||
|
||||
expect(resultScore).toEqual(expectedScore)
|
||||
expect(resultScoreWithEmphasis).toEqual(expectedScoreWithEmphasis)
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue