diff --git a/services/backend/oodikone2-backend/src/services/studytrack.js b/services/backend/oodikone2-backend/src/services/studytrack.js index 0ded470547..d2b9d6cc10 100644 --- a/services/backend/oodikone2-backend/src/services/studytrack.js +++ b/services/backend/oodikone2-backend/src/services/studytrack.js @@ -21,7 +21,7 @@ const formatCredit = credit => { return { id, year, credits } } -const fuckup = (provider, since, studentnumbers) => Credit.findAll({ +const getCreditsForStudentsInThatProgram = (provider, since, studentnumbers, failed) => Credit.findAll({ attributes: ['id', 'course_code', 'credits', 'attainment_date', 'student_studentnumber'], include: { model: Course, @@ -48,9 +48,12 @@ const fuckup = (provider, since, studentnumbers) => Credit.findAll({ }, student_studentnumber: { [Op.in]: studentnumbers + }, + grade: { + [Op.notIn]: failed } } -}).map(formatCredit) +}) const getCreditsForProvider = (provider, since) => Credit.findAll({ attributes: ['id', 'course_code', 'credits', 'attainment_date'], @@ -129,26 +132,10 @@ const graduatedStatsFromStudyrights = studyrights => { stats[year] = { graduated: graduated + 1, timesToGraduation: stats[year] ? - [...stats[year].timesToGraduation, timeToGraduation || 0] : [ timeToGraduation || 0] + [...stats[year].timesToGraduation, timeToGraduation || 0] : [timeToGraduation || 0] } graduationTimes = [...graduationTimes, timeToGraduation || 0] }) - const median = (values) => { - if (values.length === 0) return 0 - - values.sort((a, b) => a - b) - - var half = Math.floor(values.length / 2) - - if (values.length % 2) - return values[half] - - return (values[half - 1] + values[half]) / 2.0 - } - Object.keys(stats).forEach(year => { - stats[year].medianGraduationTime = median(stats[year].timesToGraduation) - }) - stats['medianGraduationTime'] = median(graduationTimes) return stats } @@ -202,33 +189,38 @@ const thesisProductivityForStudytrack = async code => { return thesisProductivityFromCredits(credits) } -const combineStatistics = (creditStats, studyrightStats, thesisStats) => { +const combineStatistics = (creditStats, studyrightStats, thesisStats, creditsForPercentage) => { const stats = { ...creditStats } Object.keys(stats).forEach(year => { const thesis = thesisStats[year] || {} stats[year].graduated = studyrightStats[year] ? studyrightStats[year].graduated : 0 - stats[year].medianGraduationTime = studyrightStats[year] ? studyrightStats[year].medianGraduationTime : 0 + // stats[year].medianGraduationTime = studyrightStats[year] ? studyrightStats[year].medianGraduationTime : 0 stats[year].bThesis = thesis.bThesis || 0 stats[year].mThesis = thesis.mThesis || 0 + stats[year].creditsForPercentage = creditsForPercentage[year] || 0 }) return Object.values(stats) } -// providercode here const productivityStatsForStudytrack = async (studytrack, since) => { const providercode = studytrackToProviderCode(studytrack) + const year = new Date(since).getFullYear() + const startDate = `${year}-${semesterStart['FALL']}` + const endDate = `${moment(since, 'YYYY').add(1, 'years').format('YYYY')}-${semesterEnd['SPRING']}` + const studentnumbers = await studentnumbersWithAllStudyrightElements([studytrack], startDate, endDate, false, false) const promises = [ graduatedStatsForStudytrack(studytrack, since), productivityStatsForProvider(providercode, since), - thesisProductivityForStudytrack(studytrack) + thesisProductivityForStudytrack(studytrack), + getCreditsFromStudyprogrammeStudents(studytrack, since, studentnumbers) ] - const [studyrightStats, creditStats, thesisStats] = await Promise.all( + const [studyrightStats, creditStats, thesisStats, creditsForPercentage] = await Promise.all( promises ) return { id: studytrack, status: null, - data: combineStatistics(creditStats, studyrightStats, thesisStats) + data: combineStatistics(creditStats, studyrightStats, thesisStats, creditsForPercentage) } } @@ -373,10 +365,23 @@ const tranferredToStudyprogram = async (studentnumbers, startDate, studytrack, e }) } +const formatCreditsForPercentage = (credits) => { + return credits.map(formatCredit).reduce(function (acc, curr) { + var key = curr['year'] + if (!acc[key]) { + acc[key] = [] + } + acc[key] = Number(acc[key]) + Number(curr.credits) + return acc + }, {}) +} + const getCreditsFromStudyprogrammeStudents = async (studytrack, startDate, studentnumbers) => { const providercode = studytrackToProviderCode(studytrack) - const test = await fuckup(providercode, startDate, studentnumbers) - return test.map(formatCredit) + const failed = ['0', 'Hyl.', 'Luop', 'Eisa'] + const credits = await getCreditsForStudentsInThatProgram(providercode, startDate, studentnumbers, failed) + const formattedStudentCredits = formatCreditsForPercentage(credits) + return formattedStudentCredits } const productivityStats = async (studentnumbers, startDate, studytrack, endDate) => { @@ -417,19 +422,29 @@ const throughputStatsForStudytrack = async (studytrack, since) => { const years = getYears(since) // studyprogramme starts with K if bachelors and M if masters const graduationTimeLimit = studytrack[0] === 'K' ? 36 : 24 + const median = (values) => { + if (values.length === 0) return 0 + + values.sort((a, b) => a - b) + var half = Math.floor(values.length / 2) + + if (values.length % 2) + return values[half] + + return (values[half - 1] + values[half]) / 2.0 + } + let allGraduationTimes = [] const arr = await Promise.all(years.map(async year => { const startDate = `${year}-${semesterStart['FALL']}` const endDate = `${moment(year, 'YYYY').add(1, 'years').format('YYYY')}-${semesterEnd['SPRING']}` const studentnumbers = await studentnumbersWithAllStudyrightElements([studytrack], startDate, endDate, false, false) const creditsForStudyprogramme = await getCreditsFromStudyprogrammeStudents(studytrack, startDate, studentnumbers) + const [credits, graduated, theses, genders, countries, transferred] = await productivityStats(studentnumbers, startDate, studytrack, endDate) delete genders[null] delete countries[null] - const test = creditsForStudyprogramme.map(formatCredit) - console.log(test) - const creditValues = credits.reduce((acc, curr) => { acc.mte30 = curr >= 30 ? acc.mte30 + 1 : acc.mte30 acc.mte60 = curr >= 60 ? acc.mte60 + 1 : acc.mte60 @@ -451,20 +466,24 @@ const throughputStatsForStudytrack = async (studytrack, since) => { totals.countries[countryKey] + Number(countries[countryKey]) : Number(countries[countryKey]) }) - const inTargetTime = graduated.filter(g => - moment(g.enddate).diff(g.startstududate, 'months') <= graduationTimeLimit).length + const graduationTimes = graduated.map(g => moment(g.enddate).diff(g.studystartdate, 'months')) + const inTargetTime = graduationTimes.filter(time => + time <= graduationTimeLimit).length + allGraduationTimes = [...allGraduationTimes, ...graduationTimes] totals.thesisM = theses.MASTER ? totals.thesisM + theses.MASTER : totals.thesisM totals.thesisB = theses.BACHELOR ? totals.thesisB + theses.BACHELOR : totals.thesisB totals.students = totals.students + credits.length totals.graduated = totals.graduated + graduated.length, - totals.inTargetTime = totals.inTargetTime + inTargetTime, - totals.transferred = totals.transferred + transferred.count + totals.medianGraduationTime = median(allGraduationTimes) + totals.inTargetTime = totals.inTargetTime + inTargetTime + totals.transferred = totals.transferred + transferred.count return { year: `${year}-${year + 1}`, credits: credits.map(cr => cr === null ? 0 : cr), - creditsForStudyprogramme: creditsForStudyprogramme.map(cr => cr === null ? 0 : cr), + creditsForStudyprogramme: creditsForStudyprogramme, graduated: graduated.length, + medianGraduationTime: median(graduationTimes), inTargetTime, thesisM: theses.MASTER || 0, thesisB: theses.BACHELOR || 0, diff --git a/services/backend/oodikone2-backend/src/services/studytrack.test.js b/services/backend/oodikone2-backend/src/services/studytrack.test.js index 0650f0196b..3e6db98994 100644 --- a/services/backend/oodikone2-backend/src/services/studytrack.test.js +++ b/services/backend/oodikone2-backend/src/services/studytrack.test.js @@ -125,8 +125,8 @@ test('graduatedStatsFromStudyrights calculates stats correctly', () => { ] const stats = graduatedStatsFromStudyrights(studyrights) expect(stats).toMatchObject({ - 2015: { graduated: 2., medianGraduationTime: 0, timesToGraduation: [ 0, 0 ]}, - 2014: { graduated: 1., medianGraduationTime: 0, timesToGraduation: [ 0 ]} + 2015: { graduated: 2. }, + 2014: { graduated: 1. } }) }) @@ -137,21 +137,26 @@ test('combineStatistics returns correctly formatted array', () => { 2014: { year: 2014, credits: 20 } } const studyrightStats = { - 2015: { graduated: 2, medianGraduationTime: 1.5, timesToGraduation: [1, 2] }, - 2016: { graduated: 1, medianGraduationTime: 0, timesToGraduation: [ 0 ] } + 2015: { graduated: 2 }, + 2016: { graduated: 1 } } const thesisStats = { 2014: { mThesis: 1 }, 2015: { mThesis: 2, bThesis: 1 } } - const stats = combineStatistics(creditStats, studyrightStats, thesisStats) + const creditsForPercentage = { + 2014: 10, + 2015: 22 + } + + const stats = combineStatistics(creditStats, studyrightStats, thesisStats, creditsForPercentage) expect(stats).toContainEqual({ year: 2015, mThesis: 2, bThesis: 1, credits: 40, - medianGraduationTime: 1.5, - graduated: 2 + graduated: 2, + creditsForPercentage: 22 }) expect(stats).toContainEqual({ year: 2014, @@ -159,7 +164,7 @@ test('combineStatistics returns correctly formatted array', () => { bThesis: 0, credits: 20, graduated: 0, - medianGraduationTime: 0 + creditsForPercentage: 10 }) expect(stats).toContainEqual({ year: 2016, @@ -167,7 +172,7 @@ test('combineStatistics returns correctly formatted array', () => { bThesis: 0, credits: 5, graduated: 1, - medianGraduationTime: 0 + creditsForPercentage: 0 }) }) @@ -179,7 +184,7 @@ test('productivityStatsForStudytrack integrates', async () => { bThesis: 0, mThesis: 1, credits: 40, - medianGraduationTime: 0 + creditsForPercentage: 0 }) expect(stats.data).toContainEqual({ year: 2016, @@ -187,7 +192,7 @@ test('productivityStatsForStudytrack integrates', async () => { mThesis: 0, bThesis: 0, credits: 5, - medianGraduationTime: 0 + creditsForPercentage: 0 }) }) diff --git a/services/oodikone2-frontend/src/components/StudyProgramme/ProductivityTable/index.jsx b/services/oodikone2-frontend/src/components/StudyProgramme/ProductivityTable/index.jsx index 60d396ea54..5048195b6b 100644 --- a/services/oodikone2-frontend/src/components/StudyProgramme/ProductivityTable/index.jsx +++ b/services/oodikone2-frontend/src/components/StudyProgramme/ProductivityTable/index.jsx @@ -12,13 +12,12 @@ const ProductivityTable = ({ productivity, thesis, loading, error, studyprogramm if (thesis) { thesisTypes = thesis.map(t => t.thesisType) } - const headerList = ['Year', 'Credits', thesisTypes.includes('MASTER') && 'Masters Thesis', thesisTypes.includes('BACHELOR') && 'Bachelors Thesis', 'Graduated', 'Graduation median time'].filter(_ => _) + const headerList = ['Year', 'Credits', thesisTypes.includes('MASTER') && 'Masters Thesis', thesisTypes.includes('BACHELOR') && 'Bachelors Thesis', 'Graduated', 'Credits given to students in selected programme'].filter(_ => _) const refresh = () => { callApi('/v2/studyprogrammes/productivity/recalculate', 'get', null, { code: studyprogramme }) .then(() => { dispatchGetProductivity(studyprogramme) }) } - console.log(productivity) return (
@@ -32,7 +31,7 @@ const ProductivityTable = ({ productivity, thesis, loading, error, studyprogramm productivity.lastUpdated ? moment(productivity.lastUpdated).format('HH:mm:ss MM-DD-YYYY') : 'unknown' - } ${productivity.status || ''}`} + } ${productivity.status || ''}`} )} @@ -58,21 +57,21 @@ const ProductivityTable = ({ productivity, thesis, loading, error, studyprogramm {productivity && productivity.data ? productivity.data - .sort((year1, year2) => year2.year - year1.year) - .map(year => ( - - {year.year} - {Math.floor(year.credits)} - {thesisTypes.includes('BACHELOR') && ( - {year.bThesis} - )} - {thesisTypes.includes('MASTER') && ( - {year.mThesis} - )} - {year.graduated} - {year.medianGraduationTime} months - - )) + .sort((year1, year2) => year2.year - year1.year) + .map(year => ( + + {year.year} + {Math.floor(year.credits)} + {thesisTypes.includes('BACHELOR') && ( + {year.bThesis} + )} + {thesisTypes.includes('MASTER') && ( + {year.mThesis} + )} + {year.graduated} + {year.creditsForPercentage} + + )) : null} @@ -89,7 +88,8 @@ ProductivityTable.propTypes = { credits: number, mThesis: number, bThesis: number, - graduated: number + graduated: number, + creditsForPercentage: number })) }), thesis: arrayOf(shape({ diff --git a/services/oodikone2-frontend/src/components/StudyProgramme/ThroughputTable/index.jsx b/services/oodikone2-frontend/src/components/StudyProgramme/ThroughputTable/index.jsx index e1ebf005be..f28274bae3 100644 --- a/services/oodikone2-frontend/src/components/StudyProgramme/ThroughputTable/index.jsx +++ b/services/oodikone2-frontend/src/components/StudyProgramme/ThroughputTable/index.jsx @@ -67,7 +67,7 @@ const ThroughputTable = ({ history, throughput, thesis, loading, error, studypro Students : Students } - Graduated + Graduated Transferred to this program { @@ -88,6 +88,8 @@ const ThroughputTable = ({ history, throughput, thesis, loading, error, studypro {genders.map(gender => )} Graduated overall Graduated in time + Graduation median time + {renderCountries ? countries.map(country => ) : null} @@ -121,6 +123,7 @@ const ThroughputTable = ({ history, throughput, thesis, loading, error, studypro ))} {year.graduated} {year.inTargetTime} + {year.medianGraduationTime ? `${year.medianGraduationTime} months` : '∞'} {year.transferred} {renderCountries ? countries.map(country => ( @@ -152,6 +155,7 @@ const ThroughputTable = ({ history, throughput, thesis, loading, error, studypro ))} {throughput.totals.graduated} {throughput.totals.inTargetTime} + {throughput.totals.medianGraduationTime ? `${throughput.totals.medianGraduationTime} months` : '∞'} {throughput.totals.transferred} {renderCountries ? Object.keys(throughput.totals.countries).map(countryKey => (