Skip to content

Instantly share code, notes, and snippets.

@chrcit
Last active April 21, 2023 15:58
Show Gist options
  • Save chrcit/8c7b2f81e8f6dc608fd97c7b43e96066 to your computer and use it in GitHub Desktop.
Save chrcit/8c7b2f81e8f6dc608fd97c7b43e96066 to your computer and use it in GitHub Desktop.
Code for calculating the score for the "SPÖ Vorsitzbefragungs-Kabine" on mitentscheiden.at
import { QuestionType } from "@prisma/client";
export type ScaleOptionValueType = typeof options[number]["value"];
export const getScaleOptionTendency = (option: ScaleOptionValueType) => {
return option > 0 ? "positive" : "negative";
};
export const options = [
{
label: "voll zu",
value: 2,
},
{
label: "eher zu",
value: 1,
},
{
label: "eher nicht zu",
value: -1,
},
{
label: "gar nicht zu",
value: -2,
},
] as const;
export const yesNoOptions = [
{
label: "Ja",
value: 3, // Settings a different value to be able to differnentiate. Can normalise afterwards.
},
{
label: "Nein",
value: -3, // Settings a different value to be able to differnentiate. Can normalise afterwards.
},
] as const;
export const getOptionsBasedOnType = (type: QuestionType) => {
switch (type) {
case "YesNo":
return yesNoOptions;
case "Range":
return options;
default:
return options;
}
};
export const optionLabelForValue = (value: number) => {
const option = options.find((option) => option.value === value);
return option ? option.label : "";
};
export const optionLabelForYesNoValue = (value: number) => {
const option = yesNoOptions.find((option) => option.value === value);
return option ? option.label : "";
};
export const getWeightingTendency = (weighting: WeightingValueType) => {
if (weighting === 0 || weighting === 1) return "negative";
if (weighting === 2 || weighting === 3) return "positive";
};
export type WeightingValueType = typeof weightings[number]["value"];
export const weightings = [
{
label: "sehr wichtig",
value: 3,
},
{
label: "eher wichtig",
value: 2,
},
{
label: "eher nicht wichtig",
value: 1,
},
{
label: "gar nicht wichtig",
value: 0,
},
] as const;
export const weightingLabelForValue = (value: number) => {
const weighting = weightings.find((weighting) => weighting.value === value);
return weighting ? weighting.label : "";
};
export const CATEGORIES = [
{
label: "Klima & Umwelt",
hex: "#38be23",
},
{
label: "Frauen",
hex: "#2766d4",
},
{
label: "Demokratie & Mitbestimmung",
hex: "#EB5757",
},
{
label: "Arbeit & Soziales",
hex: "#e8ce26",
},
{
label: "Internes",
hex: "#6FCF97",
},
{
label: "Europa & Internationales",
hex: "#BB6BD9",
},
{
label: "Bildung",
hex: "#c16926",
},
{
label: "Zusatz",
hex: "#485d7c",
},
];
export const categoryHexForLabel = (label: string) => {
const category = CATEGORIES.find((category) => category.label === label);
return category ? category.hex : "";
};
import {
ScaleOptionValueType,
WeightingValueType,
getScaleOptionTendency,
getWeightingTendency,
} from "~/data/answers";
import {
CandidateQuestionAnswer,
Question,
VoterQuestionAnswer,
} from "@prisma/client";
const calculateWeightingRelevancy = (
voterWeighting: WeightingValueType,
candidateWeighting: WeightingValueType
) => {
if (voterWeighting === candidateWeighting) return 0.15;
if (
getWeightingTendency(voterWeighting) ===
getWeightingTendency(candidateWeighting)
) {
return 0.075;
}
return 0;
};
const calculateMatchForScaleOption = (
voterAnswer: ScaleOptionValueType,
candidateAnswer: ScaleOptionValueType
) => {
if (voterAnswer === candidateAnswer) return 1;
if (
getScaleOptionTendency(voterAnswer) ===
getScaleOptionTendency(candidateAnswer)
) {
return 0.7;
}
return 0;
};
export const calculateScore = (
voterAnswers: (VoterQuestionAnswer & {
question: Question;
})[],
candidateAnswers: (CandidateQuestionAnswer & {
question: Question;
})[]
) => {
let score = 0;
voterAnswers.forEach((voterAnswer) => {
const candidateAnswer = candidateAnswers.find(
(candidateAnswer) => candidateAnswer.questionId === voterAnswer.questionId
);
if (
voterAnswer.skipped ||
(candidateAnswer?.option === null && candidateAnswer.weighting === null)
) {
return;
}
if (voterAnswer.question.type === "YesNo") {
if (voterAnswer.option === candidateAnswer?.option) {
score += 1;
}
}
if (voterAnswer.question.type === "Range") {
const matchScore = calculateMatchForScaleOption(
voterAnswer.option! as ScaleOptionValueType,
candidateAnswer!.option! as ScaleOptionValueType
);
score += matchScore;
}
const weightingRelevancy = calculateWeightingRelevancy(
voterAnswer.weighting! as WeightingValueType,
candidateAnswer!.weighting! as WeightingValueType
);
score += weightingRelevancy;
});
return score;
};
import {
Candidate,
CandidateQuestionAnswer,
Question,
VoterQuestionAnswer,
} from "@prisma/client";
import { calculateScore } from "~/data/calucate-score";
type VoterAnswer = VoterQuestionAnswer & {
question: Question;
};
type CandidateWithAnswers = Candidate & {
answers: (CandidateQuestionAnswer & {
question: Question;
})[];
};
const getAnsweredQuestionsLength = (
voterAnswers: VoterAnswer[],
candidateAnswers: CandidateWithAnswers["answers"]
) => {
return candidateAnswers.filter((answer) => {
const voterAnswer = voterAnswers.find(
(voterAnswer) => voterAnswer.questionId === answer.questionId
);
return (
!voterAnswer?.skipped &&
answer.option !== null &&
answer.weighting !== null
);
}).length;
};
export const rateCandidate = (
voterAnswers: VoterAnswer[],
candidate: CandidateWithAnswers
) => {
const maxScore =
getAnsweredQuestionsLength(voterAnswers, candidate.answers) * 1.15;
const score = calculateScore(voterAnswers, candidate.answers);
console.log({ score, maxScore });
const scorePercentageRaw = maxScore !== 0 ? (score / maxScore) * 100 : 0;
const scorePercentage = Math.round(scorePercentageRaw);
return {
...candidate,
score,
scorePercentage,
scorePercentageRaw,
};
};
export const rateCandidates = (
voterAnswers: VoterAnswer[],
candidates: CandidateWithAnswers[]
) => {
return candidates
.map((candidate) => rateCandidate(voterAnswers, candidate))
.sort((a, b) => b.score - a.score);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment