Skip to content

Commit 961c23d

Browse files
committed
Correcly assign scores to allcorrect questions
1 parent cf16658 commit 961c23d

File tree

3 files changed

+81
-14
lines changed

3 files changed

+81
-14
lines changed

packages/quizms-mdx/src/components/client/answers.tsx

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
useState,
1313
} from "react";
1414

15+
import { decodeAllCorrectAnswer, encodeAllCorrectAnswer } from "@olinfo/quizms/models";
1516
import { useStudent } from "@olinfo/quizms/student";
1617
import clsx from "clsx";
1718
import { Trash2 } from "lucide-react";
@@ -122,20 +123,24 @@ export function AllCorrectAnswerClient({ correct, children }: AnswerProps) {
122123
const { registerCorrectOption } = use(MultipleChoiceContext);
123124

124125
const answer = student.answers?.[problemId!];
125-
const currentlyChecked = useMemo(
126-
() => typeof answer === "string" && answer.indexOf(id) !== -1,
127-
[answer, id],
126+
const parsedAnswer = useMemo<string[]>(() => decodeAllCorrectAnswer(answer), [answer]);
127+
const currentlyChecked = useMemo(() => parsedAnswer.indexOf(id) !== -1, [parsedAnswer, id]);
128+
const setAnswer = useCallback(
129+
async (value: string, checked: boolean) => {
130+
const newAnswer = parsedAnswer;
131+
const index = parsedAnswer.indexOf(value);
132+
if (checked && index === -1) {
133+
newAnswer.push(value);
134+
} else if (!checked && index !== -1) {
135+
newAnswer.splice(index, 1);
136+
}
137+
await setStudent({
138+
...student,
139+
answers: { ...student.answers, [problemId!]: encodeAllCorrectAnswer(newAnswer) },
140+
});
141+
},
142+
[parsedAnswer, student, problemId, setStudent],
128143
);
129-
const setAnswer = async (value: string, checked: boolean) => {
130-
const currentAnswer = typeof answer !== "string" ? "" : answer;
131-
let newAnswer = currentAnswer;
132-
if (checked && !currentlyChecked) {
133-
newAnswer = currentAnswer.split("").concat(value).sort().join("");
134-
} else if (!checked && currentlyChecked) {
135-
newAnswer = currentAnswer.replace(value, "");
136-
}
137-
await setStudent({ ...student, answers: { ...student.answers, [problemId!]: newAnswer } });
138-
};
139144

140145
const answerId = useId();
141146

packages/quizms/src/models/variant.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,21 @@ export type Schema = Variant["schema"];
5959
export type ClientVariant = z.infer<typeof clientVariantSchema>;
6060
export type ClientSchema = ClientVariant["schema"];
6161

62+
export function decodeAllCorrectAnswer(answer?: Answer): string[] {
63+
if (typeof answer !== "string") {
64+
return [];
65+
}
66+
try {
67+
return JSON.parse(answer);
68+
} catch {
69+
return [];
70+
}
71+
}
72+
73+
export function encodeAllCorrectAnswer(value: string[]): Answer {
74+
return JSON.stringify(value);
75+
}
76+
6277
export function parseAnswer(answer: string, schema: Schema[string]): Answer {
6378
let value: Answer = answer.trim().toUpperCase();
6479
if (!value) return null;
@@ -68,10 +83,30 @@ export function parseAnswer(answer: string, schema: Schema[string]): Answer {
6883
value = Number(value);
6984
}
7085
}
86+
87+
if (schema.kind === "allCorrect") {
88+
value = encodeAllCorrectAnswer(answer.split(""));
89+
}
90+
7191
isValidAnswer(value, schema);
7292
return value;
7393
}
7494

95+
export function displayAnswer(answer: Answer, kind: Schema[string]["kind"]): string {
96+
switch (kind) {
97+
case "allCorrect": {
98+
const values = decodeAllCorrectAnswer(answer);
99+
return values.join("");
100+
}
101+
default: {
102+
if (answer === null) {
103+
return "";
104+
}
105+
return `${answer}`;
106+
}
107+
}
108+
}
109+
75110
export function isValidAnswer(answer: Answer, schema: Schema[string]) {
76111
if (answer == null) return;
77112
if (schema.type === "number" && !Number.isInteger(answer)) {
@@ -86,6 +121,16 @@ export function isValidAnswer(answer: Answer, schema: Schema[string]) {
86121
break;
87122
}
88123
case "allCorrect": {
124+
const values = decodeAllCorrectAnswer(answer);
125+
const wrong = values.filter(
126+
(value) => !schema.options.some((option) => option.value === value),
127+
);
128+
if (wrong.length >= 1) {
129+
throw new Error(`Opzioni non valide: ${wrong.join("")}`);
130+
}
131+
if (new Set(values).size !== values.length) {
132+
throw new Error(`Opzioni ripetute: ${values.join("")}`);
133+
}
89134
break;
90135
}
91136
default: {
@@ -114,6 +159,20 @@ export function calcScore(student: Student, schema?: Schema) {
114159
}
115160

116161
export function calcProblemPoints(problem: Schema[string], answer?: Answer) {
162+
if (problem.kind === "allCorrect") {
163+
const values = decodeAllCorrectAnswer(answer);
164+
if (values.length === 0) {
165+
return 1; // TODO: save points in schema
166+
}
167+
const correctOptions = problem.options.filter((option) => option.points === 5);
168+
if (
169+
correctOptions.length !== values.length &&
170+
correctOptions.some((option) => !values.some((value) => option.value === value))
171+
) {
172+
return 0;
173+
}
174+
return 5;
175+
}
117176
for (const option of problem.options ?? []) {
118177
if (option.value === (answer ?? null)) {
119178
return option.points;

packages/quizms/src/web/teacher/table/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { Download, FileCheck, Trash2, TriangleAlert, Upload, UserCheck } from "l
1515
import {
1616
type Contest,
1717
calcScore,
18+
displayAnswer,
1819
formatUserData,
1920
parseAnswer,
2021
parseUserData,
@@ -299,7 +300,9 @@ function columnDefinition(
299300
valueGetter: ({ data }) => {
300301
if (data.absent || data.disabled) return "";
301302
if (!(id in (data.answers ?? {}))) return "";
302-
return data.answers[id] ?? "";
303+
return (
304+
displayAnswer(data.answers[id], variants[data.variant as string].schema[id].kind) ?? ""
305+
);
303306
},
304307
tooltipValueGetter: ({ data }) => data.answers?.[id],
305308
editable: ({ data }) => contest.allowAnswerEdit && !data.absent && !data.disabled,

0 commit comments

Comments
 (0)