forked from Courseography/courseography
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTables.hs
More file actions
332 lines (289 loc) · 9.11 KB
/
Copy pathTables.hs
File metadata and controls
332 lines (289 loc) · 9.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
{-# LANGUAGE DataKinds, DeriveGeneric, DerivingStrategies, EmptyDataDecls, FlexibleContexts,
FlexibleInstances, GADTs, GeneralizedNewtypeDeriving, MultiParamTypeClasses,
QuasiQuotes, StandaloneDeriving, TemplateHaskell, TypeFamilies, TypeOperators,
UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
{-|
Module : Database.Tables
Description : The database schema (and some helpers).
This module defines the database schema. It uses Template Haskell to also
create new types for these values so that they can be used in the rest of
the application.
Though types and typeclass instances are created automatically, we currently
have a few manually-generated spots to clean up. This should be rather
straightforward.
-}
module Database.Tables where
import Data.Aeson (FromJSON (parseJSON), ToJSON (toJSON), genericToJSON, withObject,
(.!=), (.:), (.:?))
import Data.Aeson.Types (Options (..), Parser, Value (Object), defaultOptions)
import Data.Char (toLower)
import qualified Data.Text as T
import Data.Time.Clock (UTCTime)
import Database.DataType
import Database.Persist.Sqlite (Key, SqlPersistM, entityVal, selectFirst, (==.))
import Database.Persist.TH
import GHC.Generics
-- | A two-dimensional point.
type Point = (Double, Double)
-- | A matrix of any dimensions.
type Matrix = [[Double]]
-- | A vector of any dimesions.
type Vector = [Double]
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Department json
name T.Text
Primary name
UniqueName name
Courses
code T.Text
Primary code
title T.Text Maybe
description T.Text Maybe
prereqs T.Text Maybe
exclusions T.Text Maybe
breadth BreadthId Maybe
distribution DistributionId Maybe
prereqString T.Text Maybe
coreqs T.Text Maybe
videoUrls [T.Text]
deriving Show
Meeting
code T.Text
session T.Text
section T.Text
cap Int
instructor T.Text
enrol Int
wait Int
extra Int
deriving Generic Show Eq
UniqueMeeting code session section
Times
weekDay Double
startHour Double
endHour Double
meeting MeetingId
firstRoom T.Text Maybe
secondRoom T.Text Maybe
Breadth
description T.Text
deriving Show
Distribution
description T.Text
deriving Show
Graph json
title T.Text
width Double
height Double
dynamic Bool
deriving Show
Text json
graph GraphId
rId T.Text
pos Point
text T.Text
align T.Text
fill T.Text
deriving Show Eq
transform [Double] default=[1,0,0,1,0,0]
Shape json
graph GraphId
id_ T.Text
pos Point
width Double
height Double
fill T.Text
stroke T.Text
text [Text]
type_ ShapeType
deriving Show Eq
transform [Double] default=[1,0,0,1,0,0]
Path json
graph GraphId
id_ T.Text
points [Point]
fill T.Text
stroke T.Text
isRegion Bool
source T.Text
target T.Text
deriving Show Eq
transform [Double] default=[1,0,0,1,0,0]
Program
name ProgramType
department T.Text
code T.Text
--UniqueProgramCode code
--Primary code
description T.Text
requirements T.Text
created UTCTime
modified UTCTime
deriving Show Eq Generic
ProgramCategory
program ProgramId
name T.Text
deriving Show
Building
code T.Text
name T.Text
address T.Text
postalCode T.Text
lat Double
lng Double
deriving Generic Show
SchemaVersion
version Int
deriving Show Eq
|]
-- ** TODO: Remove these extra types and class instances
data Time' =
Time' { weekDay' :: Double,
startHour' :: Double,
endHour' :: Double,
firstLocation' :: Maybe T.Text,
secondLocation' :: Maybe T.Text
} deriving (Show, Eq, Generic)
data Time =
Time { weekDay :: Double,
startHour :: Double,
endHour :: Double,
firstLocation :: Maybe Building,
secondLocation :: Maybe Building
} deriving (Show, Generic)
-- | A Meeting with its associated Times.
data MeetTime = MeetTime {meetInfo :: Meeting, timeInfo :: [Time'] }
deriving (Show, Generic)
data MeetTime' = MeetTime' { meetData :: Meeting, timeData :: [Time] }
deriving (Show, Generic)
-- | A Course. TODO: remove this data type (it's redundant).
data Course =
Course { breadth :: Maybe T.Text,
description :: Maybe T.Text,
title :: Maybe T.Text,
prereqString :: Maybe T.Text,
allMeetingTimes :: Maybe [MeetTime'],
name :: !T.Text,
exclusions :: Maybe T.Text,
distribution :: Maybe T.Text,
coreqs :: Maybe T.Text,
videoUrls :: [T.Text]
} deriving (Show, Generic)
instance ToJSON Course
instance ToJSON Program
instance ToJSON Time
instance ToJSON MeetTime'
instance ToJSON Building
instance ToJSON Meeting where
toJSON = genericToJSON defaultOptions {
fieldLabelModifier =
lowerFirst .
drop 7
}
where
lowerFirst :: [Char] -> String
lowerFirst [] = ""
lowerFirst (fieldHead: fieldTail) = toLower fieldHead: fieldTail
instance FromJSON Meeting where
parseJSON = withObject "Expected Object for Lecture, Tutorial or Practical" $ \o -> do
teachingMethod :: T.Text <- o .:? "teachMethod" .!= ""
sectionNumber :: T.Text <- o .:? "sectionNumber" .!= ""
let sectionId = T.concat [teachingMethod, sectionNumber]
cap <- o .:? "maxEnrolment" .!= (-1)
enrol <- o .:? "currentEnrolment" .!= 0
wait <- o .:? "currentWaitlist" .!= 0
instrList <- o .:? "instructors" .!= []
instrs <- mapM parseInstr instrList
let extra = 0
let instructor = T.intercalate "; " $ filter (not . T.null) instrs
if teachingMethod == "LEC" || teachingMethod == "TUT" || teachingMethod == "PRA"
then
return $ Meeting "" "" sectionId cap instructor enrol wait extra
else
fail "Not a lecture, Tutorial or Practical"
instance FromJSON Time' where
parseJSON = withObject "Expected Object for Times" $ \o -> do
startObject <- o .: "start"
endObject <- o .: "end"
meetingDay :: Maybe Int <- startObject .:? "day" .!= Nothing
meetingStartTime :: Maybe Int <- startObject .:? "millisofday" .!= Nothing
meetingEndTime :: Maybe Int <- endObject .:? "millisofday" .!= Nothing
building <- o .: "building"
buildingCode <- building .: "buildingCode"
buildingRoomNumber <- building .: "buildingRoomNumber"
let meetingRoom1 = Just (T.concat [buildingCode, buildingRoomNumber])
meetingRoom2 <- o .:? "assignedRoom2" .!= Nothing
let (adjustedDay, adjustedStartTime, adjustedEndTime) = convertTimeVals meetingDay meetingStartTime meetingEndTime
return $ Time' adjustedDay adjustedStartTime adjustedEndTime meetingRoom1 meetingRoom2
instance FromJSON MeetTime where
parseJSON (Object o) = do
meeting <- parseJSON (Object o)
timesList :: [Time'] <- o .:? "meetingTimes" .!= []
return $ MeetTime meeting timesList
parseJSON _ = fail "Invalid meeting"
-- | Helpers for parsing JSON
parseInstr :: Value -> Parser T.Text
parseInstr (Object io) = do
firstName <- io .:? "firstName" .!= ""
lastName <- io .:? "lastName" .!= ""
return (T.concat [firstName, ". ", lastName])
parseInstr _ = return ""
-- | Converts the miliseconds time into hourly time
-- | Assumes times are rounded to the nearest hour
getHourVal :: Int -> Double
getHourVal millis =
let
seconds = fromIntegral millis / 1000.0
minutes = seconds / 60
hours = minutes / 60
in
hours
-- | Converts a the given day into a double representation for the database
-- | Monday (1) to Friday (5) becomes 0.0 to 4.0
getDayVal :: Int -> Double
getDayVal 1 = 0.0
getDayVal 2 = 1.0
getDayVal 3 = 2.0
getDayVal 4 = 3.0
getDayVal 5 = 4.0
getDayVal _ = 4.0
-- | Convert the given day, start time and end time to a tuple of Doubles. If nothing is given,
-- the place holder is 5 and 25, indicating the day and times are invalid.
convertTimeVals :: Maybe Int -> Maybe Int -> Maybe Int -> (Double, Double, Double)
convertTimeVals (Just day) (Just start) (Just end) =
let dayDbl = getDayVal day
startDbl = getHourVal start
endDbl = getHourVal end
in (dayDbl, startDbl, endDbl)
convertTimeVals _ _ _ = (5.0, 25.0, 25.0)
-- | Convert Times into Time
buildTime :: Times -> SqlPersistM Time
buildTime t = do
room1 <- getBuilding (timesFirstRoom t)
room2 <- getBuilding (timesSecondRoom t)
return $ Time (timesWeekDay t)
(timesStartHour t)
(timesEndHour t)
room1
room2
buildTimes :: Key Meeting -> Time' -> Times
buildTimes meetingKey t =
Times (weekDay' t)
(startHour' t)
(endHour' t)
meetingKey
(firstLocation' t)
(secondLocation' t)
-- | Given a building code, get the persistent Building associated with it
getBuilding :: Maybe T.Text -> SqlPersistM (Maybe Building)
getBuilding rm = do
case rm of
Nothing -> return Nothing
Just r -> do
maybeEntityBuilding <- selectFirst [BuildingCode ==. T.take 2 r] []
case maybeEntityBuilding of
Nothing -> return Nothing
Just entBuilding -> do
let building = entityVal entBuilding
return $ Just building