]> gitweb.michael.orlitzky.com - dead/htsn-import.git/blob - src/TSN/XML/MLBBoxScore.hs
Add more DB code to TSN.XML.MLBBoxScore.
[dead/htsn-import.git] / src / TSN / XML / MLBBoxScore.hs
1 {-# LANGUAGE DeriveGeneric #-}
2 {-# LANGUAGE FlexibleInstances #-}
3 {-# LANGUAGE GADTs #-}
4 {-# LANGUAGE QuasiQuotes #-}
5 {-# LANGUAGE RecordWildCards #-}
6 {-# LANGUAGE TemplateHaskell #-}
7 {-# LANGUAGE TypeFamilies #-}
8
9 -- | Parse TSN XML for the DTD \"MLB_Boxscore_XML.dtd\".
10 --
11 module TSN.XML.MLBBoxScore (
12 dtd,
13 pickle_message,
14 -- * Tests
15 -- auto_racing_results_tests,
16 -- * WARNING: these are private but exported to silence warnings
17 MLBBoxScore_MLBBoxScoreTeamBreakdownConstructor(..),
18 MLBBoxScoreConstructor(..),
19 MLBBoxScoreHomerunStats(..),
20 MLBBoxScoreMiscellaneousGameInfo(..),
21 MLBBoxScoreMiscPitchingStats(..),
22 MLBBoxScoreRunsByInningsConstructor(..),
23 MLBBoxScoreTeamBreakdownConstructor(..),
24 MLBBoxScoreTeamSummary(..)
25 )
26 -- AutoRacingResultsListingConstructor(..),
27 -- AutoRacingResultsRaceInformationConstructor(..) )
28 where
29
30 -- System imports.
31 import Control.Monad ( forM_ )
32 import Data.Time ( UTCTime(..) )
33 import Data.Tuple.Curry ( uncurryN )
34 import Database.Groundhog (
35 insert,
36 insert_,
37 migrate )
38 import Database.Groundhog.Core ( DefaultKey )
39 import Database.Groundhog.TH (
40 groundhog,
41 mkPersist )
42 import qualified GHC.Generics as GHC ( Generic )
43 import Text.XML.HXT.Core (
44 PU,
45 xp4Tuple,
46 xp23Tuple,
47 xpAttr,
48 xpElem,
49 xpInt,
50 xpList,
51 xpOption,
52 xpPair,
53 xpText,
54 xpTriple,
55 xpUnit,
56 xpWrap )
57
58 -- Local imports.
59 import Generics ( Generic(..), to_tuple )
60 import TSN.Codegen ( tsn_codegen_config )
61 import TSN.DbImport ( DbImport(..), ImportResult(..), run_dbmigrate )
62 import TSN.Picklers (
63 xp_date,
64 xp_time,
65 xp_time_stamp )
66 import TSN.Team ( Team(..), FromXmlFkTeams(..) )
67 import TSN.XmlImport ( XmlImport(..), XmlImportFk(..) )
68 import Xml (
69 Child(..),
70 FromXml(..),
71 FromXmlFk(..),
72 ToDb(..) )
73
74
75 -- | The DTD to which this module corresponds. Used to invoke dbimport.
76 --
77 dtd :: String
78 dtd = "MLB_Boxscore_XML.dtd"
79
80 --
81 -- * DB/XML data types
82 --
83
84 -- MLBBoxScore/Message
85
86 -- | Database representation of a 'Message'. The vteam/hteam have been
87 -- removed since they use the TSN.Team representation. The
88 -- 'xml_game_date' and 'xml_game_time' fields have also been
89 -- combined into 'db_game_time'. Finally, the summaries are missing
90 -- since they'll be keyed to us.
91 --
92 data MLBBoxScore =
93 MLBBoxScore {
94 db_xml_file_id :: Int,
95 db_heading :: String,
96 db_category :: String,
97 db_sport :: String,
98 db_game_id :: Int,
99 db_schedule_id :: Int,
100 db_vteam_id :: DefaultKey Team,
101 db_hteam_id :: DefaultKey Team,
102 db_season :: String,
103 db_season_type :: String,
104 db_game_time :: UTCTime,
105 db_game_number :: Int,
106 db_capacity :: Int,
107 db_title :: String,
108 db_time_stamp :: UTCTime }
109
110
111
112
113 -- | XML Representation of an 'MBLBoxScore'. It has the same fields,
114 -- but in addition contains the hteam/vteams and a game_date that
115 -- will eventually be combined with the time. It also has a list of
116 -- summaries.
117 --
118 data Message =
119 Message {
120 xml_xml_file_id :: Int,
121 xml_heading :: String,
122 xml_category :: String,
123 xml_sport :: String,
124 xml_game_id :: Int,
125 xml_schedule_id :: Int,
126 xml_vteam :: String,
127 xml_hteam :: String,
128 xml_vteam_id :: String,
129 xml_hteam_id :: String,
130 xml_season :: String,
131 xml_season_type :: String,
132 xml_title :: String,
133 xml_game_date :: UTCTime,
134 xml_game_time :: UTCTime,
135 xml_game_number :: Int,
136 xml_capacity :: Int,
137 xml_game_breakdown :: MLBBoxScoreGameBreakdownXml,
138 xml_team_summaries :: [MLBBoxScoreTeamSummaryXml],
139 xml_misc_pitching_stats :: MLBBoxScoreMiscPitchingStatsXml,
140 xml_homerun_stats :: Maybe MLBBoxScoreHomerunStatsXml,
141 xml_miscellaneous_game_info :: MLBBoxScoreMiscellaneousGameInfoXml,
142 xml_time_stamp :: UTCTime }
143 deriving (Eq, GHC.Generic, Show)
144
145
146 -- | For 'Generics.to_tuple'.
147 --
148 instance Generic Message
149
150 instance ToDb Message where
151 -- | The database analogue of a 'Message' is a 'MLBBoxScore'.
152 --
153 type Db Message = MLBBoxScore
154
155
156
157 -- | This ugly hack allows us to make 'Message' an instance of
158 -- 'FromXmlFkTeams'. That class usually requires that its instances
159 -- have a parent, but 'Message' does not. So we declare it the
160 -- parent of itself, and then ignore it.
161 instance Child Message where
162 type Parent Message = MLBBoxScore
163
164 -- | The 'FromXmlFk' instance for 'Message' is required for the
165 -- 'XmlImport' instance.
166 instance FromXmlFkTeams Message where
167 -- | To convert a 'Message' to an 'MLBBoxScore', we drop the
168 -- teams/summaries and combine the date/time. Also missing are the
169 -- embedded elements game_breakdown, homerun_stats, and
170 -- miscellaneous_game_info.
171 --
172 from_xml_fk_teams _ vteam_id hteam_id Message{..} =
173 MLBBoxScore {
174 db_xml_file_id = xml_xml_file_id,
175 db_heading = xml_heading,
176 db_category = xml_category,
177 db_sport = xml_sport,
178 db_game_id = xml_game_id,
179 db_schedule_id = xml_schedule_id,
180 db_vteam_id = vteam_id,
181 db_hteam_id = hteam_id,
182 db_season = xml_season,
183 db_season_type = xml_season_type,
184 db_game_time = make_game_time,
185 db_game_number = xml_game_number,
186 db_capacity = xml_capacity,
187 db_title = xml_title,
188 db_time_stamp = xml_time_stamp }
189 where
190 make_game_time =
191 UTCTime (utctDay xml_game_date) (utctDayTime xml_game_time)
192
193
194 data MLBBoxScoreTeamSummary = MLBBoxScoreTeamSummary
195 data MLBBoxScoreTeamSummaryXml = MLBBoxScoreTeamSummaryXml deriving (Eq, Show)
196
197 data MLBBoxScoreGameBreakdownXml =
198 MLBBoxScoreGameBreakdownXml {
199 xml_away_team :: MLBBoxScoreTeamBreakdownXml,
200 xml_home_team :: MLBBoxScoreTeamBreakdownXml }
201 deriving (Eq, GHC.Generic, Show)
202
203 -- | For 'Generics.to_tuple'
204 --
205 instance Generic MLBBoxScoreGameBreakdownXml
206
207
208 data MLBBoxScoreHomerunStats = MLBBoxScoreHomerunStats
209 data MLBBoxScoreHomerunStatsXml = MLBBoxScoreHomerunStatsXml
210 deriving (Eq, Show)
211
212 data MLBBoxScoreMiscellaneousGameInfo = MLBBoxScoreMiscellaneousGameInfo
213 data MLBBoxScoreMiscellaneousGameInfoXml = MLBBoxScoreMiscellaneousGameInfoXml
214 deriving (Eq, Show)
215
216
217 -- Team Breakdown
218 data MLBBoxScoreTeamBreakdown =
219 MLBBoxScoreTeamBreakdown {
220 db_runs :: Int,
221 db_hits :: Int,
222 db_errors :: Int }
223 data MLBBoxScoreTeamBreakdownXml =
224 MLBBoxScoreTeamBreakdownXml {
225 xml_runs_by_innings :: [MLBBoxScoreRunsByInningsXml],
226 xml_runs :: Int,
227 xml_hits :: Int,
228 xml_errors :: Int }
229 deriving (Eq, GHC.Generic, Show)
230
231
232 -- | For 'Generics.to_tuple'.
233 instance Generic MLBBoxScoreTeamBreakdownXml
234
235 instance ToDb MLBBoxScoreTeamBreakdownXml where
236 -- | The database analogue of a 'MLBBoxScoreTeamBreakdownXml' is
237 -- a 'MLBBoxScoreTeamBreakdown'.
238 --
239 type Db MLBBoxScoreTeamBreakdownXml = MLBBoxScoreTeamBreakdown
240
241
242 -- | The 'FromXml' instance for 'MLBBoxScoreTeamBreakdownXml' is
243 -- required for the 'XmlImport' instance.
244 --
245 instance FromXml MLBBoxScoreTeamBreakdownXml where
246 -- | To convert a 'MLBBoxScoreTeamBreakdownXml' to an
247 -- 'MLBBoxScoreTeamBreakdown', we just drop the
248 -- 'xml_runs_by_innings'.
249 --
250 from_xml MLBBoxScoreTeamBreakdownXml{..} =
251 MLBBoxScoreTeamBreakdown {
252 db_runs = xml_runs,
253 db_hits = xml_hits,
254 db_errors = xml_errors }
255
256 instance XmlImport MLBBoxScoreTeamBreakdownXml
257
258 -- Runs by innings
259 data MLBBoxScoreRunsByInnings =
260 MLBBoxScoreRunsByInnings {
261 db_mlb_box_scores_team_breakdowns_id :: DefaultKey
262 MLBBoxScoreTeamBreakdown,
263 db_runs_by_innings_inning_number :: Int,
264 db_runs_by_innings_runs :: Int }
265
266 data MLBBoxScoreRunsByInningsXml =
267 MLBBoxScoreRunsByInningsXml {
268 xml_runs_by_innings_inning_number :: Int,
269 xml_runs_by_innings_runs :: Int }
270 deriving (Eq, GHC.Generic, Show)
271
272
273 -- * MLBBoxScore_MLBBoxScoreTeamSummary
274
275 -- | Mapping between 'MLBBoxScore' records and
276 -- 'MLBBoxScoreTeamSummary' records in the database. We don't use
277 -- the names anywhere, so we let Groundhog choose them.
278 --
279 data MLBBoxScore_MLBBoxScoreTeamBreakdown =
280 MLBBoxScore_MLBBoxScoreTeamBreakdown
281 (DefaultKey MLBBoxScore)
282 (DefaultKey MLBBoxScoreTeamBreakdown) -- Away team
283 (DefaultKey MLBBoxScoreTeamBreakdown) -- Home team
284
285
286
287 -- | For 'Generics.to_tuple'.
288 --
289 instance Generic MLBBoxScoreRunsByInningsXml
290
291
292 instance ToDb MLBBoxScoreRunsByInningsXml where
293 -- | The database analogue of a 'MLBBoxScoreRunsByInningsXml' is
294 -- a 'MLBBoxScoreRunsByInnings'.
295 --
296 type Db MLBBoxScoreRunsByInningsXml = MLBBoxScoreRunsByInnings
297
298
299 instance Child MLBBoxScoreRunsByInningsXml where
300 -- | Each 'MLBBoxScoreRunsByInningsXml' is contained in (i.e. has a
301 -- foreign key to) a 'MLBBoxScoreTeamBreakdownXml'.
302 --
303 type Parent MLBBoxScoreRunsByInningsXml = MLBBoxScoreTeamBreakdown
304
305
306 instance FromXmlFk MLBBoxScoreRunsByInningsXml where
307 -- | To convert an 'MLBBoxScoreRunsByInningsXml' to an
308 -- 'MLBBoxScoreRunsByInnings', we add the foreign key and copy
309 -- everything else verbatim.
310 --
311 from_xml_fk fk MLBBoxScoreRunsByInningsXml{..} =
312 MLBBoxScoreRunsByInnings {
313 db_mlb_box_scores_team_breakdowns_id = fk,
314 db_runs_by_innings_inning_number = xml_runs_by_innings_inning_number,
315 db_runs_by_innings_runs = xml_runs_by_innings_runs }
316
317
318 -- | This allows us to insert the XML representation
319 -- 'MLBBoxScoreRunsByInningsXml' directly.
320 --
321 instance XmlImportFk MLBBoxScoreRunsByInningsXml
322
323
324
325
326 data MLBBoxScoreMiscPitchingStats = MLBBoxScoreMiscPitchingStats
327 data MLBBoxScoreMiscPitchingStatsXml =
328 MLBBoxScoreMiscPitchingStatsXml {
329 xml_wild_pitches :: Maybe Int,
330 xml_intentional_walks :: [MLBBoxScoreMiscPitchingStatsIntentionalWalkXml],
331 xml_hits_by_pitch :: [MLBBoxScoreMiscPitchingStatsHitByPitchXml] }
332 deriving (Eq, GHC.Generic, Show)
333
334
335 -- | For 'Generics.to_tuple'.
336 instance Generic MLBBoxScoreMiscPitchingStatsXml
337
338
339 data MLBBoxScoreMiscPitchingStatsIntentionalWalkXml =
340 MLBBoxScoreMiscPitchingStatsIntentionalWalkXml {
341 xml_iw_batter_id :: Int,
342 xml_iw_pitcher_id :: Int,
343 xml_iw_number_of_times_walked :: Int }
344 deriving (Eq, GHC.Generic, Show)
345
346
347 -- | For 'Generics.to_tuple'.
348 instance Generic MLBBoxScoreMiscPitchingStatsIntentionalWalkXml
349
350
351 data MLBBoxScoreMiscPitchingStatsHitByPitchXml =
352 MLBBoxScoreMiscPitchingStatsHitByPitchXml {
353 xml_hbp_batter_id :: Int,
354 xml_hbp_pitcher_id :: Int,
355 xml_hbp_number_of_times_hit :: Int }
356 deriving (Eq, GHC.Generic, Show)
357
358
359 -- | For 'Generics.to_tuple'.
360 --
361 instance Generic MLBBoxScoreMiscPitchingStatsHitByPitchXml
362
363
364 --
365 -- * Database
366 --
367
368 instance DbImport Message where
369 dbmigrate _ =
370 run_dbmigrate $ do
371 migrate (undefined :: MLBBoxScore)
372
373 -- | We insert the message.
374 dbimport m = do
375 -- First, get the vteam/hteam out of the XML message.
376 let vteam = Team (xml_vteam_id m) Nothing (Just $ xml_vteam m)
377 let hteam = Team (xml_hteam_id m) Nothing (Just $ xml_hteam m)
378
379 -- Insert them...
380 vteam_fk <- insert vteam
381 hteam_fk <- insert hteam
382
383 -- Now we can key the message to the teams/breakdowns we just
384 -- inserted.
385 let db_msg = from_xml_fk_teams undefined vteam_fk hteam_fk m
386 msg_id <- insert db_msg
387
388 -- Next, the vteam/hteam breakdowns, also needed to construct the
389 -- main message record
390 let vteam_bd = xml_away_team $ xml_game_breakdown m
391 let hteam_bd = xml_home_team $ xml_game_breakdown m
392
393 vteam_bd_fk <- insert_xml vteam_bd
394 hteam_bd_fk <- insert_xml hteam_bd
395
396 -- Insert the runs-by-innings associated with the vteam/hteam
397 -- breakdowns.
398 forM_ (xml_runs_by_innings vteam_bd) $ insert_xml_fk_ vteam_bd_fk
399 forM_ (xml_runs_by_innings hteam_bd) $ insert_xml_fk_ hteam_bd_fk
400
401 -- Now the join table record that ties the message to its two team
402 -- breakdowns.
403 let msg__breakdown = MLBBoxScore_MLBBoxScoreTeamBreakdown
404 msg_id
405 vteam_bd_fk
406 hteam_bd_fk
407
408 insert_ msg__breakdown
409
410 return ImportSucceeded
411
412
413
414 mkPersist tsn_codegen_config [groundhog|
415 - entity: MLBBoxScore
416 dbName: mlb_box_scores
417 constructors:
418 - name: MLBBoxScore
419 uniques:
420 - name: unique_mlb_box_scores
421 type: constraint
422 # Prevent multiple imports of the same message.
423 fields: [db_xml_file_id]
424
425
426 - entity: MLBBoxScoreTeamBreakdown
427 dbName: mlb_box_scores_team_breakdowns
428 constructors:
429 - name: MLBBoxScoreTeamBreakdown
430
431 - entity: MLBBoxScoreRunsByInnings
432 dbName: mlb_box_scores_team_breakdowns_runs_by_innings
433 constructors:
434 - name: MLBBoxScoreRunsByInnings
435 fields:
436 - name: db_mlb_box_scores_team_breakdowns_id
437 reference:
438 onDelete: cascade
439
440
441 - entity: MLBBoxScore_MLBBoxScoreTeamBreakdown
442 dbName: mlb_box_scores__mlb_box_scores_team_breakdowns
443 constructors:
444 - name: MLBBoxScore_MLBBoxScoreTeamBreakdown
445 fields:
446 - name: mLBBoxScore_MLBBoxScoreTeamBreakdown0
447 dbName: mlb_box_scores_id
448 reference:
449 onDelete: cascade
450 - name: mLBBoxScore_MLBBoxScoreTeamBreakdown1
451 dbName: mlb_box_scores_team_breakdowns_away_team_id
452 reference:
453 onDelete: cascade
454 - name: mLBBoxScore_MLBBoxScoreTeamBreakdown2
455 dbName: db_mlb_box_scores_team_breakdowns_home_team_id
456 reference:
457 onDelete: cascade
458 |]
459
460
461
462 --
463 -- * Pickling
464 --
465
466 pickle_message :: PU Message
467 pickle_message =
468 xpElem "message" $
469 xpWrap (from_tuple, to_tuple) $
470 xp23Tuple (xpElem "XML_File_ID" xpInt)
471 (xpElem "heading" xpText)
472 (xpElem "category" xpText)
473 (xpElem "sport" xpText)
474 (xpElem "game_id" xpInt)
475 (xpElem "schedule_id" xpInt)
476 (xpElem "vteam" xpText)
477 (xpElem "hteam" xpText)
478 (xpElem "vteam_id" xpText)
479 (xpElem "hteam_id" xpText)
480 (xpElem "Season" xpText)
481 (xpElem "SeasonType" xpText)
482 (xpElem "title" xpText)
483 (xpElem "Game_Date" xp_date)
484 (xpElem "Game_Time" xp_time)
485 (xpElem "GameNumber" xpInt)
486 (xpElem "Capacity" xpInt)
487 pickle_game_breakdown
488 (xpList pickle_team_summary)
489 pickle_misc_pitching_stats
490 pickle_homerun_stats
491 pickle_miscellaneous_game_info
492 (xpElem "time_stamp" xp_time_stamp)
493 where
494 from_tuple = uncurryN Message
495
496
497 pickle_team_summary :: PU MLBBoxScoreTeamSummaryXml
498 pickle_team_summary =
499 xpElem "Team_Summary" $ xpWrap (from_tuple, to_tuple') $ xpUnit
500 where
501 from_tuple _ = MLBBoxScoreTeamSummaryXml
502 to_tuple' _ = ()
503
504 pickle_game_breakdown :: PU MLBBoxScoreGameBreakdownXml
505 pickle_game_breakdown =
506 xpElem "Game_Breakdown" $
507 xpWrap (from_tuple, to_tuple) $
508 xpPair pickle_away_team
509 pickle_home_team
510 where
511 from_tuple = uncurry MLBBoxScoreGameBreakdownXml
512
513
514 pickle_runs_by_innings :: PU MLBBoxScoreRunsByInningsXml
515 pickle_runs_by_innings =
516 xpElem "Runs_By_Innings" $
517 xpWrap (from_tuple, to_tuple) $
518 xpPair (xpAttr "Inning" xpInt)
519 xpInt
520 where
521 from_tuple = uncurry MLBBoxScoreRunsByInningsXml
522
523
524 pickle_team :: PU MLBBoxScoreTeamBreakdownXml
525 pickle_team =
526 xpWrap (from_tuple, to_tuple) $
527 xp4Tuple (xpList pickle_runs_by_innings)
528 (xpElem "Runs" xpInt)
529 (xpElem "Hits" xpInt)
530 (xpElem "Errors" xpInt)
531 where
532 from_tuple = uncurryN MLBBoxScoreTeamBreakdownXml
533
534
535 pickle_away_team :: PU MLBBoxScoreTeamBreakdownXml
536 pickle_away_team =
537 xpElem "AwayTeam" pickle_team
538
539 pickle_home_team :: PU MLBBoxScoreTeamBreakdownXml
540 pickle_home_team =
541 xpElem "HomeTeam" pickle_team
542
543 pickle_homerun_stats :: PU (Maybe MLBBoxScoreHomerunStatsXml)
544 pickle_homerun_stats =
545 xpOption $ xpElem "Homerun_Stats" $ xpWrap (from_tuple, to_tuple') $ xpUnit
546 where
547 from_tuple _ = MLBBoxScoreHomerunStatsXml
548 to_tuple' _ = ()
549
550
551 pickle_misc_pitching_stats :: PU MLBBoxScoreMiscPitchingStatsXml
552 pickle_misc_pitching_stats =
553 xpElem "Misc_Pitching_Stats" $
554 xpWrap (from_tuple, to_tuple) $
555 xpTriple (xpOption $ xpElem "Wild_Pitches" xpInt)
556 pickle_intentional_walks
557 pickle_hits_by_pitch
558 where
559 from_tuple = uncurryN MLBBoxScoreMiscPitchingStatsXml
560
561
562
563 pickle_intentional_walks :: PU [MLBBoxScoreMiscPitchingStatsIntentionalWalkXml]
564 pickle_intentional_walks =
565 xpElem "Intentional_Walks" $ xpList $ xpElem "IW_Listing" $
566 xpWrap (from_tuple, to_tuple) $
567 xpTriple (xpElem "IW_Batter_ID" xpInt)
568 (xpElem "IW_Pitcher_ID" xpInt)
569 (xpElem "IW_Number_Of_Times_Walked" xpInt)
570 where
571 from_tuple = uncurryN MLBBoxScoreMiscPitchingStatsIntentionalWalkXml
572
573
574
575 pickle_hits_by_pitch :: PU [MLBBoxScoreMiscPitchingStatsHitByPitchXml]
576 pickle_hits_by_pitch =
577 xpElem "Hit_By_Pitch" $ xpList $ xpElem "HBP_Listing" $
578 xpWrap (from_tuple, to_tuple) $
579 xpTriple (xpElem "HBP_Batter_ID" xpInt)
580 (xpElem "HBP_Pitcher_ID" xpInt)
581 (xpElem "HBP_Number_Of_Times_Hit" xpInt)
582 where
583 from_tuple = uncurryN MLBBoxScoreMiscPitchingStatsHitByPitchXml
584
585
586
587 pickle_miscellaneous_game_info :: PU MLBBoxScoreMiscellaneousGameInfoXml
588 pickle_miscellaneous_game_info =
589 xpElem "Miscelaneous_Game_Info" $ xpWrap (from_tuple, to_tuple') $ xpUnit
590 where
591 from_tuple _ = MLBBoxScoreMiscellaneousGameInfoXml
592 to_tuple' _ = ()