-- unorganized crap.
--
module TSN.XML.Odds (
- Odds,
- Message,
- odds_tests )
+ odds_tests,
+ pickle_message,
+ -- * WARNING: these are private but exported to silence warnings
+ Odds_OddsGameConstructor(..),
+ OddsCasinoConstructor(..),
+ OddsConstructor(..),
+ OddsGame_OddsGameTeamConstructor(..),
+ OddsGameConstructor(..),
+ OddsGameLineConstructor(..),
+ OddsGameTeamConstructor(..) )
where
-import Control.Monad ( forM_ )
+import Control.Monad ( forM_, join )
import Data.Tuple.Curry ( uncurryN )
import Database.Groundhog (
(=.),
mkPersist )
import Test.Tasty ( TestTree, testGroup )
import Test.Tasty.HUnit ( (@?=), testCase )
+import Text.Read ( readMaybe )
import Text.XML.HXT.Core (
PU,
- XmlPickler(..),
xp5Tuple,
xp6Tuple,
xp8Tuple,
xpList,
xpOption,
xpPair,
- xpPrim,
xpText,
xpTriple,
xpWrap )
import TSN.Codegen (
tsn_codegen_config )
import TSN.DbImport ( DbImport(..), ImportResult(..), run_dbmigrate )
+import TSN.Picklers ( xp_team_id )
import TSN.XmlImport ( XmlImport(..) )
import Xml ( FromXml(..), pickle_unpickle, unpickleable )
+-- | The home/away lines are 'Double's, but the over/under lines are
+-- textual. If we want to use one data type for both, we have to go
+-- with a String and then attempt to 'read' a 'Double' later when we
+-- go to insert the thing.
+--
data OddsGameCasinoXml =
OddsGameCasinoXml {
xml_casino_client_id :: Int,
xml_casino_name :: String,
- xml_casino_line :: Maybe Double }
+ xml_casino_line :: Maybe String }
deriving (Eq, Show)
+-- | Try to get a 'Double' out of the 'xml_casino_line' which is a
+-- priori textual (because it might be an over/under line).
+--
+home_away_line :: OddsGameCasinoXml -> Maybe Double
+home_away_line = join . (fmap readMaybe) . xml_casino_line
+
-- | The casinos should have their own table, but the lines don't
-- belong in that table. (There should be another table joining the
-- casinos and the thing the lines are for together.)
-- We don't need the key argument (from_xml_fk) since the XML type
-- contains more information in this case.
- from_xml OddsGameCasinoXml{..} = OddsCasino
- xml_casino_client_id
- xml_casino_name
+ from_xml OddsGameCasinoXml{..} =
+ OddsCasino {
+ casino_client_id = xml_casino_client_id,
+ casino_name = xml_casino_name }
+
instance XmlImport OddsGameCasinoXml
data OddsGameHomeTeamXml =
OddsGameHomeTeamXml {
- xml_home_team_id :: Int,
+ xml_home_team_id :: String, -- ^ These are three-character IDs.
xml_home_rotation_number :: Int,
xml_home_abbr :: String,
xml_home_team_name :: String,
instance FromXml OddsGameHomeTeamXml where
type Db OddsGameHomeTeamXml = OddsGameTeam
- from_xml OddsGameHomeTeamXml{..} = OddsGameTeam
- xml_home_team_id
- xml_home_abbr
- xml_home_team_name
+ from_xml OddsGameHomeTeamXml{..} =
+ OddsGameTeam {
+ db_team_id = xml_home_team_id,
+ db_abbr = xml_home_abbr,
+ db_team_name = xml_home_team_name }
instance XmlImport OddsGameHomeTeamXml where
data OddsGameTeam =
OddsGameTeam {
- db_team_id :: Int,
+ db_team_id :: String, -- ^ The home/away team IDs are 3 characters
db_abbr :: String,
db_team_name :: String }
deriving (Eq, Show)
data OddsGameAwayTeamXml =
OddsGameAwayTeamXml {
- xml_away_team_id :: Int,
+ xml_away_team_id :: String, -- ^ These are 3 character IDs.
xml_away_rotation_number :: Int,
xml_away_abbr :: String,
xml_away_team_name :: String,
-- | This database representation of the casino lines can't be
-- constructed from the one in the XML. The casinos within
--- Game>HomeTeam, Game>AwayTeam, and Game>Over_Under are all the
--- same. We don't need a bajillion different tables to store that --
--- just one tying the casino/game pair to the three lines.
+-- Game>HomeTeam, Game>AwayTeam, and Game>Over_Under are all more or
+-- less the same. We don't need a bajillion different tables to
+-- store that, just one tying the casino/game pair to the three
+-- lines.
+--
+-- The one small difference between the over/under casinos and the
+-- home/away ones is that the home/away lines are all 'Double's, but
+-- the over/under lines appear to be textual.
+--
data OddsGameLine =
OddsGameLine {
ogl_odds_games_id :: DefaultKey OddsGame,
ogl_odds_casinos_id :: DefaultKey OddsCasino,
- ogl_over_under :: Maybe Double,
+ ogl_over_under :: Maybe String,
ogl_away_line :: Maybe Double,
ogl_home_line :: Maybe Double }
instance FromXml OddsGameXml where
type Db OddsGameXml = OddsGame
- from_xml OddsGameXml{..} = OddsGame
- xml_game_id
- xml_game_date
- xml_game_time
- (xml_away_rotation_number xml_game_away_team)
- (xml_home_rotation_number xml_game_home_team)
+ from_xml OddsGameXml{..} =
+ OddsGame {
+ db_game_id = xml_game_id,
+ db_game_date = xml_game_date,
+ db_game_time = xml_game_time,
+ db_game_away_team_rotation_number =
+ (xml_away_rotation_number xml_game_away_team),
+ db_game_home_team_rotation_number =
+ (xml_home_rotation_number xml_game_home_team) }
instance XmlImport OddsGameXml
-- | Map 'Odds' to their children 'OddsGame's.
-data Odds_OddsGame =
- Odds_OddsGame {
- oog_odds_id :: DefaultKey Odds,
- oog_odds_games_id :: DefaultKey OddsGame }
+--
+data Odds_OddsGame = Odds_OddsGame
+ (DefaultKey Odds)
+ (DefaultKey OddsGame)
+
-- | This is our best guess at what occurs in the Odds_XML
-- documents. It looks like each consecutive set of games can
-- We don't need the key argument (from_xml_fk) since the XML type
-- contains more information in this case.
- from_xml (Message _ _ _ d e f _ _) =
- Odds d e f
+ from_xml Message{..} =
+ Odds {
+ db_sport = xml_sport,
+ db_title = xml_title,
+ db_line_time = xml_line_time }
instance XmlImport Message
dbName: odds_games_teams
constructors:
- name: OddsGameTeam
+ fields:
+ - name: db_team_id
+ type: varchar(3)
uniques:
- name: unique_odds_games_team
type: constraint
- entity: Odds_OddsGame
dbName: odds__odds_games
+ constructors:
+ - name: Odds_OddsGame
+ fields:
+ - name: odds_OddsGame0
+ dbName: odds_id
+ - name: odds_OddsGame1
+ dbName: odds_games_id
- entity: OddsGame_OddsGameTeam
dbName: odds_games__odds_games_teams
away_team_id <- insert_xml_or_select (xml_game_away_team g)
home_team_id <- insert_xml_or_select (xml_game_home_team g)
- -- Insert a record into odds_games__odds_games_teams
- -- mapping the home/away teams to this game.
- insert_ (OddsGame_OddsGameTeam game_id away_team_id home_team_id)
+ -- Insert a record into odds_games__odds_games_teams mapping the
+ -- home/away teams to this game. Use the full record syntax
+ -- because the types would let us mix up the home/away teams.
+ insert_ OddsGame_OddsGameTeam {
+ ogogt_odds_games_id = game_id,
+ ogogt_away_team_id = away_team_id,
+ ogogt_home_team_id = home_team_id }
-- Finaly, we insert the lines. The over/under entries for this
-- game and the lines for the casinos all wind up in the same
ou_casino_id <- insert_xml_or_select c
-- Now add the over/under entry with the casino's id.
- let ogl = OddsGameLine
- game_id
- ou_casino_id
- (xml_casino_line c)
- Nothing
- Nothing
+ let ogl = OddsGameLine {
+ ogl_odds_games_id = game_id,
+ ogl_odds_casinos_id = ou_casino_id,
+ ogl_over_under = (xml_casino_line c),
+ ogl_away_line = Nothing,
+ ogl_home_line = Nothing }
insertByAll ogl
-- insert, or more likely retrieve the existing, casino
a_casino_id <- insert_xml_or_select c
+ -- Get a Maybe Double instead of the Maybe String that's in there.
+ let away_line = home_away_line c
+
-- Unconditionally update that casino's away team line with ours.
- update [Ogl_Away_Line =. (xml_casino_line c)] $ -- WHERE
+ update [Ogl_Away_Line =. away_line] $ -- WHERE
Ogl_Odds_Casinos_Id ==. a_casino_id
-- Repeat all that for the home team.
forM_ (xml_home_casinos $ xml_game_home_team g) $ \c ->do
h_casino_id <- insert_xml_or_select c
- update [Ogl_Home_Line =. (xml_casino_line c)] $ -- WHERE
+ let home_line = home_away_line c
+ update [Ogl_Home_Line =. home_line] $ -- WHERE
Ogl_Odds_Casinos_Id ==. h_casino_id
return game_id
xpTriple
(xpAttr "ClientID" xpInt)
(xpAttr "Name" xpText)
- (xpOption xpPrim) -- Double
+ (xpOption xpText)
where
from_tuple = uncurryN OddsGameCasinoXml
-- Use record wildcards to avoid unused field warnings.
xml_casino_name,
xml_casino_line)
-instance XmlPickler OddsGameCasinoXml where
- xpickle = pickle_casino
-
pickle_home_team :: PU OddsGameHomeTeamXml
pickle_home_team =
xpElem "HomeTeam" $
xpWrap (from_tuple, to_tuple) $
xp5Tuple
- (xpElem "HomeTeamID" xpInt)
+ (xpElem "HomeTeamID" xp_team_id)
(xpElem "HomeRotationNumber" xpInt)
(xpElem "HomeAbbr" xpText)
(xpElem "HomeTeamName" xpText)
xml_home_team_name,
xml_home_casinos)
-instance XmlPickler OddsGameHomeTeamXml where
- xpickle = pickle_home_team
pickle_away_team :: PU OddsGameAwayTeamXml
xpElem "AwayTeam" $
xpWrap (from_tuple, to_tuple) $
xp5Tuple
- (xpElem "AwayTeamID" xpInt)
+ (xpElem "AwayTeamID" xp_team_id)
(xpElem "AwayRotationNumber" xpInt)
(xpElem "AwayAbbr" xpText)
(xpElem "AwayTeamName" xpText)
from_tuple = uncurryN OddsGameAwayTeamXml
-- Use record wildcards to avoid unused field warnings.
to_tuple OddsGameAwayTeamXml{..} = (xml_away_team_id,
- xml_away_rotation_number,
- xml_away_abbr,
- xml_away_team_name,
- xml_away_casinos)
-
+ xml_away_rotation_number,
+ xml_away_abbr,
+ xml_away_team_name,
+ xml_away_casinos)
-instance XmlPickler OddsGameAwayTeamXml where
- xpickle = pickle_away_team
pickle_over_under :: PU OddsGameOverUnderXml
from_newtype (OddsGameOverUnderXml cs) = cs
to_newtype = OddsGameOverUnderXml
-instance XmlPickler OddsGameOverUnderXml where
- xpickle = pickle_over_under
pickle_game :: PU OddsGameXml
xml_game_home_team,
xml_game_over_under)
-instance XmlPickler OddsGameXml where
- xpickle = pickle_game
-
pickle_message :: PU Message
pickle_message =
xml_time_stamp m)
-instance XmlPickler Message where
- xpickle = pickle_message
-
-
-
-- * Tasty Tests
odds_tests :: TestTree
-- | Warning, succeess of this test does not mean that unpickling
-- succeeded.
test_pickle_of_unpickle_is_identity :: TestTree
-test_pickle_of_unpickle_is_identity =
- testCase "pickle composed with unpickle is the identity" $ do
- let path = "test/xml/Odds_XML.xml"
- (expected, actual) <- pickle_unpickle pickle_message path
- actual @?= expected
+test_pickle_of_unpickle_is_identity = testGroup "pickle-unpickle tests"
+ [ check "pickle composed with unpickle is the identity"
+ "test/xml/Odds_XML.xml",
+
+ check "pickle composed with unpickle is the identity (non-int team_id)"
+ "test/xml/Odds_XML-noninteger-team-id.xml",
+
+ check "pickle composed with unpickle is the identity (positive(+) line)"
+ "test/xml/Odds_XML-positive-line.xml",
+
+ check "pickle composed with unpickle is the identity (large file)"
+ "test/xml/Odds_XML-largefile.xml" ]
+ where
+ check desc path = testCase desc $ do
+ (expected, actual) <- pickle_unpickle pickle_message path
+ actual @?= expected
test_unpickle_succeeds :: TestTree
-test_unpickle_succeeds =
- testCase "unpickling succeeds" $ do
- let path = "test/xml/Odds_XML.xml"
- actual <- unpickleable path pickle_message
- let expected = True
- actual @?= expected
+test_unpickle_succeeds = testGroup "unpickle tests"
+ [ check "unpickling succeeds"
+ "test/xml/Odds_XML.xml",
+
+ check "unpickling succeeds (non-int team_id)"
+ "test/xml/Odds_XML-noninteger-team-id.xml",
+
+ check "unpickling succeeds (positive(+) line)"
+ "test/xml/Odds_XML-positive-line.xml",
+
+ check "unpickling succeeds (large file)"
+ "test/xml/Odds_XML-largefile.xml" ]
+ where
+ check desc path = testCase desc $ do
+ actual <- unpickleable path pickle_message
+ let expected = True
+ actual @?= expected