Skip to content

Commit

Permalink
Merge pull request #139 from avantifellows/scorecard-upd
Browse files Browse the repository at this point in the history
adds subject wise table to scorecard
  • Loading branch information
suryabulusu authored Nov 28, 2023
2 parents ba75319 + 7063461 commit 7906cc5
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 6 deletions.
34 changes: 31 additions & 3 deletions src/components/Scorecard.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<template>
<div class="flex flex-col bg-[#FFEDDA] w-full h-full overflow-hidden">
<div class="flex flex-col bg-[#FFEDDA] w-full">
<div
class="flex justify-center w-full mx-auto my-auto h-full py-4"
ref="container"
>
<div
class="flex flex-col justify-center w-5/6"
class="flex flex-col justify-center w-full sm:w-5/6"
:class="{
'space-y-8': !isCircularProgressShown && !isMobileLandscape,
'space-y-4': !isCircularProgressShown && isMobileLandscape,
Expand Down Expand Up @@ -85,6 +85,24 @@
</div>
</div>

<!-- question set metrics table -->
<div class="flex flex-col w-full mx-auto my-4 overflow-auto">
<div class="flex border-b-2 border-gray-400 p-2 font-bold">
<div :class="tableCellClass">Name</div>
<div :class="tableCellClass">Marks Scored</div>
<div v-if="!isPortrait" :class="tableCellClass">Total No. Of Questions</div>
<div :class="tableCellClass">Attempt Rate (%)</div>
<div :class="tableCellClass">Accuracy Rate (%)</div>
</div>
<div v-for="metric in $props.qsetMetrics" :key="metric.name" class="flex border-b border-gray-100 p-2">
<div :class="tableCellClass">{{ metric.name }}</div>
<div :class="tableCellClass">{{ metric.marksScored }}</div>
<div v-if="!isPortrait" :class="tableCellClass">{{ metric.maxQuestionsAllowedToAttempt }}</div>
<div :class="tableCellClass">{{ formatPercentage(metric.attemptRate) }}</div>
<div :class="tableCellClass">{{ formatPercentage(metric.accuracyRate) }}</div>
</div>
</div>

