This repository has been archived by the owner on May 15, 2024. It is now read-only.
/
ScoringService.ts
112 lines (98 loc) · 4.39 KB
/
ScoringService.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import { candidates } from "../data/Candidates";
import { propositions } from "../data/Propositions";
import { Answer, CandidateAnswer, CandidateID, PropositionID, Score, UserAnswer } from "../types";
import { StorageService } from "./StorageService";
const CandidateValues = new Map([
[CandidateAnswer.NO, - 1],
[CandidateAnswer.NEUTRAL, 0],
[CandidateAnswer.YES, 1],
]);
const UserValues = new Map([
[UserAnswer.MUST_NOT, - 2],
[UserAnswer.NO, - 1],
[UserAnswer.NEUTRAL, 0],
[UserAnswer.YES, 1],
[UserAnswer.MUST, 2],
]);
/**
* A service to manage scoring.
*/
export class ScoringService {
private static instance?: ScoringService;
/**
* A static method to retrieve the singleton instance of this service.
* If the instance doesn't exist, the singleton will initialize.
* @returns The ScoringService instance.
*/
static getInstance(): ScoringService {
if (! ScoringService.instance) {
ScoringService.instance = new ScoringService();
}
return ScoringService.instance;
}
/**
* Compute the score of a candidate using answers stored in the local storage.
* @param candidate Candidate id
*/
public computeScore(candidate: CandidateID) {
const storageService = StorageService.getInstance();
const answers = storageService.getAnswers();
const candidateAnswers = candidates.get(candidate)?.opinion;
if (candidateAnswers === undefined || answers === undefined) {
return new Score();
}
return this.computeScoreWithAnswers(candidateAnswers, answers);
}
public computeScoreWithAnswers(candidateAnswers: Map<PropositionID, Answer>, userAnswers: [PropositionID, UserAnswer][]) {
const abs = Math.abs;
const sign = Math.sign;
let norm = 0;
let sum = 0;
let agreements = 0;
let disagreements = 0;
let important_agreements = 0;
let important_disagreements = 0;
candidateAnswers.forEach((value, key) => {
const userAnswer = userAnswers.filter(a => a[0] === key)[0];
if (userAnswer !== undefined) {
const m = UserValues.get(userAnswer[1]);
const c = CandidateValues.get(value.value);
if (m !== undefined && c !== undefined && m !== 0 && c !== 0) {
const a = abs(m);
// nonzero += 1;
norm += a;
sum += a * abs(c) * (sign(m) === sign(c) ? 1 : - 1);
if (m === UserValues.get(UserAnswer.MUST) && c === CandidateValues.get(CandidateAnswer.YES)
|| m === UserValues.get(UserAnswer.MUST_NOT) && c === CandidateValues.get(CandidateAnswer.NO)) {
important_agreements += 1;
} else if (m === UserValues.get(UserAnswer.MUST_NOT) && c === CandidateValues.get(CandidateAnswer.YES)
|| m === UserValues.get(UserAnswer.MUST) && c === CandidateValues.get(CandidateAnswer.NO)) {
important_disagreements += 1;
} else if (m === UserValues.get(UserAnswer.NO) && c === CandidateValues.get(CandidateAnswer.NO)
|| m === UserValues.get(UserAnswer.YES) && c === CandidateValues.get(CandidateAnswer.YES)) {
agreements += 1;
} else if (m === UserValues.get(UserAnswer.NO) && c === CandidateValues.get(CandidateAnswer.YES)
|| m === UserValues.get(UserAnswer.YES) && c === CandidateValues.get(CandidateAnswer.NO)) {
disagreements += 1;
}
}
}
});
/**
* To avoid dividing by zero when we divide by norm, we replace norm by 1
* This should not impact the final result as in this case sum should
* be null also. We choose the score 50 if all answers are null.
*/
if (norm === 0) norm = 1;
return new Score(
{
score: Math.round(50.0 * (1.0 + sum / norm)),
hearts: important_agreements,
skulls: important_disagreements,
agreements: agreements,
disagreements: disagreements,
neutral: propositions.length - (important_agreements + important_disagreements + agreements + disagreements),
}
);
}
}