Correction stats coded, but not yet used online
This commit is contained in:
parent
9b7d63d469
commit
718a2b026c
5
.vscode/tasks.json
vendored
5
.vscode/tasks.json
vendored
@ -58,6 +58,11 @@
|
|||||||
"type": "npm",
|
"type": "npm",
|
||||||
"script": "start",
|
"script": "start",
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "frontend:lint",
|
||||||
|
"problemMatcher": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -394,14 +394,21 @@ UpdatedAssignedCorrectorsAuto num@Int64: #{display num} Abgaben wurden unter den
|
|||||||
CouldNotAssignCorrectorsAuto num@Int64: #{display num} Abgaben konnten nicht automatisch zugewiesen werden:
|
CouldNotAssignCorrectorsAuto num@Int64: #{display num} Abgaben konnten nicht automatisch zugewiesen werden:
|
||||||
SelfCorrectors num@Int64: #{display num} Abgaben wurden Abgebenden als eigenem Korrektor zugeteilt!
|
SelfCorrectors num@Int64: #{display num} Abgaben wurden Abgebenden als eigenem Korrektor zugeteilt!
|
||||||
|
|
||||||
|
|
||||||
|
CorrectionSheets: Übersicht Korrekturen nach Blättern
|
||||||
|
CorrectionCorrectors: Übersicht Korrekturen nach Korrektoren
|
||||||
AssignSubmissionExceptionNoCorrectors: Es sind keine Korrektoren eingestellt
|
AssignSubmissionExceptionNoCorrectors: Es sind keine Korrektoren eingestellt
|
||||||
AssignSubmissionExceptionNoCorrectorsByProportion: Es sind keine Korrektoren mit Anteil ungleich Null eingestellt
|
AssignSubmissionExceptionNoCorrectorsByProportion: Es sind keine Korrektoren mit Anteil ungleich Null eingestellt
|
||||||
AssignSubmissionExceptionSubmissionsNotFound n@Int: #{tshow n} Abgaben konnten nicht gefunden werden
|
AssignSubmissionExceptionSubmissionsNotFound n@Int: #{tshow n} Abgaben konnten nicht gefunden werden
|
||||||
|
NrSubmittorsTotal: Abgebende
|
||||||
NrSubmissionsTotal: Abgaben
|
NrSubmissionsTotal: Abgaben
|
||||||
NrSubmissionsUnassigned: Ohne Korrektor
|
NrSubmissionsUnassigned: Ohne Korrektor
|
||||||
|
NoCorrectorAssigned: Ohne Korrektor
|
||||||
NrCorrectors: Korrektoren
|
NrCorrectors: Korrektoren
|
||||||
NrSubmissionsNewlyAssigned: Neu zugeteilt
|
NrSubmissionsNewlyAssigned: Neu zugeteilt
|
||||||
NrSubmissionsNotAssigned: Nicht zugeteilt
|
NrSubmissionsNotAssigned: Nicht zugeteilt
|
||||||
|
NrSubmissionsNotCorrected: Unkorrigiert
|
||||||
|
CorrectionTime: Korrekturdauer (Min/Avg/Max)
|
||||||
|
|
||||||
CorrectionsUploaded num@Int64: #{display num} Korrekturen wurden gespeichert:
|
CorrectionsUploaded num@Int64: #{display num} Korrekturen wurden gespeichert:
|
||||||
NoCorrectionsUploaded: In der hochgeladenen Datei wurden keine Korrekturen gefunden.
|
NoCorrectionsUploaded: In der hochgeladenen Datei wurden keine Korrekturen gefunden.
|
||||||
@ -971,7 +978,7 @@ TutorialDelete: Löschen
|
|||||||
|
|
||||||
CourseTutorials: Übungen
|
CourseTutorials: Übungen
|
||||||
|
|
||||||
ParticipantsN n@Int: Teilnehmer
|
ParticipantsN n@Int: #{tshow n} Teilnehmer
|
||||||
TutorialDeleteQuestion: Wollen Sie das unten aufgeführte Tutorium wirklich löschen?
|
TutorialDeleteQuestion: Wollen Sie das unten aufgeführte Tutorium wirklich löschen?
|
||||||
TutorialDeleted: Tutorium gelöscht
|
TutorialDeleted: Tutorium gelöscht
|
||||||
|
|
||||||
@ -1007,6 +1014,7 @@ HealthLDAPAdmins: Anteil der Administratoren, die im LDAP-Verzeichnis gefunden w
|
|||||||
HealthSMTPConnect: SMTP-Server kann erreicht werden
|
HealthSMTPConnect: SMTP-Server kann erreicht werden
|
||||||
HealthWidgetMemcached: Memcached-Server liefert Widgets korrekt aus
|
HealthWidgetMemcached: Memcached-Server liefert Widgets korrekt aus
|
||||||
|
|
||||||
|
CourseParticipants n@Int: Derzeit #{tshow n} angemeldete Kursteilnehmer
|
||||||
CourseParticipantsInvited n@Int: #{tshow n} #{pluralDE n "Einladung" "Einladungen"} per E-Mail verschickt
|
CourseParticipantsInvited n@Int: #{tshow n} #{pluralDE n "Einladung" "Einladungen"} per E-Mail verschickt
|
||||||
CourseParticipantsAlreadyRegistered n@Int: #{tshow n} Teilnehmer #{pluralDE n "ist" "sind"} bereits angemeldet
|
CourseParticipantsAlreadyRegistered n@Int: #{tshow n} Teilnehmer #{pluralDE n "ist" "sind"} bereits angemeldet
|
||||||
CourseParticipantsRegisteredWithoutField n@Int: #{tshow n} Teilnehmer #{pluralDE n "wurde ohne assoziiertes Hauptfach" "wurden assoziierte Hauptfächer"} angemeldet, da #{pluralDE n "kein eindeutiges Hauptfach bestimmt werden konnte" "keine eindeutigen Hauptfächer bestimmt werden konnten"}
|
CourseParticipantsRegisteredWithoutField n@Int: #{tshow n} Teilnehmer #{pluralDE n "wurde ohne assoziiertes Hauptfach" "wurden assoziierte Hauptfächer"} angemeldet, da #{pluralDE n "kein eindeutiges Hauptfach bestimmt werden konnte" "keine eindeutigen Hauptfächer bestimmt werden konnten"}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ Sheet -- exercise sheet for a given course
|
|||||||
description Html Maybe
|
description Html Maybe
|
||||||
type SheetType -- Does it count towards overall course grade?
|
type SheetType -- Does it count towards overall course grade?
|
||||||
grouping SheetGroup -- May participants submit in groups of certain sizes?
|
grouping SheetGroup -- May participants submit in groups of certain sizes?
|
||||||
markingText Html Maybe -- Instructions for correctors, included in marking templates
|
markingText Html Maybe -- Instructons for correctors, included in marking templates
|
||||||
visibleFrom UTCTime Maybe -- Invisible to enrolled participants before
|
visibleFrom UTCTime Maybe -- Invisible to enrolled participants before
|
||||||
activeFrom UTCTime -- Download of questions and submission is permitted afterwards
|
activeFrom UTCTime -- Download of questions and submission is permitted afterwards
|
||||||
activeTo UTCTime -- Submission is only permitted before
|
activeTo UTCTime -- Submission is only permitted before
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import Import
|
|||||||
|
|
||||||
import Jobs
|
import Jobs
|
||||||
import Handler.Utils
|
import Handler.Utils
|
||||||
|
import Handler.Utils.Corrections
|
||||||
import Handler.Utils.Submission
|
import Handler.Utils.Submission
|
||||||
import Handler.Utils.Table.Cells
|
import Handler.Utils.Table.Cells
|
||||||
import Handler.Utils.SheetType
|
import Handler.Utils.SheetType
|
||||||
@ -13,11 +14,11 @@ import Handler.Utils.Delete
|
|||||||
|
|
||||||
import Utils.Lens
|
import Utils.Lens
|
||||||
|
|
||||||
import Data.List (nub)
|
import Data.List as List (nub, foldl, foldr)
|
||||||
import Data.Set (Set)
|
import Data.Set (Set)
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
import Data.Map (Map, (!))
|
import Data.Map.Strict (Map, (!))
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map.Strict as Map
|
||||||
|
|
||||||
import qualified Data.Text as Text
|
import qualified Data.Text as Text
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
@ -250,7 +251,7 @@ makeCorrectionsTable whereClause dbtColonnade dbtFilterUI psValidator dbtProj' d
|
|||||||
E.orderBy [E.asc $ user E.^. UserSurname, E.asc $ user E.^. UserDisplayName]
|
E.orderBy [E.asc $ user E.^. UserSurname, E.asc $ user E.^. UserDisplayName]
|
||||||
return (user, pseudonym E.?. SheetPseudonymPseudonym)
|
return (user, pseudonym E.?. SheetPseudonymPseudonym)
|
||||||
let
|
let
|
||||||
submittorMap = foldr (\(Entity userId user, E.Value pseudo) -> Map.insert userId (user, pseudo)) Map.empty submittors
|
submittorMap = List.foldr (\(Entity userId user, E.Value pseudo) -> Map.insert userId (user, pseudo)) Map.empty submittors
|
||||||
dbtProj' (submission, sheet, (courseName, courseShorthand, courseTerm, courseSchool), mCorrector, mbLastEdit, submittorMap)
|
dbtProj' (submission, sheet, (courseName, courseShorthand, courseTerm, courseSchool), mCorrector, mbLastEdit, submittorMap)
|
||||||
dbTable psValidator DBTable
|
dbTable psValidator DBTable
|
||||||
{ dbtSQLQuery
|
{ dbtSQLQuery
|
||||||
@ -1035,7 +1036,7 @@ embedRenderMessage ''UniWorX ''ButtonSubmissionsAssign id
|
|||||||
instance Button UniWorX ButtonSubmissionsAssign where
|
instance Button UniWorX ButtonSubmissionsAssign where
|
||||||
btnClasses BtnSubmissionsAssign = [BCIsButton, BCPrimary]
|
btnClasses BtnSubmissionsAssign = [BCIsButton, BCPrimary]
|
||||||
|
|
||||||
-- | Gather info about corrector assignment per sheet
|
-- | DEPRECATED use CorrectorInfo instead. Gather info about corrector assignment per sheet
|
||||||
data SubAssignInfo = SubAssignInfo { saiName :: SheetName, saiSubmissionNr, saiCorrectorNr, saiUnassignedNr :: Int }
|
data SubAssignInfo = SubAssignInfo { saiName :: SheetName, saiSubmissionNr, saiCorrectorNr, saiUnassignedNr :: Int }
|
||||||
|
|
||||||
getCAssignR, postCAssignR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
getCAssignR, postCAssignR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||||
@ -1092,3 +1093,134 @@ assignHandler tid ssh csh rawSids = do
|
|||||||
then linkBack -- TODO: convenience link might be unnecessary: either via breadcrumb or closing model. Remove, if modal works fine. Otherwise change to PrimaryAction?
|
then linkBack -- TODO: convenience link might be unnecessary: either via breadcrumb or closing model. Remove, if modal works fine. Otherwise change to PrimaryAction?
|
||||||
else btnForm
|
else btnForm
|
||||||
|
|
||||||
|
|
||||||
|
assignHandler' :: TermId -> SchoolId -> CourseShorthand -> [SheetId] -> Handler Html
|
||||||
|
assignHandler' tid ssh csh _rawSids = do
|
||||||
|
-- gather data
|
||||||
|
(nrParticipants, groupsPossible, infoMap, correctorMap) <- runDB $ do
|
||||||
|
cid <- getKeyBy404 $ TermSchoolCourseShort tid ssh csh
|
||||||
|
nrParticipants <- count [CourseParticipantCourse ==. cid]
|
||||||
|
|
||||||
|
sheetList <- selectList [SheetCourse ==. cid] [Asc SheetName]
|
||||||
|
let sheets = entities2map sheetList
|
||||||
|
sheetIds = Map.keys sheets
|
||||||
|
groupsPossible :: Bool
|
||||||
|
groupsPossible =
|
||||||
|
let foldFun (Entity _ Sheet{sheetGrouping=sgr}) acc = acc || sgr /= NoGroups
|
||||||
|
in List.foldr foldFun False sheetList
|
||||||
|
|
||||||
|
correctors <- E.select . E.from $ \(corrector `E.InnerJoin` user) -> do
|
||||||
|
E.on $ corrector E.^. SheetCorrectorUser E.==. user E.^. UserId
|
||||||
|
E.where_ $ corrector E.^. SheetCorrectorSheet `E.in_` E.valList sheetIds
|
||||||
|
return (corrector, user)
|
||||||
|
let correctorMap :: Map UserId (SheetCorrector,User)
|
||||||
|
correctorMap = foldl (\m (Entity _ corr, Entity uid user)-> Map.insert uid (corr,user) m) Map.empty correctors
|
||||||
|
|
||||||
|
submissions <- E.select . E.from $ \submission -> do
|
||||||
|
E.where_ $ submission E.^. SubmissionSheet `E.in_` E.valList sheetIds
|
||||||
|
let numSubmittors = E.sub_select . E.from $ \subUser -> do
|
||||||
|
E.where_ $ submission E.^. SubmissionId E.==. subUser E.^. SubmissionUserSubmission
|
||||||
|
return E.countRows
|
||||||
|
return (submission, numSubmittors)
|
||||||
|
-- prepare map
|
||||||
|
let infoMap :: Map SheetName (Map (Maybe UserId) CorrectionInfo)
|
||||||
|
infoMap = List.foldl (flip buildS) emptySheets submissions
|
||||||
|
|
||||||
|
-- ensure that all sheets are shown, including those without any submissions
|
||||||
|
emptySheets = foldl (\m sid -> Map.insert (sheetName $ sheets ! sid) Map.empty m) Map.empty sheetIds
|
||||||
|
|
||||||
|
buildS :: (Entity Submission, E.Value Int64) -> Map SheetName (Map (Maybe UserId) CorrectionInfo) -> Map SheetName (Map (Maybe UserId) CorrectionInfo)
|
||||||
|
buildS (Entity _sheetId Submission{..}, E.Value nrSbmtrs) m =
|
||||||
|
let shnm = sheetName $ sheets ! submissionSheet
|
||||||
|
corTime = diffUTCTime <$> submissionRatingTime <*> submissionRatingAssigned
|
||||||
|
cinf = Map.singleton submissionRatingBy $ CorrectionInfo
|
||||||
|
{ ciSubmittors = fromIntegral nrSbmtrs
|
||||||
|
, ciSubmissions = 1
|
||||||
|
, ciAssigned = maybe 0 (const 1) submissionRatingBy
|
||||||
|
, ciCorrected = maybe 0 (const 1) submissionRatingTime
|
||||||
|
, ciCorrector = submissionRatingBy
|
||||||
|
, ciMin = corTime
|
||||||
|
, ciTot = corTime
|
||||||
|
, ciMax = corTime
|
||||||
|
}
|
||||||
|
in Map.insertWith (Map.unionWith (<>)) shnm cinf m
|
||||||
|
return (nrParticipants, groupsPossible, infoMap, correctorMap)
|
||||||
|
|
||||||
|
let -- create aggregate maps
|
||||||
|
sheetMap :: Map SheetName CorrectionInfo
|
||||||
|
sheetMap = Map.map fold infoMap
|
||||||
|
corrMap :: Map (Maybe UserId) CorrectionInfo
|
||||||
|
corrMap = Map.unionsWith (<>) $ Map.elems infoMap
|
||||||
|
sheetNames = Map.keys infoMap
|
||||||
|
let -- whamlet convenience functions
|
||||||
|
showCorrector :: Maybe UserId -> Widget
|
||||||
|
showCorrector (Just uid)
|
||||||
|
| Just (_,User{..}) <- Map.lookup uid correctorMap
|
||||||
|
= nameEmailWidget userEmail userDisplayName userSurname
|
||||||
|
showCorrector _ = [whamlet|_{MsgNoCorrectorAssigned}|]
|
||||||
|
showDiffDays :: Maybe NominalDiffTime -> Text
|
||||||
|
showDiffDays = foldMap formatDiffDays
|
||||||
|
showAvgsDays :: Maybe NominalDiffTime -> Integer -> Text
|
||||||
|
showAvgsDays Nothing _ = mempty
|
||||||
|
showAvgsDays (Just dt) n = formatDiffDays $ dt / fromIntegral n
|
||||||
|
let headingShort = MsgMenuCorrectionsAssign
|
||||||
|
headingLong = prependCourseTitle tid ssh csh MsgMenuCorrectionsAssign
|
||||||
|
siteLayoutMsg headingShort $ do
|
||||||
|
setTitleI headingLong
|
||||||
|
-- TODO: Move whamlet into separate Widget-File, once completed
|
||||||
|
[whamlet|
|
||||||
|
<div>
|
||||||
|
<h2>_{MsgCorrectionSheets}
|
||||||
|
_{MsgCourseParticipants nrParticipants}
|
||||||
|
<table .table .table--striped .table--hover>
|
||||||
|
<tr .table__row .table__row--head>
|
||||||
|
<th .table__th>_{MsgSheet}
|
||||||
|
$if groupsPossible
|
||||||
|
<th .table__th>_{MsgNrSubmittorsTotal}
|
||||||
|
<th .table__th>_{MsgNrSubmissionsTotal}
|
||||||
|
<th .table__th>_{MsgNrSubmissionsNotAssigned}
|
||||||
|
<th .table__th>_{MsgNrSubmissionsNotCorrected}
|
||||||
|
<th .table__th colspan=3>_{MsgCorrectionTime}
|
||||||
|
$forall (sheetName, CorrectionInfo{ciSubmittors, ciSubmissions, ciAssigned, ciCorrected, ciMin, ciTot, ciMax}) <- Map.toList sheetMap
|
||||||
|
<tr .table__row>
|
||||||
|
<td .table__td>^{simpleLink (toWidget sheetName) (CSheetR tid ssh csh sheetName SSubsR)}
|
||||||
|
$if groupsPossible
|
||||||
|
<td .table__td>#{ciSubmittors}
|
||||||
|
<td .table__td>#{ciSubmissions}
|
||||||
|
<td .table__td>#{ciSubmissions - ciAssigned}
|
||||||
|
<td .table__td>#{ciSubmissions - ciCorrected}
|
||||||
|
<td .table__td>#{showDiffDays ciMin}
|
||||||
|
<td .table__td>#{showAvgsDays ciTot ciCorrected}
|
||||||
|
<td .table__td>#{showDiffDays ciMax}
|
||||||
|
<div>
|
||||||
|
<h2>_{MsgCorrectionCorrectors}
|
||||||
|
<table .table .table--striped .table--hover>
|
||||||
|
<tr .table__row .table__row--head>
|
||||||
|
<th .table__th>_{MsgCorrector}
|
||||||
|
<th .table__th>_{MsgNrSubmissionsTotal}
|
||||||
|
<th .table__th>_{MsgNrSubmissionsNotCorrected}
|
||||||
|
<th .table__th colspan=3>_{MsgCorrectionTime}
|
||||||
|
$forall shn <- sheetNames
|
||||||
|
<th .table__th colspan=3>#{shn}
|
||||||
|
$forall (CorrectionInfo{ciCorrector, ciSubmissions, ciCorrected, ciMin, ciTot, ciMax}) <- Map.elems corrMap
|
||||||
|
<tr .table__row>
|
||||||
|
<td .table__td>^{showCorrector ciCorrector}
|
||||||
|
<td .table__td>#{ciSubmissions}
|
||||||
|
<td .table__td>#{ciSubmissions - ciCorrected}
|
||||||
|
<td .table__td>#{showDiffDays ciMin}
|
||||||
|
<td .table__td>#{showAvgsDays ciTot ciCorrected}
|
||||||
|
<td .table__td>#{showDiffDays ciMax}
|
||||||
|
$forall shn <- sheetNames
|
||||||
|
$maybe smap <- Map.lookup shn infoMap
|
||||||
|
$maybe CorrectionInfo{ciAssigned,ciCorrected,ciTot} <- Map.lookup ciCorrector smap
|
||||||
|
<td .table__td>#{ciAssigned}
|
||||||
|
<td .table__td>#{ciAssigned - ciCorrected}
|
||||||
|
<td .table__td>#{showAvgsDays ciTot ciCorrected}
|
||||||
|
$nothing
|
||||||
|
<td .table__td colspan=3>
|
||||||
|
$nothing
|
||||||
|
<td .table__td colspan=3>
|
||||||
|
|]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -136,7 +136,7 @@ postTDeleteR tid ssh csh tutn = do
|
|||||||
return E.countRows
|
return E.countRows
|
||||||
return (course, tutorial, participants :: E.SqlExpr (E.Value Int))
|
return (course, tutorial, participants :: E.SqlExpr (E.Value Int))
|
||||||
, drRenderRecord = \(Entity _ Course{..}, Entity _ Tutorial{..}, E.Value ps) ->
|
, drRenderRecord = \(Entity _ Course{..}, Entity _ Tutorial{..}, E.Value ps) ->
|
||||||
return [whamlet|_{prependCourseTitle courseTerm courseSchool courseShorthand (CI.original tutorialName)} (#{tshow ps} _{MsgParticipantsN ps})|]
|
return [whamlet|_{prependCourseTitle courseTerm courseSchool courseShorthand (CI.original tutorialName)} (_{MsgParticipantsN ps})|]
|
||||||
, drRecordConfirmString = \(Entity _ Course{..}, Entity _ Tutorial{..}, E.Value ps) ->
|
, drRecordConfirmString = \(Entity _ Course{..}, Entity _ Tutorial{..}, E.Value ps) ->
|
||||||
return [st|#{termToText (unTermKey courseTerm)}/#{unSchoolKey courseSchool}/#{courseShorthand}/#{tutorialName}+#{tshow ps}|]
|
return [st|#{termToText (unTermKey courseTerm)}/#{unSchoolKey courseSchool}/#{courseShorthand}/#{tutorialName}+#{tshow ps}|]
|
||||||
, drCaption = SomeMessage MsgTutorialDeleteQuestion
|
, drCaption = SomeMessage MsgTutorialDeleteQuestion
|
||||||
|
|||||||
45
src/Handler/Utils/Corrections.hs
Normal file
45
src/Handler/Utils/Corrections.hs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
module Handler.Utils.Corrections where
|
||||||
|
|
||||||
|
import Import
|
||||||
|
|
||||||
|
|
||||||
|
-- CorrectionInfo has seeming redundancies, but these are useful for aggregation
|
||||||
|
-- INVARIANT: isJust ciTot `implies` ciCorrected > 0
|
||||||
|
data CorrectionInfo = CorrectionInfo
|
||||||
|
{ ciSubmittors, ciSubmissions, ciAssigned, ciCorrected :: Integer
|
||||||
|
, ciCorrector :: Maybe UserId
|
||||||
|
, ciTot, ciMin, ciMax :: Maybe NominalDiffTime
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Semigroup CorrectionInfo where
|
||||||
|
corrA <> corrB =
|
||||||
|
assert (isJust (ciTot corrA) `implies` (ciCorrected corrA > 0)) $
|
||||||
|
assert (isJust (ciTot corrB) `implies` (ciCorrected corrB > 0))
|
||||||
|
CorrectionInfo
|
||||||
|
{ ciSubmittors = ciSubmittors `mergeWith` (+)
|
||||||
|
, ciSubmissions = ciSubmissions `mergeWith` (+)
|
||||||
|
, ciAssigned = ciAssigned `mergeWith` (+)
|
||||||
|
, ciCorrected = ciCorrected `mergeWith` (+)
|
||||||
|
, ciCorrector = ciCorrector `mergeWith` keepEqual
|
||||||
|
, ciTot = ciTot `mergeWith` ignoreNothing (+)
|
||||||
|
, ciMin = ciMin `mergeWith` ignoreNothing min
|
||||||
|
, ciMax = ciMax `mergeWith` ignoreNothing max
|
||||||
|
}
|
||||||
|
where
|
||||||
|
mergeWith :: (CorrectionInfo -> a) -> (a -> a -> c) -> c
|
||||||
|
mergeWith prj f = on f prj corrA corrB
|
||||||
|
|
||||||
|
keepEqual (Just x) (Just y) | x==y = Just x
|
||||||
|
keepEqual _ _ = Nothing
|
||||||
|
|
||||||
|
instance Monoid CorrectionInfo where
|
||||||
|
mappend = (<>)
|
||||||
|
mempty = CorrectionInfo { ciSubmittors = 0
|
||||||
|
, ciSubmissions = 0
|
||||||
|
, ciAssigned = 0
|
||||||
|
, ciCorrected = 0
|
||||||
|
, ciCorrector = Nothing
|
||||||
|
, ciMin = Nothing
|
||||||
|
, ciTot = Nothing
|
||||||
|
, ciMax = Nothing
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ module Handler.Utils.DateTime
|
|||||||
( utcToLocalTime
|
( utcToLocalTime
|
||||||
, localTimeToUTC, TZ.LocalToUTCResult(..)
|
, localTimeToUTC, TZ.LocalToUTCResult(..)
|
||||||
, toMidnight, beforeMidnight, toMidday, toMorning
|
, toMidnight, beforeMidnight, toMidday, toMorning
|
||||||
|
, formatDiffDays
|
||||||
, formatTime, formatTime', formatTimeW
|
, formatTime, formatTime', formatTimeW
|
||||||
, getTimeLocale, getDateTimeFormat
|
, getTimeLocale, getDateTimeFormat
|
||||||
, validDateTimeFormats, dateTimeFormatOptions
|
, validDateTimeFormats, dateTimeFormatOptions
|
||||||
@ -29,6 +30,37 @@ import qualified Data.Set as Set
|
|||||||
import Data.Time.Clock.System (systemEpochDay)
|
import Data.Time.Clock.System (systemEpochDay)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--------------------
|
||||||
|
-- NominalDiffTime
|
||||||
|
|
||||||
|
-- | One hour in 'NominalDiffTime'.
|
||||||
|
nominalHour :: NominalDiffTime
|
||||||
|
nominalHour = 3600
|
||||||
|
|
||||||
|
-- | One minute in 'NominalDiffTime'.
|
||||||
|
nominalMinute :: NominalDiffTime
|
||||||
|
nominalMinute= 60
|
||||||
|
|
||||||
|
formatDiffDays :: NominalDiffTime -> Text
|
||||||
|
formatDiffDays t
|
||||||
|
| t > nominalDay = inDays <> "d"
|
||||||
|
| t > nominalHour = inHours <> "h"
|
||||||
|
| t > nominalMinute = inMinutes <> "m"
|
||||||
|
| otherwise = tshow $ roundToDigits 0 t
|
||||||
|
where
|
||||||
|
convertBy :: NominalDiffTime -> Double
|
||||||
|
convertBy len = realToFrac $ roundToDigits 1 $ t / len
|
||||||
|
inDays = tshow $ convertBy nominalDay
|
||||||
|
inHours = tshow $ convertBy nominalHour
|
||||||
|
inMinutes = tshow $ convertBy nominalMinute
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
------------
|
||||||
|
-- UTCTime
|
||||||
|
|
||||||
|
|
||||||
utcToLocalTime :: UTCTime -> LocalTime
|
utcToLocalTime :: UTCTime -> LocalTime
|
||||||
utcToLocalTime = TZ.utcToLocalTimeTZ appTZ
|
utcToLocalTime = TZ.utcToLocalTimeTZ appTZ
|
||||||
|
|
||||||
@ -52,7 +84,6 @@ toMorning :: Day -> UTCTime
|
|||||||
toMorning d = localTimeToUTCTZ appTZ $ LocalTime d $ TimeOfDay 6 0 0
|
toMorning d = localTimeToUTCTZ appTZ $ LocalTime d $ TimeOfDay 6 0 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class FormatTime t => HasLocalTime t where
|
class FormatTime t => HasLocalTime t where
|
||||||
toLocalTime :: t -> LocalTime
|
toLocalTime :: t -> LocalTime
|
||||||
|
|
||||||
|
|||||||
23
src/Utils.hs
23
src/Utils.hs
@ -343,6 +343,17 @@ notUsedT = notUsed
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----------
|
||||||
|
-- Bool --
|
||||||
|
----------
|
||||||
|
|
||||||
|
-- | Logical implication, readable synonym for (<=) which appears the wrong way around
|
||||||
|
implies :: Bool -> Bool -> Bool
|
||||||
|
implies True x = x
|
||||||
|
implies _ _ = True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-------------
|
-------------
|
||||||
-- Numeric --
|
-- Numeric --
|
||||||
-------------
|
-------------
|
||||||
@ -523,12 +534,6 @@ flipMaybe :: b -> Maybe a -> Maybe b
|
|||||||
flipMaybe x Nothing = Just x
|
flipMaybe x Nothing = Just x
|
||||||
flipMaybe _ (Just _) = Nothing
|
flipMaybe _ (Just _) = Nothing
|
||||||
|
|
||||||
|
|
||||||
maybeAdd :: Num a => Maybe a -> Maybe a -> Maybe a -- treats Nothing as neutral/zero, unlike fmap/ap
|
|
||||||
maybeAdd (Just x) (Just y) = Just (x + y)
|
|
||||||
maybeAdd Nothing y = y
|
|
||||||
maybeAdd x Nothing = x
|
|
||||||
|
|
||||||
-- | Deep alternative to avoid any occurrence of Nothing at all costs, left-biased
|
-- | Deep alternative to avoid any occurrence of Nothing at all costs, left-biased
|
||||||
deepAlt :: Maybe (Maybe a) -> Maybe (Maybe a) -> Maybe (Maybe a)
|
deepAlt :: Maybe (Maybe a) -> Maybe (Maybe a) -> Maybe (Maybe a)
|
||||||
deepAlt Nothing altSnd = altSnd
|
deepAlt Nothing altSnd = altSnd
|
||||||
@ -574,6 +579,12 @@ mcons :: Maybe a -> [a] -> [a]
|
|||||||
mcons Nothing xs = xs
|
mcons Nothing xs = xs
|
||||||
mcons (Just x) xs = x:xs
|
mcons (Just x) xs = x:xs
|
||||||
|
|
||||||
|
-- | apply binary function to maybes, but ignores Nothing by using id if possible, unlike fmap/ap
|
||||||
|
ignoreNothing :: (a -> a -> a) -> Maybe a -> Maybe a -> Maybe a
|
||||||
|
ignoreNothing _ Nothing y = y
|
||||||
|
ignoreNothing _ x Nothing = x
|
||||||
|
ignoreNothing f (Just x) (Just y) = Just $ f x y
|
||||||
|
|
||||||
newtype NTop a = NTop { nBot :: a } -- treat Nothing as Top for Ord (Maybe a); default implementation treats Nothing as bottom
|
newtype NTop a = NTop { nBot :: a } -- treat Nothing as Top for Ord (Maybe a); default implementation treats Nothing as bottom
|
||||||
|
|
||||||
instance Eq a => Eq (NTop (Maybe a)) where
|
instance Eq a => Eq (NTop (Maybe a)) where
|
||||||
|
|||||||
Reference in New Issue
Block a user