<!-- action buttons -->
<div
class="place-self-center flex h-20"
Expand Down Expand Up @@ -138,7 +156,7 @@ import BaseIcon from "./UI/Icons/BaseIcon.vue";
import IconButton from "./UI/Buttons/IconButton.vue";
import domtoimage from "dom-to-image";
import { useStore } from "vuex";
import { ScorecardMetric, CircularProgressResult, quizTitleType } from "../types";
import { ScorecardMetric, CircularProgressResult, quizTitleType, QuestionSetMetric } from "../types";
const confetti = require("canvas-confetti");
const PROGRESS_BAR_ANIMATION_DELAY_TIME = 500; // a time delay to be used for animating the progress bar
Expand Down Expand Up @@ -196,6 +214,10 @@ export default defineComponent({
type: Object as PropType<CircularProgressResult>,
default: () => {},
},
qsetMetrics: {
required: true,
type: Array as PropType<QuestionSetMetric[]>,
}
},
setup(props, context) {
const store = useStore();
Expand All @@ -212,6 +234,7 @@ export default defineComponent({
"bg-back-color hover:bg-primary-hover bp-500:w-40 px-6 py-3 bp-500:p-4 bp-500:px-10 sm:p-6 rounded-2xl md:rounded-xl shadow-xl disabled:opacity-50 disabled:pointer-events-none invisible",
shareButtonClass:
"flex justify-center bg-share-color hover:bg-green-600 bp-500:w-40 px-6 py-3 bp-500:p-4 bp-500:px-10 sm:p-6 rounded-2xl md:rounded-xl shadow-xl disabled:opacity-50 disabled:pointer-events-none",
tableCellClass: "px-2 sm:px-4 flex-1 whitespace-normal break-words",
isPortrait: true,
isMobileLandscape: false, // whether the screen corresponds to a mobile screen in landscape mode
confettiHandler,
Expand Down Expand Up @@ -320,6 +343,10 @@ export default defineComponent({
};
});
function formatPercentage(value: number) {
return `${(value * 100).toFixed(2)}%`;
}
/**
* checks whether the current screen corresponds to a mobile-sized
* screen in landscape mode
Expand Down Expand Up @@ -439,6 +466,7 @@ export default defineComponent({
shareButtonTitleConfig,
circularProgressRadius,
circularProgressStroke,
formatPercentage
};
},
emits: ["go-back"],
Expand Down
10 changes: 10 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ export interface ScorecardMetric {
value: number;
}

export interface QuestionSetMetric {
name: string | null,
marksScored: number,
maxQuestionsAllowedToAttempt: number,
numAnswered: number,
accuratelyAnswered: number,
attemptRate: number,
accuracyRate: number
}

export interface QuizMetadata {
quiz_type: quizType;
grade: string;
Expand Down
34 changes: 31 additions & 3 deletions src/views/Player.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
:result="scorecardResult"
:metrics="scorecardMetrics"
:progressPercentage="scorecardProgress"
:qsetMetrics="qsetMetrics"
:isShown="isScorecardShown"
:title="title"
:userId="userId"
Expand Down Expand Up @@ -142,6 +143,7 @@ import {
TimeSpentEntry,
UpdateSessionAnswerAPIPayload,
UpdateSessionAnswersAtSpecificPositionsAPIPayload,
QuestionSetMetric
} from "../types";
import { useToast, POSITION } from "vue-toastification"
import BaseIcon from "../components/UI/Icons/BaseIcon.vue";
Expand Down Expand Up @@ -210,6 +212,7 @@ export default defineComponent({
continueAfterAnswerSubmit: true as boolean, // do we continue after submitting answer
timeSpentOnQuestion: [] as TimeSpentEntry[], // stores time spent on each question
timerInterval: null as number | null, // variable used in computing time spent on question
qsetMetrics: [] as QuestionSetMetric[],
toast: useToast(),
});
Expand Down Expand Up @@ -681,15 +684,32 @@ export default defineComponent({
state.numPartiallyCorrect = 0;
state.marksScored = 0;
state.questions.forEach((questionDetail) => {
updateQuestionMetrics(questionDetail, state.responses[index].answer);
// Initialize metrics for each question set
state.qsetMetrics = state.questionSets.map((qset) => ({
name: qset.title,
marksScored: 0,
maxQuestionsAllowedToAttempt: qset.max_questions_allowed_to_attempt,
numAnswered: 0,
accuratelyAnswered: 0,
attemptRate: 0,
accuracyRate: 0
}));
state.questions.forEach((questionDetail, questionIndex) => {
const [currentQsetIndex] = getQsetLimits(questionIndex);
const qsetMetricsObj = state.qsetMetrics[currentQsetIndex];
updateQuestionMetrics(questionDetail, state.responses[index].answer, qsetMetricsObj);
if (qsetMetricsObj.numAnswered != 0) qsetMetricsObj.accuracyRate = qsetMetricsObj.accuratelyAnswered / qsetMetricsObj.numAnswered;
state.qsetMetrics[currentQsetIndex].attemptRate = qsetMetricsObj.numAnswered / qsetMetricsObj.maxQuestionsAllowedToAttempt;
state.qsetMetrics[currentQsetIndex] = qsetMetricsObj;
index += 1;
});
}
function updateQuestionMetrics(
questionDetail: Question,
userAnswer: submittedAnswer
userAnswer: submittedAnswer,
qsetMetricObj: QuestionSetMetric
) {
const markingScheme = questionDetail.marking_scheme;
const doesPartialMarkingExist = markingScheme?.partial != null;
Expand All @@ -698,12 +718,17 @@ export default defineComponent({
state.numCorrect += 1;
// default marks for correctly answered questions = 1
state.marksScored += markingScheme?.correct || 1;
qsetMetricObj.marksScored += markingScheme?.correct || 1;
qsetMetricObj.numAnswered += 1;
qsetMetricObj.accuratelyAnswered += 1;
}
function updateMetricsForWrongAnswer() {
state.numWrong += 1;
// default marks for wrongly answered questions = 0
state.marksScored += markingScheme?.wrong || 0;
qsetMetricObj.marksScored += markingScheme?.wrong || 0;
qsetMetricObj.numAnswered += 1;
}
function updateMetricsForPartiallyCorrectAnswer() {
Expand All @@ -715,6 +740,9 @@ export default defineComponent({
if (userAnswer.length === condition.num_correct_selected) {
conditionMatched = true;
state.marksScored += partialMarkRule.marks;
qsetMetricObj.marksScored += partialMarkRule.marks;
qsetMetricObj.numAnswered += 1;
qsetMetricObj.accuratelyAnswered += 0.5; // 0.5 weight default for partially answered
break;
}
}
Expand Down

0 comments on commit 7906cc5

Please sign in to comment.