most of the api endpoints completed

This commit is contained in:
Matti Luukkainen
2017-10-18 18:22:35 +03:00
parent e9cab4622c
commit a5c5cfd7a9
12 changed files with 3379 additions and 61 deletions
+2 -1
View File
@@ -1,2 +1,3 @@
node_modules
.vscode
.vscode
src/tester.js
+2 -1
View File
@@ -1 +1,2 @@
require('./src/app')
require('./src/tester')
//require('./src/app')
+2255 -2
View File
File diff suppressed because it is too large Load Diff
+12 -5
View File
@@ -1,15 +1,19 @@
{
"name": "nodetest",
"version": "1.0.0",
"description": "",
"name": "oodikone-backend",
"version": "0.0.1",
"description": "Backend for Oodikone",
"main": "index.js",
"scripts": {
"start": "nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"author": "Matti Luukkainen",
"license": "MIT",
"dependencies": {
"axios": "^0.16.2",
"body-parser": "^1.18.2",
"cors": "^2.8.4",
"express": "^4.16.2",
"install": "^0.10.1",
"knex": "^0.13.0",
"moment": "^2.19.1",
@@ -17,5 +21,8 @@
"pg": "^7.3.0",
"pg-hstore": "^2.3.2",
"sequelize": "^4.13.8"
},
"devDependencies": {
"nodemon": "^1.12.1"
}
}
+85 -50
View File
@@ -1,61 +1,96 @@
const Sequelize = require('sequelize')
const moment = require('moment')
const { Student, Credit, Studyright, sequelize } = require('./models')
const Op = Sequelize.Op;
const express = require('express')
const cors = require('cors')
const app = express()
const bodyParser = require('body-parser')
const studyrightsStartedAt = (date) => {
return Studyright.findAll({
limit : 200,
include: [ {
model: Student,
include: [ Credit ]
} ],
where: {
startdate: {
[Op.eq]: new Date(date)
}
}
})
}
const Department = require('./services/departments')
const Student = require('./services/students')
const Course = require('./services/courses')
const Teacher = require('./services/teachers')
const Population = require('./services/populations')
const studentsStartedAtDepartment = (studyrights, dep, startDate) => {
const hasNoPreviousStudies = (s) => {
app.use(cors({credentials: true, origin: 'http://localhost:8000'}))
app.use(bodyParser.json())
return true
app.get('/api/departmentsuccess', async function (req, res) {
const startDate = req.query.date? req.query.date.split('.').join('-'): '2005-08-01'
const months = 13
const results = await Department.averagesInMonths(startDate, months)
res.json(results)
})
app.get('/api/students', async function (req, res) {
let results = []
if (req.query.searchTerm) {
results = await Student.bySeachTerm(req.query.searchTerm)
}
const hasStarted = (s) => {
return s.credits.length>0
}
res.json(results)
})
return studyrights
.filter( s => s.highlevelname === dep )
.map( s => s.student )
.filter( hasStarted )
.filter( hasNoPreviousStudies )
}
app.get('/api/students/:id', async function (req, res) {
const results = await Student.withId(req.params.id)
res.json(results)
})
app.get('/api/courses', async function (req, res) {
let results = []
if (req.query.name) {
results = await Course.bySeachTerm(req.query.name)
}
res.json(results)
})
app.post('/api/courselist', async function(req, res) {
const results = await Course.instancesOf(req.body.code)
res.json(results)
})
app.post('/api/coursestatistics', async function(req, res) {
const code = req.body.code;
const date = req.body.date.split('.').join('-')
const months = req.body.subsequentMonthsToInvestigate
const results = await Course.statisticsOf(code, date, months)
res.json(results)
})
app.post('/api/teacherstatistics', async function(req, res) {
const courses = req.body.courses.map(c => c.code)
const fromDate = req.body.fromDate.split('.').join('-')
const toDate = req.body.toDate.split('.').join('-')
const minCourses = req.body.minCourses || 1
const minStudents = req.body.minStudents || 1
const studyRights = req.body.studyRights || 1
const results = await Teacher.statisticsOf(courses, fromDate, toDate, minCourses, minStudents, studyRights)
res.json(results)
})
app.get('/api/studyrightkeywords', async function(req, res) {
let results = []
if (req.query.search) {
results = await Population.studyrightsByKeyword(req.query.search)
}
res.json(results)
})
app.get('/api/enrollmentdates', async function(req, res) {
const results = await Population.universityEnrolmentDates()
res.json(results)
})
async function main() {
try {
const BSC_CS = 'Bachelor of Science, Computer Science'
const BSC_MATH = 'Bachelor of Science, Mathematics'
const startDate = '2005-08-01'
const studyrights = await studyrightsStartedAt(startDate)
const cs = studentsStartedAtDepartment(studyrights, BSC_CS, moment(startDate))
app.get('*', async function (req, res) {
const results = { error: "unknown endpoint" }
res.status(404).json(results)
})
cs.forEach( s => console.log(s.studentnumber))
//console.log(studyrights.map(s=>s.student.studentnumber))
} catch (e) {
console.log(e)
}
sequelize.close()
}
main()
app.listen(8080, function () {
console.log('Example app listening on port 8080!')
})
+141 -2
View File
@@ -1,11 +1,11 @@
const Sequelize = require('sequelize')
const moment = require('moment')
const sequelize = new Sequelize('tkt_oodi', 'tkt_oodi', process.ENV.PW, {
host: 'localhost',
dialect:'postgres',
});
const Student = sequelize.define('student',
{
studentnumber: {
@@ -35,6 +35,44 @@ const Student = sequelize.define('student',
}
);
Student.hasNoPreviousStudies = (startDate) => (student) => {
const by = (a, b) => moment(a).isSameOrBefore(b) ? -1 : 1
const dates = student.credits.map(c=>c.courseinstance.coursedate).sort(by)
const earliestCreditDate = dates[0]
return moment(startDate).isSameOrBefore(earliestCreditDate)
}
Student.hasStarted = (student) => {
return student.credits.length > 0
}
const TagStudent = sequelize.define('tag_student',
{
taggedstudents_studentnumber: { type: Sequelize.STRING },
tags_tagname: { type: Sequelize.STRING },
},
{
tableName: 'tag_student',
timestamps: false,
}
);
TagStudent.removeAttribute('id')
const Tag = sequelize.define('tag',
{
tagname: {
primaryKey: true,
type: Sequelize.STRING
},
},
{
tableName: 'tag',
timestamps: false,
}
);
const Credit = sequelize.define('credit',
{
id: {
@@ -55,6 +93,23 @@ const Credit = sequelize.define('credit',
}
);
Credit.inTimeRange = (date, months) => (credit) => {
const creditDate = credit.courseinstance.coursedate
const monthsFromDate = moment(date).add(months, 'months')
return moment(creditDate).isBetween(date, monthsFromDate, null, '[]')
}
Credit.notUnnecessary = (credit) => {
return credit.credits > 0 && credit.credits <= 12
}
Credit.failed = (credit) =>
['Luop', 'Hyl.', 'Eisa', '0'].includes(credit.grade)
Credit.passed = (credit) =>
!['Luop', 'Hyl.', 'Eisa', '0'].includes(credit.grade)
const Studyright = sequelize.define('studyright',
{
studyrightid: {
@@ -80,10 +135,94 @@ const Studyright = sequelize.define('studyright',
}
);
const CourseInstance = sequelize.define('courseinstance',
{
id: {
primaryKey: true,
type: Sequelize.BIGINT
},
coursedate: { type: Sequelize.DATE },
course_code: { type: Sequelize.STRING },
},
{
tableName: 'courseinstance',
timestamps: false,
}
);
const Course = sequelize.define('course',
{
code: {
primaryKey: true,
type: Sequelize.STRING
},
name: { type: Sequelize.STRING },
},
{
tableName: 'course',
timestamps: false,
}
);
const Teacher = sequelize.define('teacher',
{
id: {
primaryKey: true,
type: Sequelize.BIGINT
},
code: { type: Sequelize.STRING },
name: { type: Sequelize.STRING },
},
{
tableName: 'teacher',
timestamps: false,
}
);
const CourseTeacher = sequelize.define('courseteacher',
{
id: {
primaryKey: true,
type: Sequelize.BIGINT
},
teacherrole: { type: Sequelize.STRING },
courseinstance_id: { type: Sequelize.BIGINT },
teacher_id: { type: Sequelize.BIGINT },
},
{
tableName: 'courseteacher',
timestamps: false,
}
);
CourseInstance.belongsTo(Course, {foreignKey: 'course_code', targetKey: 'code'});
Course.hasMany(CourseInstance, {foreignKey: 'course_code', targetKey: 'code'});
CourseInstance.hasMany(Credit, {foreignKey: 'courseinstance_id', targetKey: 'id'});
Credit.belongsTo(CourseInstance, {foreignKey: 'courseinstance_id', targetKey: 'id'});
CourseInstance.hasMany(CourseTeacher, {foreignKey: 'courseinstance_id', targetKey: 'id'});
Credit.belongsTo(Student, {foreignKey: 'student_studentnumber', targetKey: 'studentnumber'});
Student.hasMany(Credit, {foreignKey: 'student_studentnumber', sourceKey: 'studentnumber'});
Student.hasMany(TagStudent, {foreignKey: 'taggedstudents_studentnumber', sourceKey: 'studentnumber'});
Tag.hasMany(TagStudent, {foreignKey: 'tags_tagname', sourceKey: 'tagname'});
// TagStudent belongsTo
Studyright.belongsTo(Student, {foreignKey: 'student_studentnumber', targetKey: 'studentnumber'});
Student.hasMany(Studyright, {foreignKey: 'student_studentnumber', sourceKey: 'studentnumber'});
module.exports = {Student, Credit, Studyright, sequelize}
module.exports = {
Student,
Credit,
Studyright,
CourseInstance,
Course,
TagStudent,
Tag,
Teacher,
CourseTeacher,
sequelize
}
+177
View File
@@ -0,0 +1,177 @@
const Sequelize = require('sequelize')
const moment = require('moment')
const { Student, Credit, CourseInstance, Course, TagStudent, Tag, CourseTeacher, sequelize } = require('../models')
const { arrayUnique } = require('../util')
const Op = Sequelize.Op;
const byNameOrCode = (searchTerm) => {
return Course.findAll({
limit: 10,
where: {
[Op.or]: [
{
name: {
[Op.iLike]: searchTerm
}
},
{
code: {
[Op.like]: searchTerm
}
}
]
}
})
}
const instanceStatistics = (code, date) => {
return CourseInstance.findOne({
include: [
{
model: Credit,
include: [ Student ]
}
],
where: {
[Op.and]: [
{
course_code: {
[Op.eq]: code
}
},
{
coursedate: {
[Op.eq]: new Date(date)
}
}
]
}
})
}
const instancesByCode = (code) => {
return CourseInstance.findAll({
include: [ Credit, CourseTeacher ],
where: {
course_code: {
[Op.eq]: code
}
}
})
}
const byIds = (ids) => {
return Student.findAll({
include: [
{
model: Credit,
include: [
{
model: CourseInstance,
}
]
}
],
where: {
studentnumber: {
[Op.in]: ids
}
}
})
}
async function bySeachTerm(term) {
const formatCourse = ({name, code}) => ({name, code})
try {
const result = await byNameOrCode(`%${term}%`)
return result.map(formatCourse)
} catch (e) {
return {
error: e
}
}
}
async function statisticsOf(code, date, months) {
const getStudents = ({credits}) => {
const all = credits.map(c=>c.student_studentnumber)
const pass = credits.filter(Credit.passed).map(c=>c.student_studentnumber)
const fail = credits.filter(Credit.failed).map(c=>c.student_studentnumber)
return {all, pass, fail}
}
const starYearsOf = (students) => {
const years = students.map(s=> moment(s.dateofuniversityenrollment).year()).sort()
return years.reduce((map, y) => { map[y] = map[y] ? map[y]+1 : 1; return map}, {})
}
const studentStatsAfter = (studentsStats, date) => {
const creditsAfter = (student) => {
return student.credits
.filter(Credit.inTimeRange(date, months))
.filter(Credit.passed)
.filter(Credit.notUnnecessary)
.reduce((set, c) => set+c.credits, 0.0)
}
const toStudent = (set, student) => {
set[student.studentnumber] = creditsAfter(student, date)
return set
}
return studentsStats.reduce(toStudent, {})
}
try {
const instanceStats = await instanceStatistics(code, date)
const students = getStudents(instanceStats)
const studentStats = await byIds(students.all)
const all = studentStatsAfter(studentStats.filter(s=>students.all.includes(s.studentnumber)), date)
const pass = studentStatsAfter(studentStats.filter(s=>students.pass.includes(s.studentnumber)), date)
const fail = studentStatsAfter(studentStats.filter(s=>students.fail.includes(s.studentnumber)), date)
return {
all, pass, fail,
startYear: starYearsOf(instanceStats.credits.map(c=>c.student))
}
} catch (e) {
console.log(e)
return {
error: e
}
}
}
async function instancesOf(code) {
const byDate = (a, b) => {
return moment(a.coursedate).isSameOrBefore(b.coursedate) ? -1 : 1
}
const formatInstance = (instance) => {
return {
id: instance.id,
date: instance.coursedate,
fail: instance.credits.filter(Credit.failed).length,
pass: instance.credits.filter(Credit.passed).length,
students: instance.credits.length,
teachers: instance.courseteachers.map(t=>t.teacher_id).filter(arrayUnique).length
}
}
try {
const result = await instancesByCode(code)
return result.sort(byDate).map(formatInstance)
} catch (e) {
console.log(e)
return {
error: e
}
}
}
module.exports = {
bySeachTerm, instancesOf, statisticsOf
}
+106
View File
@@ -0,0 +1,106 @@
const Sequelize = require('sequelize')
const moment = require('moment')
const { Student, Credit, Studyright, CourseInstance, sequelize } = require('../models')
const Op = Sequelize.Op;
const studyrightsStartedAt = (date) => {
return Studyright.findAll({
include: [
{
model: Student,
include: [
{
model: Credit,
include: [ CourseInstance ]
}
]
}
],
where: {
startdate: {
[Op.eq]: new Date(date)
}
}
})
}
const studentsStartedAtDepartment = (studyrights, department, startDate) => {
const departmentMatches = (s) => {
const name = s.highlevelname.toUpperCase()
return department.toUpperCase().split(' ').every( part => name.indexOf(part)!==-1 )
}
return studyrights
.filter( departmentMatches )
.map( s => s.student )
.filter( Student.hasStarted )
.filter( Student.hasNoPreviousStudies(startDate) )
}
const averageGradesInMonths = (students, start, months) => {
const studentsAverageCreditsInMonths = (sum, student) => {
const notRepeatEntry = (set, credit) => {
const completedCourses = set.map(c => c.courseinstance.course_code)
if ( completedCourses.includes(credit.courseinstance.course_code) ) {
return set
}
return set.concat(credit)
}
return sum + student.credits
.filter(Credit.passed)
.filter(Credit.notUnnecessary)
.filter(Credit.inTimeRange(start, months))
.reduce(notRepeatEntry, [])
.reduce((sum, c) => sum+c.credits, 0)
}
return students.reduce(studentsAverageCreditsInMonths, 0.0) / students.length
}
const departments = [
{
name: 'Chemistry Bachelor',
api_name: 'Chemistry'
},
{
name: 'Computer Science Bachelor',
api_name: 'Computer Science'
},
{
name: 'Mathematics Bachelor',
api_name: 'Mathematics'
},
{
name: 'Physics Bachelor',
api_name: 'Physics'
},
]
async function averagesInMonths(startDate, months) {
try {
const studyrights = await studyrightsStartedAt(startDate)
result = {}
departments.forEach( p => {
const students = studentsStartedAtDepartment(studyrights, p.name, startDate)
result[p.api_name] = averageGradesInMonths(students, startDate, months)
})
return result
} catch (e) {
console.log(e)
return {
error: e
}
}
}
module.exports = {
averagesInMonths, departments
}
+193
View File
@@ -0,0 +1,193 @@
const Sequelize = require('sequelize')
const moment = require('moment')
const { Studyright, Student, Credit, CourseInstance, Course, TagStudent, Tag, CourseTeacher, Teacher, sequelize } = require('../models')
const { formatStudent } = require('../services/students')
const Op = Sequelize.Op;
const enrolmentDates = () => {
const query = 'SELECT DISTINCT s.dateOfUniversityEnrollment as date FROM Student s'
return sequelize.query(query)
}
const studyRightLike = (searchTerm) => {
const query = `
SELECT DISTINCT highLevelName
FROM StudyRight
WHERE LOWER(highLevelName)
LIKE LOWER('%${searchTerm}%')`
return sequelize.query(query, { model: Studyright })
}
const byCriteria = (conf) => {
const terms = []
const enrollmentDateCriterias =
conf.enrollmentDates.map( enrollmentDate => (
{ // for some reason Op.eq does not work...
dateofuniversityenrollment: {
[Op.between]: [enrollmentDate, enrollmentDate]
}
}
)
)
terms.push({
[Op.or]: enrollmentDateCriterias
})
if ( conf.minBirthDate || conf.maxBirthDate ) {
const minBirthDate = conf.minBirthDate || '1900-01-01'
const maxBirthDate = conf.maxBirthDate || `${new Date().getFullYear()}-01-01`
terms.push({
birthdate: {
[Op.between]: [minBirthDate, maxBirthDate]
}
})
}
if ( conf.sex && ['male', 'female'].includes(conf.sex)) {
terms.push({
sex: {
[Op.eq]: conf.sex
}
})
}
if ( conf.matriculationExamination && ['true', 'false'].includes(conf.matriculationExamination) ) {
terms.push({
matriculationexamination: {
[Op.eq]: conf.matriculationExamination === 'true' ? '1' : '0'
}
})
}
if ( conf.studentNumbers && conf.studentNumbers.length>0 ) {
terms.push({
studentnumber: {
[Op.in]: conf.studentNumbers
}
})
}
let tagWithConstraint = {
model: TagStudent
}
if ( conf.tags && conf.tags.length > 0 ) {
const tagRules = conf.tags.map(tag => ({ [Op.eq]: tag }) )
tagWithConstraint.where = {
tags_tagname: {
[Op.or]: tagRules
}
}
}
let studyrightWithConstraint = {
model: Studyright
}
if ( conf.studyRights && conf.studyRights.length > 0 ) {
const studyrightRules = conf.studyRights.map(name => ({ [Op.eq]: name }) )
studyrightWithConstraint.where = {
highlevelname: {
[Op.or]: studyrightRules
}
}
}
return Student.findAll({
include: [
{
model: Credit,
include: [
{
model: CourseInstance,
include: [ Course ]
}
]
},
tagWithConstraint,
studyrightWithConstraint
],
where: {
[Op.and] : terms
}
})
}
const bySelectedCourses = (courses) => (student) => {
if (courses.length===0) {
return true
} else {
const passedCourses = student.credits.filter(Credit.passed).map(c=>c.courseinstance.course_code)
return courses.every(c => passedCourses.includes(c))
}
}
const notAmongExcludes = (conf) => (student) => {
if ( conf.excludeStudentsThatHaveNotStartedStudies &&
student.credits.length === 0 ) {
return false
}
if ( conf.excludeStudentsWithZeroCredits &&
student.creditcount === 0 ) {
return false
}
if ( conf.excludeStudentsWithPreviousStudies &&
Student.hasNoPreviousStudies(student.dateofuniversityenrollment)(student) ) {
return false
}
if ( conf.excludedTags && conf.excludedTags.length>0 ) {
const noExcludedTags = student.tag_students.every( tag =>
conf.excludedTags.includes(tag.tags_tagname)===false
)
if (!noExcludedTags) {
return false
}
}
if (conf.excludedStudentNumbers &&
conf.excludedStudentNumbers.includes(student.studentnumber) ) {
return false
}
return true
}
const restrictToMonths = (months) => (student) => {
const withinTimerange = Credit.inTimeRange(student.dateofuniversityenrollment, months)
const creditsWithinTimelimit = student.credits.filter(withinTimerange)
return {...student, credits: creditsWithinTimelimit }
}
async function studyrightsByKeyword(searchTerm) {
const result = await studyRightLike(searchTerm)
return result.map( s => s.highlevelname )
}
async function universityEnrolmentDates() {
const [result, metadata] = await enrolmentDates()
return result.map( r => r.date ).filter( d => d ).sort()
}
async function statisticsOf(conf) {
const students = (await byCriteria(conf))
.filter(bySelectedCourses(conf.courses))
.filter(notAmongExcludes(conf))
.map(restrictToMonths(conf.monthsToStudy))
return students.map(formatStudent)
}
module.exports = {
studyrightsByKeyword, universityEnrolmentDates, statisticsOf
}
+230
View File
@@ -0,0 +1,230 @@
const Sequelize = require('sequelize')
const moment = require('moment')
const { Student, Credit, CourseInstance, Course, TagStudent, Tag, sequelize } = require('../models')
const Op = Sequelize.Op;
const byId = (id) => {
return Student.findOne({
include: [
{
model: Credit,
include: [
{
model: CourseInstance,
include: [ Course ]
}
]
},
TagStudent
],
where: {
studentnumber:{
[Op.eq]: id
}
}
})
}
const byAbreviatedNameOrStudentNumber = (searchTerm) => {
return Student.findAll({
limit: 10,
where: {
[Op.or]: [
{
studentnumber: {
[Op.like]: searchTerm
}
},
{
abbreviatedname: {
[Op.iLike]: searchTerm
}
}
]
}
})
}
const withCreditsAfter = (ids, date) => {
return Student.findAll({
include: [
{
model: Credit,
include: [
{
model: CourseInstance,
where: {
course_code: {
[Op.gte] :date
}
}
}
]
}
],
where: {
studentnumber: {
[Op.in]: ids
}
}
})
}
const formatStudent = ({studentnumber, dateofuniversityenrollment, creditcount, credits, tag_students}) => {
const toCourse = ({grade, credits, status, statuscode, courseinstance}) => {
return {
course: {
code: courseinstance.course_code,
name: courseinstance.course.name
},
date: courseinstance.coursedate,
passed: Credit.passed(grade),
grade,
credits
}
}
const tagnames = (tag_students) => {
if ( tag_students === undefined || tag_students[0].tags_tagname === null ) {
return []
}
return tag_students.map(t=>t.tags_tagname)
}
const byDate = (a, b) => {
return moment(a.courseinstance.coursedate).isSameOrBefore(b.courseinstance.coursedate) ? -1 : 1
}
if ( credits === undefined) {
credits = []
}
return {
studentNumber: studentnumber,
started: dateofuniversityenrollment,
credits: creditcount,
courses: credits.sort(byDate).map(toCourse),
tags: tagnames(tag_students)
}
}
const addTagToStudent = (id, tag) => {
return TagStudent.create({
taggedstudents_studentnumber: id,
tags_tagname: tag
})
}
const findTag = (tag) => {
return Tag.findOne({
where: {
tagname: {
[Op.eq]: tag
}
}
})
}
const findTagOf = (id, tag) => {
return TagStudent.findOne({
where: {
[Op.and]: [
{
tags_tagname: {
[Op.eq]: tag
}
},
{
taggedstudents_studentnumber:{
[Op.eq]: id
}
}
]
}
})
}
const deleteTagStudent = (id, tagname) => {
return sequelize.query(
`DELETE
FROM tag_student
WHERE taggedstudents_studentnumber = '${id}'
AND tags_tagname = '${tagname}'`
)
}
async function bySeachTerm(term) {
try{
const result = await byAbreviatedNameOrStudentNumber(`%${term}%`)
return result.map(formatStudent)
} catch (e) {
return {
error: e
}
}
}
async function withId(id) {
try {
const result = await byId(id)
return formatStudent(result)
} catch (e) {
return {
error: e
}
}
}
async function addTag(id, tagname) {
try {
const student = await byId(id)
const tag = await findTag(tagname);
if (tag === null) {
sequelize.close()
return {
error: `tag '${tagname}' does not exist`
}
}
if (student.tag_students.map(t=>t.tags_tagname).includes(tagname)) {
sequelize.close()
return {
error: `tag '${tagname}' already assosiated with student '${id}'`
}
}
return await addTagToStudent(id, tagname)
} catch (e) {
return {
error: e
}
}
}
async function deleteTag(id, tagname) {
try {
const tag = await findTagOf(id, tagname)
if (tag === null) {
sequelize.close()
return {
error: `tag '${tagname}' is not assosiated with student '${id}'`
}
}
const result = await deleteTagStudent(id, tagname)
return {}
} catch (e) {
return {
error: e
}
}
}
module.exports = {
withId, bySeachTerm, addTag, deleteTag, formatStudent
}
+169
View File
@@ -0,0 +1,169 @@
const Sequelize = require('sequelize')
const moment = require('moment')
const { Student, Studyright, Credit, CourseInstance, Course, TagStudent, Tag, CourseTeacher, Teacher, sequelize } = require('../models')
const { arrayUnique } = require('../util')
const Op = Sequelize.Op;
const courseInstancesInBetween = (courses, fromDate, toDate) => {
return CourseInstance.findAll({
where: {
[Op.and]: [
{
course_code: {
[Op.in]: courses
}
},
{
coursedate: {
[Op.between]: [fromDate, toDate]
}
}
]
},
include: [
{
model: CourseTeacher
},
{
model: Credit,
include: [ { model: Student, include: [Studyright] } ]
}
]
})
}
const teachersByIds = (ids) => {
return Teacher.findAll({
where: {
id: {
[Op.in]: ids
}
}
})
}
async function statisticsOf(courses, fromDate, toDate, minCourses, minStudents, studyRights) {
const extractStatsPerTeacherId = (courses) => {
const hasAskedStudyright = (credit) => {
if ( studyRights.length==0 ) {
return true
}
const studentsRights = credit.student.studyrights.map(s => s.highlevelname)
return studyRights.map( s => studentsRights.includes(s) ).find( s => s===true )
}
const teachers = {}
const accountedCredits = {}
const attendances = {}
courses.forEach(course => {
// horrible code to prevent duplicate entries
const noRepeatedEntry = (credit) => {
if ( accountedCredits[credit.student_studentnumber]===undefined ) {
accountedCredits[credit.student_studentnumber] = {}
}
if ( accountedCredits[credit.student_studentnumber][course.course_code] ) {
return false
}
accountedCredits[credit.student_studentnumber][course.course_code] = credit
return true
}
// even worse code to prevent counting students with twice passed as attendance twice
const noRepeatedAttendance = (credit) => {
if ( attendances[credit.student_studentnumber]===undefined ) {
attendances[credit.student_studentnumber] = {}
}
const attendance = attendances[credit.student_studentnumber][course.course_code]
if ( attendance===undefined ) {
attendances[credit.student_studentnumber][course.course_code] = ''+Credit.passed(credit)
return true
}
if ( attendance === 'true' && Credit.passed(credit) ) {
return false
} else if ( attendance === 'false' && Credit.passed(credit) ) {
attendances[credit.student_studentnumber][course.course_code] = ''+Credit.passed(credit)
return true
}
return true
}
const teacher_ids = course.courseteachers.map( t => t.teacher_id ).filter(arrayUnique)
const creditsWithAskedStudyrights = course.credits.filter(hasAskedStudyright)
teacher_ids.forEach( teacher_id =>{
if ( teachers[teacher_id]===undefined ) {
teachers[teacher_id] = {
students: 0,
passed: 0,
courses: 0,
points: 0
}
}
const totalCredits = creditsWithAskedStudyrights
.filter(Credit.passed)
.filter(noRepeatedEntry)
const students = creditsWithAskedStudyrights
.filter(noRepeatedAttendance).length
teachers[teacher_id].students += students
teachers[teacher_id].courses += 1
teachers[teacher_id].passed += totalCredits.length
teachers[teacher_id].points += totalCredits.reduce((s,c) => s+c.credits, 0)
} )
})
Object.keys(teachers).forEach( teacher => {
const data = teachers[teacher]
if ( data.students<minStudents || data.courses<minCourses ) {
delete teachers[teacher]
}
})
return teachers
}
const format = (stats, teachers) => {
return Object.keys(stats).map( id => {
const teacher_stat = stats[id]
const teacher = teachers.find( t => t.id === id )
return {
teacherId: teacher.name,
credits: teacher_stat.points,
studentsPassed: teacher_stat.passed,
studentsTeached: teacher_stat.students,
instancesTeached: teacher_stat.courses
}
})
}
try {
const courseStats = await courseInstancesInBetween(courses, fromDate, toDate)
const teacherStats = extractStatsPerTeacherId(courseStats)
// take into account studyRights
const teachers = await teachersByIds(Object.keys(teacherStats))
return format(teacherStats, teachers)
} catch (e) {
console.log(e)
return {
error: e
}
}
}
module.exports = {
statisticsOf
}
+7
View File
@@ -0,0 +1,7 @@
function arrayUnique(value, index, self) {
return self.indexOf(value) === index;
}
module.exports = {
arrayUnique
}