- ^{candidateTable}
- |]
+ $(widgetFile "adminFeatures")
where
textInputCell lensRes lensDefault = formCell id (return . view (_dbrOutput . _entityKey))
(\row _mkUnique -> (\(res,fieldView) -> (set lensRes <$> res, fvInput fieldView))
@@ -246,23 +251,24 @@ postAdminFeaturesR = do
dbtRowKey = (E.^. StudyDegreeKey)
dbtProj = return
dbtColonnade = formColonnade $ mconcat
- [ sortable (Just "degree-key") (i18nCell MsgDegreeKey) (numCell . view (_dbrOutput . _entityVal . _studyDegreeKey))
- , sortable (Just "degree-name") (i18nCell MsgDegreeName) (textInputCell _1 (_dbrOutput . _entityVal . _studyDegreeName))
- , sortable (Just "degree-short") (i18nCell MsgDegreeShort) (textInputCell _2 (_dbrOutput . _entityVal . _studyDegreeShorthand))
+ [ sortable (Just "key") (i18nCell MsgDegreeKey) (numCell . view (_dbrOutput . _entityVal . _studyDegreeKey))
+ , sortable (Just "name") (i18nCell MsgDegreeName) (textInputCell _1 (_dbrOutput . _entityVal . _studyDegreeName))
+ , sortable (Just "short") (i18nCell MsgDegreeShort) (textInputCell _2 (_dbrOutput . _entityVal . _studyDegreeShorthand))
+ , dbRow
]
dbtSorting = Map.fromList
- [ ("degree-key" , SortColumn (E.^. StudyDegreeKey))
- , ("degree-name" , SortColumn (E.^. StudyDegreeName))
- , ("degree-short", SortColumn (E.^. StudyDegreeShorthand))
+ [ ("key" , SortColumn (E.^. StudyDegreeKey))
+ , ("name" , SortColumn (E.^. StudyDegreeName))
+ , ("short", SortColumn (E.^. StudyDegreeShorthand))
]
dbtFilter = mempty
dbtFilterUI = mempty
dbtParams = def { dbParamsFormAddSubmit = True } -- dbParamsFormEvaluate = liftHandlerT . (runFormPost . identifyForm "degree-table" - (identForm FIDdegree))}
- psValidator = def & defaultSorting [SortAscBy "degree-name", SortAscBy "degree-short", SortAscBy "degree-key"]
+ psValidator = def & defaultSorting [SortAscBy "name", SortAscBy "short", SortAscBy "key"]
in dbTable psValidator DBTable{..}
- mkStudytermsTable :: DB (FormResult (DBFormResult (Key StudyTerms) (Maybe Text, Maybe Text) (DBRow (Entity StudyTerms))), Widget)
- mkStudytermsTable =
+ mkStudytermsTable :: Set (Key StudyTerms) -> DB (FormResult (DBFormResult (Key StudyTerms) (Maybe Text, Maybe Text) (DBRow (Entity StudyTerms))), Widget)
+ mkStudytermsTable newKeys =
let dbtIdent = "admin-studyterms" :: Text
dbtStyle = def
dbtSQLQuery :: E.SqlExpr (Entity StudyTerms) -> E.SqlQuery ( E.SqlExpr (Entity StudyTerms))
@@ -270,19 +276,22 @@ postAdminFeaturesR = do
dbtRowKey = (E.^. StudyTermsKey)
dbtProj = return
dbtColonnade = formColonnade $ mconcat
- [ sortable (Just "studyterms-key") (i18nCell MsgStudyTermsKey) (numCell . view (_dbrOutput . _entityVal . _studyTermsKey))
- , sortable (Just "studyterms-name") (i18nCell MsgStudyTermsName) (textInputCell _1 (_dbrOutput . _entityVal . _studyTermsName))
- , sortable (Just "studyterms-short") (i18nCell MsgStudyTermsShort) (textInputCell _2 (_dbrOutput . _entityVal . _studyTermsShorthand))
+ [ sortable (Just "key") (i18nCell MsgStudyTermsKey) (numCell . view (_dbrOutput . _entityVal . _studyTermsKey))
+ , sortable (Just "isnew") (i18nCell MsgStudyTermIsNew) (isNewCell . flip Set.member newKeys . view (_dbrOutput . _entityKey))
+ , sortable (Just "name") (i18nCell MsgStudyTermsName) (textInputCell _1 (_dbrOutput . _entityVal . _studyTermsName))
+ , sortable (Just "short") (i18nCell MsgStudyTermsShort) (textInputCell _2 (_dbrOutput . _entityVal . _studyTermsShorthand))
+ , dbRow
]
dbtSorting = Map.fromList
- [ ("studyterms-key" , SortColumn (E.^. StudyTermsKey))
- , ("studyterms-name" , SortColumn (E.^. StudyTermsName))
- , ("studyterms-short", SortColumn (E.^. StudyTermsShorthand))
+ [ ("key" , SortColumn (E.^. StudyTermsKey))
+ , ("isnew" , SortColumn (\studyTerm -> studyTerm E.^. StudyTermsId `E.in_` E.valList (Set.toList newKeys)))
+ , ("name" , SortColumn (E.^. StudyTermsName))
+ , ("short" , SortColumn (E.^. StudyTermsShorthand))
]
dbtFilter = mempty
dbtFilterUI = mempty
dbtParams = def { dbParamsFormAddSubmit = True } -- , dbParamsFormEvaluate = liftHandlerT . runFormPost }
- psValidator = def & defaultSorting [SortAscBy "studyterms-name", SortAscBy "studyterms-short", SortAscBy "studyterms-key"]
+ psValidator = def & defaultSorting [SortAscBy "name", SortAscBy "short", SortAscBy "key"]
in dbTable psValidator DBTable{..}
mkCandidateTable =
@@ -293,14 +302,15 @@ postAdminFeaturesR = do
dbtRowKey = (E.^. StudyTermCandidateId)
dbtProj = return
dbtColonnade = dbColonnade $ mconcat
- [ sortable (Just "termcandidate-key") (i18nCell MsgStudyTermsKey) (numCell . view (_dbrOutput . _entityVal . _studyTermCandidateKey))
- , sortable (Just "termcandidate-name") (i18nCell MsgStudyTermsName) (textCell . view (_dbrOutput . _entityVal . _studyTermCandidateName))
- , sortable (Just "termcandidate-incidence") (i18nCell MsgStudyCandidateIncidence) (pathPieceCell . view (_dbrOutput . _entityVal . _studyTermCandidateIncidence))
+ [ dbRow
+ , sortable (Just "key") (i18nCell MsgStudyTermsKey) (numCell . view (_dbrOutput . _entityVal . _studyTermCandidateKey))
+ , sortable (Just "name") (i18nCell MsgStudyTermsName) (textCell . view (_dbrOutput . _entityVal . _studyTermCandidateName))
+ , sortable (Just "incidence") (i18nCell MsgStudyCandidateIncidence) (pathPieceCell . view (_dbrOutput . _entityVal . _studyTermCandidateIncidence))
]
dbtSorting = Map.fromList
- [ ("termcandidate-key" , SortColumn (E.^. StudyTermCandidateKey))
- , ("termcandidate-name" , SortColumn (E.^. StudyTermCandidateName))
- , ("termcandidate-incidence", SortColumn (E.^. StudyTermCandidateIncidence))
+ [ ("key" , SortColumn (E.^. StudyTermCandidateKey))
+ , ("name" , SortColumn (E.^. StudyTermCandidateName))
+ , ("incidence", SortColumn (E.^. StudyTermCandidateIncidence))
]
dbtFilter = Map.fromList
[ ("key", FilterColumn $ mkExactFilter (E.^. StudyTermCandidateKey))
@@ -314,6 +324,6 @@ postAdminFeaturesR = do
, prismAForm (singletonFilter "incidence") mPrev $ aopt (searchField False) (fslI MsgStudyCandidateIncidence)
]
dbtParams = def
- psValidator = def & defaultSorting [SortAscBy "termcandidate-key", SortAscBy "termcandidate-name"]
+ psValidator = def & defaultSorting [SortAscBy "key", SortAscBy "name"]
in dbTable psValidator DBTable{..}
diff --git a/src/Handler/Utils/Table/Cells.hs b/src/Handler/Utils/Table/Cells.hs
index d4b9e5249..7abd6b4d7 100644
--- a/src/Handler/Utils/Table/Cells.hs
+++ b/src/Handler/Utils/Table/Cells.hs
@@ -57,6 +57,10 @@ sqlCell act = mempty & cellContents .~ lift act
tickmarkCell :: (IsDBTable m a) => Bool -> DBCell m a
tickmarkCell = cell . toWidget . hasTickmark
+-- | Maybe display a exclamation icon
+isNewCell :: (IsDBTable m a) => Bool -> DBCell m a
+isNewCell = cell . toWidget . isNew
+
-- | Maybe display comment icon linking a given URL or show nothing at all
commentCell :: IsDBTable m a => Maybe (Route UniWorX) -> DBCell m a
commentCell Nothing = mempty
diff --git a/src/Handler/Utils/TermCandidates.hs b/src/Handler/Utils/TermCandidates.hs
index def3fff41..48fdec8cb 100644
--- a/src/Handler/Utils/TermCandidates.hs
+++ b/src/Handler/Utils/TermCandidates.hs
@@ -28,13 +28,10 @@ import qualified Database.Esqueleto as E
-- import Database.Esqueleto.Utils as E
-type STKey = Int -- Key StudyTerms -- for convenience, assmued identical to field StudyTermCandidateKey
+type STKey = Int -- for convenience, assmued identical to field StudyTermCandidateKey
data FailedCandidateInference = FailedCandidateInference [Entity StudyTerms]
- deriving (Typeable)
-
-instance Show FailedCandidateInference where
- show (FailedCandidateInference _) = "Failed Candidate Inference" -- TODO
+ deriving (Typeable, Show)
instance Exception FailedCandidateInference
-- Default Instance
@@ -43,54 +40,30 @@ instance Exception FailedCandidateInference
shortenStudyTerm :: Text -> Text
shortenStudyTerm = concatMap (take 4) . splitCamel
--- | Attempt to identify new StudyTerms based on observations
-inferHandler :: Handler ([UUID],([Entity StudyTerms],[Entity StudyTermCandidate],[(STKey,Text)]))
-inferHandler = do
- (ambiguous, problems) <- runDB $ (,) <$> removeAmbiguous <*> conflicts
- if (null problems)
- then do
- infRes <- inferAcc ([],[])
- return (ambiguous, infRes)
- else
- return (ambiguous,(problems,[],[]))
-
+-- | Attempt to identify new StudyTerms based on observations, returning:
+-- * list of ambiguous instances that were discarded outright (identical names for differents keys observed in single incidences)
+-- * list of problems, ie. StudyTerms that contradict observed incidences
+-- * list of redundants, i.e. redundant observed incidences
+-- * list of accepted, i.e. newly accepted key/name pairs
+inferHandler :: Handler ([Entity StudyTerms],[TermCandidateIncidence],[Entity StudyTermCandidate],[(STKey,Text)])
+inferHandler = runDB $ inferAcc ([],[],[])
where
- inferAcc (accRedundants, accAccepted) =
- handle (\(FailedCandidateInference fails) -> return (fails,accRedundants,accAccepted)) $ do
- (infReds,infAccs) <- runDB inferStep
+ inferAcc (accAmbiguous, accRedundants, accAccepted) =
+ handle (\(FailedCandidateInference fails) -> (fails,accAmbiguous,accRedundants,accAccepted) <$ E.transactionUndo) $ do
+ (infAmbis, infReds,infAccs) <- inferStep
if null infAccs
- then return ([], infReds ++ accRedundants, accAccepted)
- else inferAcc (infReds ++ accRedundants, infAccs ++ accAccepted)
+ then return ([], accAmbiguous, infReds ++ accRedundants, accAccepted)
+ else do
+ E.transactionSave -- commit transaction if there are no problems
+ inferAcc (infAmbis ++ accAmbiguous, infReds ++ accRedundants, infAccs ++ accAccepted)
inferStep = do
+ ambiguous <- removeAmbiguous
redundants <- removeRedundant
accepted <- acceptSingletons
problems <- conflicts
- when (not $ null problems) $ throw $ FailedCandidateInference problems
- return (redundants, accepted)
-
-
--- | Attempt to identify new StudyTerms based on observations
--- infer :: MonadHandler m => m ([Entity StudyTerms],[Entity StudyTerms])
-infer :: DB ([Entity StudyTerms],[(STKey, Text)])
-infer = do
- void removeAmbiguous -- TODO: show result
- inferAcc []
- where
- inferAcc prevSet = do
- problems <- conflicts
- if null problems
- then do
- void removeRedundant -- TODO: show result
- newSet <- acceptSingletons
- if null newSet
- then -- inference complete
- return ([],prevSet)
- else
- inferAcc (newSet ++ prevSet)
- else --abort
- return (problems,prevSet)
-
+ when (not $ null problems) $ throwM $ FailedCandidateInference problems
+ return (ambiguous, redundants, accepted)
{-
Candidate 1 11 "A"
@@ -113,7 +86,7 @@ as a fix we simply eliminate all observations that have the same name twice, see
-- | remove candidates with ambiguous observations,
-- ie. candidates that have duplicated term names with differing keys
-- which may happen in rare cases
-removeAmbiguous :: DB [UUID]
+removeAmbiguous :: DB [TermCandidateIncidence]
removeAmbiguous = do
ambiList <- E.select $ E.from $ \(candA `E.InnerJoin` candB) -> do
-- Either an innerJoin with itself or an exists-sub-select
@@ -165,7 +138,7 @@ acceptSingletons = do
groupedCandidates = foldl' groupFun mempty incidences
-- given a key, map each incidence to set of possible names for this key
- groupFun :: Map STKey (Map UUID (Set Text)) -> StudyTermCandidate -> Map STKey (Map UUID (Set Text))
+ groupFun :: Map STKey (Map TermCandidateIncidence (Set Text)) -> StudyTermCandidate -> Map STKey (Map TermCandidateIncidence (Set Text))
groupFun m StudyTermCandidate{..} =
insertWith (Map.unionWith Set.union)
studyTermCandidateKey
@@ -192,37 +165,11 @@ acceptSingletons = do
-- insertKey (StudyTermsKey key) $ StudyTerms key (Just $ shortenStudyTerm name) (Just name) -- name clash!
void . insert $ StudyTerms key (Just $ shortenStudyTerm name) (Just name)
-
-- register newly fixed candidates
forM_ fixedKeys registerFixed
return fixedKeys
- -- SOME EARLIER ATTEMPTS FOLLOW:
- --
- -- unknownKeys <- E.select $ E.distinct $ E.from $ \candidate -> do
- -- E.where_ $ E.notExists $ E.from $ \sterm ->
- -- E.where_ $ candidate E.^. StudyTermCandidateKey E.==. sterm E.^. StudyTermKey
- -- return $ candidate E.^. StudyTermCandidateKey
- -- forM unknownKeys $ \(E.Value key) -> do
- -- incidences <- E.select $ E.from $ \candidate -> do
- -- E.where_ $
- --
- -- -- DON'T KNOW HOW TO DO IN SQL :( BUT WE NEED THE ENTIRE TABLE ANYHOW
- -- candidates <- entityVal <$> selectList [] [] -- load entire candidate table
- -- -- create map from UUID to set of candidates for efficiency
- -- let collectCandidates m stc@StudyTermCandidate{studyTermCandidateIncidence=inci}
- -- = insertWith Set.union inci stc
- -- incidences = foldl collectCandidates Map.empty candidates
- --
- -- collectKeys m
- -- keySets = foldl collectKeys Map.empty candidates
- --
- -- -- StudyTermCandidateKey -> Set StudyTermCandidateName
-
-
-
-
-- | all existing StudyTerms that are contradiced by current observations
conflicts :: DB [Entity StudyTerms]
conflicts = E.select $ E.from $ \studyTerms -> do
diff --git a/src/Model/Types.hs b/src/Model/Types.hs
index d9cd98342..52fd5ed32 100644
--- a/src/Model/Types.hs
+++ b/src/Model/Types.hs
@@ -785,3 +785,4 @@ type UserEmail = CI Email
type PWHashAlgorithm = ByteString -> PWStore.Salt -> Int -> ByteString
type InstanceId = UUID
+type TermCandidateIncidence = UUID
diff --git a/src/Utils.hs b/src/Utils.hs
index 965a32f66..25142c944 100644
--- a/src/Utils.hs
+++ b/src/Utils.hs
@@ -161,6 +161,11 @@ hasTickmark :: Bool -> Markup
hasTickmark True = [shamlet|
|]
hasTickmark False = mempty
+isNew :: Bool -> Markup
+isNew True = [shamlet||]
+isNew False = mempty
+
+
---------------------
-- Text and String --
---------------------
diff --git a/templates/adminFeatures.hamlet b/templates/adminFeatures.hamlet
new file mode 100644
index 000000000..bda21478b
--- /dev/null
+++ b/templates/adminFeatures.hamlet
@@ -0,0 +1,19 @@
+
+ ^{degreeTable}
+
+ ^{studytermsTable}
+
+ _{MsgStudyFeatureInference}
+
+ $if null infConflicts
+ Kein Konflikte beobachtet.
+ $else
+
Studiengangseingträge mit beobachteten Konflikten:
+
+ $forall (Entity _ (StudyTerms ky _ nm)) <- infConflicts
+ - #{show ky} - #{foldMap id nm}
+