feat(allocations): improve display

This commit is contained in:
Gregor Kleen 2020-03-12 11:56:26 +01:00
parent bb20062d9f
commit 26f8f392a9
7 changed files with 175 additions and 22 deletions

1
.gitignore vendored
View File

@ -40,3 +40,4 @@ tunnel.log
/well-known /well-known
/.well-known-cache /.well-known-cache
/**/tmp-* /**/tmp-*
/testdata/bigAlloc_*.csv

View File

@ -1176,7 +1176,7 @@ a.breadcrumbs__home
font-size: 14px font-size: 14px
font-family: monospace font-family: monospace
.func-field__wrapper .func-field__wrapper, .allocation-missing-prios
max-height: 75vh max-height: 75vh
overflow: auto overflow: auto

View File

@ -156,7 +156,7 @@ postAUsersR tid ssh ash = do
, guardOn resultsDone . coursesModalAssigned . bool id assignedHeated resultsDone $ colAllocationAssigned resultAssignedCourses , guardOn resultsDone . coursesModalAssigned . bool id assignedHeated resultsDone $ colAllocationAssigned resultAssignedCourses
, coursesModalNewAssigned . assignedHeated <$> do , coursesModalNewAssigned . assignedHeated <$> do
allocMatching' <- allocMatching allocMatching' <- allocMatching
pure . sortable Nothing (i18nCell MsgAllocationUserNewMatches) . pure . sortable (Just "new-assigned") (i18nCell MsgAllocationUserNewMatches) .
views (resultUser . _entityKey) $ \uid -> cell . toWidget . toMarkup . maybe 0 olength $ allocMatching' !? uid views (resultUser . _entityKey) $ \uid -> cell . toWidget . toMarkup . maybe 0 olength $ allocMatching' !? uid
, pure $ emptyOpticColonnade' emptyPriorityCell (resultAllocationUser . _entityVal . _allocationUserPriority . _Just) colAllocationPriority , pure $ emptyOpticColonnade' emptyPriorityCell (resultAllocationUser . _entityVal . _allocationUserPriority . _Just) colAllocationPriority
] ]
@ -211,6 +211,8 @@ postAUsersR tid ssh ash = do
, sortAllocationRequested $ queryAllocationUser . to (E.^. AllocationUserTotalCourses) , sortAllocationRequested $ queryAllocationUser . to (E.^. AllocationUserTotalCourses)
, sortAllocationVetoed queryVetoedCourses , sortAllocationVetoed queryVetoedCourses
, sortAllocationPriority $ queryAllocationUser . to (E.^. AllocationUserPriority) , sortAllocationPriority $ queryAllocationUser . to (E.^. AllocationUserPriority)
, singletonMap "new-assigned" $
SortProjected . comparing $ (\uid -> maybe 0 olength $ Map.lookup uid =<< allocMatching) . view (resultUser . _entityKey)
] ]
dbtFilter = mconcat dbtFilter = mconcat
[ fltrUserName' $ queryUser . to (E.^. UserDisplayName) [ fltrUserName' $ queryUser . to (E.^. UserDisplayName)
@ -237,7 +239,7 @@ postAUsersR tid ssh ash = do
dbtCsvDecode = Nothing dbtCsvDecode = Nothing
allocationUsersDBTableValidator = def allocationUsersDBTableValidator = def
& defaultSorting [SortAscBy "priority", SortAscBy "user-matriculation"] & defaultSorting [SortAscBy "priority", SortAscBy "user-matriculation"]
& defaultPagesize PagesizeAll & defaultPagesize (PagesizeLimit 500)
usersTable <- dbTableDB' allocationUsersDBTableValidator allocationUsersDBTable usersTable <- dbTableDB' allocationUsersDBTableValidator allocationUsersDBTable

View File

@ -1021,6 +1021,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
case previousKeys of case previousKeys of
Nothing Nothing
| PagesizeLimit l <- psLimit' | PagesizeLimit l <- psLimit'
, selectPagesize
-> do -> do
E.limit l E.limit l
E.offset (psPage * l) E.offset (psPage * l)
@ -1236,7 +1237,9 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
_other -> return () _other -> return ()
let let
rowCount = olength64 rows rowCount
| selectPagesize = fromMaybe 0 $ rows' ^? _head . _1 . _Value
| otherwise = olength64 rows
rawAction = tblLink rawAction = tblLink
$ setParam (wIdent "sorting") Nothing $ setParam (wIdent "sorting") Nothing

View File

@ -4,6 +4,8 @@ module Model.Types.Allocation
, AllocationPriorityComparison(..) , AllocationPriorityComparison(..)
, AllocationFingerprint , AllocationFingerprint
, module Utils.Allocation , module Utils.Allocation
, AllocationPriorityNumericRecord(..)
, allocationPriorityNumericMap
) where ) where
import Import.NoModel import Import.NoModel
@ -44,13 +46,36 @@ deriving via E.JSONB AllocationPriority instance E.PersistFieldSql AllocationPri
instance Binary AllocationPriority instance Binary AllocationPriority
instance Csv.FromRecord (Map UserMatriculation AllocationPriority) where data AllocationPriorityNumericRecord = AllocationPriorityNumericRecord
{ apmrMatrikelnummer :: UserMatriculation
, apmrPriority :: Vector Integer
} deriving (Eq, Ord, Read, Show, Generic, Typeable)
allocationPriorityNumericMap :: Prism' (Map UserMatriculation AllocationPriority) AllocationPriorityNumericRecord
allocationPriorityNumericMap = prism' fromPrioRecord toPrioRecord
where
fromPrioRecord AllocationPriorityNumericRecord{..}
= Map.singleton apmrMatrikelnummer $ AllocationPriorityNumeric apmrPriority
toPrioRecord recordMap = do
[(matr, AllocationPriorityNumeric{..})] <- pure $ Map.toList recordMap
return $ AllocationPriorityNumericRecord matr allocationPriorities
instance Csv.FromRecord AllocationPriorityNumericRecord where
parseRecord v = parseNumeric parseRecord v = parseNumeric
where where
parseNumeric parseNumeric
| Vector.length v >= 1 = Map.singleton <$> v Csv..! 0 <*> (AllocationPriorityNumeric <$> mapM Csv.parseField (Vector.tail v)) | Vector.length v >= 1 = AllocationPriorityNumericRecord <$> v Csv..! 0 <*> mapM Csv.parseField (Vector.tail v)
| otherwise = mzero | otherwise = mzero
instance Csv.ToRecord AllocationPriorityNumericRecord where
toRecord AllocationPriorityNumericRecord{..} = Csv.record $
Csv.toField apmrMatrikelnummer
: map Csv.toField (otoList apmrPriority)
instance Csv.FromRecord (Map UserMatriculation AllocationPriority) where
parseRecord = fmap (review allocationPriorityNumericMap) . Csv.parseRecord
instance Csv.ToField AllocationPriority where instance Csv.ToField AllocationPriority where
toField (AllocationPriorityOrdinal n ) = Csv.toField n toField (AllocationPriorityOrdinal n ) = Csv.toField n

