Merge pull request #1061 from UniversityOfHelsinkiCS/trunk

Student home_country to db, CLI works and other lies
This commit is contained in:
Tuomo Torppa
2019-07-01 14:03:00 +03:00
committed by GitHub
19 changed files with 196 additions and 71 deletions
+2
View File
@@ -110,8 +110,10 @@ reset_real_db () {
docker-compose up -d db user_db
ping_psql "oodi_db" "tkt_oodi_real"
docker exec -u postgres oodi_db dropdb "tkt_oodi_real"
docker exec -u postgres oodi_db psql -c "CREATE DATABASE tkt_oodi_real"
ping_psql "oodi_user_db" "user_db_real"
docker exec -u postgres oodi_user_db dropdb "user_db_real"
docker exec -u postgres oodi_user_db psql -c "CREATE DATABASE user_db_real"
db_setup_full
docker-compose down
}
@@ -7,7 +7,7 @@ const StudyrightService = require('../services/studyrights')
// POST instead of GET because of too long params and "sensitive" data
router.post('/v2/populationstatistics/courses', async (req, res) => {
try {
if (!req.body.year || !req.body.semesters || !req.body.studyRights) {
if (!req.body.startYear || !req.body.semesters || !req.body.studyRights) {
res.status(400).json({ error: 'The body should have a year, semester and study rights defined' })
return
}
@@ -32,9 +32,9 @@ router.post('/v2/populationstatistics/courses', async (req, res) => {
})
router.get('/v3/populationstatistics', async (req, res) => {
const { year, semesters, studyRights: studyRightsJSON } = req.query
const { startYear, semesters, studyRights: studyRightsJSON } = req.query
try {
if (!year || !semesters || !studyRightsJSON) {
if (!startYear || !semesters || !studyRightsJSON) {
res.status(400).json({ error: 'The query should have a year, semester and studyRights defined' })
return
}
@@ -108,11 +108,6 @@ const formatStudentForPopulationStatistics = ({
const dateMonthsFromNow = (date, months) => moment(date).add(months, 'months').format('YYYY-MM-DD')
const getStudentsIncludeCoursesBetween = async (studentnumbers, startDate, endDate, studyright, tag) => {
const tagQuery = tag ? {
tag_id: {
[Op.eq]: tag
}
} : null
const creditsOfStudentOther = {
student_studentnumber: {
@@ -222,7 +217,6 @@ const getStudentsIncludeCoursesBetween = async (studentnumbers, startDate, endDa
{
model: TagStudent,
attributes: ['id'],
where: tagQuery,
include: [
{
model: Tag,
@@ -236,8 +230,20 @@ const getStudentsIncludeCoursesBetween = async (studentnumbers, startDate, endDa
studentnumber: {
[Op.in]: studentnumbers
}
}
},
})
if (tag) {
const studentsWithSearchedTag = {}
students.forEach(student => {
if (student.tag_students.some(t => t.tag.tag_id === tag)) {
studentsWithSearchedTag[student.studentnumber] = true
}
})
return students.filter(student => studentsWithSearchedTag[student.studentnumber])
}
return students
}
@@ -308,13 +314,13 @@ const studentnumbersWithAllStudyrightElements = async (studyRights, startDate, e
}
const parseQueryParams = query => {
const { semesters, studentStatuses, studyRights, months, year, tagYear } = query
const { semesters, studentStatuses, studyRights, months, endYear, startYear } = query
const startDate = semesters.includes('FALL') ?
`${tagYear}-${semesterStart[semesters.find(s => s === 'FALL')]}` :
`${moment(tagYear, 'YYYY').add(1, 'years').format('YYYY')}-${semesterStart[semesters.find(s => s === 'SPRING')]}`
`${startYear}-${semesterStart[semesters.find(s => s === 'FALL')]}` :
`${moment(startYear, 'YYYY').add(1, 'years').format('YYYY')}-${semesterStart[semesters.find(s => s === 'SPRING')]}`
const endDate = semesters.includes('SPRING') ?
`${moment(year, 'YYYY').add(1, 'years').format('YYYY')}-${semesterEnd[semesters.find(s => s === 'SPRING')]}` :
`${year}-${semesterEnd[semesters.find(s => s === 'FALL')]}`
`${moment(endYear, 'YYYY').add(1, 'years').format('YYYY')}-${semesterEnd[semesters.find(s => s === 'SPRING')]}` :
`${endYear}-${semesterEnd[semesters.find(s => s === 'FALL')]}`
const exchangeStudents = studentStatuses && studentStatuses.includes('EXCHANGE')
const cancelledStudents = studentStatuses && studentStatuses.includes('CANCELLED')
const nondegreeStudents = studentStatuses && studentStatuses.includes('NONDEGREE')
@@ -422,7 +428,6 @@ const optimizedStatisticsOf = async (query) => {
) {
return { error: 'Student status should be either CANCELLED or EXCHANGE or NONDEGREE' }
}
const {
studyRights, startDate, months, endDate, exchangeStudents, cancelledStudents, nondegreeStudents
} = parseQueryParams(query)
@@ -550,7 +555,7 @@ const bottlenecksOf = async (query) => {
course.credits.forEach(credit => {
const { studentnumber, passingGrade, improvedGrade, failingGrade, grade, date } = parseCreditInfo(credit)
const semester = getPassingSemester(parseInt(query.year, 10), date)
const semester = getPassingSemester(parseInt(query.endYear, 10), date)
coursestats.markCredit(studentnumber, grade, passingGrade, failingGrade, improvedGrade, semester)
})
@@ -58,8 +58,8 @@ const semesters = {
const createQueryObject = (year, semester, codes, months) => ({
studyRights: codes,
year,
tagYear: year,
endYear: year,
startYear: year,
semesters: [semester],
months
})
@@ -0,0 +1,12 @@
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('student', 'home_country_en', { type: Sequelize.STRING })
await queryInterface.addColumn('student', 'home_country_fi', { type: Sequelize.STRING })
await queryInterface.addColumn('student', 'home_country_sv', { type: Sequelize.STRING })
},
down: async () => {
await queryInterface.removeColumn('student', 'home_country_en')
await queryInterface.removeColumn('student', 'home_country_fi')
await queryInterface.removeColumn('student', 'home_country_sv')
}
}
@@ -0,0 +1,9 @@
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn('student', 'country')
},
down: async () => {
await queryInterface.addColumn('student', 'country', { type: Sequelize.STRING })
}
}
+3 -1
View File
@@ -193,7 +193,6 @@ const Student = sequelize.define('student',
abbreviatedname: { type: Sequelize.STRING },
birthdate: { type: Sequelize.DATE },
communicationlanguage: { type: Sequelize.STRING },
country: { type: Sequelize.STRING },
creditcount: { type: Sequelize.INTEGER },
dateofuniversityenrollment: { type: Sequelize.DATE },
matriculationexamination: { type: Sequelize.STRING },
@@ -214,6 +213,9 @@ const Student = sequelize.define('student',
country_fi: { type: Sequelize.STRING },
country_sv: { type: Sequelize.STRING },
country_en: { type: Sequelize.STRING },
home_country_fi: { type: Sequelize.STRING },
home_country_sv: { type: Sequelize.STRING },
home_country_en: { type: Sequelize.STRING },
gender_code: { type: Sequelize.INTEGER },
gender_fi: { type: Sequelize.STRING },
gender_sv: { type: Sequelize.STRING },
@@ -0,0 +1,55 @@
import React, { useEffect } from 'react'
import { withRouter } from 'react-router'
import { connect } from 'react-redux'
import { string, func, arrayOf, shape } from 'prop-types'
import { getFaculties } from '../../../redux/faculties'
import { getTextIn } from '../../../common'
import SortableTable from '../../SortableTable'
const FacultySelector = ({ language, handleSelect, dispatchGetFaculties, faculties }) => {
const fetchFaculties = async () => {
await dispatchGetFaculties()
}
useEffect(() => {
fetchFaculties()
}, [])
if (!faculties) return null
const headers = [
{
key: 'name',
title: 'name',
getRowVal: faculty => getTextIn(faculty.name, language)
},
{
key: 'code',
title: 'code',
getRowVal: faculty => faculty.code
}
]
return (
<SortableTable
columns={headers}
getRowKey={faculty => faculty.code}
getRowProps={faculty => ({ onClick: () => handleSelect(faculty.code), style: { cursor: 'pointer' } })}
data={faculties}
/>
)
}
FacultySelector.propTypes = {
language: string.isRequired,
handleSelect: func.isRequired,
dispatchGetFaculties: func.isRequired,
faculties: arrayOf(shape({})).isRequired
}
const mapStateToProps = ({ faculties, settings }) => ({
faculties: faculties.data,
language: settings.language
})
export default connect(mapStateToProps, { dispatchGetFaculties: getFaculties })(withRouter(FacultySelector))
@@ -0,0 +1,18 @@
import React from 'react'
import { withRouter } from 'react-router'
import { Header, Segment } from 'semantic-ui-react'
import FacultySelector from './FacultySelector'
const Faculty = () => (
<div className="segmentContainer">
<Header className="segmentTitle" size="large">
Faculties
</Header>
<Segment className="contentSegment" >
<FacultySelector />
</Segment>
</div>
)
export default withRouter(Faculty)
@@ -191,12 +191,11 @@ class PopulationCourseStats extends Component {
clearCourseStats: clearCourseStatsfn,
years
} = this.props
const yearCode = year => Object.values(years).find(yearObject =>
yearObject.yearname.slice(0, 4).includes(year)).yearcode
const { year, months } = query
const fromYear = yearCode(year)
const toYear = yearCode(moment(moment(year, 'YYYY').add(months, 'months')).format('YYYY'))
const { startYear, months } = query
const fromYear = yearCode(startYear)
const toYear = yearCode(moment(moment(startYear, 'YYYY').add(months, 'months')).format('YYYY'))
history.push('/coursestatistics/')
clearCourseStatsfn()
getStatsFn({
@@ -23,7 +23,7 @@ const PopulationQueryCard =
history.push('/populations')
removeSampleFn(uuid)
}
const { uuid, year, semesters, months, studentStatuses } = query
const { uuid, startYear, semesters, months, studentStatuses } = query
const { students } = population
if (students.length > 0) {
return (
@@ -40,7 +40,7 @@ const PopulationQueryCard =
<div className="dateItem">
<Icon name="calendar" size="small" />
{`${semesters.map(s => translate(`populationStatistics.${s}`))}/
${year}-${Number(year) + 1}, showing ${months} months.`}
${startYear}-${Number(startYear) + 1}, showing ${months} months.`}
</div>
<div>
{`${translate('populationStatistics.sampleSize', { amount: students.length })} `}
@@ -48,11 +48,11 @@ const PopulationQueryCard =
<div>
{`Updated at ${reformatDate(_.minBy(students, 'updatedAt').updatedAt, DISPLAY_DATE_FORMAT)} `}
</div>
<div>{studentStatuses.includes('EXCHANGE') ? 'Includes' : 'Excludes' } exchange students</div>
<div>{studentStatuses.includes('CANCELLED') ? 'Includes ' : 'Excludes ' }
<div>{studentStatuses.includes('EXCHANGE') ? 'Includes' : 'Excludes'} exchange students</div>
<div>{studentStatuses.includes('CANCELLED') ? 'Includes ' : 'Excludes '}
students with cancelled study right
</div>
<div>{studentStatuses.includes('NONDEGREE') ? 'Includes ' : 'Excludes ' }
<div>{studentStatuses.includes('NONDEGREE') ? 'Includes ' : 'Excludes '}
students with non-degree study right
</div>
{updating ?
@@ -83,7 +83,7 @@ const PopulationQueryCard =
<Card.Meta>
<div className="dateItem">
<Icon name="calendar" size="small" />
{`${semesters.map(s => translate(`populationStatistics.${s}`))}/${year}-${Number(year) + 1},
{`${semesters.map(s => translate(`populationStatistics.${s}`))}/${startYear}-${Number(startYear) + 1},
showing ${months} months.`}
</div>
<div>
@@ -109,7 +109,7 @@ PopulationQueryCard.propTypes = {
translate: func.isRequired,
population: shape({ students: arrayOf(object), extents: arrayOf(object) }).isRequired,
query: shape({
year: oneOfType([string, number]),
startYear: oneOfType([string, number]),
semester: string,
studyRights: shape({ programme: string, degree: string, studyTrack: string }),
uuid: string
@@ -120,7 +120,6 @@ class PopulationSearchForm extends Component {
const query = {
...initial,
...rest,
tagYear: rest.year,
studyRights: JSON.parse(studyRights),
months: JSON.parse(months)
}
@@ -162,7 +161,6 @@ class PopulationSearchForm extends Component {
handleYearSelection = (momentYear) => {
const { query } = this.state
const { studyProgrammes } = this.props
if (!moment.isMoment(momentYear)) {
this.setState({
momentYear: null,
@@ -200,13 +198,12 @@ class PopulationSearchForm extends Component {
}
}
}
this.setState({
momentYear,
query: {
...query,
year: reformatDate(momentYear, YEAR_DATE_FORMAT),
tagYear: reformatDate(momentYear, YEAR_DATE_FORMAT),
endYear: reformatDate(momentYear, YEAR_DATE_FORMAT),
startYear: reformatDate(momentYear, YEAR_DATE_FORMAT),
months: this.months(
reformatDate(momentYear, YEAR_DATE_FORMAT),
this.state.query.semesters.includes('FALL') ? 'FALL' : 'SPRING'
@@ -221,28 +218,33 @@ class PopulationSearchForm extends Component {
}
handleTagSearch = (event, { value }) => {
const { query } = this.state
const months = this.getMonths('2015', new Date(), 'FALL')
this.setState({
query: {
...query,
year: '2018',
tagYear: '2015',
months
},
selectedTag: value
})
}
handleTagYearSelect = (momentYear) => {
const { query } = this.state
const months = this.getMonths(reformatDate(momentYear, YEAR_DATE_FORMAT), new Date(), 'FALL')
this.setState({
query: {
...query,
startYear: reformatDate(momentYear, YEAR_DATE_FORMAT),
endYear: reformatDate(new Date(), YEAR_DATE_FORMAT),
months
}
})
}
addYear = () => {
const { year } = this.state.query
const nextYear = momentFromFormat(year, YEAR_DATE_FORMAT).add(1, 'year')
const { startYear } = this.state.query
const nextYear = momentFromFormat(startYear, YEAR_DATE_FORMAT).add(1, 'year')
this.handleYearSelection(nextYear)
}
subtractYear = () => {
const { year } = this.state.query
const previousYear = momentFromFormat(year, YEAR_DATE_FORMAT).subtract(1, 'year')
const { startYear } = this.state.query
const previousYear = momentFromFormat(startYear, YEAR_DATE_FORMAT).subtract(1, 'year')
this.handleYearSelection(previousYear)
}
@@ -254,7 +256,7 @@ class PopulationSearchForm extends Component {
query: {
...query,
semesters,
months: this.months(this.state.query.year, semesters.includes('FALL') ? 'FALL' : 'SPRING')
months: this.months(this.state.query.startYear, semesters.includes('FALL') ? 'FALL' : 'SPRING')
}
})
}
@@ -327,7 +329,7 @@ class PopulationSearchForm extends Component {
handleMonthsChange = (value) => {
const { query } = this.state
const months = this.getMonths(query.year, value, this.state.query.semesters.includes('FALL') ? 'FALL' : 'SPRING')
const months = this.getMonths(query.startYear, value, this.state.query.semesters.includes('FALL') ? 'FALL' : 'SPRING')
this.setState({
query: {
...query,
@@ -349,17 +351,17 @@ class PopulationSearchForm extends Component {
})
}
getMonths = (year, end, term) => {
getMonths = (startYear, end, term) => {
const lastDayOfMonth = moment(end).endOf('month')
const start = term === 'FALL' ? `${year}-08-01` : `${year}-01-01`
const start = term === 'FALL' ? `${startYear}-08-01` : `${startYear}-01-01`
this.setState({
floatMonths: moment.duration(moment(lastDayOfMonth).diff(moment(start))).asMonths()
})
return Math.round(moment.duration(moment(lastDayOfMonth).diff(moment(start))).asMonths())
}
getMonthValue = (year, months) => {
const start = `${year}-08-01`
getMonthValue = (startYear, months) => {
const start = `${startYear}-08-01`
return moment(start).add(months - 1, 'months').format('MMMM YY')
}
@@ -373,7 +375,7 @@ class PopulationSearchForm extends Component {
return this.props.studyProgrammes[this.state.query.studyRights.programme].enrollmentStartYears[momentYear.year()] != null
}
getMinSelection = (year, semester) => (semester === 'FALL' ? `${year}-08-01` : `${year}-01-01`)
getMinSelection = (startYear, semester) => (semester === 'FALL' ? `${startYear}-08-01` : `${startYear}-01-01`)
fetchPopulationFromUrlParams() {
const query = this.parseQueryFromUrl()
@@ -382,8 +384,8 @@ class PopulationSearchForm extends Component {
}
initialQuery = () => ({
year: Datetime.moment('2017-01-01').year(),
tagYear: Datetime.moment('2017-01-01').year(),
endYear: Datetime.moment('2017-01-01').year(),
startYear: Datetime.moment('2017-01-01').year(),
semesters: ['FALL', 'SPRING'],
studentStatuses: [],
studyRights: [],
@@ -404,7 +406,7 @@ class PopulationSearchForm extends Component {
renderEnrollmentDateSelector = () => {
const { query, momentYear } = this.state
const { semesters, year } = query
const { semesters, startYear } = query
return (
<Form.Group key="year" className="enrollmentSelectorGroup">
<Form.Field error={!this.validYearCheck(momentYear)} className="yearSelect">
@@ -416,7 +418,7 @@ class PopulationSearchForm extends Component {
timeFormat={false}
renderYear={(props, selectableYear) => <td {...props}>{`${selectableYear}-${selectableYear + 1}`}</td>}
closeOnSelect
value={`${year}-${moment().year(year).add(1, 'years').format('YYYY')}`}
value={`${startYear}-${moment().year(startYear).add(1, 'years').format('YYYY')}`}
isValidDate={this.validYearCheck}
onChange={this.handleYearSelection}
/>
@@ -431,10 +433,10 @@ class PopulationSearchForm extends Component {
<label>Statistics until</label>
<Datetime
dateFormat="MMMM YYYY"
defaultValue={this.getMonthValue(this.state.query.year, this.state.floatMonths)}
defaultValue={this.getMonthValue(this.state.query.startYear, this.state.floatMonths)}
onChange={value => this.handleMonthsChange(value)}
isValidDate={current => current.isBefore(moment()) &&
current.isAfter(this.getMinSelection(year, semesters[1] || semesters[0]))}
current.isAfter(this.getMinSelection(startYear, semesters[1] || semesters[0]))}
/>
</Form.Field>
</Form.Group>
@@ -562,7 +564,7 @@ class PopulationSearchForm extends Component {
const { translate, tags } = this.props
const { query } = this.state
const { semesters, studentStatuses } = query
const { semesters, studentStatuses, startYear } = query
const options = this.state.isAdmin ? tags.map(tag => ({ key: tag.tag_id, text: tag.tagname, value: tag.tag_id })) : null
return (
@@ -629,6 +631,17 @@ class PopulationSearchForm extends Component {
options={options}
onChange={this.handleTagSearch}
/>
<Datetime
className="yearSelectInput"
control={Datetime}
dateFormat={YEAR_DATE_FORMAT}
timeFormat={false}
renderYear={(props, selectableYear) => <td {...props}>{`${selectableYear}-${selectableYear + 1}`}</td>}
closeOnSelect
value={`${startYear}-${moment().year(startYear).add(1, 'years').format('YYYY')}`}
isValidDate={this.validYearCheck}
onChange={this.handleTagYearSelect}
/>
<Button onClick={() => this.fetchPopulation(this.state.query, this.state.selectedTag)}> Search by tag</Button>
</div>) : null}
</Form.Field>
@@ -15,6 +15,7 @@ const Sandbox = React.lazy(() => import('../Sandbox'))
const UsageStatistics = React.lazy(() => import('../UsageStatistics'))
const OodiLearn = React.lazy(() => import('../OodiLearn'))
const Feedback = React.lazy(() => import('../Feedback'))
const Faculty = React.lazy(() => import('../Faculty'))
const Routes = () => (
<Suspense fallback={<Loader active inline="centered" />}>
@@ -25,6 +26,7 @@ const Routes = () => (
<Route exact path="/study-programme/:studyProgrammeId/course-group/:courseGroupId" component={StudyProgramme} />
<Route exact path={routes.students.route} component={StudentStatistics} />
<Route exact path={routes.courseStatistics.route} component={CourseStatistics} />
<Route exact path={routes.faculty.route} component={Faculty} />
<Route exact path={routes.settings.route} component={Settings} />
<Route exact path={routes.users.route} component={EnableUsers} />
<Route exact path={routes.teachers.route} component={Teachers} />
@@ -22,7 +22,7 @@ const ThroughputTable = ({ history, throughput, thesis, loading, error, studypro
const year = Number(yearLabel.slice(0, 4))
const months = Math.ceil(moment.duration(moment().diff(`${year}-08-01`)).asMonths())
history.push(`/populations?months=${months}&semesters=FALL&semesters=` +
`SPRING&studyRights=%7B"programme"%3A"${studyprogramme}"%7D&year=${year}`)
`SPRING&studyRights=%7B"programme"%3A"${studyprogramme}"%7D&startYear=${year}&endYear=${year}`)
}
if (error) return <h1>Oh no so error {error}</h1>
let GRADUATED_FEATURE_TOGGLED_ON = false
@@ -59,6 +59,7 @@ export const routes = {
courseStatistics: { menuRoute: '/coursestatistics', route: '/coursestatistics', translateId: 'courseStatistics' },
teachers: { menuRoute: '/teachers', route: '/teachers/:teacherid?', translateId: 'teachers', reqRights: ['teachers'] },
users: { menuRoute: '/users', route: '/users/:userid?', translateId: 'users', reqRights: ['users'] },
faculty: { menuRoute: '/faculties', route: '/faculties/:facultyid?', translateId: 'faculty', reqRights: ['dev'] },
settings: { menuRoute: '/settings', route: '/settings', translateId: 'settings', reqRights: ['dev'] },
usage: { menuRoute: '/usage', route: '/usage', translateId: 'usage', reqRights: ['usage'] },
sandbox: { menuRoute: '/sandbox', route: '/sandbox', translateId: 'sandbox', reqRights: ['dev'] },
@@ -90,6 +90,9 @@
"courseGroups": [
"Course groups"
],
"faculty": [
"Faculty"
],
"class": [
"Search by class"
],
@@ -1,21 +1,21 @@
import { callController } from '../apiConnection'
export const getPopulationCourses = ({
year, semesters, studentStatuses, studyRights, months, uuid, selectedStudents, tagYear
endYear, semesters, studentStatuses, studyRights, months, uuid, selectedStudents, startYear
}) => {
const route = '/v2/populationstatistics/courses'
const prefix = 'GET_POPULATION_COURSES_'
const query = {
year, semesters, studentStatuses, studyRights, uuid, selectedStudents, months, tagYear
endYear, semesters, studentStatuses, studyRights, uuid, selectedStudents, months, startYear
}
const body = {
year,
endYear,
semesters,
studentStatuses,
months,
studyRights,
selectedStudents,
tagYear
startYear
}
return callController(route, prefix, body, 'post', query)
}
@@ -9,21 +9,21 @@ const initialState = {
}
export const getPopulationStatistics = ({
year, semesters, studentStatuses, studyRights, months, uuid, tag, tagYear
endYear, semesters, studentStatuses, studyRights, months, uuid, tag, startYear
}) => {
const route = '/v3/populationstatistics/'
const prefix = 'GET_POPULATION_STATISTICS_'
const query = {
year, semesters, studentStatuses, studyRights, uuid, months, tag, tagYear
endYear, semesters, studentStatuses, studyRights, uuid, months, tag, startYear
}
const params = {
year,
endYear,
semesters,
studentStatuses,
months,
studyRights,
tag,
tagYear
startYear
}
return callController(route, prefix, null, 'get', query, params)
}
@@ -30,6 +30,7 @@ const getStudentFromData = (student, studyrights) => {
const language = getTextsByLanguage(student.language)
const country = getTextsByLanguage(student.country)
const gender = getTextsByLanguage(student.gender)
const home_country = getTextsByLanguage(student.home_country)
return {
studentnumber: student.student_number,
email: student.email,
@@ -52,6 +53,9 @@ const getStudentFromData = (student, studyrights) => {
country_fi: country.fi,
country_sv: country.sv,
country_en: country.en,
home_country_fi: home_country.fi,
home_country_sv: home_country.sv,
home_country_en: home_country.en,
firstnames: student.first_names,
communicationlanguage: language.en || language.fi || language.sv,
dateofuniversityenrollment: universityEnrollmentDateFromStudyRights(studyrights),