packages feed

htsn-import 0.1.2 → 0.1.3

raw patch · 9 files changed

+139/−61 lines, 9 files

Files

doc/dbschema/scoresxml.png view

binary file changed (123239 → 123044 bytes)

htsn-import.cabal view
@@ -1,5 +1,5 @@ name:           htsn-import-version:        0.1.2+version:        0.1.3 cabal-version:  >= 1.8 author:         Michael Orlitzky maintainer:	Michael Orlitzky <michael@orlitzky.com>
src/TSN/XML/AutoRacingResults.hs view
@@ -24,6 +24,7 @@ -- System imports. import Control.Monad ( forM_ ) import Data.Data ( Data )+import Data.Maybe ( fromMaybe ) import Data.Time ( UTCTime(..) ) import Data.Tuple.Curry ( uncurryN ) import Data.Typeable ( Typeable )@@ -362,9 +363,9 @@       -- the database with an (embedded) 'MostLapsLeading' with three       -- missing fields.       most_laps_leading =-        case xml_most_laps_leading of-          Just mll -> mll-          Nothing  -> MostLapsLeading Nothing Nothing Nothing+        fromMaybe (MostLapsLeading Nothing Nothing Nothing)+                  xml_most_laps_leading+  -- | This allows us to insert the XML representation --   'AutoRacingResultsRaceInformationXml' directly.
src/TSN/XML/Odds.hs view
@@ -61,8 +61,8 @@   xpWrap )  -- Local imports.-import TSN.Codegen (-  tsn_codegen_config )+import TSN.Codegen ( tsn_codegen_config )+import TSN.Database ( insert_or_select ) import TSN.DbImport ( DbImport(..), ImportResult(..), run_dbmigrate ) import TSN.Picklers ( xp_date_padded, xp_tba_time, xp_time_stamp ) import TSN.Team ( FromXmlFkTeams(..), Team(..) )@@ -108,10 +108,18 @@ --   with a 'String' and then attempt to 'read' a 'Double' later when we --   go to insert the thing. --+--   The client_id and name shouldn't really be optional, but TSN has+--   started to send us empty casinos:+--+--     \<Casino ClientID=\"\" Name=\"\"\>\</Casino\>+--+--   We need to parse these, but we'll silently drop them during the+--   database import.+-- data OddsGameCasinoXml =   OddsGameCasinoXml {-    xml_casino_client_id :: Int,-    xml_casino_name      :: String,+    xml_casino_client_id :: Maybe Int,+    xml_casino_name      :: Maybe String,     xml_casino_line      :: Maybe String }   deriving (Eq, Show) @@ -126,24 +134,25 @@  instance ToDb OddsGameCasinoXml where   -- | The database representation of an 'OddsGameCasinoXml' is an-  --   'OddsCasino'.+  --   'OddsCasino'. When our XML representation is missing a+  --   client_id or a name, we want to ignore it. So in that case,+  --   when we convert to the database type, we want 'Nothing'.   ---  type Db OddsGameCasinoXml = OddsCasino+  type Db OddsGameCasinoXml = Maybe OddsCasino   instance FromXml OddsGameCasinoXml where-  -- | We convert from XML to the database by dropping the line field.+  -- | We convert from XML to the database by dropping the+  --   'xml_casino_line' field. If either the 'xml_casino_client_id'+  --   or 'xml_casino_name' is missing ('Nothing'), we'll return+  --   'Nothing'.   ---  from_xml OddsGameCasinoXml{..} =-    OddsCasino {-      casino_client_id = xml_casino_client_id,-      casino_name      = xml_casino_name }+  from_xml (OddsGameCasinoXml Nothing _ _) = Nothing+  from_xml (OddsGameCasinoXml _ Nothing _) = Nothing +  from_xml (OddsGameCasinoXml (Just c) (Just n) _) =+    Just OddsCasino { casino_client_id = c, casino_name = n } --- | This allows us to insert the XML representation 'OddsGameCasinoXml'---   directly.----instance XmlImport OddsGameCasinoXml   -- * OddsGameTeamXml / OddsGameTeamStarterXml@@ -514,46 +523,75 @@       -- Finally, we insert the lines. The over/under entries for this       -- game and the lines for the casinos all wind up in the same       -- table, odds_games_lines. We can insert the over/under entries-      -- freely with empty away/home lines:-      forM_ (xml_over_under_casinos game) $ \c -> do-        -- Start by inderting the casino.-        ou_casino_id <- insert_xml_or_select c+      -- freely with empty away/home lines.+      --+      -- Before we continue, we drop all casinos that are missing+      -- either a client_id or name field.+      --+      let ou_casinos = filter nonempty_casino $ xml_over_under_casinos game -        -- Now add the over/under entry with the casino's id.-        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 }+      forM_ ou_casinos $ \c ->+        -- Since we already filtered out the casinos without a+        -- client_id or a name, the database conversion should always+        -- return (Just something).+        case (from_xml c) of+          Nothing -> return () -- Should never happen, we filtered them out.+          Just casino -> do+            -- Start by inserting the casino.+            ou_casino_id <- insert_or_select casino -        insert_ ogl+            -- Now add the over/under entry with the casino's id.+            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 } +            insert_ ogl+       -- ...but then when we insert the home/away team lines, we       -- prefer to update the existing entry rather than overwrite it       -- or add a new record.-      forM_ (xml_team_casinos $ xml_away_team game) $ \c -> do-        -- insert, or more likely retrieve the existing, casino-        a_casino_id <- insert_xml_or_select c+      let away_casinos = filter nonempty_casino $+                           xml_team_casinos (xml_away_team game) -        -- Get a Maybe Double instead of the Maybe String that's in there.-        let away_line = home_away_line c+      forM_ away_casinos $ \c ->+        case (from_xml c) of+          Nothing -> return () -- Should never happen, we filtered them out.+          Just casino -> do+            -- insert, or more likely retrieve the existing, casino+            a_casino_id <- insert_or_select casino -        -- Unconditionally update that casino's away team line with ours.-        update [Ogl_Away_Line =. away_line] $ -- WHERE-          Ogl_Odds_Casinos_Id ==. a_casino_id+            -- 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 =. away_line] $ -- WHERE+              Ogl_Odds_Casinos_Id ==. a_casino_id+       -- Repeat all that for the home team.-      forM_ (xml_team_casinos $ xml_home_team game) $ \c ->do-        h_casino_id <- insert_xml_or_select c-        let home_line = home_away_line c-        update [Ogl_Home_Line =. home_line] $ -- WHERE-          Ogl_Odds_Casinos_Id ==. h_casino_id+      let home_casinos = filter nonempty_casino $+                           xml_team_casinos (xml_home_team game) +      forM_ home_casinos $ \c ->+        case (from_xml c) of+          Nothing -> return () -- Should never happen, we filtered them out.+          Just casino -> do+            h_casino_id <- insert_or_select casino+            let home_line = home_away_line c+            update [Ogl_Home_Line =. home_line] $ -- WHERE+              Ogl_Odds_Casinos_Id ==. h_casino_id+       return game_id      return ImportSucceeded +    where+      nonempty_casino :: OddsGameCasinoXml -> Bool+      nonempty_casino (OddsGameCasinoXml Nothing _ _) = False+      nonempty_casino (OddsGameCasinoXml _ Nothing _) = False+      nonempty_casino _ = True  -- -- Pickling@@ -580,8 +618,8 @@   xpElem "Casino" $   xpWrap (from_tuple, to_tuple) $   xpTriple-    (xpAttr "ClientID" xpInt)-    (xpAttr "Name" xpText)+    (xpAttr "ClientID" $ xpOption xpInt)+    (xpAttr "Name" $ xpOption xpText)     (xpOption xpText)   where     from_tuple = uncurryN OddsGameCasinoXml@@ -769,7 +807,10 @@           "test/xml/Odds_XML-missing-starters.xml",      check "pickle composed with unpickle is the identity (TBA game time)"-          "test/xml/Odds_XML-tba-game-time.xml"]+          "test/xml/Odds_XML-tba-game-time.xml",++    check "pickle composed with unpickle is the identity (empty casino)"+          "test/xml/Odds_XML-empty-casino.xml" ]   where     check desc path = testCase desc $ do       (expected, actual) <- pickle_unpickle pickle_message path@@ -799,7 +840,10 @@           "test/xml/Odds_XML-missing-starters.xml",      check "unpickling succeeds (TBA game time)"-          "test/xml/Odds_XML-tba-game-time.xml" ]+          "test/xml/Odds_XML-tba-game-time.xml",++    check "unpickling succeeds (empty casino)"+          "test/xml/Odds_XML-empty-casino.xml" ]   where     check desc path = testCase desc $ do       actual <- unpickleable path pickle_message@@ -842,6 +886,10 @@     check "deleting odds deleted its children (TBA game time)"           "test/xml/Odds_XML-tba-game-time.xml"           119 -- 5 casinos, 114 teams+    ,+    check "deleting odds deleted its children (empty casino)"+          "test/xml/Odds_XML-empty-casino.xml"+          11 -- 5 casinos, 6 teams     ]   where     check desc path expected = testCase desc $ do
src/TSN/XML/Scores.hs view
@@ -159,7 +159,7 @@ -- data ScoreGameStatus =   ScoreGameStatus {-    db_status_numeral :: Int,+    db_status_numeral :: Maybe Int,     db_status_type :: Maybe String, -- ^ These are probably only one-character,                                     --   long, but they all take the same                                     --    amount of space in Postgres.@@ -431,20 +431,26 @@   --- | Convert a 'ScoreGameStatus' to/from \<status\>.+-- | Convert a 'ScoreGameStatus' to/from \<status\>. The \"type\"+--   attribute can be either missing or empty, so we're really parsing+--   a double-Maybe here. We use the monad join to collapse it into+--   one. See also: the hteam/vteam picklers. -- pickle_status :: PU ScoreGameStatus pickle_status =   xpElem "status" $     xpWrap (from_tuple, to_tuple) $-      xpTriple (xpAttr "numeral" xpInt)-               (xpOption $ xpAttr "type" xpText)+      xpTriple (xpAttr "numeral" $ xpOption xpInt)+               (xpOption $ xpAttr "type" $ xpOption xpText)                xpText   where-    from_tuple = uncurryN ScoreGameStatus-    to_tuple ScoreGameStatus{..} = (db_status_numeral,-                                    db_status_type,-                                    db_status_text)+    from_tuple (x,y,z) = ScoreGameStatus x (join y) z+    to_tuple ScoreGameStatus{..} =+      (db_status_numeral, s, db_status_text)+      where+      s = case db_status_type of+            Nothing -> Nothing+            Just _  -> Just db_status_type   -- | Convert a 'ScoreGameXml' to/from \<game\>.@@ -552,7 +558,13 @@           "test/xml/scoresxml-no-locations.xml",      check "pickle composed with unpickle is the identity (pitcher, no type)"-          "test/xml/scoresxml-pitcher-no-type.xml"]+          "test/xml/scoresxml-pitcher-no-type.xml",++    check "pickle composed with unpickle is the identity (empty numeral)"+          "test/xml/scoresxml-empty-numeral.xml",++    check "pickle composed with unpickle is the identity (empty type)"+          "test/xml/scoresxml-empty-type.xml" ]   where     check desc path = testCase desc $ do       (expected, actual) <- pickle_unpickle pickle_message path@@ -570,7 +582,13 @@           "test/xml/scoresxml-no-locations.xml",      check "unpickling succeeds (pitcher, no type)"-          "test/xml/scoresxml-pitcher-no-type.xml" ]+          "test/xml/scoresxml-pitcher-no-type.xml",++    check "unpickling succeeds (empty numeral)"+          "test/xml/scoresxml-empty-numeral.xml",++    check "unpickling succeeds (empty type)"+          "test/xml/scoresxml-empty-type.xml" ]   where     check desc path = testCase desc $ do       actual <- unpickleable path pickle_message@@ -593,7 +611,15 @@      check "unpickling succeeds (pitcher, no type)"           "test/xml/scoresxml-pitcher-no-type.xml"-          3 -- 2 teams, 1 location+          3, -- 2 teams, 1 location++    check "unpickling succeeds (empty numeral)"+          "test/xml/scoresxml-empty-numeral.xml"+          3, -- 2 teams, 1 location++    check "unpickling succeeds (empty type)"+          "test/xml/scoresxml-empty-type.xml"+          4 -- 2 teams, 2 locations   ]   where     check desc path expected = testCase desc $ do
test/shell/import-duplicates.test view
@@ -16,7 +16,7 @@ # and a newsxml that aren't really supposed to import. find ./test/xml -maxdepth 1 -name '*.xml' | wc -l >>>-35+38 >>>= 0  # Run the imports again; we should get complaints about the duplicate@@ -24,7 +24,7 @@ # occurrences of the string 'ERROR'. ./dist/build/htsn-import/htsn-import -c 'shelltest.sqlite3' test/xml/*.xml 2>&1 | grep ERROR | wc -l >>>-62+68 >>>= 0  # Finally, clean up after ourselves.
+ test/xml/Odds_XML-empty-casino.xml view
@@ -0,0 +1,1 @@+<?xml version="1.0" standalone="no" ?>
<!DOCTYPE message PUBLIC "-//TSN//DTD Statistics 1.0/EN" "Odds_XML.dtd">
<message>
<XML_File_ID>21985005</XML_File_ID>
<heading>ADO;NHL-ODDS</heading>
<category>Odds</category>
<sport>NHL</sport>
<Title>Las Vegas National Hockey League Line</Title>
<Line_Time>Current Line as of 12:30 P.M. ET</Line_Time>
<Game>
<GameID>19287</GameID>
<Game_Date>10/15/2014</Game_Date>
<Game_Time>08:00 PM</Game_Time>
<AwayTeam>
<AwayTeamID>123</AwayTeamID>
<AwayRotationNumber>051</AwayRotationNumber>
<AwayAbbr>CAL</AwayAbbr>
<AwayTeamName>Calgary</AwayTeamName>
<Casino ClientID="116" Name="5Dimes">+225</Casino>
<Casino ClientID="110" Name="Betus">+230</Casino>
<Casino ClientID="104" Name="BOVADA">+240</Casino>
<Casino ClientID="121" Name="Mirage">+220</Casino>
<Casino ClientID="127" Name="DonBest Consensus">+225</Casino>
<Casino ClientID="" Name=""></Casino>
</AwayTeam>
<HomeTeam>
<HomeTeamID>124</HomeTeamID>
<HomeRotationNumber>052</HomeRotationNumber>
<HomeAbbr>CHI</HomeAbbr>
<HomeTeamName>Chicago</HomeTeamName>
<Casino ClientID="116" Name="5Dimes">-265</Casino>
<Casino ClientID="110" Name="Betus">-270</Casino>
<Casino ClientID="104" Name="BOVADA">-280</Casino>
<Casino ClientID="121" Name="Mirage">-260</Casino>
<Casino ClientID="127" Name="DonBest Consensus">-265</Casino>
<Casino ClientID="" Name=""></Casino>
</HomeTeam>
<Over_Under>
<Casino ClientID="116" Name="5Dimes">5.5p</Casino>
<Casino ClientID="110" Name="Betus">5.5u</Casino>
<Casino ClientID="104" Name="BOVADA">5.5p</Casino>
<Casino ClientID="121" Name="Mirage">5.5p</Casino>
<Casino ClientID="127" Name="DonBest Consensus">5.5p</Casino>
<Casino ClientID="" Name=""></Casino>
</Over_Under>
</Game>
<Game>
<GameID>19285</GameID>
<Game_Date>10/15/2014</Game_Date>
<Game_Time>08:00 PM</Game_Time>
<AwayTeam>
<AwayTeamID>121</AwayTeamID>
<AwayRotationNumber>053</AwayRotationNumber>
<AwayAbbr>BOS</AwayAbbr>
<AwayTeamName>Boston</AwayTeamName>
<Casino ClientID="116" Name="5Dimes">-120</Casino>
<Casino ClientID="110" Name="Betus">-120</Casino>
<Casino ClientID="104" Name="BOVADA">-125</Casino>
<Casino ClientID="121" Name="Mirage">-115</Casino>
<Casino ClientID="127" Name="DonBest Consensus">-120</Casino>
<Casino ClientID="" Name=""></Casino>
</AwayTeam>
<HomeTeam>
<HomeTeamID>125</HomeTeamID>
<HomeRotationNumber>054</HomeRotationNumber>
<HomeAbbr>DET</HomeAbbr>
<HomeTeamName>Detroit</HomeTeamName>
<Casino ClientID="116" Name="5Dimes">even</Casino>
<Casino ClientID="110" Name="Betus">even</Casino>
<Casino ClientID="104" Name="BOVADA">+105</Casino>
<Casino ClientID="121" Name="Mirage">-105</Casino>
<Casino ClientID="127" Name="DonBest Consensus">even</Casino>
<Casino ClientID="" Name=""></Casino>
</HomeTeam>
<Over_Under>
<Casino ClientID="116" Name="5Dimes">5p</Casino>
<Casino ClientID="110" Name="Betus">5u</Casino>
<Casino ClientID="104" Name="BOVADA">5p</Casino>
<Casino ClientID="121" Name="Mirage">5p</Casino>
<Casino ClientID="127" Name="DonBest Consensus">5p</Casino>
<Casino ClientID="" Name=""></Casino>
</Over_Under>
</Game>
<Game>
<GameID>19289</GameID>
<Game_Date>10/15/2014</Game_Date>
<Game_Time>10:30 PM</Game_Time>
<AwayTeam>
<AwayTeamID>126</AwayTeamID>
<AwayRotationNumber>055</AwayRotationNumber>
<AwayAbbr>EDM</AwayAbbr>
<AwayTeamName>Edmonton</AwayTeamName>
<Casino ClientID="116" Name="5Dimes">+160</Casino>
<Casino ClientID="110" Name="Betus">+160</Casino>
<Casino ClientID="104" Name="BOVADA">+165</Casino>
<Casino ClientID="121" Name="Mirage">+165</Casino>
<Casino ClientID="127" Name="DonBest Consensus">+165</Casino>
<Casino ClientID="" Name=""></Casino>
</AwayTeam>
<HomeTeam>
<HomeTeamID>141</HomeTeamID>
<HomeRotationNumber>056</HomeRotationNumber>
<HomeAbbr>ARI</HomeAbbr>
<HomeTeamName>Arizona</HomeTeamName>
<Casino ClientID="116" Name="5Dimes">-185</Casino>
<Casino ClientID="110" Name="Betus">-190</Casino>
<Casino ClientID="104" Name="BOVADA">-190</Casino>
<Casino ClientID="121" Name="Mirage">-185</Casino>
<Casino ClientID="127" Name="DonBest Consensus">-185</Casino>
<Casino ClientID="" Name=""></Casino>
</HomeTeam>
<Over_Under>
<Casino ClientID="116" Name="5Dimes">5.5u</Casino>
<Casino ClientID="110" Name="Betus">5.5u</Casino>
<Casino ClientID="104" Name="BOVADA">5.5u</Casino>
<Casino ClientID="121" Name="Mirage">5.5u</Casino>
<Casino ClientID="127" Name="DonBest Consensus">5.5u</Casino>
<Casino ClientID="" Name=""></Casino>
</Over_Under>
</Game>
<time_stamp> October 15, 2014, at 12:27 PM ET </time_stamp>
</message>
+ test/xml/scoresxml-empty-numeral.xml view
@@ -0,0 +1,1 @@+<?xml version="1.0" standalone="no" ?>
<!DOCTYPE message PUBLIC "-//TSN//DTD Scores 1.0/EN" "scoresxml.dtd">
<message>
<XML_File_ID>21984547</XML_File_ID>
<heading>BC-ACF+109:104*11712999</heading>
<game_id></game_id>
<schedule_id></schedule_id>
<category>Scores</category>
<sport>NBA</sport>
<location>
<city>Sacramento</city>
<state>CA</state>
<country>USA</country>
</location>
<seasontype></seasontype>
<game>
<vteam id="109">Sacramento</vteam>
<hteam id="104">Brooklyn</hteam>
<vscore>117</vscore>
<hscore>129</hscore>
<status numeral="" type="F">FINAL</status>
</game>
<time_stamp> October 15, 2014, at 10:17 AM ET </time_stamp>
</message>
+ test/xml/scoresxml-empty-type.xml view
@@ -0,0 +1,1 @@+<?xml version="1.0" standalone="no" ?>
<!DOCTYPE message PUBLIC "-//TSN//DTD Scores 1.0/EN" "scoresxml.dtd">
<message>
<XML_File_ID>21986921</XML_File_ID>
<heading>BC-ADP+126:141*  4  6 3R 1:05</heading>
<game_id>19289</game_id>
<schedule_id>19289</schedule_id>
<category>Scores</category>
<sport>NHL</sport>
<location>
<city>Edmonton</city>
<state>ALBERTA</state>
<country>CANADA</country>
</location>
<location>
<city>Arizona</city>
<state>AZ</state>
<country>USA</country>
</location>
<seasontype>Regular</seasontype>
<game>
<vteam id="126">Edmonton</vteam>
<hteam id="141">Arizona</hteam>
<vscore>4</vscore>
<hscore>6</hscore>
<time_r>1:05</time_r>
<status numeral="3" type="">3 Period</status>
<notes>Lauri Korpikoski slap shot is over the net.</notes>
</game>
<time_stamp> October 16, 2014, at 01:27 AM ET </time_stamp>
</message>