{-# LANGUAGE CPP, FlexibleContexts #-}
module WeekDaze.Aggregate.TeacherRegister(
TeacherRegister,
CoursesByTeacherId,
CoursesByTeacherIdBySynchronisationId,
NTimeslotsByTeacherIdBySubject,
tag,
calculateWorkloadBoundsBySubject,
countAvailableTeacherDays,
extractDistinctCourses,
extractDistinctOwnLocationIds,
extractDistinctSubjects,
findSuitableCourseByTeacherId,
findCoursesByTeacherIdBySynchronisationId,
findDistinctCoursesBySynchronisationId,
findSpecifiedTimes,
findSubjectsOfferedInMultipleCoursesRequiringDifferentLessonsPerWeek,
mergeConstraintsOnSynchronisedCourses,
countLessonsPerWeekByFacilityName,
getTeacherIds,
getTeacherProfiles,
#ifdef USE_HDBC
fromDatabase,
#endif
hasAnyFreePeriodPreference,
hasAnySynchronisedCourses,
hasAnyIdealTimeslotRequests,
hasAnyCourseMaximumClassSizes,
hasAnySpecificTimeRequests,
hasAnyTimeslotRequests,
hasAnySpecialists,
isInhabited
) where
import qualified Control.Arrow
import Control.Arrow((&&&), (***))
import qualified Data.Foldable
import qualified Data.Map
import Data.Map((!))
import qualified Data.Maybe
import qualified Data.Set
import qualified WeekDaze.Data.Course as Data.Course
import qualified WeekDaze.Data.HumanResource as Data.HumanResource
import qualified WeekDaze.Data.Location as Data.Location
import qualified WeekDaze.Data.Resource as Data.Resource
import qualified WeekDaze.Data.Subject as Data.Subject
import qualified WeekDaze.Data.Teacher as Data.Teacher
import qualified WeekDaze.Size as Size
import qualified WeekDaze.Temporal.Time as Temporal.Time
import qualified WeekDaze.Temporal.TimeslotRequest as Temporal.TimeslotRequest
import qualified WeekDaze.Temporal.Workload as Temporal.Workload
#ifdef USE_HDBC
import qualified Database.HDBC
import qualified Data.Convertible
import qualified Data.IntMap
import qualified Data.Typeable
import qualified WeekDaze.Database.Selector as Database.Selector
import qualified WeekDaze.Data.Group as Data.Group
import qualified WeekDaze.Temporal.Availability as Temporal.Availability
import qualified WeekDaze.Temporal.Day as Temporal.Day
import qualified WeekDaze.Temporal.FreePeriodPreference as Temporal.FreePeriodPreference
fromDatabase :: (
Database.HDBC.IConnection connection,
Data.Convertible.Convertible Database.HDBC.SqlValue level,
Data.Convertible.Convertible Database.HDBC.SqlValue locationId,
Data.Convertible.Convertible Database.HDBC.SqlValue synchronisationId,
Data.Convertible.Convertible Database.HDBC.SqlValue teacherId,
Data.Convertible.Convertible Database.HDBC.SqlValue teachingRatio,
Data.Convertible.Convertible Database.HDBC.SqlValue timeslotId,
Data.Typeable.Typeable teachingRatio,
Ord level,
Ord synchronisationId,
Ord teacherId,
Ord timeslotId,
RealFrac teachingRatio,
Show level,
Show synchronisationId,
Show timeslotId
)
=> connection
-> Database.HDBC.SqlValue
-> IO (TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio)
fromDatabase connection projectIdSql = let
idealTimeslotRequestColumnName, teacherRegisterIdColumnName :: Database.Selector.ColumnName
idealTimeslotRequestColumnName = "idealTimeslotRequest"
teacherRegisterIdColumnName = showString tag "Id";
requiredFacilityTableName, serviceTableName, specialtyTopicTableName, specificTimeRequestsTableName, teacherGroupMembershipTableName, teacherRegisterTableName :: Database.Selector.TableName
[requiredFacilityTableName, serviceTableName, specialtyTopicTableName, specificTimeRequestsTableName, teacherGroupMembershipTableName, teacherRegisterTableName] = map (showString Database.Selector.tablePrefix) ["requiredFacility", Data.Teacher.serviceTag, Data.Teacher.specialtyTopicTag, "specificTimeRequest", "teacherGroupMembership", tag]
in do
facilityNameByFacilityTypeId <- Data.Location.findFacilityNameByFacilityTypeId connection projectIdSql
#ifdef USE_HDBC_ODBC
[
selectRequiredFacilitiesForTeacherRegisterId,
selectSpecificTimeRequestsForTeacherRegisterId,
selectServiceForTeacherRegisterId,
selectGroupIdsForTeacherRegisterId,
selectSpecialtyTopicForTeacherRegisterId
] <- mapM (
\(columnNames, tableName) -> Database.Selector.prepare connection columnNames [tableName] [teacherRegisterIdColumnName]
) [
(
[
Data.Subject.topicTag,
Data.Subject.levelTag,
Data.Location.facilityTypeIdTag
],
requiredFacilityTableName
), (
[
Data.Subject.topicTag,
Data.Subject.levelTag,
Temporal.Day.tag,
Database.Selector.timeslotIdColumnName
],
specificTimeRequestsTableName
), (
[
Data.Subject.topicTag,
Data.Subject.levelTag,
Data.Course.requiredLessonsPerWeekTag,
Data.Course.minimumConsecutiveLessonsTag,
Data.Course.maximumClassSizeTag,
Database.Selector.synchronisationIdColumnName,
idealTimeslotRequestColumnName
],
serviceTableName
), (
[Data.Group.groupIdTag],
teacherGroupMembershipTableName
), (
[Data.Subject.topicTag],
specialtyTopicTableName
)
]
#endif /* USE_HDBC_ODBC */
Database.Selector.select connection [
teacherRegisterIdColumnName,
Database.Selector.teacherIdColumnName,
Temporal.Availability.tag,
Database.Selector.locationIdColumnName,
Data.Teacher.maximumTeachingRatioTag,
Temporal.FreePeriodPreference.tag
] [teacherRegisterTableName] [(Database.Selector.projectIdColumnName, projectIdSql)] >>= fmap Data.Map.fromList . mapM (
\teacherRow -> case teacherRow of
[
teacherRegisterIdSql,
teacherIdSql,
availabilitySql,
locationIdSql,
maximumTeachingRatioSql,
freePeriodPreferenceSql
] -> do
#ifndef USE_HDBC_ODBC
let primaryKey = [(teacherRegisterIdColumnName, teacherRegisterIdSql)]
#endif
requiredFacilityNamesBySubject <- (
Data.Map.fromListWith Data.Set.union . map (
\requiredFacilityRow -> case requiredFacilityRow of
[topicSql, levelSql, facilityTypeIdSql] -> let
facilityTypeId = Data.Maybe.fromMaybe (
error . showString "WeekDaze.Aggregate.TeacherRegister.fromDatabase:\tnull " $ shows Data.Location.facilityTypeIdTag "."
) . either (
error . showString "WeekDaze.Aggregate.TeacherRegister.fromDatabase:\tfailed to parse the value for " . shows Data.Location.facilityTypeIdTag . showString " read from the database; " . show
) id $ Database.HDBC.safeFromSql facilityTypeIdSql
in (
(,) (Data.Subject.mkSubjectFromSql topicSql levelSql) . Data.Set.singleton
) . Data.Maybe.fromMaybe (
error . showString "WeekDaze.Aggregate.TeacherRegister.fromDatabase:\tunknown " . showString Data.Location.facilityTypeIdTag . showChar '=' $ shows facilityTypeId "."
) $ Data.IntMap.lookup facilityTypeId facilityNameByFacilityTypeId
_ -> error . showString "WeekDaze.Aggregate.TeacherRegister.fromDatabase:\tunexpected number of columns=" . shows (length requiredFacilityRow) . showString " in row of table " $ shows requiredFacilityTableName "."
)
#ifdef USE_HDBC_ODBC
) `fmap` (
Database.HDBC.execute selectRequiredFacilitiesForTeacherRegisterId [teacherRegisterIdSql] >> Database.HDBC.fetchAllRows' selectRequiredFacilitiesForTeacherRegisterId
)
#else
) `fmap` Database.Selector.select connection [
Data.Subject.topicTag,
Data.Subject.levelTag,
Data.Location.facilityTypeIdTag
] [requiredFacilityTableName] primaryKey
#endif
specificTimeRequestsBySubject <- (
Data.Map.fromListWith Data.Set.union . map (
\specificTimeRow -> case specificTimeRow of
[topic, level, day, timeslotId] -> (
Data.Subject.mkSubjectFromSql topic level,
Data.Set.singleton $ Temporal.Time.mkTimeFromSql day timeslotId
)
_ -> error . showString "WeekDaze.Aggregate.TeacherRegister.fromDatabase:\tunexpected number of columns=" . shows (length specificTimeRow) . showString " in row of table " $ shows specificTimeRequestsTableName "."
)
#ifdef USE_HDBC_ODBC
) `fmap` (
Database.HDBC.execute selectSpecificTimeRequestsForTeacherRegisterId [teacherRegisterIdSql] >> Database.HDBC.fetchAllRows' selectSpecificTimeRequestsForTeacherRegisterId
)
#else
) `fmap` Database.Selector.select connection [
Data.Subject.topicTag,
Data.Subject.levelTag,
Temporal.Day.tag,
Database.Selector.timeslotIdColumnName
] [specificTimeRequestsTableName] primaryKey
#endif
service <- (
Data.Set.fromList . map (
\courseRow -> case courseRow of
[topicSql, levelSql, requiredLessonsPerWeekSql, minimumConsecutiveLessonsSql, maximumClassSizeSql, synchronisationIdSql, idealTimeslotRequestSql] -> let
subject = Data.Subject.mkSubjectFromSql topicSql levelSql
in Data.Course.mkCourse subject (
Data.Maybe.fromMaybe (
error . showString "WeekDaze.Aggregate.TeacherRegister.fromDatabase:\tnull " $ shows Data.Course.requiredLessonsPerWeekTag "."
) . either (
error . showString "WeekDaze.Aggregate.TeacherRegister.fromDatabase:\tfailed to parse the value for " . shows Data.Course.requiredLessonsPerWeekTag . showString " read from the database; " . show
) id $ Database.HDBC.safeFromSql requiredLessonsPerWeekSql
) (
Data.Maybe.fromMaybe Data.Set.empty $ Data.Map.lookup subject requiredFacilityNamesBySubject
) (
Data.Maybe.maybe (
Temporal.TimeslotRequest.Specifically . Data.Maybe.fromMaybe Data.Set.empty $ Data.Map.lookup subject specificTimeRequestsBySubject
) Temporal.TimeslotRequest.Ideally $ Database.HDBC.fromSql idealTimeslotRequestSql
) (
Data.Maybe.fromMaybe Data.Course.defaultMinimumConsecutiveLessons . either (
error . showString "WeekDaze.Aggregate.TeacherRegister.fromDatabase:\tfailed to parse the value for " . shows Data.Course.minimumConsecutiveLessonsTag . showString " read from the database; " . show
) id $ Database.HDBC.safeFromSql minimumConsecutiveLessonsSql
) (
either (
error . showString "WeekDaze.Aggregate.TeacherRegister.fromDatabase:\tfailed to parse the value for " . shows Data.Course.maximumClassSizeTag . showString " read from the database; " . show
) id $ Database.HDBC.safeFromSql maximumClassSizeSql
) $ Database.HDBC.fromSql synchronisationIdSql
_ -> error . showString "WeekDaze.Aggregate.TeacherRegister.fromDatabase:\tunexpected number of columns=" . shows (length courseRow) . showString " in row of table " $ shows serviceTableName "."
)
#ifdef USE_HDBC_ODBC
) `fmap` (
Database.HDBC.execute selectServiceForTeacherRegisterId [teacherRegisterIdSql] >> Database.HDBC.fetchAllRows' selectServiceForTeacherRegisterId
)
#else
) `fmap` Database.Selector.select connection [
Data.Subject.topicTag,
Data.Subject.levelTag,
Data.Course.requiredLessonsPerWeekTag,
Data.Course.minimumConsecutiveLessonsTag,
Data.Course.maximumClassSizeTag,
Database.Selector.synchronisationIdColumnName,
idealTimeslotRequestColumnName
] [serviceTableName] primaryKey
#endif
groupMembership <- (
Data.Set.fromList . map (
Data.Maybe.fromMaybe (
error . showString "WeekDaze.Aggregate.TeacherRegister.fromDatabase:\tnull " $ shows Data.Group.groupIdTag "."
) . Database.HDBC.fromSql . head
)
#ifdef USE_HDBC_ODBC
) `fmap` (
Database.HDBC.execute selectGroupIdsForTeacherRegisterId [teacherRegisterIdSql] >> Database.HDBC.fetchAllRows' selectGroupIdsForTeacherRegisterId
)
#else
) `fmap` Database.Selector.select connection [Data.Group.groupIdTag] [teacherGroupMembershipTableName] primaryKey
#endif
maybeSpecialtyTopic <- (
Data.Maybe.listToMaybe . map (
Database.HDBC.fromSql . head
)
#ifdef USE_HDBC_ODBC
) `fmap` (
Database.HDBC.execute selectSpecialtyTopicForTeacherRegisterId [teacherRegisterIdSql] >> Database.HDBC.fetchAllRows' selectSpecialtyTopicForTeacherRegisterId
)
#else
) `fmap` Database.Selector.select connection [Data.Subject.topicTag] [specialtyTopicTableName] primaryKey
#endif
return (
Data.Maybe.fromMaybe (
error . showString "WeekDaze.Aggregate.TeacherRegister.fromDatabase:\tnull " $ shows Database.Selector.teacherIdColumnName "."
) $ Database.HDBC.fromSql teacherIdSql,
Data.Teacher.mkProfile service (
Database.HDBC.fromSql locationIdSql
) (
Data.Maybe.fromMaybe (
error . showString "WeekDaze.Aggregate.TeacherRegister.fromDatabase:\tnull " $ shows Temporal.Availability.tag "."
) $ Database.HDBC.fromSql availabilitySql
) (
Database.Selector.fromSqlFractional (
error . showString "WeekDaze.Aggregate.TeacherRegister.fromDatabase:\tnull " $ shows Data.Teacher.maximumTeachingRatioTag "."
) maximumTeachingRatioSql
) groupMembership maybeSpecialtyTopic $ Database.HDBC.fromSql freePeriodPreferenceSql
)
_ -> error . showString "WeekDaze.Aggregate.TeacherRegister.fromDatabase:\tunexpected number of columns=" . shows (length teacherRow) . showString " in row of table " $ shows teacherRegisterTableName "."
)
#endif /* USE_HDBC */
tag :: String
tag = "teacherRegister"
type TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio = Data.Resource.ResourceMap teacherId (Data.Teacher.Profile synchronisationId level timeslotId locationId teachingRatio)
getTeacherIds :: TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> [teacherId]
getTeacherIds = Data.Map.keys
getTeacherProfiles :: TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> [Data.Teacher.Profile synchronisationId level timeslotId locationId teachingRatio]
getTeacherProfiles = Data.Map.elems
extractDistinctCourses :: (
Ord level,
Ord synchronisationId,
Ord timeslotId
) => TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> Data.Teacher.Service synchronisationId level timeslotId
extractDistinctCourses = Data.Map.foldr (Data.Set.union . Data.Teacher.getService) Data.Set.empty
extractDistinctOwnLocationIds :: Ord locationId => TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> Data.Location.Locus locationId
extractDistinctOwnLocationIds = Data.Map.foldr (\profile locus -> Data.Maybe.maybe locus (`Data.Set.insert` locus) $ Data.Teacher.getMaybeOwnLocationId profile) Data.Set.empty
extractDistinctSubjects :: (
Ord level,
Ord synchronisationId,
Ord timeslotId
) => TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> Data.Subject.Knowledge level
extractDistinctSubjects = Data.Set.map Data.Course.getSubject . extractDistinctCourses
extractDistinctRequiredFacilityNames :: (
Ord level,
Ord synchronisationId,
Ord timeslotId
) => TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> Data.Location.FacilityNames
extractDistinctRequiredFacilityNames = Data.Set.foldr (Data.Set.union . Data.Course.getRequiredFacilityNames) Data.Set.empty . extractDistinctCourses
calculateWorkloadBoundsBySubject :: (
Ord level,
Ord synchronisationId,
Ord timeslotId
) => TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> Data.Map.Map (Data.Subject.Subject level) Temporal.Workload.Bounds
calculateWorkloadBoundsBySubject = Data.Map.map (minimum &&& maximum) . Data.Map.fromListWith (++) . map (Data.Course.getSubject &&& return . Data.Course.getRequiredLessonsPerWeek) . Data.Set.toList . extractDistinctCourses
findSuitableCourseByTeacherId
:: Eq level
=> Size.NStudents
-> Data.Subject.Subject level
-> TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio
-> Data.Map.Map teacherId (Data.Course.Course synchronisationId level timeslotId)
findSuitableCourseByTeacherId nStudents subject = Data.Map.mapMaybe (Data.Teacher.lookupSuitableCourse nStudents subject)
countAvailableTeacherDays :: TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> Size.NDays
countAvailableTeacherDays = Data.Map.foldr ((+) . Data.Resource.countDaysPerWeekAvailable) 0
countLessonsPerWeekByFacilityName :: TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> Data.Map.Map Data.Location.FacilityName Size.NTimeslots
countLessonsPerWeekByFacilityName = Data.Map.foldr (
\profile m -> Data.Set.foldr (
\course m' -> Data.Set.foldr (
Data.Map.insertWith (+) `flip` Data.Course.getRequiredLessonsPerWeek course
) m' $ Data.Course.getRequiredFacilityNames course
) m $ Data.Teacher.getService profile
) Data.Map.empty
isInhabited :: Eq locationId => locationId -> TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> Bool
isInhabited locationId = Data.Foldable.any (Data.Teacher.inhabits locationId)
type CoursesByTeacherId synchronisationId teacherId level timeslotId = Data.Map.Map teacherId (Data.Course.Course synchronisationId level timeslotId)
type CoursesByTeacherIdBySynchronisationId synchronisationId teacherId level timeslotId = Data.Map.Map synchronisationId (CoursesByTeacherId synchronisationId teacherId level timeslotId)
findCoursesByTeacherIdBySynchronisationId :: (
Ord level,
Ord synchronisationId,
Ord teacherId,
Ord timeslotId
) => TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> CoursesByTeacherIdBySynchronisationId synchronisationId teacherId level timeslotId
findCoursesByTeacherIdBySynchronisationId = Data.Map.foldrWithKey (
\teacherId -> flip $ Data.Set.foldr (
uncurry (Data.Map.insertWith Data.Map.union) . Control.Arrow.second (Data.Map.singleton teacherId)
)
) Data.Map.empty . Data.Map.map (
Data.Set.map (
Data.Maybe.fromJust . Data.Course.getMaybeSynchronisationId &&& id
) . Data.Set.filter Data.Course.isSynchronised . Data.Teacher.getService
)
findDistinctCoursesBySynchronisationId :: (
Ord level,
Ord synchronisationId,
Ord timeslotId
) => CoursesByTeacherIdBySynchronisationId synchronisationId teacherId level timeslotId -> Data.Map.Map synchronisationId (Data.Set.Set (Data.Course.Course synchronisationId level timeslotId))
findDistinctCoursesBySynchronisationId = Data.Map.map $ Data.Set.fromList . Data.Map.elems
hasAnyFreePeriodPreference :: RealFrac teachingRatio => TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> Bool
hasAnyFreePeriodPreference = Data.Foldable.any Data.HumanResource.hasFreePeriodPreference
hasAnySynchronisedCourses :: TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> Bool
hasAnySynchronisedCourses = Data.Foldable.any Data.Teacher.offersAnySynchronisedCourse
hasAnyIdealTimeslotRequests :: TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> Bool
hasAnyIdealTimeslotRequests = Data.Foldable.any Data.Teacher.hasAnyIdealTimeslotRequest
hasAnyCourseMaximumClassSizes :: (
Ord level,
Ord synchronisationId,
Ord timeslotId
) => TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> Bool
hasAnyCourseMaximumClassSizes = Data.Foldable.any (Data.Maybe.isJust . Data.Course.getMaybeMaximumClassSize) . extractDistinctCourses
hasAnySpecificTimeRequests :: TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> Bool
hasAnySpecificTimeRequests = Data.Foldable.any Data.Teacher.hasAnySpecificTimeRequest
hasAnyTimeslotRequests :: TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> Bool
hasAnyTimeslotRequests = Data.Foldable.any (uncurry (||) . (Data.Teacher.hasAnyIdealTimeslotRequest &&& Data.Teacher.hasAnySpecificTimeRequest))
hasAnySpecialists :: TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> Bool
hasAnySpecialists = Data.Foldable.any Data.Teacher.hasSpecialtyTopic
findSpecifiedTimes :: Ord timeslotId => TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> Temporal.Time.TimeSet timeslotId
findSpecifiedTimes = Data.Map.foldr (Data.Set.union . Data.Teacher.findSpecifiedTimes) Data.Set.empty
type NTimeslotsByTeacherIdBySubject level teacherId = Data.Map.Map (Data.Subject.Subject level) (Data.Map.Map teacherId Size.NTimeslots)
findSubjectsOfferedInMultipleCoursesRequiringDifferentLessonsPerWeek
:: (Ord level, Ord teacherId)
=> TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio
-> NTimeslotsByTeacherIdBySubject level teacherId
findSubjectsOfferedInMultipleCoursesRequiringDifferentLessonsPerWeek = Data.Map.filter (
(> 1) . Data.Set.size . Data.Set.fromList . Data.Map.elems
) . Data.Map.foldrWithKey (
\teacherId teacherProfile m -> Data.Set.foldr (
\course -> uncurry (
Data.Map.insertWith Data.Map.union
) (
Data.Course.getSubject &&& Data.Map.singleton teacherId . Data.Course.getRequiredLessonsPerWeek $ course
)
) m $ Data.Teacher.getService teacherProfile
) Data.Map.empty
mergeConstraintsOnSynchronisedCourses :: (
Ord level,
Ord synchronisationId,
Ord teacherId,
Ord timeslotId,
Show synchronisationId,
Show timeslotId
) => TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio -> TeacherRegister teacherId synchronisationId level timeslotId locationId teachingRatio
mergeConstraintsOnSynchronisedCourses teacherRegister = Data.Map.map (
\profile -> profile {
Data.Teacher.getService = Data.Set.map (
\course -> Data.Maybe.maybe course (
\synchronisationId -> let
distinctCourses = findDistinctCoursesBySynchronisationId (findCoursesByTeacherIdBySynchronisationId teacherRegister) ! synchronisationId
in course {
Data.Course.getMinimumConsecutiveLessons = Data.Set.findMax $ Data.Set.map Data.Course.getMinimumConsecutiveLessons distinctCourses,
Data.Course.getTimeslotRequest = let
timeslotRequests = Data.Set.map Data.Course.getTimeslotRequest distinctCourses
(idealTimeslotIds, specifiedTimes) = (
Data.Set.map (Data.Maybe.fromJust . Temporal.TimeslotRequest.getMaybeIdealTimeslotId) *** Data.Set.unions . map Temporal.TimeslotRequest.getSpecifiedTimes . Data.Set.toList
) $ Data.Set.partition Temporal.TimeslotRequest.isIdeally timeslotRequests
hasZeroIdealTimeslotIds, hasZeroSpecifiedTimes :: Bool
hasZeroIdealTimeslotIds = Data.Set.null idealTimeslotIds
hasZeroSpecifiedTimes = Data.Set.null specifiedTimes
in if hasZeroIdealTimeslotIds && hasZeroSpecifiedTimes
then Data.Course.getTimeslotRequest course
else if Data.Set.size idealTimeslotIds == 1 && hasZeroSpecifiedTimes
then Temporal.TimeslotRequest.Ideally $ Data.Set.findMin idealTimeslotIds
else if hasZeroIdealTimeslotIds && not hasZeroSpecifiedTimes
then Temporal.TimeslotRequest.Specifically specifiedTimes
else error . showString "WeekDaze.Aggregate.TeacherRegister.mergeConstraintsOnSynchronisedCourses:\tincompatible TimeslotRequests; " $ shows (synchronisationId, Data.Set.toList timeslotRequests) "."
}
) $ Data.Course.getMaybeSynchronisationId course
) $ Data.Teacher.getService profile
}
) teacherRegister