Files
VeracrossAnalyzerDemo/src/logic/course.ts
T
2019-12-21 20:13:53 -05:00

391 lines
10 KiB
TypeScript

import {FormatUtils} from '@/logic/utils/format-utils';
import {CourseUtils} from '@/logic/utils/course-utils';
import Navigation from '@/components/navigation/navigation';
import {GPAUtils} from '@/logic/utils/gpa-utils';
import CacheUtils from '@/logic/utils/cache-utils';
import Constants from '@/constants';
import {Index} from '@/logic/nav-controller';
import App from '@/components/app/app';
/**
* Objects of this interface represent assignment grades.
*/
export class Assignment
{
id: number;
scoreId: number;
type: string;
typeId: number;
description: string;
time: number;
complete: string;
include: boolean;
display: boolean;
unread: boolean;
scoreMax: number;
score: number;
gradingPeriod: number;
// Callbacks when this object updates
private updateCallbacks: (() => void)[] = [];
/**
* Construct assignment with json object
*
* @param json Json object
*/
constructor(json: any)
{
this.id = json.assignment_id;
this.scoreId = json.score_id;
this.type = json.assignment_type;
this.typeId = json.assignment_type_id;
this.description = json.assignment_description;
this.time = new Date(json._date).getTime();
this.complete = json.completion_status;
this.include = json.include_in_calculated_grade == 1;
this.display = json.display_grade == 1;
this.unread = json.is_unread == 1;
this.scoreMax = json.maximum_score;
this.score = +json.raw_score;
this.gradingPeriod = +json.grading_period.replace('Quarter ', '') - 1;
}
/**
* Graded or not
*/
get graded()
{
return this.complete == 'Complete';
}
/**
* Add callback
*
* @param callback
*/
addCallback(callback: () => void)
{
this.updateCallbacks.push(callback);
}
/**
* Mark as read
*/
markAsRead(): Promise<void>
{
return new Promise((resolve, reject) => {
App.http.post('/mark-as-read', {scoreId: this.scoreId})
.then(response =>
{
// Check success
if (response.success)
{
this.unread = false;
this.updateCallbacks.forEach(callback => callback());
resolve();
}
else reject(response.data);
})
.catch(reject)
})
}
}
export interface AssignmentType
{
id: number
name: string
weight: number
scoreMax: number
score: number
percent: number
assignmentCount: number
}
export interface Grading
{
method: string
weightingMap: {[index: string]: number}
}
export default class Course
{
id: number;
assignmentsId: number;
name: string;
teacherName: string;
status: string;
rawAssignments: Assignment[];
rawLetterGrade?: string;
rawNumericGrade?: number;
level: string;
scaleUp: number;
termGrading: Grading[];
termAssignments: Assignment[][];
cache: CacheUtils = new CacheUtils();
/**
* Construct a course with a course json object
*
* @param courseJson Course json object
*/
constructor(courseJson: any)
{
this.id = courseJson.id;
this.assignmentsId = courseJson.assignmentsId;
this.name = FormatUtils.parseText(courseJson.name).trim();
this.teacherName = courseJson.teacherName;
this.status = courseJson.status;
this.rawLetterGrade = courseJson.letterGrade;
this.rawNumericGrade = courseJson.numericGrade;
// Other api issue
if (this.rawLetterGrade == '')
{
this.rawNumericGrade = undefined;
this.rawLetterGrade = undefined;
}
// Level and scaleUp
let level = CourseUtils.detectLevel(this.name);
if (level != undefined)
{
this.level = level.level;
this.scaleUp = level.scaleUp;
}
else this.level = 'Unknown';
this.termGrading = new Array(4).fill(null);
}
/**
* Load in assignments data
*
* @param data Assignments data
*/
loadAssignments(data: any)
{
// Load assignments
// Parse json and filter it
this.rawAssignments = data.assignments.map((a: any) => new Assignment(a));
// Sort by date (Latest is at 0)
this.rawAssignments.sort((a, b) => b.time - a.time);
// Filter assignments into terms
this.termAssignments = [[], [], [], []];
// Loop through it by time order
this.rawAssignments.forEach(a => this.termAssignments[a.gradingPeriod].push(a));
}
/**
* Is graded or not
*/
get isGraded(): boolean
{
// Skip future or past courses
if (this.status != 'active') return false;
// Skip courses without levels TODO: Ask for user input
if (this.level == 'None' || this.level == 'Unknown' || this.scaleUp == -1) return false;
// Skip courses without graded assignments
if (this.assignments.length == 0) return false;
// Skip if there are no grading scale
// if (course.grading.method == 'NOT_GRADED') return;
// Is graded
return true;
}
/**
* Get currently selected grading periods
*/
get gradingPeriods(): number[]
{
return this.cache.get('GradingPeriods', () =>
{
return (this.rawSelectedTerm == -1 ? [0, 1, 2, 3] : [this.rawSelectedTerm]).filter(term =>
this.termAssignments[term].filter(a => a.graded).length != 0);
})
}
/**
* Get currently selected grading periods
*/
get allGradingPeriods(): number[]
{
return this.cache.get('AllGradingPeriods', () =>
{
return [0, 1, 2, 3].filter(term => this.termAssignments[term].filter(a => a.graded).length != 0);
})
}
/**
* Get assignments of the selected grading periods
*/
get assignments(): Assignment[]
{
return this.gradingPeriods
.flatMap(term => this.termAssignments[term])
.filter(a => a.graded)
.sort((a, b) => b.time - a.time);
}
/**
* Get assignments before a certain date
*
* @param time
*/
getAssignmentsBefore(time: number): {term: number, assignments: Assignment[]}
{
let term = Constants.getTerm(new Date(time));
let assignments = this.assignments.filter(a => a.gradingPeriod == term && a.time <= time);
return {term: term, assignments: assignments}
}
/**
* Get letter grade
*/
get letterGrade(): string
{
return this.cache.get('LetterGrade', () =>
{
// Get scale
let scale = GPAUtils.findScale(this.numericGrade);
// Scale not found
return scale == undefined ? '--' : scale.letter;
})
}
/**
* Get letter grade by term
*
* @param term
*/
letterGradeTerm(term: number): string
{
return this.cache.get('LetterGrade' + term, () =>
{
// Get scale
let scale = GPAUtils.findScale(this.numericGradeTerm(term));
// Scale not found
return scale == undefined ? '--' : scale.letter;
})
}
/**
* Get numeric grade
*/
get numericGrade()
{
return this.cache.get('NumericGrade', () =>
{
return this.gradingPeriods.map(term => this.numericGradeTerm(term))
.reduce((p, v) => p + v) / this.gradingPeriods.length
})
}
/**
* Get numeric grade by term
*
* @param term
*/
numericGradeTerm(term: number): number
{
return this.cache.get('NumericGrade' + term, () =>
{
// Calculate
if (this.termGrading[term].method == 'PERCENT_TYPE')
{
return GPAUtils.getPercentTypeAverage(this.termGrading[term], this.termAssignments[term]);
}
else if (this.termGrading[term].method == 'TOTAL_MEAN')
{
return GPAUtils.getTotalMeanAverage(this.termAssignments[term]);
}
else return -1;
})
}
/**
* Get assignment types
*/
get assignmentTypes(): AssignmentType[]
{
return this.cache.get('AssignmentTypes', () =>
{
// Get all types
let types = this.assignments.map(a => a.type);
// Remove duplicates
types = types.filter((type, i, a) => a.indexOf(type) == i);
// Get total possible score for weight calculation
let totalScoreMax = this.assignments.reduce((sum, a) => sum + a.scoreMax, 0);
// For every type...
return types.map(type =>
{
// Get assignments of the type
let typeAssignments = this.assignments.filter(a => a.type == type);
// Count scores and max scores
let score = typeAssignments.reduce((sum, a) => sum + a.score, 0);
let scoreMax = typeAssignments.reduce((sum, a) => sum + a.scoreMax, 0);
// Calculate weight
let weight = this.termGrading[0].method == 'PERCENT_TYPE'
? this.termGrading[0].weightingMap[type] : scoreMax / totalScoreMax;
// Return
return {name: type, id: typeAssignments[0].typeId, weight: +(weight * 100).toFixed(2),
scoreMax: scoreMax, score: score, percent: +(score / scoreMax * 100).toFixed(2),
assignmentCount: typeAssignments.length}
})
})
}
/**
* Get url hash code
*/
get urlHash(): string
{
return `course/${this.id}`
}
/**
* Get navigation index
*/
get urlIndex(): Index
{
return {hash: this.urlHash, title: this.name, identifier: 'course', info: {id: this.id}}
}
/**
* Selected term
*/
get rawSelectedTerm(): number
{
return Navigation.instance.getSelectedTerm()
}
}