Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ describe('controller', () => {
const returnOutcome = (session) => {
it(`returns score of 0 and empty: true if session is ${JSON.stringify(session)}`, async () => {
const result = await outcome(question, session);
expect(result).toEqual({ score: 0, empty: true });
expect(result).toEqual({ score: 0, empty: true, traceLog: ['Student did not place any images into placement containers. Score is 0.'] });
});
};

Expand Down
87 changes: 80 additions & 7 deletions packages/image-cloze-association/controller/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,18 +183,91 @@ const getScore = (config, session, env = {}) => {
return isPartialScoring ? getPartialScore(config, session) : correct ? 1 : 0;
};

/**
* Generates detailed trace log for scoring evaluation
* @param {Object} model - the question model
* @param {Object} session - the student session
* @param {Object} env - the environment
* @returns {Array} traceLog - array of trace messages
*/
export const getLogTrace = (model, session, env) => {
const traceLog = [];
const { answers } = session || {};

const validResponse = model.validation?.validResponse?.value || [];
const totalContainers = validResponse.length;
traceLog.push(`${totalContainers} placement container(s) defined in this question.`);

if (answers && answers.length > 0) {
traceLog.push(`Student placed ${answers.length} image(s) into placement containers.`);

const answersByContainer = {};
answers.forEach((answer) => {
if (!answersByContainer[answer.containerIndex]) {
answersByContainer[answer.containerIndex] = [];
}
answersByContainer[answer.containerIndex].push(answer.value);
});

validResponse.forEach((container, containerIndex) => {
const correctImages = container.images || [];
const studentImages = answersByContainer[containerIndex] || [];

if (correctImages.length > 0) {
if (studentImages.length === 0) {
traceLog.push(`Container ${containerIndex + 1}: student left empty (should contain ${correctImages.length} image(s)).`);
} else {
const correctCount = studentImages.filter(img => correctImages.includes(img)).length;
const incorrectCount = studentImages.length - correctCount;

if (correctCount > 0 && incorrectCount === 0) {
traceLog.push(`Container ${containerIndex + 1}: student placed ${correctCount} correct image(s).`);
} else if (correctCount === 0 && incorrectCount > 0) {
traceLog.push(`Container ${containerIndex + 1}: student placed ${incorrectCount} incorrect image(s).`);
} else {
traceLog.push(`Container ${containerIndex + 1}: student placed ${correctCount} correct and ${incorrectCount} incorrect image(s).`);
}
}
}
});
} else {
traceLog.push('Student did not place any images into placement containers.');
}

const altResponses = model.validation?.altResponses || [];
if (altResponses.length > 0) {
traceLog.push(`${altResponses.length} alternate response combination(s) are accepted for this question.`);
}

const partialScoringEnabled = partialScoring.enabled(model, env);
const scoringMethod = partialScoringEnabled ? 'partial scoring' : 'all-or-nothing scoring';
traceLog.push(`Score calculated using ${scoringMethod}.`);

const score = getScore(model, session, env);
traceLog.push(`Final score: ${score}.`);

return traceLog;
}

export const outcome = (config, session, env = {}) => {
return new Promise((resolve) => {
log('outcome...');
if (!session || isEmpty(session)) {
resolve({ score: 0, empty: true });
}

const configCamelized = camelizeKeys(config);

if (session.answers || []) {
resolve({
score: 0,
empty: true,
traceLog: ['Student did not place any images into placement containers. Score is 0.']
});
} else {
const configCamelized = camelizeKeys(config);
const traceLog = getLogTrace(configCamelized, session, env);
const score = getScore(configCamelized, session, env);
resolve({ score });

resolve({
score,
empty: false,
traceLog
});
}
});
};
Expand Down