mirror of
https://github.com/UniversityOfHelsinkiCS/oodikone.git
synced 2026-10-12 19:03:39 +03:00
most of the api endpoints completed
This commit is contained in:
+2
-1
@@ -1,2 +1,3 @@
|
|||||||
node_modules
|
node_modules
|
||||||
.vscode
|
.vscode
|
||||||
|
src/tester.js
|
||||||
@@ -1 +1,2 @@
|
|||||||
require('./src/app')
|
require('./src/tester')
|
||||||
|
//require('./src/app')
|
||||||
Generated
+2255
-2
File diff suppressed because it is too large
Load Diff
+12
-5
@@ -1,15 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "nodetest",
|
"name": "oodikone-backend",
|
||||||
"version": "1.0.0",
|
"version": "0.0.1",
|
||||||
"description": "",
|
"description": "Backend for Oodikone",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"start": "nodemon index.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "Matti Luukkainen",
|
||||||
"license": "ISC",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.16.2",
|
"axios": "^0.16.2",
|
||||||
|
"body-parser": "^1.18.2",
|
||||||
|
"cors": "^2.8.4",
|
||||||
|
"express": "^4.16.2",
|
||||||
"install": "^0.10.1",
|
"install": "^0.10.1",
|
||||||
"knex": "^0.13.0",
|
"knex": "^0.13.0",
|
||||||
"moment": "^2.19.1",
|
"moment": "^2.19.1",
|
||||||
@@ -17,5 +21,8 @@
|
|||||||
"pg": "^7.3.0",
|
"pg": "^7.3.0",
|
||||||
"pg-hstore": "^2.3.2",
|
"pg-hstore": "^2.3.2",
|
||||||
"sequelize": "^4.13.8"
|
"sequelize": "^4.13.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^1.12.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+85
-50
@@ -1,61 +1,96 @@
|
|||||||
const Sequelize = require('sequelize')
|
const express = require('express')
|
||||||
const moment = require('moment')
|
const cors = require('cors')
|
||||||
const { Student, Credit, Studyright, sequelize } = require('./models')
|
const app = express()
|
||||||
const Op = Sequelize.Op;
|
const bodyParser = require('body-parser')
|
||||||
|
|
||||||
const studyrightsStartedAt = (date) => {
|
const Department = require('./services/departments')
|
||||||
return Studyright.findAll({
|
const Student = require('./services/students')
|
||||||
limit : 200,
|
const Course = require('./services/courses')
|
||||||
include: [ {
|
const Teacher = require('./services/teachers')
|
||||||
model: Student,
|
const Population = require('./services/populations')
|
||||||
include: [ Credit ]
|
|
||||||
} ],
|
|
||||||
where: {
|
|
||||||
startdate: {
|
|
||||||
[Op.eq]: new Date(date)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const studentsStartedAtDepartment = (studyrights, dep, startDate) => {
|
app.use(cors({credentials: true, origin: 'http://localhost:8000'}))
|
||||||
const hasNoPreviousStudies = (s) => {
|
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) => {
|
res.json(results)
|
||||||
return s.credits.length>0
|
})
|
||||||
}
|
|
||||||
|
|
||||||
return studyrights
|
app.get('/api/students/:id', async function (req, res) {
|
||||||
.filter( s => s.highlevelname === dep )
|
const results = await Student.withId(req.params.id)
|
||||||
.map( s => s.student )
|
res.json(results)
|
||||||
.filter( hasStarted )
|
})
|
||||||
.filter( hasNoPreviousStudies )
|
|
||||||
}
|
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)
|
app.get('*', async function (req, res) {
|
||||||
|
const results = { error: "unknown endpoint" }
|
||||||
const cs = studentsStartedAtDepartment(studyrights, BSC_CS, moment(startDate))
|
res.status(404).json(results)
|
||||||
|
})
|
||||||
|
|
||||||
cs.forEach( s => console.log(s.studentnumber))
|
app.listen(8080, function () {
|
||||||
|
console.log('Example app listening on port 8080!')
|
||||||
//console.log(studyrights.map(s=>s.student.studentnumber))
|
})
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
sequelize.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
||||||
+141
-2
@@ -1,11 +1,11 @@
|
|||||||
const Sequelize = require('sequelize')
|
const Sequelize = require('sequelize')
|
||||||
|
const moment = require('moment')
|
||||||
|
|
||||||
const sequelize = new Sequelize('tkt_oodi', 'tkt_oodi', process.ENV.PW, {
|
const sequelize = new Sequelize('tkt_oodi', 'tkt_oodi', process.ENV.PW, {
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
dialect:'postgres',
|
dialect:'postgres',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const Student = sequelize.define('student',
|
const Student = sequelize.define('student',
|
||||||
{
|
{
|
||||||
studentnumber: {
|
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',
|
const Credit = sequelize.define('credit',
|
||||||
{
|
{
|
||||||
id: {
|
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',
|
const Studyright = sequelize.define('studyright',
|
||||||
{
|
{
|
||||||
studyrightid: {
|
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'});
|
Credit.belongsTo(Student, {foreignKey: 'student_studentnumber', targetKey: 'studentnumber'});
|
||||||
Student.hasMany(Credit, {foreignKey: 'student_studentnumber', sourceKey: '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'});
|
Studyright.belongsTo(Student, {foreignKey: 'student_studentnumber', targetKey: 'studentnumber'});
|
||||||
Student.hasMany(Studyright, {foreignKey: 'student_studentnumber', sourceKey: '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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
function arrayUnique(value, index, self) {
|
||||||
|
return self.indexOf(value) === index;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
arrayUnique
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user