View File

@ -2,12 +2,13 @@ $newline never
<p> <p>
Die folgenden Benutzer nehmen nicht an der Zentralvergabe teil, da # Die folgenden Benutzer nehmen nicht an der Zentralvergabe teil, da #
ihnen keine zentrale Dringlichkeit zugeordnet wurde: ihnen keine zentrale Dringlichkeit zugeordnet wurde:
<ul> <div .allocation-missing-prios>
$forall User{userDisplayName, userSurname, userMatrikelnummer} <- usersWithoutPrio <ul>
<li> $forall User{userDisplayName, userSurname, userMatrikelnummer} <- usersWithoutPrio
^{nameWidget userDisplayName userSurname} <li>
$maybe matrikel <- userMatrikelnummer ^{nameWidget userDisplayName userSurname}
\ (#{matrikel}) $maybe matrikel <- userMatrikelnummer
\ (#{matrikel})
^{checkBoxFieldView} ^{checkBoxFieldView}
<p> <p>
Benutzern, die nicht an der Zentralvergabe teilnehmen, werden # Benutzern, die nicht an der Zentralvergabe teilnehmen, werden #

View File

@ -5,8 +5,10 @@ module Database.Fill
import "uniworx" Import hiding (Option(..), currentYear) import "uniworx" Import hiding (Option(..), currentYear)
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Set as Set import qualified Data.Set as Set
import qualified Data.Map as Map
import Data.Time.Calendar.OrdinalDate import Data.Time.Calendar.OrdinalDate
import Data.Time.Calendar.WeekDate import Data.Time.Calendar.WeekDate
@ -15,14 +17,21 @@ import Control.Applicative (ZipList(..))
import Handler.Utils.DateTime import Handler.Utils.DateTime
import Control.Monad.Random.Class (weighted)
import System.Random.Shuffle (shuffleM) import System.Random.Shuffle (shuffleM)
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
import qualified Data.Csv as Csv
testdataDir :: FilePath
testdataDir = "testdata"
insertFile :: FilePath -> DB FileId insertFile :: FilePath -> DB FileId
insertFile fileTitle = do insertFile fileTitle = do
fileContent <- liftIO . fmap Just . BS.readFile $ "testdata" </> fileTitle fileContent <- liftIO . fmap Just . BS.readFile $ testdataDir </> fileTitle
fileModified <- liftIO getCurrentTime fileModified <- liftIO getCurrentTime
insert File{..} insert File{..}
@ -267,6 +276,7 @@ fillDb = do
, "Clark", "Lewis", "Robinson", "Walker" , "Clark", "Lewis", "Robinson", "Walker"
, "Perez", "Hall", "Young", "Allen" , "Perez", "Hall", "Young", "Allen"
] ]
middlenames = [ Nothing, Just "Jamesson" ]
toMatrikel :: [Int] -> [Text] toMatrikel :: [Int] -> [Text]
toMatrikel ns toMatrikel ns
| (cs, rest) <- splitAt 8 ns | (cs, rest) <- splitAt 8 ns
@ -274,7 +284,7 @@ fillDb = do
= foldMap tshow cs : toMatrikel rest = foldMap tshow cs : toMatrikel rest
| otherwise | otherwise
= [] = []
manyUser (userFirstName, userSurname) (Just -> userMatrikelnummer) = User manyUser (firstName, middleName, userSurname) (Just -> userMatrikelnummer) = User
{ userIdent { userIdent
, userAuthentication = AuthLDAP , userAuthentication = AuthLDAP
, userLastAuthentication = Nothing , userLastAuthentication = Nothing
@ -282,9 +292,11 @@ fillDb = do
, userMatrikelnummer , userMatrikelnummer
, userEmail = userIdent , userEmail = userIdent
, userDisplayEmail = userIdent , userDisplayEmail = userIdent
, userDisplayName = [st|#{userFirstName} #{userSurname}|] , userDisplayName = case middleName of
Just middleName' -> [st|#{firstName} #{middleName'} #{userSurname}|]
Nothing -> [st|#{firstName} #{userSurname}|]
, userSurname , userSurname
, userFirstName , userFirstName = maybe id (\m f -> f <> " " <> m) middleName firstName
, userTitle = Nothing , userTitle = Nothing
, userMaxFavourites = userDefaultMaxFavourites , userMaxFavourites = userDefaultMaxFavourites
, userMaxFavouriteTerms = userDefaultMaxFavourites , userMaxFavouriteTerms = userDefaultMaxFavourites
@ -304,9 +316,11 @@ fillDb = do
} }
where where
userIdent :: IsString t => t userIdent :: IsString t => t
userIdent = fromString $ repack [st|#{userFirstName}.#{userSurname}@campus.lmu.de|] userIdent = fromString $ case middleName of
Just middleName' -> repack [st|#{firstName}.#{middleName'}.#{userSurname}@example.invalid|]
Nothing -> repack [st|#{firstName}.#{userSurname}@example.invalid|]
matrikel <- toMatrikel <$> getRandomRs (0 :: Int, 9 :: Int) matrikel <- toMatrikel <$> getRandomRs (0 :: Int, 9 :: Int)
manyUsers <- insertMany . getZipList $ manyUser <$> ZipList ((,) <$> firstNames <*> surnames) <*> ZipList matrikel manyUsers <- insertMany . getZipList $ manyUser <$> ZipList ((,,) <$> firstNames <*> middlenames <*> surnames) <*> ZipList matrikel
forM_ [prevPrevTerm, prevTerm, currentTerm, nextTerm] $ \term@TermIdentifier{..} -> case season of forM_ [prevPrevTerm, prevTerm, currentTerm, nextTerm] $ \term@TermIdentifier{..} -> case season of
Summer -> let (wYearStart, wWeekStart, _) = toWeekDate $ fromGregorian year 04 01 Summer -> let (wYearStart, wWeekStart, _) = toWeekDate $ fromGregorian year 04 01
@ -933,7 +947,7 @@ fillDb = do
insert_ $ CourseEdit gkleen now bs insert_ $ CourseEdit gkleen now bs
void . insert' $ Lecturer gkleen bs CourseLecturer void . insert' $ Lecturer gkleen bs CourseLecturer
void . insertMany $ do void . insertMany $ do
uid <- manyUsers uid <- take 1024 manyUsers
return $ CourseParticipant bs uid now Nothing Nothing return $ CourseParticipant bs uid now Nothing Nothing
forM_ [1..14] $ \shNr -> do forM_ [1..14] $ \shNr -> do
shId <- insert Sheet shId <- insert Sheet
@ -951,7 +965,7 @@ fillDb = do
, sheetSolutionFrom = Nothing , sheetSolutionFrom = Nothing
, sheetAutoDistribute = False , sheetAutoDistribute = False
} }
manyUsers' <- shuffleM manyUsers manyUsers' <- shuffleM $ take 1024 manyUsers
groupSizes <- getRandomRs (1, 3) groupSizes <- getRandomRs (1, 3)
let groups = go groupSizes manyUsers' let groups = go groupSizes manyUsers'
where go [] _ = [] where go [] _ = []
@ -998,9 +1012,116 @@ fillDb = do
, courseApplicationsRatingsVisible = False , courseApplicationsRatingsVisible = False
} }
insert_ $ CourseEdit gkleen now cid insert_ $ CourseEdit gkleen now cid
void . insert' $ Lecturer gkleen cid CourseLecturer -- void . insert' $ Lecturer gkleen cid CourseLecturer
participants <- getRandomR (0, 50) participants <- getRandomR (0, 50)
manyUsers' <- shuffleM manyUsers manyUsers' <- shuffleM $ take 1024 manyUsers
forM_ (take participants manyUsers') $ \uid -> forM_ (take participants manyUsers') $ \uid ->
void . insert $ CourseParticipant cid uid now Nothing Nothing void . insert $ CourseParticipant cid uid now Nothing Nothing
bigAlloc <- insert' Allocation
{ allocationName = "Große Zentralanmeldung"
, allocationShorthand = "big"
, allocationTerm = TermKey $ seasonTerm True Summer
, allocationSchool = ifi
, allocationDescription = Nothing
, allocationStaffDescription = Nothing
, allocationStaffRegisterFrom = Nothing
, allocationStaffRegisterTo = Nothing
, allocationStaffAllocationFrom = Nothing
, allocationStaffAllocationTo = Nothing
, allocationRegisterFrom = Nothing
, allocationRegisterTo = Just now
, allocationRegisterByStaffFrom = Nothing
, allocationRegisterByStaffTo = Nothing
, allocationRegisterByCourse = Nothing
, allocationOverrideDeregister = Just $ termTime True Summer 1 False Monday toMidnight
}
bigAllocCourses <- forM ([1..40] :: [Int]) $ \n -> do
csh <- pack . take 3 <$> getRandomRs ('A', 'Z')
cap <- getRandomR (10,50)
minCap <- round . (* fromIntegral cap) <$> getRandomR (0, 0.5 :: Double)
cid <- insert' Course
{ courseName = CI.mk [st|Zentralanmeldungskurs #{n} (#{csh})|]
, courseDescription = Nothing
, courseLinkExternal = Nothing
, courseShorthand = CI.mk csh
, courseTerm = TermKey $ seasonTerm False Winter
, courseSchool = ifi
, courseCapacity = Just cap
, courseRegisterFrom = Nothing
, courseRegisterTo = Nothing
, courseDeregisterUntil = Nothing
, courseRegisterSecret = Nothing
, courseMaterialFree = True
, courseApplicationsRequired = False
, courseApplicationsInstructions = Nothing
, courseApplicationsText = False
, courseApplicationsFiles = NoUpload
, courseApplicationsRatingsVisible = False
}
insert_ $ CourseEdit gkleen now cid
insert_ $ AllocationCourse bigAlloc cid minCap
-- void . insert' $ Lecturer gkleen cid CourseLecturer
return cid
forM_ manyUsers $ \uid -> do
totalCourses <- weighted $ do
n <- [1..10]
return (n, fromIntegral $ (1 - 10) ^ 2 - (1 - n) ^ 2)
void . insert $ AllocationUser bigAlloc uid (fromIntegral totalCourses) Nothing
appliedCourses <- weighted $ do
n <- [totalCourses - 2..totalCourses + 5]
return (n, fromIntegral $ (totalCourses + 1 - totalCourses - 5) ^ 2 - (totalCourses + 1 - n) ^ 2)
appliedCourses' <- take appliedCourses <$> shuffleM bigAllocCourses
forM_ (zip [0..] appliedCourses') $ \(prio, cid) -> do
rating <- weighted . Map.toList . Map.fromListWith (+) $ do
veto <- universeF :: [Bool]
grade <- universeF :: [ExamGrade]
rated <- universeF
return ( bool Nothing (Just (veto, grade)) rated
, bool 5 1 veto * bool 5 1 rated
)
void $ insert CourseApplication
{ courseApplicationCourse = cid
, courseApplicationUser = uid
, courseApplicationField = Nothing
, courseApplicationText = Nothing
, courseApplicationRatingVeto = maybe False (view _1) rating
, courseApplicationRatingPoints = view _2 <$> rating
, courseApplicationRatingComment = Nothing
, courseApplicationAllocation = Just bigAlloc
, courseApplicationAllocationPriority = Just prio
, courseApplicationTime = now
, courseApplicationRatingTime = now <$ rating
}
numericPriorities <- flip foldMapM manyUsers $ \uid -> do
uRec <- get uid
case uRec of
Just User{ userMatrikelnummer = Just matr } -> do
prios <- replicateM 3 $ getRandomR (0, 300)
return . pure . AllocationPriorityNumericRecord matr . fromList $ sortOn Down prios
_other -> return mempty
liftIO . LBS.writeFile (testdataDir </> "bigAlloc_numeric.csv") $ Csv.encode numericPriorities
ordinalPriorities <- do
manyUsers' <- shuffleM manyUsers
flip foldMapM manyUsers' $ \uid -> do
uRec <- get uid
case uRec of
Just User{ userMatrikelnummer = Just matr } ->
return . pure $ Csv.Only matr
_other -> return mempty
liftIO . LBS.writeFile (testdataDir </> "bigAlloc_ordinal.csv") $ Csv.encode ordinalPriorities