feat(allocations): improve acceptance display
This commit is contained in:
parent
648d733f3f
commit
cf03277874
1
.gitignore
vendored
1
.gitignore
vendored
@ -41,3 +41,4 @@ tunnel.log
|
|||||||
/.well-known-cache
|
/.well-known-cache
|
||||||
/**/tmp-*
|
/**/tmp-*
|
||||||
/testdata/bigAlloc_*.csv
|
/testdata/bigAlloc_*.csv
|
||||||
|
/sessions
|
||||||
2
db.sh
2
db.sh
@ -6,4 +6,6 @@ set -e
|
|||||||
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en .stack-work.lock "$0" "$@" || :
|
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en .stack-work.lock "$0" "$@" || :
|
||||||
|
|
||||||
stack build --fast --flag uniworx:-library-only --flag uniworx:dev
|
stack build --fast --flag uniworx:-library-only --flag uniworx:dev
|
||||||
|
|
||||||
|
export SERVER_SESSION_ACID_FALLBACK=${SERVER_SESSION_ACID_FALLBACK:-true}
|
||||||
stack exec uniworxdb -- $@
|
stack exec uniworxdb -- $@
|
||||||
|
|||||||
@ -674,6 +674,12 @@ section
|
|||||||
background-color: hsla($hue, 75%, 50%, $opacity) !important
|
background-color: hsla($hue, 75%, 50%, $opacity) !important
|
||||||
font-weight: calc(((var(--hotness) - 1) * (var(--hotness) - 1)) * 200 + 400)
|
font-weight: calc(((var(--hotness) - 1) * (var(--hotness) - 1)) * 200 + 400)
|
||||||
|
|
||||||
|
.degenerate
|
||||||
|
$hue: calc(240 + var(--hotness) * 60)
|
||||||
|
|
||||||
|
background-color: hsla($hue, 75%, 50%, $opacity) !important
|
||||||
|
|
||||||
|
|
||||||
.uuid
|
.uuid
|
||||||
font-family: monospace
|
font-family: monospace
|
||||||
|
|
||||||
|
|||||||
@ -2420,5 +2420,7 @@ AllocationTime: Zeitpunkt der Vergabe
|
|||||||
AllocationRequestedPlaces: Angefragte Plätze
|
AllocationRequestedPlaces: Angefragte Plätze
|
||||||
AllocationOfferedPlaces: Angebotene Plätze
|
AllocationOfferedPlaces: Angebotene Plätze
|
||||||
AllocationUserNewMatches: Neue Zuteilungen
|
AllocationUserNewMatches: Neue Zuteilungen
|
||||||
|
AllocationUsersCount: Teilnehmer
|
||||||
|
AllocationCoursesCount: Kurse
|
||||||
|
|
||||||
CourseOption tid@TermId ssh@SchoolId coursen@CourseName: #{tid} - #{ssh} - #{coursen}
|
CourseOption tid@TermId ssh@SchoolId coursen@CourseName: #{tid} - #{ssh} - #{coursen}
|
||||||
@ -88,7 +88,7 @@ import qualified Data.Set as Set
|
|||||||
|
|
||||||
import Handler.Utils.Routes (classifyHandler)
|
import Handler.Utils.Routes (classifyHandler)
|
||||||
|
|
||||||
import qualified Data.Acid.Memory as Acid
|
import qualified Data.Acid as Acid
|
||||||
import qualified Web.ServerSession.Backend.Acid as Acid
|
import qualified Web.ServerSession.Backend.Acid as Acid
|
||||||
|
|
||||||
-- Import all relevant handler modules here.
|
-- Import all relevant handler modules here.
|
||||||
@ -250,7 +250,7 @@ mkSessionStore AppSettings{..} mcdSqlConnPool
|
|||||||
let mcdSqlMemcachedExpiration = memcachedExpiry
|
let mcdSqlMemcachedExpiration = memcachedExpiry
|
||||||
return $ _SessionStorageMemcachedSql # MemcachedSqlStorage{..}
|
return $ _SessionStorageMemcachedSql # MemcachedSqlStorage{..}
|
||||||
| appServerSessionAcidFallback = liftIO $
|
| appServerSessionAcidFallback = liftIO $
|
||||||
review _SessionStorageAcid . Acid.AcidStorage <$> Acid.openMemoryState Acid.emptyState
|
review _SessionStorageAcid . Acid.AcidStorage <$> Acid.openLocalStateFrom "sessions" Acid.emptyState
|
||||||
| otherwise = throwM SessionStoreNotAvailable
|
| otherwise = throwM SessionStoreNotAvailable
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -95,7 +95,7 @@ allocationAcceptForm aId = runMaybeT $ do
|
|||||||
, Map.delete cid allocCounts
|
, Map.delete cid allocCounts
|
||||||
))
|
))
|
||||||
([] , courseAllocations) allocationCourses
|
([] , courseAllocations) allocationCourses
|
||||||
in guardOn (null leftoverAllocs) res :: Maybe [((Entity AllocationCourse, Entity Course, Int), Integer)]
|
in guardOn (null leftoverAllocs) res :: Maybe [((Entity AllocationCourse, Entity Course, Int), Int)]
|
||||||
|
|
||||||
let unmatchedCourses = olength $ filter ((<= 0) . view _2) allocationCourses'
|
let unmatchedCourses = olength $ filter ((<= 0) . view _2) allocationCourses'
|
||||||
|
|
||||||
@ -123,10 +123,9 @@ allocationAcceptForm aId = runMaybeT $ do
|
|||||||
| allocationCapacity == 0 = 0
|
| allocationCapacity == 0 = 0
|
||||||
| otherwise = fromIntegral allocationPlacesRequested % fromIntegral allocationCapacity
|
| otherwise = fromIntegral allocationPlacesRequested % fromIntegral allocationCapacity
|
||||||
allocHeat capN allocated
|
allocHeat capN allocated
|
||||||
| optimumAllocated capN >= capN
|
= invDualHeat (optimumAllocated capN) capN allocated
|
||||||
= 2 - coHeat capN allocated * 2
|
degenerateHeat capN
|
||||||
| otherwise
|
= capN <= optimumAllocated capN
|
||||||
= 2 - dualHeat (optimumAllocated capN) capN allocated
|
|
||||||
|
|
||||||
return (prevAllocMatches, $(widgetFile "allocation/accept"))
|
return (prevAllocMatches, $(widgetFile "allocation/accept"))
|
||||||
|
|
||||||
|
|||||||
@ -153,22 +153,23 @@ postAUsersR tid ssh ash = do
|
|||||||
, pure $ colAllocationRequested (resultAllocationUser . _entityVal . _allocationUserTotalCourses)
|
, pure $ colAllocationRequested (resultAllocationUser . _entityVal . _allocationUserTotalCourses)
|
||||||
, pure . coursesModalApplied $ colAllocationApplied resultAppliedCourses
|
, pure . coursesModalApplied $ colAllocationApplied resultAppliedCourses
|
||||||
, pure . coursesModalVetoed $ colAllocationVetoed resultVetoedCourses
|
, pure . coursesModalVetoed $ colAllocationVetoed resultVetoedCourses
|
||||||
, guardOn resultsDone . coursesModalAssigned . bool id assignedHeated resultsDone $ colAllocationAssigned resultAssignedCourses
|
, guardOn resultsDone . coursesModalAssigned . bool id (assignedHeated $ view resultAssignedCourses) resultsDone $ colAllocationAssigned resultAssignedCourses
|
||||||
, coursesModalNewAssigned . assignedHeated <$> do
|
, coursesModalNewAssigned <$> do
|
||||||
allocMatching' <- allocMatching
|
allocMatching' <- allocMatching
|
||||||
pure . sortable (Just "new-assigned") (i18nCell MsgAllocationUserNewMatches) .
|
let newAssigned uid = maybe 0 olength $ allocMatching' !? uid
|
||||||
views (resultUser . _entityKey) $ \uid -> cell . toWidget . toMarkup . maybe 0 olength $ allocMatching' !? uid
|
pure . assignedHeated (views (resultUser . _entityKey) newAssigned) . sortable (Just "new-assigned") (i18nCell MsgAllocationUserNewMatches) .
|
||||||
|
views (resultUser . _entityKey) $ cell . toWidget . toMarkup . newAssigned
|
||||||
, pure $ emptyOpticColonnade' emptyPriorityCell (resultAllocationUser . _entityVal . _allocationUserPriority . _Just) colAllocationPriority
|
, pure $ emptyOpticColonnade' emptyPriorityCell (resultAllocationUser . _entityVal . _allocationUserPriority . _Just) colAllocationPriority
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
emptyPriorityCell = addCellClass ("table__td--center" :: Text) . cell $
|
emptyPriorityCell = addCellClass ("table__td--center" :: Text) . cell $
|
||||||
messageTooltip =<< messageIconI Error IconMissingAllocationPriority MsgAllocationMissingPrioritiesIgnored
|
messageTooltip =<< messageIconI Error IconMissingAllocationPriority MsgAllocationMissingPrioritiesIgnored
|
||||||
assignedHeated = imapColonnade assignedHeated'
|
assignedHeated fAssigned = imapColonnade assignedHeated'
|
||||||
where
|
where
|
||||||
assignedHeated' res
|
assignedHeated' res
|
||||||
= let maxAssign = min (res ^. resultAllocationUser . _entityVal . _allocationUserTotalCourses . to fromIntegral)
|
= let maxAssign = min (res ^. resultAllocationUser . _entityVal . _allocationUserTotalCourses . to fromIntegral)
|
||||||
(res ^. resultAppliedCourses)
|
(res ^. resultAppliedCourses)
|
||||||
assigned = maxAssign - res ^. resultAssignedCourses
|
assigned = fAssigned res
|
||||||
in cellAttrs <>~ [ ("class", "heated")
|
in cellAttrs <>~ [ ("class", "heated")
|
||||||
, ("style", [st|--hotness: #{tshow (coHeat maxAssign assigned)}|])
|
, ("style", [st|--hotness: #{tshow (coHeat maxAssign assigned)}|])
|
||||||
]
|
]
|
||||||
@ -177,23 +178,36 @@ postAUsersR tid ssh ash = do
|
|||||||
E.where_ $ courseApplication E.^. CourseApplicationAllocation E.==. E.val (Just aId)
|
E.where_ $ courseApplication E.^. CourseApplicationAllocation E.==. E.val (Just aId)
|
||||||
E.&&. courseApplication E.^. CourseApplicationUser E.==. E.val (res ^. resultUser . _entityKey)
|
E.&&. courseApplication E.^. CourseApplicationUser E.==. E.val (res ^. resultUser . _entityKey)
|
||||||
E.orderBy [E.desc $ courseApplication E.^. CourseApplicationAllocationPriority]
|
E.orderBy [E.desc $ courseApplication E.^. CourseApplicationAllocationPriority]
|
||||||
return course
|
return ( course
|
||||||
|
, courseApplication E.^. CourseApplicationRatingPoints
|
||||||
|
, E.just $ courseApplication E.^. CourseApplicationRatingVeto
|
||||||
|
)
|
||||||
coursesModalVetoed = coursesModal $ \res -> E.from $ \(course `E.InnerJoin` courseApplication) -> do
|
coursesModalVetoed = coursesModal $ \res -> E.from $ \(course `E.InnerJoin` courseApplication) -> do
|
||||||
E.on $ course E.^. CourseId E.==. courseApplication E.^. CourseApplicationCourse
|
E.on $ course E.^. CourseId E.==. courseApplication E.^. CourseApplicationCourse
|
||||||
E.where_ $ courseApplication E.^. CourseApplicationAllocation E.==. E.val (Just aId)
|
E.where_ $ courseApplication E.^. CourseApplicationAllocation E.==. E.val (Just aId)
|
||||||
E.&&. courseApplication E.^. CourseApplicationUser E.==. E.val (res ^. resultUser . _entityKey)
|
E.&&. courseApplication E.^. CourseApplicationUser E.==. E.val (res ^. resultUser . _entityKey)
|
||||||
E.where_ $ courseApplication E.^. CourseApplicationRatingVeto
|
E.where_ $ courseApplication E.^. CourseApplicationRatingVeto
|
||||||
E.||. courseApplication E.^. CourseApplicationRatingPoints `E.in_` E.valList (map Just $ filter (view $ passingGrade . _Wrapped . to not) universeF)
|
E.||. courseApplication E.^. CourseApplicationRatingPoints `E.in_` E.valList (map Just $ filter (view $ passingGrade . _Wrapped . to not) universeF)
|
||||||
return course
|
return ( course
|
||||||
|
, E.nothing
|
||||||
|
, E.nothing
|
||||||
|
)
|
||||||
coursesModalAssigned = coursesModal $ \res -> E.from $ \(course `E.InnerJoin` courseParticipant) -> do
|
coursesModalAssigned = coursesModal $ \res -> E.from $ \(course `E.InnerJoin` courseParticipant) -> do
|
||||||
E.on $ courseParticipant E.^. CourseParticipantCourse E.==. course E.^. CourseId
|
E.on $ courseParticipant E.^. CourseParticipantCourse E.==. course E.^. CourseId
|
||||||
E.&&. courseParticipant E.^. CourseParticipantAllocated E.==. E.val (Just aId)
|
E.&&. courseParticipant E.^. CourseParticipantAllocated E.==. E.val (Just aId)
|
||||||
E.where_ $ courseParticipant E.^. CourseParticipantUser E.==. E.val (res ^. resultUser . _entityKey)
|
E.where_ $ courseParticipant E.^. CourseParticipantUser E.==. E.val (res ^. resultUser . _entityKey)
|
||||||
E.orderBy [E.asc $ courseParticipant E.^. CourseParticipantRegistration]
|
E.orderBy [E.asc $ courseParticipant E.^. CourseParticipantRegistration]
|
||||||
return course
|
return ( course
|
||||||
|
, E.nothing
|
||||||
|
, E.nothing
|
||||||
|
)
|
||||||
coursesModalNewAssigned = coursesModal $ \res -> E.from $ \course -> do
|
coursesModalNewAssigned = coursesModal $ \res -> E.from $ \course -> do
|
||||||
E.where_ $ course E.^. CourseId `E.in_` E.valList (maybe [] otoList $ Map.lookup (res ^. resultUser . _entityKey) =<< allocMatching)
|
E.where_ $ course E.^. CourseId `E.in_` E.valList (maybe [] otoList $ Map.lookup (res ^. resultUser . _entityKey) =<< allocMatching)
|
||||||
return course
|
return ( course
|
||||||
|
, E.nothing
|
||||||
|
, E.nothing
|
||||||
|
)
|
||||||
|
coursesModal :: (_ -> E.SqlQuery (E.SqlExpr (Entity Course), E.SqlExpr (E.Value (Maybe ExamGrade)), E.SqlExpr (E.Value (Maybe Bool)))) -> _ -> _
|
||||||
coursesModal courseSel = imapColonnade coursesModal'
|
coursesModal courseSel = imapColonnade coursesModal'
|
||||||
where
|
where
|
||||||
coursesModal' res innerCell = review dbCell . (innerCell ^. cellAttrs, ) $ do
|
coursesModal' res innerCell = review dbCell . (innerCell ^. cellAttrs, ) $ do
|
||||||
|
|||||||
@ -97,20 +97,57 @@ editedByW fmt tm usr = do
|
|||||||
|
|
||||||
heat :: ( Real a, Real b )
|
heat :: ( Real a, Real b )
|
||||||
=> a -> b -> Milli
|
=> a -> b -> Milli
|
||||||
|
-- ^ Distinguishes @full@, zero is mapped to 1, @full@ is mapped to 0
|
||||||
heat (realToFrac -> full) (realToFrac -> achieved)
|
heat (realToFrac -> full) (realToFrac -> achieved)
|
||||||
= fromRational $ cutOffCoPercent 0.3 (full^2) (achieved^2) --
|
= fromRational $ cutOffCoPercent 0.3 (full^2) (achieved^2)
|
||||||
|
|
||||||
|
invHeat :: ( Real a, Real b )
|
||||||
|
=> a -> b -> Milli
|
||||||
|
-- ^ Distinguishes @full@, zero is mapped to 0, @full@ is mapped to 1
|
||||||
|
invHeat full achieved = 1 - heat full achieved
|
||||||
|
|
||||||
coHeat :: ( Real a, Real b)
|
coHeat :: ( Real a, Real b)
|
||||||
=> a -> b -> Milli
|
=> a -> b -> Milli
|
||||||
coHeat (realToFrac -> full) (realToFrac -> achieved)
|
-- ^ Distinguishes zero, zero is mapped to 1, @full@ is mapped to 0
|
||||||
|
coHeat full achieved = 1 - invCoHeat full achieved
|
||||||
|
|
||||||
|
invCoHeat :: ( Real a, Real b)
|
||||||
|
=> a -> b -> Milli
|
||||||
|
-- ^ Distinguishes zero, zero is mapped to 0, @full@ is mapped to 1
|
||||||
|
invCoHeat (realToFrac -> full) (realToFrac -> achieved)
|
||||||
= fromRational $ cutOffPercent 0.3 (full^2) (achieved^2)
|
= fromRational $ cutOffPercent 0.3 (full^2) (achieved^2)
|
||||||
|
|
||||||
|
|
||||||
dualHeat :: ( Real a, Real b, Real c )
|
dualHeat :: ( Real a, Real b, Real c )
|
||||||
=> a -> b -> c -> Milli
|
=> a -> b -> c -> Milli
|
||||||
|
-- ^ Distinguishes zero, zero is mapped to 0, @optimal@ is mapped to 1, @full@ is mapped to 2
|
||||||
|
--
|
||||||
|
-- Falls back to `invCoHeat` if @full <= optimal@
|
||||||
dualHeat (realToFrac -> optimal) (realToFrac -> full) (realToFrac -> achieved)
|
dualHeat (realToFrac -> optimal) (realToFrac -> full) (realToFrac -> achieved)
|
||||||
| achieved <= optimal = fromRational $ cutOffPercent 0.3 (optimal ^ 2) (achieved ^ 2)
|
| full <= optimal = 2 * invCoHeat full achieved
|
||||||
|
| achieved <= optimal = invCoHeat optimal achieved
|
||||||
| otherwise = fromRational $ 1 + cutOffPercent 0 ((full - optimal) ^ 2) ((achieved - optimal) ^ 2)
|
| otherwise = fromRational $ 1 + cutOffPercent 0 ((full - optimal) ^ 2) ((achieved - optimal) ^ 2)
|
||||||
|
|
||||||
|
dualCoHeat :: ( Real a, Real b, Real c )
|
||||||
|
=> a -> b -> c -> Milli
|
||||||
|
-- ^ Distinguishes @full@, zero is mapped to 0, @optimal@ is mapped to 1, @full@ is mapped to 2
|
||||||
|
--
|
||||||
|
-- Falls back to `invHeat` if @full <= optimal@
|
||||||
|
dualCoHeat (realToFrac -> optimal) (realToFrac -> full) (realToFrac -> achieved)
|
||||||
|
| full <= optimal = 2 * invHeat full achieved
|
||||||
|
| achieved <= optimal = fromRational $ cutOffPercent 0 (optimal ^ 2) (achieved ^ 2)
|
||||||
|
| otherwise = 1 + invHeat (full - optimal) (achieved - optimal)
|
||||||
|
|
||||||
|
invDualHeat :: ( Real a, Real b, Real c )
|
||||||
|
=> a -> b -> c -> Milli
|
||||||
|
-- ^ Distinguishes zero, zero is mapped to 2, @optimal@ is mapped to 1, @full@ is mapped to 0
|
||||||
|
invDualHeat optimal full achieved = 2 - dualHeat optimal full achieved
|
||||||
|
|
||||||
|
invDualCoHeat :: ( Real a, Real b, Real c )
|
||||||
|
=> a -> b -> c -> Milli
|
||||||
|
-- ^ Distinguishes @full@, zero is mapped to 2, @optimal@ is mapped to 1, @full@ is mapped to 0
|
||||||
|
invDualCoHeat optimal full achieved = 2 - dualCoHeat optimal full achieved
|
||||||
|
|
||||||
i18n :: forall m msg.
|
i18n :: forall m msg.
|
||||||
( MonadWidget m
|
( MonadWidget m
|
||||||
, RenderMessage (HandlerSite m) msg
|
, RenderMessage (HandlerSite m) msg
|
||||||
|
|||||||
@ -6,6 +6,14 @@ $newline never
|
|||||||
_{MsgComputedAllocation}
|
_{MsgComputedAllocation}
|
||||||
|
|
||||||
<dl .deflist>
|
<dl .deflist>
|
||||||
|
<dt .deflist__dt>
|
||||||
|
_{MsgAllocationUsersCount}
|
||||||
|
<dd .deflist__dd>
|
||||||
|
#{olength allocationUsers}
|
||||||
|
<dt .deflist__dt>
|
||||||
|
_{MsgAllocationCoursesCount}
|
||||||
|
<dd .deflist__dd>
|
||||||
|
#{olength allocationCourses}
|
||||||
<dt .deflist__dt>
|
<dt .deflist__dt>
|
||||||
_{MsgAllocationRequestedPlaces}
|
_{MsgAllocationRequestedPlaces}
|
||||||
<dd .deflist__dd>
|
<dd .deflist__dd>
|
||||||
@ -76,7 +84,7 @@ $newline never
|
|||||||
<div .table__td-content>
|
<div .table__td-content>
|
||||||
#{participants}
|
#{participants}
|
||||||
$maybe capN <- courseCapacity
|
$maybe capN <- courseCapacity
|
||||||
<td .table__td .dual-heated style="--hotness: #{allocHeat capN allocated}">
|
<td .table__td .dual-heated :degenerateHeat capN:.degenerate style="--hotness: #{allocHeat capN (allocated + participants)}">
|
||||||
<div .table__td-content>
|
<div .table__td-content>
|
||||||
#{allocated}
|
#{allocated}
|
||||||
$nothing
|
$nothing
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
$newline never
|
$newline never
|
||||||
<ul>
|
<ul>
|
||||||
$forall Entity _ Course{courseTerm, courseSchool, courseName} <- courses
|
$forall (Entity _ Course{courseTerm, courseSchool, courseName}, E.Value mbRating, E.Value mbVeto) <- courses
|
||||||
<li>
|
<li>
|
||||||
#{courseTerm} - #{courseSchool} - #{courseName}
|
#{courseTerm} - #{courseSchool} - #{courseName}
|
||||||
|
$case (mbRating, mbVeto)
|
||||||
|
$of (_, Just True)
|
||||||
|
\ (_{MsgApplicationVeto})
|
||||||
|
$of (Just g, _)
|
||||||
|
\ (_{g})
|
||||||
|
$of _
|
||||||
|
|||||||
Reference in New Issue
Block a user