import { Exception, mean } from "@rolesvillesoftware/tools/dist"; import { QuestionFact, ExamFact, ExamStat } from "."; export class QuestionStat { id: number; examQuestionFactId: number; studentAnswers: number; totalCorrect: number; totalIncorrect: number; majorityCorrect: number; mqmp: number; pq: number; biserial: number; majorityCorrectWeight: number; qt: number; courseFev: number; biserialWeight: number; coreConceptWeight: number; aoaWeight: number; percentCorrectWeight: number; partialQuestionFev: number; percentCorrect: number; percentIncorrect: number; fev: number; pev: number; private determineMajorityCorrect(questionFacts: QuestionFact[], studentFact: ExamFact[]) { if (this.percentCorrect < 0.85) { return 0; } const top27 = studentFact.filter(item => item.examRank <= questionFacts.length * 0.27); return top27.every(item => questionFacts.some(q => q.studentDimId === item.studentDimId && q.isCorrect === 1)) ? 1 : 0; } /** * @description Averages the scores of the student based on if they got the question correct or not * @author Rolesville Software LLC * @private * @param {QuestionFact[]} questionFacts Question scores * @param {ExamStat} exam Exam stats - contains student scores * @param {boolean} isCorrect Flag to determine if wanting to average of correct or incorrect students * @returns {number} The average score (questions - no percent) * @memberof QuestionStat */ private calculateMqmp(questionFacts: QuestionFact[], exam: ExamStat, isCorrect: boolean): number { const correctStudents = questionFacts.filter(item => item.isCorrect === (isCorrect ? 1 : 0)); if (correctStudents.length === 0) { return 0; } const examScores = exam.students.filter(student => correctStudents.some(q => q.studentDimId === student.studentDimId)); if (examScores.length === 0) { return 0; } if (correctStudents.length !== examScores.length) { throw new Exception(`Possible data loss - students who got question correct do not match student exam count`); } return mean(examScores, "examPoints"); } private calculateBiserial(questionFacts: QuestionFact[], exam: ExamStat) { if (this.percentCorrect === 1) { return 0; } const part1 = ((this.mqmp - exam.mean) / exam.standardDev); const part2 = Math.sqrt(this.percentCorrect / (1 - this.percentCorrect)); return part1 * part2; } private calculateQt(questionFacts: QuestionFact[], exam: ExamStat): number { /** Need Course Mappings */ return 1; } private calculateCourseFEV(questionFacts: QuestionFact[], exam: ExamStat): number { /** Need Category Mappings */ return 0.01; } private calculateBiserialWeight(): number { const returnValue = Math.pow(this.biserial * 5, 2); return Math.min(returnValue, 9); } private calculateCoreConceptWeight(): number { return 0; } private calculateAOAWeight(): number { return 0; } private calculatePercentCorrectWeight(): number { return Math.pow(((1 - (Math.pow((this.percentCorrect * 10 + 0.2) - 9, 2) / 100)) * 1.5) - (this.percentCorrect / 1.8), 20) + 0.01; } private calculatePartialQuestionFev(): number { return this.majorityCorrectWeight + this.qt + this.courseFev + this.biserialWeight + this.percentCorrectWeight + this.coreConceptWeight + this.aoaWeight; } populate(questionFacts: QuestionFact[], exam: ExamStat): QuestionStat { this.studentAnswers = questionFacts.length; this.totalCorrect = questionFacts.filter(item => item.isCorrect === 1).length; this.totalIncorrect = questionFacts.filter(item => item.isCorrect !== 1).length; this.percentCorrect = this.studentAnswers != 0 ? this.totalCorrect / this.studentAnswers : 0; this.percentIncorrect = this.studentAnswers != 0 ? this.totalIncorrect / this.studentAnswers : 0; this.pq = this.percentCorrect * this.percentIncorrect; this.majorityCorrect = this.determineMajorityCorrect(questionFacts, exam.students); this.mqmp = this.calculateMqmp(questionFacts, exam, true); this.biserial = this.calculateBiserial(questionFacts, exam); this.majorityCorrectWeight = this.majorityCorrect * 3.5; this.qt = this.calculateQt(questionFacts, exam); this.courseFev = this.calculateCourseFEV(questionFacts, exam); this.biserialWeight = this.calculateBiserialWeight(); this.coreConceptWeight = this.calculateCoreConceptWeight(); this.aoaWeight = this.calculateAOAWeight(); this.percentCorrectWeight = this.calculatePercentCorrectWeight(); this.partialQuestionFev = this.calculatePartialQuestionFev(); return this; } Pev(exam: ExamStat) { this.fev = this.partialQuestionFev; this.pev = exam.aav * exam.krv * this.fev; } }