├── .gitignore ├── LICENSE ├── README.md ├── build.sbt ├── dependencies.sbt ├── fetch-nba-play-by-plays.sh ├── fetch-wnba-play-by-plays.sh ├── generated └── ontology.ttl ├── pbprdf ├── pbprdf.bat ├── project ├── build.properties └── plugins.sbt └── src ├── main ├── resources │ └── logback.xml └── scala │ └── com │ └── stellmangreene │ └── pbprdf │ ├── EspnPlayByPlay.scala │ ├── Event.scala │ ├── GamePeriodInfo.scala │ ├── InvalidPlayByPlayException.scala │ ├── PbpRdfApp.scala │ ├── PlayByPlay.scala │ ├── model │ ├── EntityIriFactory.scala │ ├── Ontology.scala │ ├── OntologyAnnotationHelper.java │ ├── OntologyClass.java │ ├── OntologyComment.java │ ├── OntologyObjectProperty.java │ ├── OntologyPrefix.java │ ├── OntologyProperty.java │ ├── OntologyRdfRepository.scala │ └── OntologySubClassOf.java │ ├── plays │ ├── BlockPlay.scala │ ├── DelayOfGamePlay.scala │ ├── DoubleTechnicalFoulPlay.scala │ ├── EjectionPlay.scala │ ├── EndOfPlay.scala │ ├── EnterPlay.scala │ ├── FiveSecondViolationPlay.scala │ ├── FoulPlay.scala │ ├── JumpBallPlay.scala │ ├── Play.scala │ ├── PlayMatcher.scala │ ├── ReboundPlay.scala │ ├── ShotPlay.scala │ ├── TechnicalFoulPlay.scala │ ├── ThreeSecondViolationPlay.scala │ ├── TimeoutPlay.scala │ └── TurnoverPlay.scala │ └── util │ ├── RdfOperations.scala │ └── XmlHelper.scala └── test ├── resources └── com │ └── stellmangreene │ └── pbprdf │ └── test │ └── htmldata │ ├── 400610636-gameinfo.html │ ├── 400610636.html │ ├── 400927553-gameinfo.html │ ├── 400927553.html │ ├── 401029417-gameinfo.html │ └── 401029417.html └── scala └── com └── stellmangreene └── pbprdf ├── model └── test │ └── OntologyRdfRepositorySpec.scala ├── plays └── test │ ├── BlockPlaySpec.scala │ ├── DelayOfGamePlaySpec.scala │ ├── EjectionPlaySpec.scala │ ├── EndOfPlaySpec.scala │ ├── EnterPlaySpec.scala │ ├── FiveSecondViolationPlaySpec.scala │ ├── FoulPlaySpec.scala │ ├── JumpBallPlaySpec.scala │ ├── ReboundPlaySpec.scala │ ├── ShotPlaySpec.scala │ ├── TechnicalFoulPlaySpec.scala │ ├── ThreeSecondViolationPlaySpec.scala │ ├── TimeoutPlaySpec.scala │ └── TurnoverPlaySpec.scala ├── test ├── EspnPlayByPlaySpec.scala ├── EventSpec.scala ├── GamePeriodInfoSpec.scala └── TestIri.scala └── util └── test ├── RdfOperationsSpec.scala └── XmlHelperSpec.scala /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | pbprdf.jar 4 | 5 | # sbt specific 6 | .cache 7 | .history 8 | .lib/ 9 | dist/* 10 | target/ 11 | lib_managed/ 12 | src_managed/ 13 | project/boot/ 14 | project/plugins/project/ 15 | project/project/ 16 | 17 | # Scala-IDE specific 18 | .scala_dependencies 19 | .worksheet 20 | /bin/ 21 | 22 | # sbt-eclipse specific (Eclipse project files) 23 | .cache-main 24 | .cache-tests 25 | .classpath 26 | .project 27 | .settings/ 28 | 29 | # data folders 30 | data/ 31 | tmp/ 32 | logs/ 33 | 34 | # MacOS 35 | .DS_Store 36 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Stellman and Greene Consulting LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | lazy val root = (project in file(".")) 2 | .settings( 3 | name := "pbprdf", 4 | organization := "com.stellmangreene", 5 | version := "1.0.0", 6 | 7 | // Scala options 8 | scalaVersion := "2.12.6", 9 | scalacOptions ++= Seq("-feature"), 10 | fork in run := true, 11 | 12 | // Java options 13 | javaOptions ++= Seq("-Xms2G", "-Xmx8G"), 14 | 15 | // sbt options 16 | logLevel := Level.Info, 17 | 18 | // sbt-assembly options 19 | mainClass in assembly := Some("com.stellmangreene.pbprdf.PbpRdfApp"), 20 | assemblyOutputPath in assembly := file("./pbprdf.jar") 21 | 22 | ) 23 | 24 | // sbt-eclipse settings 25 | EclipseKeys.withSource := true 26 | 27 | -------------------------------------------------------------------------------- /dependencies.sbt: -------------------------------------------------------------------------------- 1 | // Runtime dependencies 2 | libraryDependencies ++= Seq( 3 | "org.scala-lang.modules" %% "scala-xml" % "1.0.6", 4 | "com.typesafe.scala-logging" %% "scala-logging" % "3.7.2" 5 | // Exclude conflicting transitive dependency 6 | excludeAll( 7 | ExclusionRule(organization = "org.scala-lang", name = "scala-reflect") 8 | ), 9 | "org.slf4j" % "slf4j-api" % "1.7.12", 10 | "ch.qos.logback" % "logback-classic" % "1.0.13", 11 | "org.ccil.cowan.tagsoup" % "tagsoup" % "1.2", 12 | "joda-time" % "joda-time" % "2.8.2", 13 | "org.joda" % "joda-convert" % "1.7", 14 | "org.eclipse.rdf4j" % "rdf4j-runtime" % "2.2.2", 15 | "commons-logging" % "commons-logging" % "1.2", 16 | "com.github.pathikrit" %% "better-files" % "3.4.0", 17 | 18 | // Test dependencies 19 | "org.scalatest" %% "scalatest" % "3.0.1" % "test" 20 | // Exclude conflicting transitive dependency 21 | excludeAll( 22 | ExclusionRule(organization = "org.scala-lang", name = "scala-reflect") 23 | ) 24 | ) 25 | -------------------------------------------------------------------------------- /fetch-nba-play-by-plays.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FOLDER=data/retrieved_`date +%s` 4 | echo "Fetching play-by-play files into $FOLDER" 5 | mkdir -p $FOLDER/ 6 | 7 | # fetch 2017-18 regular season (up to 12-25-2017) 8 | mkdir $FOLDER/nba-2017-2018-season 9 | #for ((i=400974437;i<=400974449;i++)) 10 | #do 11 | # curl http://www.espn.com/nba/playbyplay?gameId=$i > $FOLDER/nba-2017-2018-season/$i.html 12 | # curl http://www.espn.com/nba/game?gameId=$i > $FOLDER/nba-2017-2018-season/$i-gameinfo.html 13 | #done 14 | 15 | for ((i=400974700;i<=400974705;i++)) 16 | do 17 | curl http://www.espn.com/nba/playbyplay?gameId=$i > $FOLDER/nba-2017-2018-season/$i.html 18 | curl http://www.espn.com/nba/game?gameId=$i > $FOLDER/nba-2017-2018-season/$i-gameinfo.html 19 | done 20 | 21 | for ((i=400974766;i<=400975243;i++)) 22 | do 23 | curl http://www.espn.com/nba/playbyplay?gameId=$i > $FOLDER/nba-2017-2018-season/$i.html 24 | curl http://www.espn.com/nba/game?gameId=$i > $FOLDER/nba-2017-2018-season/$i-gameinfo.html 25 | done 26 | 27 | exit 28 | 29 | # fetch 2017-18 regular season (12-25-2017 to 2018) 30 | for ((i=400975244;i<=400975976;i++)) 31 | do 32 | curl http://www.espn.com/nba/playbyplay?gameId=$i > $FOLDER/nba-2017-2018-season/$i.html 33 | curl http://www.espn.com/nba/game?gameId=$i > $FOLDER/nba-2017-2018-season/$i-gameinfo.html 34 | done 35 | 36 | # fetch 2018 playoffs 37 | mkdir $FOLDER/nba-2018-playoffs 38 | for i in 401029441 401029410 401029439 401029459 401029429 401029417 401029438 401029411 401029442 401029446 401029412 401029430 401029460 401029421 401029440 401029414 401029443 401029461 401029453 401029424 401029413 401029432 401029444 401029462 401029418 401029445 401029434 401029455 401029415 401029427 401029422 401029450 401029435 401029447 401029456 401029416 401029428 401029423 401029451 401029436 401029419 401029431 401029452 401029437 401031412 401029433 401031590 401031671 401031713 401031645 401031639 401031714 401031672 401031646 401031640 401031673 401031715 401031647 401031641 401031674 401031716 401031642 401031648 401031675 401032840 401032761 401032841 401032762 401032842 401032763 401032843 401032764 401032844 401032765 401032845 401032766 39 | do 40 | curl http://www.espn.com/nba/playbyplay?gameId=$i > $FOLDER/nba-2018-playoffs/$i.html 41 | curl http://www.espn.com/nba/game?gameId=$i > $FOLDER/nba-2018-playoffs/$i-gameinfo.html 42 | done 43 | 44 | echo "Fetched play-by-play files into $FOLDER" 45 | 46 | -------------------------------------------------------------------------------- /fetch-wnba-play-by-plays.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FOLDER=data/retrieved_`date +%s` 4 | echo "Fetching play-by-play files into $FOLDER" 5 | mkdir -p $FOLDER/ 6 | 7 | # fetch 2018 playoffs 8 | mkdir $FOLDER/wnba-2018-playoffs 9 | for i in 401074453 401074454 401074455 401074456 401074459 401074457 401074460 401074458 401074473 401074461 401074462 401074463 401074465 401074464 401079693 401079694 401079695 10 | do 11 | curl http://www.espn.com/wnba/playbyplay?gameId=$i > $FOLDER/wnba-2018-playoffs/$i.html 12 | curl http://www.espn.com/wnba/game?gameId=$i > $FOLDER/wnba-2018-playoffs/$i-gameinfo.html 13 | done 14 | 15 | # fetch 2018 regular season 16 | mkdir $FOLDER/wnba-2018-season 17 | for ((i=401018781;i<=401018984;i++)) 18 | do 19 | curl http://www.espn.com/wnba/playbyplay?gameId=$i > $FOLDER/wnba-2018-season/$i.html 20 | curl http://www.espn.com/wnba/game?gameId=$i > $FOLDER/wnba-2018-season/$i-gameinfo.html 21 | done 22 | 23 | 24 | # fetch 2017 playoffs 25 | mkdir $FOLDER/wnba-2017-playoffs 26 | for i in 400981090 400981091 400981092 400981093 400981094 400981099 400981095 400981100 400981096 400981101 400981428 400981429 400981430 401018784 400981432 27 | do 28 | curl http://www.espn.com/wnba/playbyplay?gameId=$i > $FOLDER/wnba-2017-playoffs/$i.html 29 | curl http://www.espn.com/wnba/game?gameId=$i > $FOLDER/wnba-2017-playoffs/$i-gameinfo.html 30 | done 31 | 32 | # fetch 2017 regular season 33 | mkdir $FOLDER/wnba-2017-season 34 | for ((i=400927392;i<=400927595;i++)) 35 | do 36 | curl http://www.espn.com/wnba/playbyplay?gameId=$i > $FOLDER/wnba-2017-season/$i.html 37 | curl http://www.espn.com/wnba/game?gameId=$i > $FOLDER/wnba-2017-season/$i-gameinfo.html 38 | done 39 | 40 | 41 | # fetch 2016 playoffs 42 | mkdir $FOLDER/wnba-2016-playoffs 43 | for i in 400910430 400910431 400910450 400910451 400910452 400910457 400910453 400910458 400910459 400910454 400910460 400920400 400920461 400920462 400920463 400920464 44 | do 45 | curl http://www.espn.com/wnba/playbyplay?gameId=$i > $FOLDER/wnba-2016-playoffs/$i.html 46 | curl http://www.espn.com/wnba/game?gameId=$i > $FOLDER/wnba-2016-playoffs/$i-gameinfo.html 47 | done 48 | 49 | # fetch 2016 regular season 50 | mkdir $FOLDER/wnba-2016-season 51 | for ((i=400864357;i<=400864495;i++)) 52 | do 53 | curl http://www.espn.com/wnba/playbyplay?gameId=$i > $FOLDER/wnba-2016-season/$i.html 54 | curl http://www.espn.com/wnba/game?gameId=$i > $FOLDER/wnba-2016-season/$i-gameinfo.html 55 | done 56 | 57 | 58 | # fetch 2015 regular season 59 | mkdir $FOLDER/wnba-2015-season 60 | for ((i=400610636;i<=400610839;i++)) 61 | do 62 | curl http://www.espn.com/wnba/playbyplay?gameId=$i > $FOLDER/wnba-2015-season/$i.html 63 | curl http://www.espn.com/wnba/game?gameId=$i > $FOLDER/wnba-2015-season/$i-gameinfo.html 64 | done 65 | 66 | # fetch 2015 playoffs 67 | mkdir $FOLDER/wnba-2015-playoffs 68 | for i in 400839633 400839118 400839630 400838810 400839634 400839119 400839631 400838811 400839635 400839632 400844305 400844348 400844306 400844349 400844307 400847195 400847196 400847197 400847198 400847199 69 | do 70 | curl http://www.espn.com/wnba/playbyplay?gameId=$i > $FOLDER/wnba-2015-playoffs/$i.html 71 | curl http://www.espn.com/wnba/game?gameId=$i > $FOLDER/wnba-2015-playoffs/$i-gameinfo.html 72 | done 73 | 74 | 75 | # fetch 2014 regular season 76 | mkdir $FOLDER/wnba-2014-season 77 | for ((i=400539461;i<=400539662;i++)) 78 | do 79 | curl http://www.espn.com/wnba/playbyplay?gameId=$i > $FOLDER/wnba-2014-season/$i.html 80 | curl http://www.espn.com/wnba/game?gameId=$i > $FOLDER/wnba-2014-season/$i-gameinfo.html 81 | done 82 | curl http://www.espn.com/wnba/playbyplay?gameId=$i > $FOLDER/wnba-2014-season/400539664.html 83 | curl http://www.espn.com/wnba/game?gameId=$i > $FOLDER/wnba-2014-season/400539664-gameinfo.html 84 | 85 | 86 | # fetch 2014 playoffs 87 | mkdir $FOLDER/wnba-2014-playoffs 88 | for i in 400580067 400580068 400580069 400580070 400580071 400578287 400578288 400578290 400578291 400581627 400581628 400581629 400581821 400581822 400581823 400582773 400582774 400582775 89 | do 90 | curl http://www.espn.com/wnba/playbyplay?gameId=$i > $FOLDER/wnba-2014-playoffs/$i.html 91 | curl http://www.espn.com/wnba/game?gameId=$i > $FOLDER/wnba-2014-playoffs/$i-gameinfo.html 92 | done 93 | 94 | 95 | # fetch 2013 regular season 96 | mkdir $FOLDER/wnba-2013-season 97 | for ((i=400445705;i<=400445900;i++)) 98 | do 99 | curl http://www.espn.com/wnba/playbyplay?gameId=$i > $FOLDER/wnba-2013-season/$i.html 100 | curl http://www.espn.com/wnba/game?gameId=$i > $FOLDER/wnba-2013-season/$i-gameinfo.html 101 | done 102 | 103 | 104 | # fetch 2013 playoffs 105 | mkdir $FOLDER/wnba-2013-playoffs 106 | for i in 400496774 400496775 400496776 400496777 400496778 400496779 400496780 400496781 400496783 400496784 400499743 400499744 400499746 400499747 400505694 400505695 400505696 107 | do 108 | curl http://www.espn.com/wnba/playbyplay?gameId=$i > $FOLDER/wnba-2013-playoffs/$i.html 109 | curl http://www.espn.com/wnba/game?gameId=$i > $FOLDER/wnba-2013-playoffs/$i-gameinfo.html 110 | done 111 | 112 | echo "Fetched play-by-play files into $FOLDER" 113 | 114 | -------------------------------------------------------------------------------- /generated/ontology.ttl: -------------------------------------------------------------------------------- 1 | @prefix rdf: . 2 | @prefix rdfs: . 3 | @prefix xsd: . 4 | @prefix pbprdf: . 5 | 6 | pbprdf:Game a ; 7 | rdfs:label "A game" . 8 | 9 | pbprdf:Event a ; 10 | rdfs:label "An event in a play-by-play" . 11 | 12 | pbprdf:Play a ; 13 | rdfs:label "A play in a play-by-play" ; 14 | rdfs:subClassOf pbprdf:Event . 15 | 16 | pbprdf:Team a ; 17 | rdfs:label "A team" . 18 | 19 | pbprdf:Player a ; 20 | rdfs:label "A player" . 21 | 22 | pbprdf:Roster a ; 23 | rdfs:label "A game roster" . 24 | 25 | pbprdf:EndOfPeriod a ; 26 | rdfs:label "End of period" ; 27 | rdfs:subClassOf pbprdf:Event , pbprdf:Play . 28 | 29 | pbprdf:EndOfGame a ; 30 | rdfs:label "End of game" ; 31 | rdfs:subClassOf pbprdf:Event , pbprdf:Play . 32 | 33 | pbprdf:Timeout a ; 34 | rdfs:label "A timeout" ; 35 | rdfs:subClassOf pbprdf:Event , pbprdf:Play . 36 | 37 | pbprdf:JumpBall a ; 38 | rdfs:label "A jump ball" ; 39 | rdfs:subClassOf pbprdf:Event , pbprdf:Play . 40 | 41 | pbprdf:Rebound a ; 42 | rdfs:label "A rebound" ; 43 | rdfs:subClassOf pbprdf:Event , pbprdf:Play . 44 | 45 | pbprdf:Shot a ; 46 | rdfs:label "A shot" ; 47 | rdfs:subClassOf pbprdf:Event , pbprdf:Play . 48 | 49 | pbprdf:Block a ; 50 | rdfs:label "A block" ; 51 | rdfs:subClassOf pbprdf:Event , pbprdf:Play , pbprdf:Shot . 52 | 53 | pbprdf:Foul a ; 54 | rdfs:label "A foul" ; 55 | rdfs:subClassOf pbprdf:Event , pbprdf:Play . 56 | 57 | pbprdf:TechnicalFoul a ; 58 | rdfs:label "A technical foul" ; 59 | rdfs:subClassOf pbprdf:Event , pbprdf:Play . 60 | 61 | pbprdf:FiveSecondViolation a ; 62 | rdfs:label "A five-second violation" ; 63 | rdfs:subClassOf pbprdf:Event , pbprdf:Play . 64 | 65 | pbprdf:Ejection a ; 66 | rdfs:label "An ejection" ; 67 | rdfs:subClassOf pbprdf:Event , pbprdf:Play . 68 | 69 | pbprdf:Enters a ; 70 | rdfs:label "A player entering the game" ; 71 | rdfs:subClassOf pbprdf:Event , pbprdf:Play . 72 | 73 | pbprdf:Turnover a ; 74 | rdfs:label "A turnover" ; 75 | rdfs:subClassOf pbprdf:Event , pbprdf:Play . 76 | 77 | pbprdf:gameTime rdfs:label "The game time" ; 78 | rdfs:range xsd:dateTime ; 79 | rdfs:domain pbprdf:Game ; 80 | a . 81 | 82 | pbprdf:gameLocation rdfs:label "The game location" ; 83 | rdfs:range xsd:string ; 84 | rdfs:domain pbprdf:Game ; 85 | a . 86 | 87 | pbprdf:homeTeam rdfs:label "The home team for a game" ; 88 | rdfs:range pbprdf:Team ; 89 | rdfs:domain pbprdf:Game ; 90 | a . 91 | 92 | pbprdf:awayTeam rdfs:label "The away team for a game" ; 93 | rdfs:range pbprdf:Team ; 94 | rdfs:domain pbprdf:Game ; 95 | a . 96 | 97 | pbprdf:hasHomeTeamRoster rdfs:label "The home team roster for a game" ; 98 | rdfs:range pbprdf:Roster ; 99 | rdfs:domain pbprdf:Game ; 100 | a ; 101 | rdfs:comment "The home team roster is typically represented by a bnode in the Game entity" . 102 | 103 | pbprdf:hasAwayTeamRoster rdfs:label "The away team roster for a game" ; 104 | rdfs:range pbprdf:Roster ; 105 | rdfs:domain pbprdf:Game ; 106 | a ; 107 | rdfs:comment "The away team roster is typically represented by a bnode in the Game entity" . 108 | 109 | pbprdf:rosterTeam rdfs:label "The team for a home or away roster" ; 110 | rdfs:range pbprdf:Team ; 111 | rdfs:domain pbprdf:Roster ; 112 | a . 113 | 114 | pbprdf:hasPlayer rdfs:label "A player on a roster" ; 115 | rdfs:range pbprdf:Player ; 116 | rdfs:domain pbprdf:Roster ; 117 | a . 118 | 119 | pbprdf:inGame rdfs:label "The game the event occurred in" ; 120 | rdfs:range pbprdf:Game ; 121 | rdfs:domain pbprdf:Event ; 122 | a . 123 | 124 | pbprdf:forTeam rdfs:label "The team that an event is for" ; 125 | rdfs:range pbprdf:Team ; 126 | rdfs:domain pbprdf:Event ; 127 | a . 128 | 129 | pbprdf:time rdfs:label "The game time of an event (eg. 9:37)" ; 130 | rdfs:range xsd:string ; 131 | rdfs:domain pbprdf:Event ; 132 | a . 133 | 134 | pbprdf:period rdfs:label "The period the event occurred in" ; 135 | rdfs:range xsd:int ; 136 | rdfs:domain pbprdf:Event ; 137 | a ; 138 | rdfs:comment "Regulation periods are 1 through 4, overtime periods start at 5" . 139 | 140 | pbprdf:secondsIntoGame rdfs:label "The number of seconds into a game that an event occurred" ; 141 | rdfs:range xsd:int ; 142 | rdfs:domain pbprdf:Event ; 143 | a . 144 | 145 | pbprdf:secondsLeftInPeriod rdfs:label "The number of seconds left in the period when an event occurred" ; 146 | rdfs:range xsd:int ; 147 | rdfs:domain pbprdf:Event ; 148 | a . 149 | 150 | pbprdf:timeoutDuration rdfs:label "The duration of a timeout" ; 151 | rdfs:range xsd:string ; 152 | rdfs:domain pbprdf:Timeout ; 153 | a ; 154 | rdfs:comment "A string description (eg. Full timeout, 20 Sec." . 155 | 156 | pbprdf:isOfficial rdfs:label "Determines if a timeout is an official timeout" ; 157 | rdfs:range xsd:boolean ; 158 | rdfs:domain pbprdf:Timeout ; 159 | a ; 160 | rdfs:comment "True for an official timeout" . 161 | 162 | pbprdf:jumpBallHomePlayer rdfs:label "The home player contesting the jump ball" ; 163 | rdfs:range pbprdf:Player ; 164 | rdfs:domain pbprdf:JumpBall ; 165 | a . 166 | 167 | pbprdf:jumpBallAwayPlayer rdfs:label "The away player contesting the jump ball" ; 168 | rdfs:range pbprdf:Player ; 169 | rdfs:domain pbprdf:JumpBall ; 170 | a . 171 | 172 | pbprdf:jumpBallGainedPossession rdfs:label "The player who gained possession of the jump ball" ; 173 | rdfs:range pbprdf:Player ; 174 | rdfs:domain pbprdf:JumpBall ; 175 | a . 176 | 177 | pbprdf:reboundedBy rdfs:label "The player who rebounded the ball" ; 178 | rdfs:range pbprdf:Player ; 179 | rdfs:domain pbprdf:Rebound ; 180 | a . 181 | 182 | pbprdf:isOffensive rdfs:label "Determines if a foul or rebound is offensive" ; 183 | rdfs:range xsd:boolean ; 184 | rdfs:domain pbprdf:Foul , pbprdf:Rebound ; 185 | a ; 186 | rdfs:comment "Only ever has the value xsd:true, no value exists if the play was defensive" . 187 | 188 | pbprdf:shotMade rdfs:label "True if a shot was made" ; 189 | rdfs:range xsd:boolean ; 190 | rdfs:domain pbprdf:Shot ; 191 | a ; 192 | rdfs:comment "Can have the value xsd:true for made shots or xsd:false for missed shots, will always be present for a shot" . 193 | 194 | pbprdf:shotBy rdfs:label "The player that attempted the shot" ; 195 | rdfs:range pbprdf:Player ; 196 | rdfs:domain pbprdf:Shot ; 197 | a . 198 | 199 | pbprdf:shotType rdfs:label "The type of shot" ; 200 | rdfs:range xsd:string ; 201 | rdfs:domain pbprdf:Shot ; 202 | a ; 203 | rdfs:comment "Will be a description such as layup or 21-foot jumper" . 204 | 205 | pbprdf:shotAssistedBy rdfs:label "The player that assisted the shot" ; 206 | rdfs:range pbprdf:Player ; 207 | rdfs:domain pbprdf:Shot ; 208 | a . 209 | 210 | pbprdf:shotPoints rdfs:label "The number of points scored by the shot" ; 211 | rdfs:range xsd:int ; 212 | rdfs:domain pbprdf:Shot ; 213 | a ; 214 | rdfs:comment "Will only be present for made shots, will contain 1 for free throws, 2 for field goals, etc." . 215 | 216 | pbprdf:shotBlockedBy rdfs:label "The player that blocked the shot" ; 217 | rdfs:range pbprdf:Player ; 218 | rdfs:domain pbprdf:Block ; 219 | a . 220 | 221 | pbprdf:foulCommittedBy rdfs:label "The player who committed the foul" ; 222 | rdfs:range pbprdf:Player ; 223 | rdfs:domain pbprdf:Foul ; 224 | a . 225 | 226 | pbprdf:foulDrawnBy rdfs:label "The player who drew the foul" ; 227 | rdfs:range pbprdf:Player ; 228 | rdfs:domain pbprdf:Foul ; 229 | a . 230 | 231 | pbprdf:isShootingFoul rdfs:label "Determines if a foul is a shooting foul" ; 232 | rdfs:range xsd:boolean ; 233 | rdfs:domain pbprdf:Foul ; 234 | a ; 235 | rdfs:comment "Only ever has the value xsd:true, no value exists if the play was not a shooting foul" . 236 | 237 | pbprdf:isLooseBallFoul rdfs:label "Determines if a foul is a loose ball foul" ; 238 | rdfs:range xsd:boolean ; 239 | rdfs:domain pbprdf:Foul ; 240 | a ; 241 | rdfs:comment "Only ever has the value xsd:true, no value exists if the play was not a loose ball foul" . 242 | 243 | pbprdf:isCharge rdfs:label "Determines if a foul is a charge" ; 244 | rdfs:range xsd:boolean ; 245 | rdfs:domain pbprdf:Foul ; 246 | a ; 247 | rdfs:comment "Only ever has the value xsd:true, no value exists if the play was not a charge" . 248 | 249 | pbprdf:isPersonalBlockingFoul rdfs:label "Determines if a foul is a personal blocking foul" ; 250 | rdfs:range xsd:boolean ; 251 | rdfs:domain pbprdf:Foul ; 252 | a ; 253 | rdfs:comment "Only ever has the value xsd:true, no value exists if the play was not a personal blocking foul" . 254 | 255 | pbprdf:isThreeSecond rdfs:label "Determines if a technical foul is a three second violation" ; 256 | rdfs:range xsd:boolean ; 257 | rdfs:domain pbprdf:TechnicalFoul ; 258 | a ; 259 | rdfs:comment "Only ever has the value xsd:true, no value exists if the play was not a three second violation" . 260 | 261 | pbprdf:isDelayOfGame rdfs:label "Determines if a technical foul is a delay of game violation" ; 262 | rdfs:range xsd:boolean ; 263 | rdfs:domain pbprdf:TechnicalFoul ; 264 | a ; 265 | rdfs:comment "Only ever has the value xsd:true, no value exists if the play was not a delay of game violation" . 266 | 267 | pbprdf:technicalFoulNumber rdfs:label "The technical foul number for a player" ; 268 | rdfs:range xsd:int ; 269 | rdfs:domain pbprdf:TechnicalFoul ; 270 | a ; 271 | rdfs:comment "Will be 1 for first technical foul, 2 for 2nd" . 272 | 273 | pbprdf:playerEjected rdfs:label "The player that was ejected" ; 274 | rdfs:range pbprdf:Player ; 275 | rdfs:domain pbprdf:Ejection ; 276 | a . 277 | 278 | pbprdf:playerEntering rdfs:label "The player entering the game" ; 279 | rdfs:range pbprdf:Player ; 280 | rdfs:domain pbprdf:Enters ; 281 | a . 282 | 283 | pbprdf:playerExiting rdfs:label "The player exiting the game" ; 284 | rdfs:range pbprdf:Player ; 285 | rdfs:domain pbprdf:Enters ; 286 | a . 287 | 288 | pbprdf:turnoverType rdfs:label "The type of turnover" ; 289 | rdfs:range xsd:string ; 290 | rdfs:domain pbprdf:Turnover ; 291 | a ; 292 | rdfs:comment "Contains text that describs the turnover (eg. traveling, kicked ball violation, etc." . 293 | 294 | pbprdf:stolenBy rdfs:label "The player who stole the ball" ; 295 | rdfs:range pbprdf:Player ; 296 | rdfs:domain pbprdf:Turnover ; 297 | a . 298 | 299 | pbprdf:turnedOverBy rdfs:label "The player who turned the ball over" ; 300 | rdfs:range pbprdf:Player ; 301 | rdfs:domain pbprdf:Turnover ; 302 | a . 303 | 304 | pbprdf:eventNumber rdfs:label "The event number" ; 305 | rdfs:range xsd:int ; 306 | rdfs:domain pbprdf:Event ; 307 | a . 308 | 309 | pbprdf:previousEvent rdfs:label "The previous event in the game" ; 310 | rdfs:range pbprdf:Event ; 311 | rdfs:domain pbprdf:Event ; 312 | a . 313 | 314 | pbprdf:nextEvent rdfs:label "The next event in the game" ; 315 | rdfs:range pbprdf:Event ; 316 | rdfs:domain pbprdf:Event ; 317 | a . 318 | 319 | pbprdf:secondsSincePreviousEvent rdfs:label "The number of seconds since the previous event" ; 320 | rdfs:range xsd:int ; 321 | rdfs:domain pbprdf:Event ; 322 | a . 323 | 324 | pbprdf:secondsUntilNextEvent rdfs:label "The number of seconds until the next event" ; 325 | rdfs:range xsd:int ; 326 | rdfs:domain pbprdf:Event ; 327 | a . 328 | 329 | pbprdf:homeScore rdfs:label "Score for the home team" ; 330 | rdfs:range xsd:int ; 331 | rdfs:domain pbprdf:Event ; 332 | a . 333 | 334 | pbprdf:awayScore rdfs:label "Score for the away team" ; 335 | rdfs:range xsd:int ; 336 | rdfs:domain pbprdf:Event ; 337 | a . 338 | -------------------------------------------------------------------------------- /pbprdf: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | JAVA_OPT="-Xms2G -Xmx8G" 4 | 5 | java $JAVA_OPT -jar pbprdf.jar $* 6 | 7 | -------------------------------------------------------------------------------- /pbprdf.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem Slurp the command line arguments. This loop allows for an unlimited number 4 | rem of arguments (up to the command line limit, anyway). 5 | set CMD_LINE_ARGS=%1 6 | if ""%1""=="""" goto setupArgsEnd 7 | shift 8 | :setupArgs 9 | if ""%1""=="""" goto setupArgsEnd 10 | set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1 11 | shift 12 | goto setupArgs 13 | 14 | :setupArgsEnd 15 | 16 | if "%JAVA_HOME%" == "" goto noJavaHome 17 | if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome 18 | goto javaHome 19 | 20 | :noJavaHome 21 | set JAVA=java 22 | goto javaHomeEnd 23 | 24 | :javaHome 25 | set JAVA=%JAVA_HOME%\bin\java 26 | 27 | :javaHomeEnd 28 | 29 | :checkJdk18 30 | "%JAVA%" -version 2>&1 | findstr "1.8" >NUL 31 | IF ERRORLEVEL 0 goto java8 32 | echo Java 8 or newer required to run pbprdf 33 | goto end 34 | 35 | :java8 36 | rem use java 6+ wildcard feature 37 | rem echo Using wildcard to set classpath 38 | "%JAVA%" -jar pbprdf.jar %CMD_LINE_ARGS% 39 | goto end 40 | 41 | :end 42 | 43 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.1.5 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.2") 2 | 3 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6") 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | logs/pbprdf-${bySecond}.log 13 | true 14 | 15 | %-5relative %-5level %logger{35} - %msg%n 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/EspnPlayByPlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf 2 | 3 | import scala.language.postfixOps 4 | import scala.util.{ Try, Success, Failure } 5 | 6 | import org.joda.time._ 7 | import org.joda.time.format._ 8 | 9 | import better.files._ 10 | 11 | import org.eclipse.rdf4j.model.IRI 12 | 13 | import com.stellmangreene.pbprdf.model.EntityIriFactory 14 | import com.stellmangreene.pbprdf.util.XmlHelper 15 | 16 | import com.typesafe.scalalogging.LazyLogging 17 | 18 | /** 19 | * Read an ESPN-style play-by-play file 20 | * @author andrewstellman 21 | */ 22 | class EspnPlayByPlay(path: String, playByPlayFilename: String, gameInfoFilename: String) extends PlayByPlay with LazyLogging { 23 | 24 | /** Game source (eg. filename) */ 25 | override val gameSource = playByPlayFilename 26 | 27 | private val playByPlayXmlFile = path / playByPlayFilename 28 | private val playByPlayRootElem = Try(XmlHelper.parseXml(playByPlayXmlFile.newInputStream)) match { 29 | case Success(s) => s 30 | case Failure(t: Throwable) => { 31 | val msg = s"Unable to read ${playByPlayXmlFile.pathAsString}: ${t.getMessage}" 32 | logger.error(msg) 33 | throw new InvalidPlayByPlayException(msg) 34 | } 35 | } 36 | private val divs = (playByPlayRootElem \\ "body" \\ "div") 37 | 38 | private val gameInfoXmlFile = path / gameInfoFilename 39 | private val gameInfoRootElem = Try(XmlHelper.parseXml(gameInfoXmlFile.newInputStream)) match { 40 | case Success(s) => s 41 | case Failure(t: Throwable) => { 42 | val msg = s"Unable to read ${gameInfoXmlFile.pathAsString}: ${t.getMessage}" 43 | logger.error(msg) 44 | throw new InvalidPlayByPlayException(msg) 45 | } 46 | } 47 | private val gameinfoDivs = (gameInfoRootElem \\ "body" \\ "div") 48 | 49 | private val awayTeamElems = XmlHelper.getElemByClassAndTag(divs, "team away", "a") 50 | 51 | private def getElementFromXml(clazz: String, tag: String): String = { 52 | Try(XmlHelper.getElemByClassAndTag(divs, clazz, tag).map(_.text).get) match { 53 | case Success(s) => s 54 | case _ => { 55 | val msg = s"Unable to find ${clazz} in ${playByPlayFilename}" 56 | logger.error(msg) 57 | throw new InvalidPlayByPlayException(msg) 58 | } 59 | } 60 | } 61 | 62 | /** returned by getTeamNameAndScore */ 63 | private case class TeamNameAndScoreInfo(name: String, score: String, pngFilename: String, isWnba: Boolean) 64 | 65 | /** extracts the team name, score, and image filename (*.png) for a team */ 66 | private def getTeamNameAndScore(teamType: String): TeamNameAndScoreInfo = { 67 | val teamNode = XmlHelper.getElemByClassAndTag(divs, s"team $teamType", "div") 68 | if (teamNode.isEmpty) logMessageAndThrowException(s"Unable to find ${teamType} team name in ${playByPlayFilename}") 69 | 70 | val teamContainer = teamNode.get.find(_.attribute("class").mkString == "team-container") 71 | if (teamContainer.isEmpty) logMessageAndThrowException(s"Unable to find team-container for ${teamType} team name in ${playByPlayFilename}") 72 | 73 | val foundWnba = teamContainer.mkString.toLowerCase.contains("wnba") 74 | 75 | val nameSpan = teamContainer.get \\ "span" 76 | val name = nameSpan.find(_.attribute("class").mkString == "short-name").get.text 77 | val pngRegex = ".*/([A-Za-z]+\\.png).*".r 78 | val img = (teamContainer.get \\ "img") 79 | .find(_.attribute("src").isDefined) 80 | .map(_.attribute("src").mkString) 81 | 82 | val pngFilename = img match { 83 | case Some(s) if pngRegex.pattern.matcher(s).matches => { 84 | pngRegex.findAllIn(s).group(1) 85 | } 86 | case _ => logMessageAndThrowException(s"Unable to find name container for ${teamType} team name in ${playByPlayFilename}") 87 | } 88 | 89 | val scoreContainer = teamNode.get.find(_.attribute("class").mkString == "score-container") 90 | if (teamContainer.isEmpty) logMessageAndThrowException(s"Unable to find score-container for ${teamType} team name in ${playByPlayFilename}") 91 | 92 | val score = (scoreContainer.get \ "div").text 93 | 94 | TeamNameAndScoreInfo(name, score, pngFilename, foundWnba) 95 | } 96 | 97 | private val away = getTeamNameAndScore("away") 98 | 99 | /** Away team name */ 100 | override val awayTeam = away.name 101 | 102 | /** Away team score */ 103 | override val awayScore = away.score 104 | 105 | private val awayImageFilename = away.pngFilename 106 | 107 | private val home = getTeamNameAndScore("home") 108 | 109 | private val isWnba = home.isWnba 110 | 111 | override val gamePeriodInfo = if (isWnba) GamePeriodInfo.WNBAPeriodInfo else GamePeriodInfo.NBAPeriodInfo 112 | 113 | /** Home team name */ 114 | override val homeTeam = home.name 115 | 116 | /** Home team score */ 117 | override val homeScore = home.score 118 | 119 | private val homeImageFilename = home.pngFilename 120 | 121 | private val dataDateSpan = XmlHelper.getElemByClassAndTag(gameinfoDivs, "game-date-time", "span") 122 | if (dataDateSpan.isEmpty || dataDateSpan.get.isEmpty || dataDateSpan.get.head.attribute("data-date").isEmpty) 123 | logMessageAndThrowException(s"Unable to find game time in ${gameInfoFilename}") 124 | private val timestamp = dataDateSpan.get.head.attribute("data-date") 125 | 126 | /** Game time */ 127 | override val gameTime: DateTime = { 128 | val dataDateDiv = XmlHelper.getElemByClassAndTag(gameinfoDivs, "game-date-time", "div") 129 | if (dataDateDiv.isEmpty || (dataDateDiv.head \ "span").isEmpty || (dataDateDiv.get \ "span").head.attribute("data-date").isEmpty) 130 | logMessageAndThrowException(s"Unable to find game time and location in ${gameInfoFilename}") 131 | val dataDateValue = (dataDateDiv.get \ "span").head.attribute("data-date").mkString 132 | .replace("Z", ":00.00+0000") 133 | 134 | val formatter = ISODateTimeFormat.dateTime().withZone(DateTimeZone.getDefault()) 135 | Try(formatter.parseDateTime(dataDateValue)) match { 136 | case Success(dateTime) => { 137 | dateTime 138 | } 139 | case Failure(e: Throwable) => logMessageAndThrowException(s"Unable to parse game time in ${gameInfoFilename}: ${e.getMessage}") 140 | } 141 | } 142 | 143 | /** Game location */ 144 | override val gameLocation = { 145 | val locationDiv = XmlHelper.getElemByClassAndTag(gameinfoDivs, "caption-wrapper", "div") 146 | if (locationDiv.isEmpty || locationDiv.get.isEmpty || locationDiv.get.head.text.trim.isEmpty) { 147 | logger.warn(s"Unable get location in ${gameInfoFilename}") 148 | None 149 | } else { 150 | Some(locationDiv.get.head.text.trim) 151 | } 152 | } 153 | 154 | /** IRI of this game */ 155 | val gameIri: IRI = EntityIriFactory.getGameIri(homeTeam, awayTeam, gameTime) 156 | 157 | /** Events from the play-by-play */ 158 | override val events: Seq[Event] = readEvents() 159 | 160 | private def readEvents(): Seq[Event] = { 161 | logger.debug("Reading game: " + (awayTeam, awayScore, homeTeam, homeScore).toString) 162 | 163 | val quarterDivs = (playByPlayRootElem \\ "li" \ "div").filter(_.attribute("id").map(_.text).getOrElse("").startsWith("gp-quarter-")) 164 | if (quarterDivs.size < 4) logMessageAndThrowException(s"Unable find play-by-play events (only found ${quarterDivs.size} quarters) in ${playByPlayFilename}") 165 | 166 | val periodsAndPlayData: Seq[(Int, scala.xml.Node)] = quarterDivs.map(quarterDiv => { 167 | val period = quarterDiv.attribute("id").get.text.replace("gp-quarter-", "").toInt 168 | period -> quarterDiv 169 | }) 170 | .sortBy(_._1) 171 | 172 | periodsAndPlayData.groupBy(_._1).map(e => { 173 | val (period, nodes) = e 174 | val timeStampTd = (nodes.head._2 \\ "td").find(_.attribute("class").mkString == "time-stamp") 175 | if (timeStampTd.isEmpty) logMessageAndThrowException(s"Invalid event found in in ${playByPlayFilename}: ${nodes.head._2.mkString}") 176 | period -> timeStampTd.get.text 177 | }).toMap 178 | 179 | val eventsAndPeriods = periodsAndPlayData.flatMap(e => { 180 | val (period: Int, quarterDiv: scala.xml.Node) = e 181 | 182 | val playRows = quarterDiv.head \\ "tr" 183 | playRows.tail.map(tr => { 184 | 185 | def findTd(clazz: String) = { 186 | val td = (tr \ "td").find(_.attribute("class").mkString == clazz) 187 | if (td.isEmpty) logMessageAndThrowException(s"Invalid event found in in ${playByPlayFilename}: ${tr.mkString}") 188 | td 189 | } 190 | 191 | val gameDetails = findTd("game-details").get.text 192 | val timeStamp = findTd("time-stamp").get.text 193 | val score = findTd("combined-score").get.text.replaceAll(" ", "") 194 | 195 | val logo = findTd("logo") 196 | if (logo.isEmpty || (logo.get \ "img").isEmpty || (logo.get \ "img").head.attribute("src").isEmpty) logMessageAndThrowException(s"Invalid event found in in ${playByPlayFilename}: ${tr.mkString}") 197 | val logoHref = (logo.get \ "img").head.attribute("src").mkString 198 | val teamName = 199 | logoHref match { 200 | case s if s.contains(homeImageFilename) => homeTeam 201 | case s if s.contains(awayImageFilename) => awayTeam 202 | case _ => logMessageAndThrowException(s"Invalid team logo href (${logoHref}) found in in ${playByPlayFilename}: ${tr.mkString}") 203 | } 204 | 205 | (period, gameDetails, timeStamp, teamName, score) 206 | }) 207 | }) 208 | 209 | val eventData: Seq[Event] = eventsAndPeriods.zipWithIndex.map(e => { 210 | val ((period, play, timeStamp, teamName, score), eventIndex) = e 211 | Event(gameIri, playByPlayFilename, eventIndex + 1, period, timeStamp, teamName, play, score, gamePeriodInfo) 212 | }) 213 | 214 | if (eventData.isEmpty) 215 | logger.warn(s"No events read from ${playByPlayFilename}") 216 | logger.debug(s"Finished reading game") 217 | 218 | eventData 219 | } 220 | 221 | /** log a message and throw an InvalidPlayByPlayException */ 222 | private def logMessageAndThrowException(message: String) = { 223 | logger.error(message) 224 | throw new InvalidPlayByPlayException(message) 225 | } 226 | 227 | } 228 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/Event.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf 2 | 3 | import org.eclipse.rdf4j.model._ 4 | import org.eclipse.rdf4j.model.vocabulary._ 5 | import org.eclipse.rdf4j.repository.Repository 6 | 7 | import better.files._ 8 | 9 | import com.stellmangreene.pbprdf.model.EntityIriFactory 10 | import com.stellmangreene.pbprdf.model.Ontology 11 | import com.stellmangreene.pbprdf.plays._ 12 | 13 | import com.stellmangreene.pbprdf.util.RdfOperations._ 14 | 15 | import com.typesafe.scalalogging.LazyLogging 16 | 17 | /** 18 | * A play-by-play event that can be parsed into RDF triples 19 | * 20 | * @param gameIri 21 | * IRI of the game 22 | * @param eventNumber 23 | * Sequential number of this event 24 | * @param period 25 | * Period this occurred in (overtime starts with period 5) 26 | * @param time 27 | * Time of the event 28 | * @param description 29 | * Description of the event (eg. "Washington full timeout") 30 | * @param gamePeriodInfo 31 | * Period information 32 | * @param team 33 | * Name of the team (to include in the text file contents for the play-by-play) 34 | * @param Score 35 | * Score for the event (to include in the text file contents for the play-by-play) 36 | * @param play 37 | * Trimmed text of the play (to include in the file contents for the play-by-play) 38 | * 39 | * @author andrewstellman 40 | */ 41 | case class Event(gameIri: IRI, eventNumber: Int, period: Int, time: String, description: String)(val gamePeriodInfo: GamePeriodInfo, val team: String, val score: String, play: String) 42 | extends LazyLogging { 43 | 44 | override def toString = "Period " + period + " " + time + " - " + description 45 | 46 | /** IRI of this event for RDF */ 47 | val eventIri = EntityIriFactory.getEventIri(gameIri, eventNumber) 48 | 49 | /** 50 | * Add this event to an RDF repository 51 | * 52 | * @param rep 53 | * rdf4j repository to add the events to 54 | */ 55 | def addRdf(rep: Repository) = { 56 | val valueFactory = rep.getValueFactory 57 | rep.addTriples(eventTriples(valueFactory)) 58 | rep.addTriples(secondsIntoGameTriple(valueFactory)) 59 | rep.addTriples(scoreTriples(valueFactory)) 60 | } 61 | 62 | /** Generates the type, period, time, and label triples that every event must have */ 63 | private def eventTriples(valueFactory: ValueFactory): Set[(Resource, IRI, Value)] = { 64 | Set( 65 | (eventIri, RDF.TYPE, Ontology.EVENT), 66 | (eventIri, Ontology.IN_GAME, gameIri), 67 | (eventIri, Ontology.PERIOD, valueFactory.createLiteral(period)), 68 | (eventIri, Ontology.TIME, valueFactory.createLiteral(time)), 69 | (eventIri, RDFS.LABEL, valueFactory.createLiteral(description))) 70 | } 71 | 72 | /** Generate the pbprdf:secondsIntoGame and pbprdf:secondsLeftInPeriod triples */ 73 | private def secondsIntoGameTriple(valueFactory: ValueFactory): Set[(Resource, IRI, Value)] = { 74 | val secondsLeft = gamePeriodInfo.clockToSecondsLeft(period, time) 75 | secondsLeft.map(eventTimes => { 76 | Set[(Resource, IRI, Value)]( 77 | (eventIri, Ontology.SECONDS_INTO_GAME, valueFactory.createLiteral(eventTimes.secondsIntoGame)), 78 | (eventIri, Ontology.SECONDS_LEFT_IN_PERIOD, valueFactory.createLiteral(eventTimes.secondsLeftInPeriod))) 79 | 80 | }) 81 | .getOrElse(Set[(Resource, IRI, Value)]()) 82 | } 83 | 84 | /** Generate the score triples */ 85 | private def scoreTriples(valueFactory: ValueFactory): Set[(Resource, IRI, Value)] = { 86 | val scoreRegex = """\s*(\d+)\s*-\s*(\d+)\s*""".r 87 | score match { 88 | case scoreRegex(awayScore, homeScore) => Set( 89 | (eventIri, Ontology.AWAY_SCORE, valueFactory.createLiteral(awayScore.toInt)), 90 | (eventIri, Ontology.HOME_SCORE, valueFactory.createLiteral(homeScore.toInt))) 91 | case _ => { 92 | logger.warn(s"Unable to parse score in play: $play") 93 | Set() 94 | } 95 | } 96 | } 97 | 98 | /** Returns a text description of this event */ 99 | def getText: String = { 100 | s"$team\t$period\t$time\t$score\t${play.replaceAll("\t", " ")}" 101 | } 102 | } 103 | 104 | /** 105 | * Factory to create Play objects, choosing the subclass based on the play description 106 | * 107 | * @author andrewstellman 108 | */ 109 | object Event extends LazyLogging { 110 | 111 | /** 112 | * Create an instance of a play class, choosing the specific class based on the play description 113 | * 114 | * @param gameID 115 | * Unique ID of the game 116 | * @param eventNumber 117 | * Sequential number of this event 118 | * @param period 119 | * Period this occurred in (overtime starts with period 5) 120 | * @param team 121 | * Name of the team 122 | * @param play 123 | * Description of the play (eg. "Alyssa Thomas makes free throw 2 of 2") 124 | * @param score 125 | * Game score ("10-4") 126 | * @param gamePeriodInfo 127 | * Period length in minutes 128 | * 129 | * @author andrewstellman 130 | */ 131 | def apply(gameIri: IRI, filename: String, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo): Event = { 132 | 133 | val trimmedPlay = play.trim.replaceAll(" +", " ") 134 | 135 | trimmedPlay match { 136 | case trimmedPlay if BlockPlay.matches(trimmedPlay) => new BlockPlay(gameIri, eventNumber, period, time, team, trimmedPlay, score, gamePeriodInfo) 137 | case trimmedPlay if DelayOfGamePlay.matches(trimmedPlay) => new DelayOfGamePlay(gameIri, eventNumber, period, time, team, trimmedPlay, score, gamePeriodInfo) 138 | case trimmedPlay if EnterPlay.matches(trimmedPlay) => new EnterPlay(gameIri, eventNumber, period, time, team, trimmedPlay, score, gamePeriodInfo) 139 | case trimmedPlay if FoulPlay.matches(trimmedPlay) => new FoulPlay(gameIri, eventNumber, period, time, team, trimmedPlay, score, gamePeriodInfo) 140 | case trimmedPlay if JumpBallPlay.matches(trimmedPlay) => new JumpBallPlay(gameIri, eventNumber, period, time, team, trimmedPlay, score, gamePeriodInfo) 141 | case trimmedPlay if ReboundPlay.matches(trimmedPlay) => new ReboundPlay(gameIri, eventNumber, period, time, team, trimmedPlay, score, gamePeriodInfo) 142 | case trimmedPlay if ShotPlay.matches(trimmedPlay) => new ShotPlay(gameIri, eventNumber, period, time, team, trimmedPlay, score, gamePeriodInfo) 143 | case trimmedPlay if DoubleTechnicalFoulPlay.matches(trimmedPlay) => new DoubleTechnicalFoulPlay(gameIri, eventNumber, period, time, team, trimmedPlay, score, gamePeriodInfo) 144 | case trimmedPlay if TechnicalFoulPlay.matches(trimmedPlay) => new TechnicalFoulPlay(gameIri, eventNumber, period, time, team, trimmedPlay, score, gamePeriodInfo) 145 | case trimmedPlay if ThreeSecondViolationPlay.matches(trimmedPlay) => new ThreeSecondViolationPlay(gameIri, eventNumber, period, time, team, trimmedPlay, score, gamePeriodInfo) 146 | case trimmedPlay if FiveSecondViolationPlay.matches(trimmedPlay) => new FiveSecondViolationPlay(gameIri, eventNumber, period, time, team, trimmedPlay, score, gamePeriodInfo) 147 | case trimmedPlay if TurnoverPlay.matches(trimmedPlay) => new TurnoverPlay(gameIri, eventNumber, period, time, team, trimmedPlay, score, gamePeriodInfo) 148 | case trimmedPlay if TimeoutPlay.matches(trimmedPlay) => new TimeoutPlay(gameIri, eventNumber, period, time, team, trimmedPlay, score, gamePeriodInfo) 149 | case trimmedPlay if EndOfPlay.matches(trimmedPlay) => new EndOfPlay(gameIri, eventNumber, period, time, team, trimmedPlay, score, gamePeriodInfo) 150 | case trimmedPlay if EjectionPlay.matches(trimmedPlay) => new EjectionPlay(gameIri, eventNumber, period, time, team, trimmedPlay, score, gamePeriodInfo) 151 | case trimmedPlay => { 152 | logger.warn(s"Could not match play description in ${filename}: ${trimmedPlay}") 153 | Event(gameIri, eventNumber, period, time, trimmedPlay)(gamePeriodInfo, team, score, play) 154 | } 155 | } 156 | } 157 | 158 | /** 159 | * Adds pbprdf:nextEvent and pbprdf:previousEvent triples to a list of events 160 | */ 161 | def addPreviousAndNextTriples(rep: Repository, events: Seq[Event]) = { 162 | events 163 | .sortBy(_.eventNumber) 164 | .zipWithIndex.foreach(e => { 165 | val (event, index) = e 166 | rep.addTriple(event.eventIri, Ontology.EVENT_NUMBER, rep.getValueFactory.createLiteral(index + 1)) 167 | 168 | if (index + 1 < events.size) { 169 | val nextEvent = events(index + 1) 170 | rep.addTriple(event.eventIri, Ontology.NEXT_EVENT, nextEvent.eventIri) 171 | 172 | if (nextEvent.period == event.period) { 173 | val eventSecondsLeft = event.gamePeriodInfo.clockToSecondsLeft(nextEvent.period, nextEvent.time) 174 | val nextEventSecondsLeft = event.gamePeriodInfo.clockToSecondsLeft(event.period, event.time) 175 | if (nextEventSecondsLeft.isDefined && eventSecondsLeft.isDefined) { 176 | val secondsUntilNextEvent = nextEventSecondsLeft.get.secondsLeftInPeriod - eventSecondsLeft.get.secondsLeftInPeriod 177 | rep.addTriple(event.eventIri, Ontology.SECONDS_UNTIL_NEXT_EVENT, rep.getValueFactory.createLiteral(secondsUntilNextEvent)) 178 | } 179 | } 180 | } 181 | 182 | if (index > 0) { 183 | val previousEvent = events(index - 1) 184 | rep.addTriple(event.eventIri, Ontology.PREVIOUS_EVENT, previousEvent.eventIri) 185 | 186 | if (previousEvent.period == event.period) { 187 | val eventSecondsLeft = event.gamePeriodInfo.clockToSecondsLeft(previousEvent.period, previousEvent.time) 188 | val previousEventSecondsLeft = event.gamePeriodInfo.clockToSecondsLeft(event.period, event.time) 189 | if (previousEventSecondsLeft.isDefined && eventSecondsLeft.isDefined) { 190 | val secondsSincePreviousEvent = eventSecondsLeft.get.secondsLeftInPeriod - previousEventSecondsLeft.get.secondsLeftInPeriod 191 | rep.addTriple(event.eventIri, Ontology.SECONDS_SINCE_PREVIOUS_EVENT, rep.getValueFactory.createLiteral(secondsSincePreviousEvent)) 192 | } 193 | } 194 | } 195 | }) 196 | } 197 | 198 | } 199 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/GamePeriodInfo.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf 2 | 3 | import com.typesafe.scalalogging.LazyLogging 4 | 5 | /** Times for a specific event */ 6 | case class EventTimes(secondsIntoGame: Int, secondsLeftInPeriod: Int) 7 | 8 | /** Companion object for GamePeriodInfo that defines period information for various leagues */ 9 | object GamePeriodInfo { 10 | val WNBAPeriodInfo = GamePeriodInfo(10, 5, 4) 11 | val NBAPeriodInfo = GamePeriodInfo(12, 5, 4) 12 | val NCAAWPeriodInfo = GamePeriodInfo(10, 5, 4) 13 | val NCAAMPeriodInfo = GamePeriodInfo(20, 5, 2) 14 | } 15 | 16 | /** Information about the number of periods in the game and the number of minutes in each period */ 17 | case class GamePeriodInfo(regulationPeriodMinutes: Int, overtimePeriodMinutes: Int, regulationPeriods: Int) extends LazyLogging { 18 | 19 | /** 20 | * converts a clock reading into seconds into the game and left in period 21 | * 22 | * @param period 23 | * the period of the game 24 | * @param time 25 | * a clock reading (e.g. "9:47", "37.6") 26 | * @return a tuple of (seconds into the game, seconds left in period) 27 | */ 28 | def clockToSecondsLeft(period: Int, time: String): Option[EventTimes] = { 29 | 30 | val timeRegex = """^(\d+):(\d+)$""".r 31 | val secondsRegex = """^(\d+).(\d+)$""".r 32 | 33 | val result = 34 | time match { 35 | case timeRegex(minutes, seconds) => { 36 | Some((minutes.toInt, seconds.toInt, 0)) 37 | } 38 | case secondsRegex(seconds, fraction) => { 39 | Some((0, seconds.toInt, fraction.toInt)) 40 | } 41 | case _ => { 42 | logger.warn(s"Unable to calculate seconds into game from time ${time}") 43 | None 44 | } 45 | } 46 | 47 | result.map(e => { 48 | val (minutes, seconds, fraction) = e 49 | 50 | val inOvertime = (period > regulationPeriods) 51 | 52 | val secondsIntoPeriod = 53 | inOvertime match { 54 | case false if (minutes == regulationPeriodMinutes) => 0 // 10:00 in a WNBA game means zero seconds have elapsed 55 | case false => ((regulationPeriodMinutes - 1 - minutes) * 60) + (60 - seconds) 56 | case true if (minutes == overtimePeriodMinutes) => 0 57 | case true => ((overtimePeriodMinutes - 1 - minutes) * 60) + (60 - seconds) 58 | } 59 | 60 | val previousRegulationPeriodSecondsElapsed = 61 | if (inOvertime) regulationPeriods * regulationPeriodMinutes * 60 62 | else (period - 1) * regulationPeriodMinutes * 60 63 | 64 | val previousOvertimePeriodSecondsElapsed = 65 | if (inOvertime) (period - regulationPeriods - 1) * overtimePeriodMinutes * 60 66 | else 0 67 | 68 | val secondsIntoGame = secondsIntoPeriod + previousRegulationPeriodSecondsElapsed + previousOvertimePeriodSecondsElapsed 69 | 70 | val secondsLeftInPeriod = 71 | if (inOvertime) (overtimePeriodMinutes * 60) - secondsIntoPeriod 72 | else (regulationPeriodMinutes * 60) - secondsIntoPeriod 73 | 74 | EventTimes(secondsIntoGame, secondsLeftInPeriod) 75 | }) 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/InvalidPlayByPlayException.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf 2 | 3 | /** 4 | * An InvalidPlayByPlayException is thrown when an invalid play-by-play is read from a file 5 | * @author andrewstellman 6 | */ 7 | class InvalidPlayByPlayException(message: String) extends Exception(message) 8 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/PbpRdfApp.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf 2 | 3 | import java.io.File 4 | 5 | import com.stellmangreene.pbprdf.model.OntologyRdfRepository 6 | 7 | import com.stellmangreene.pbprdf.util.RdfOperations._ 8 | 9 | import better.files._ 10 | import org.joda.time._ 11 | import org.joda.time.format._ 12 | 13 | import com.typesafe.scalalogging.LazyLogging 14 | import com.stellmangreene.pbprdf.model.OntologyRdfRepository 15 | import com.stellmangreene.pbprdf.model.EntityIriFactory 16 | import org.joda.time.format.DateTimeFormat 17 | import org.eclipse.rdf4j.repository.sail.SailRepository 18 | import org.eclipse.rdf4j.sail.memory.MemoryStore 19 | 20 | object PbpRdfApp extends App with LazyLogging { 21 | 22 | def printUsageAndExit(message: Option[String] = None) = { 23 | if (message.isDefined) 24 | println(message.get) 25 | println("""usage: pbprdf folder [filename.ttl] 26 | Read all of the files in the folder and attempt to process them 27 | Write all plays for each game to stdout, or a file if specified 28 | 29 | pbprdf --ontology [filename.ttl] 30 | Write the ontology to stout, or a file if specified""") 31 | System.exit(0) 32 | } 33 | 34 | if (args.size != 1 && args.size != 2) { 35 | printUsageAndExit() 36 | } else { 37 | 38 | val outputFile = 39 | if (args.size >= 2) { 40 | if (new File(args(1)).exists) 41 | printUsageAndExit(Some(s"File already exists, will not overwrite: ${args(1)}")) 42 | Some(args(1)) 43 | 44 | } else { 45 | None 46 | 47 | } 48 | 49 | if (args(0) == "--ontology") { 50 | 51 | logger.info("Writing ontology statements") 52 | OntologyRdfRepository.rep.writeAllStatements(outputFile) 53 | 54 | } else { 55 | 56 | val fmt = DateTimeFormat.forPattern("YYYY-MM-dd_hh-mm-ss") 57 | val textFileFolder = "./play-by-play-textfiles" / fmt.print(new LocalDateTime()) 58 | textFileFolder.createIfNotExists(true, true) 59 | 60 | logger.info(s"Writing text files to folder ${textFileFolder.pathAsString}") 61 | 62 | val inputFolder = args(0).toFile 63 | if (!inputFolder.exists || !inputFolder.isDirectory) 64 | printUsageAndExit(Some(s"Invalid folder: ${inputFolder}")) 65 | 66 | if (inputFolder.isEmpty) 67 | printUsageAndExit(Some(s"No files found in folder: ${inputFolder}")) 68 | 69 | val playByPlayRegex = """^\d+\.html$""" 70 | 71 | val playByPlayFilenames = inputFolder.list.map(_.name).toArray 72 | val inputFiles = playByPlayFilenames 73 | .filter(!_.contains("-gameinfo")) 74 | .map(f => { 75 | val base = f.split("\\.").head 76 | (s"$base.html", s"$base-gameinfo.html") 77 | }) 78 | .filter(e => playByPlayFilenames.contains(e._1) && playByPlayFilenames.contains(e._2)) 79 | 80 | logger.info(s"Reading ${playByPlayFilenames.size} sets of play-by-play files from folder ${inputFolder}") 81 | 82 | var rep = new SailRepository(new MemoryStore) 83 | rep.initialize 84 | OntologyRdfRepository.addRdfPrefixes(rep) 85 | 86 | inputFiles 87 | .zipWithIndex 88 | .foreach(e => { 89 | val ((playByPlayFile, gameInfoFile), index) = e 90 | logger.info(s"Reading plays from $playByPlayFile and $gameInfoFile (file ${index + 1} of ${inputFiles.size})") 91 | try { 92 | val playByPlay: PlayByPlay = new EspnPlayByPlay(inputFolder.pathAsString, playByPlayFile, gameInfoFile) 93 | playByPlay.addRdf(rep) 94 | writeTextFile(playByPlay, textFileFolder) 95 | } catch { 96 | case e: InvalidPlayByPlayException => { 97 | logger.error(s"Error reading play-by-play: ${e.getMessage}") 98 | } 99 | } 100 | }) 101 | 102 | logger.info("Finished reading files") 103 | 104 | rep.writeAllStatements(outputFile) 105 | 106 | logger.info(s"Finished writing Turtle") 107 | logger.info(s"Wrote text files to folder ${textFileFolder.pathAsString}") 108 | 109 | } 110 | } 111 | 112 | /** 113 | * Writes this play-by-play to a file in a folder, overwriting any existing file 114 | * @param folder folder to write the file into 115 | */ 116 | def writeTextFile(playByPlay: PlayByPlay, folder: better.files.File) = { 117 | 118 | if (playByPlay.textFileContents.isEmpty) { 119 | logger.warn(s"Did not write empty play-by-play file for ${playByPlay}") 120 | } else 121 | 122 | try { 123 | val filename = EntityIriFactory.getGameIdentifierString(playByPlay.homeTeam, playByPlay.awayTeam, playByPlay.gameTime) + ".txt" 124 | val file = folder / filename 125 | if (file.exists) { 126 | logger.info(s"Overwriting ${file.pathAsString}") 127 | file.delete(true) 128 | } 129 | file.writeText(playByPlay.textFileContents.get.mkString("\n")) 130 | } catch { 131 | case t: Throwable => 132 | { 133 | logger.error(s"Error writing text file for game ${toString}") 134 | } 135 | } 136 | 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/PlayByPlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf 2 | 3 | import org.joda.time.DateTime 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.eclipse.rdf4j.model.vocabulary.RDF 6 | import org.eclipse.rdf4j.model.vocabulary.RDFS 7 | import org.eclipse.rdf4j.repository.Repository 8 | 9 | import com.stellmangreene.pbprdf.model.EntityIriFactory 10 | import com.stellmangreene.pbprdf.model.Ontology 11 | import com.stellmangreene.pbprdf.plays.EnterPlay 12 | 13 | import com.stellmangreene.pbprdf.util.RdfOperations._ 14 | 15 | import better.files._ 16 | 17 | import com.typesafe.scalalogging.LazyLogging 18 | import org.joda.time.format.ISODateTimeFormat 19 | import org.joda.time.format.DateTimeFormat 20 | import javax.xml.datatype.DatatypeFactory 21 | 22 | //TODO: Add triples for the box score, test against official box scores 23 | //TODO: Add triples for the players on the court for each possession 24 | 25 | /** 26 | * Play by play that can generate RDF and contents of a text file 27 | * 28 | * @author andrewstellman 29 | */ 30 | abstract class PlayByPlay extends LazyLogging { 31 | 32 | /** Events from the play-by-play */ 33 | val events: Seq[Event] 34 | 35 | /** IRI of this game */ 36 | val gameIri: IRI 37 | 38 | /** Name of the home team */ 39 | val homeTeam: String 40 | 41 | /** Final score for the home team */ 42 | val homeScore: String 43 | 44 | /** Name of the away team */ 45 | val awayTeam: String 46 | 47 | /** Final score for the away team */ 48 | val awayScore: String 49 | 50 | /** Game location */ 51 | val gameLocation: Option[String] 52 | 53 | /** Game time */ 54 | val gameTime: DateTime 55 | 56 | /** Game source (eg. filename) */ 57 | val gameSource: String 58 | 59 | /** Game period information */ 60 | val gamePeriodInfo: GamePeriodInfo 61 | 62 | /** returns the league (e.g. Some("WNBA")) based on GamePeriodInfo, None if unrecognized */ 63 | def league = { 64 | gamePeriodInfo match { 65 | case GamePeriodInfo.WNBAPeriodInfo => Some("WNBA") 66 | case GamePeriodInfo.NBAPeriodInfo => Some("NBA") 67 | case GamePeriodInfo.NCAAWPeriodInfo => Some("NCAAW") 68 | case GamePeriodInfo.NCAAMPeriodInfo => Some("NCAAM") 69 | case _ => { 70 | logger.warn("Unrecognized league") 71 | None 72 | } 73 | } 74 | } 75 | 76 | /** 77 | * Add the events to an RDF repository 78 | * 79 | * @param rep 80 | * rdf4j repository to add the events to 81 | */ 82 | def addRdf(rep: Repository) = { 83 | rep.addTriple(gameIri, RDF.TYPE, Ontology.GAME) 84 | gameLocation.foreach(location => 85 | rep.addTriple(gameIri, Ontology.GAME_LOCATION, rep.getValueFactory.createLiteral(location))) 86 | rep.addTriple(gameIri, RDFS.LABEL, rep.getValueFactory.createLiteral(this.toString)) 87 | val gregorianGameTime = DatatypeFactory.newInstance().newXMLGregorianCalendar(gameTime.toGregorianCalendar()) 88 | rep.addTriple(gameIri, Ontology.GAME_TIME, rep.getValueFactory.createLiteral(gregorianGameTime)) 89 | events.foreach(_.addRdf(rep)) 90 | Event.addPreviousAndNextTriples(rep, events) 91 | addRosterBnodes(rep) 92 | } 93 | 94 | /** 95 | * Use the "player enters" events to build the home and away team rosters and 96 | * add a bnode for each roster 97 | * 98 | * @param rep 99 | * rdf4j repository to add the events to 100 | */ 101 | protected def addRosterBnodes(rep: Repository) = { 102 | 103 | val homeTeamRosterBnode = rep.getValueFactory.createBNode 104 | val awayTeamRosterBnode = rep.getValueFactory.createBNode 105 | 106 | rep.addTriple(EntityIriFactory.getTeamIri(homeTeam), RDF.TYPE, Ontology.TEAM) 107 | rep.addTriple(gameIri, Ontology.HOME_TEAM, EntityIriFactory.getTeamIri(homeTeam)) 108 | rep.addTriple(gameIri, Ontology.HAS_HOME_TEAM_ROSTER, homeTeamRosterBnode) 109 | rep.addTriple(homeTeamRosterBnode, RDF.TYPE, Ontology.ROSTER) 110 | rep.addTriple(homeTeamRosterBnode, Ontology.ROSTER_TEAM, EntityIriFactory.getTeamIri(homeTeam)) 111 | rep.addTriple(homeTeamRosterBnode, RDFS.LABEL, rep.getValueFactory.createLiteral(homeTeam)) 112 | 113 | rep.addTriple(EntityIriFactory.getTeamIri(awayTeam), RDF.TYPE, Ontology.TEAM) 114 | rep.addTriple(gameIri, Ontology.AWAY_TEAM, EntityIriFactory.getTeamIri(awayTeam)) 115 | rep.addTriple(gameIri, Ontology.HAS_AWAY_TEAM_ROSTER, awayTeamRosterBnode) 116 | rep.addTriple(awayTeamRosterBnode, RDF.TYPE, Ontology.ROSTER) 117 | rep.addTriple(awayTeamRosterBnode, Ontology.ROSTER_TEAM, EntityIriFactory.getTeamIri(awayTeam)) 118 | rep.addTriple(awayTeamRosterBnode, RDFS.LABEL, rep.getValueFactory.createLiteral(awayTeam)) 119 | 120 | val playerTeamMap: Map[String, String] = events 121 | .filter(_.isInstanceOf[EnterPlay]) 122 | .map(_.asInstanceOf[EnterPlay]) 123 | .filter(_.playerEntering.isDefined) 124 | .map(enterPlay => enterPlay.playerEntering.get -> enterPlay.getTeam) 125 | .toMap 126 | 127 | val teams = playerTeamMap.values.toSeq.distinct 128 | if (teams.size != 2) 129 | logger.warn(s"Found entry plays with invalid number of teams ${teams.size} for game <${gameIri}> in ${gameSource}") 130 | 131 | val players = playerTeamMap.keys.toSeq.distinct 132 | players.foreach(player => { 133 | rep.addTriple(EntityIriFactory.getPlayerIri(player), RDFS.LABEL, rep.getValueFactory.createLiteral(player.trim)) 134 | 135 | val playerTeam = playerTeamMap.get(player).get 136 | val playerIri = EntityIriFactory.getPlayerIri(player) 137 | rep.addTriple(playerIri, RDF.TYPE, Ontology.PLAYER) 138 | if (playerTeam == homeTeam) { 139 | rep.addTriple(homeTeamRosterBnode, Ontology.HAS_PLAYER, playerIri) 140 | } else if (playerTeam == awayTeam) { 141 | rep.addTriple(awayTeamRosterBnode, Ontology.HAS_PLAYER, playerIri) 142 | } else { 143 | logger.warn(s"Entry plays contain team ${playerTeam} that does match home team ${homeTeam} or away team ${awayTeam} in ${gameSource}") 144 | } 145 | }) 146 | } 147 | 148 | /** 149 | * returns the contents of a text file representation of this play-by-play, or None if the play can't be rendered correctly 150 | */ 151 | def textFileContents: Option[Seq[String]] = { 152 | val header = Seq( 153 | toString, 154 | s"${gameLocation.getOrElse("Unknown Location")}\t${ISODateTimeFormat.dateTime().print(gameTime)}") 155 | 156 | val eventLines = events.map(_.getText) 157 | 158 | Some(header ++ eventLines) 159 | } 160 | 161 | override def toString: String = { 162 | val fmt = DateTimeFormat.forPattern("YYYY-MM-dd") 163 | val s = s"${awayTeam} (${awayScore}) at ${homeTeam} (${homeScore}) on ${fmt.print(gameTime)}" 164 | if (events.isEmpty) s"Empty Game: $s" 165 | else { 166 | s"${league.getOrElse("Unrecognized league")} game: $s - ${events.size} events" 167 | } 168 | } 169 | 170 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/model/EntityIriFactory.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.model 2 | 3 | import org.eclipse.rdf4j.model.impl.SimpleValueFactory 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.joda.time.DateTime 6 | import org.joda.time.format.DateTimeFormat 7 | 8 | /** 9 | * RDF entities 10 | * 11 | * @author andrewstellman 12 | */ 13 | object EntityIriFactory { 14 | 15 | private val valueFactory = SimpleValueFactory.getInstance() 16 | 17 | val NAMESPACE = "http://stellman-greene.com/pbprdf/" 18 | 19 | /** 20 | * Generate the IRI for a game entity 21 | */ 22 | def getGameIri(homeTeam: String, awayTeam: String, gameTime: DateTime): IRI = { 23 | valueFactory.createIRI(NAMESPACE, s"games/${getGameIdentifierString(homeTeam, awayTeam, gameTime)}") 24 | } 25 | 26 | def getGameIdentifierString(homeTeam: String, awayTeam: String, gameTime: DateTime): String = { 27 | val fmt = DateTimeFormat.forPattern("YYYY-MM-dd") 28 | s"${fmt.print(gameTime)}_${awayTeam.trim.replaceAll(" ", "_")}_at_${homeTeam.trim.replaceAll(" ", "_")}" 29 | } 30 | 31 | /** 32 | * Generate the IRI for an event entity 33 | */ 34 | def getEventIri(gameIri: IRI, eventNumber: Int): IRI = { 35 | val fmt = DateTimeFormat.forPattern("YYYY-MM-dd") 36 | valueFactory.createIRI(s"${gameIri.stringValue}/${eventNumber.toString}") 37 | } 38 | 39 | /** 40 | * Generate the IRI for a team 41 | */ 42 | def getTeamIri(name: String): IRI = { 43 | valueFactory.createIRI(NAMESPACE, s"teams/${name.trim.replaceAll(" ", "_")}") 44 | } 45 | 46 | /** 47 | * Generate the IRI for a player 48 | */ 49 | def getPlayerIri(name: String): IRI = { 50 | valueFactory.createIRI(NAMESPACE, s"players/${name.trim.replaceAll(" ", "_")}") 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/model/OntologyAnnotationHelper.java: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.model; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | /** 6 | * Utility class to help with ontology annotations using Java reflection 7 | * (because Scala reflection is just a little too clumsy) 8 | * 9 | * @author andrewstellman 10 | */ 11 | public class OntologyAnnotationHelper { 12 | 13 | public static OntologyClass getOntologyClassAnnotation(Field field) { 14 | return field.getAnnotation(OntologyClass.class); 15 | } 16 | 17 | public static OntologySubClassOf getOntologySubClassOfAnnotation(Field field) { 18 | return field.getAnnotation(OntologySubClassOf.class); 19 | } 20 | 21 | public static OntologyProperty getOntologyPropertyAnnotation(Field field) { 22 | return field.getAnnotation(OntologyProperty.class); 23 | } 24 | 25 | public static Boolean isObjectProperty(Field field) { 26 | return field.getAnnotation(OntologyObjectProperty.class) != null; 27 | } 28 | 29 | public static OntologyPrefix getOntologyPrefixAnnotation(Field field) { 30 | return field.getAnnotation(OntologyPrefix.class); 31 | } 32 | 33 | public static String getPrefix(Field field) { 34 | OntologyPrefix ontologyPrefix = field.getAnnotation(OntologyPrefix.class); 35 | if (ontologyPrefix == null) 36 | return null; 37 | else 38 | return ontologyPrefix.prefix(); 39 | } 40 | 41 | public static String getComment(Field field) { 42 | OntologyComment ontologyComment = field.getAnnotation(OntologyComment.class); 43 | if (ontologyComment == null) 44 | return null; 45 | else 46 | return ontologyComment.comment(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/model/OntologyClass.java: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.model; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | @Retention(RetentionPolicy.RUNTIME) 7 | public @interface OntologyClass { 8 | public String label(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/model/OntologyComment.java: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.model; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | @Retention(RetentionPolicy.RUNTIME) 7 | public @interface OntologyComment { 8 | public String comment(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/model/OntologyObjectProperty.java: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.model; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | @Retention(RetentionPolicy.RUNTIME) 7 | public @interface OntologyObjectProperty { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/model/OntologyPrefix.java: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.model; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | @Retention(RetentionPolicy.RUNTIME) 7 | public @interface OntologyPrefix { 8 | public String prefix(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/model/OntologyProperty.java: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.model; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | @Retention(RetentionPolicy.RUNTIME) 7 | public @interface OntologyProperty { 8 | public String label(); 9 | 10 | public String domain() default ""; 11 | 12 | public String[] domains() default {}; 13 | 14 | public String range(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/model/OntologyRdfRepository.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.model 2 | 3 | import java.lang.reflect.Field 4 | 5 | import org.eclipse.rdf4j.model.IRI 6 | import org.eclipse.rdf4j.model.vocabulary.OWL 7 | import org.eclipse.rdf4j.model.vocabulary.RDF 8 | import org.eclipse.rdf4j.model.vocabulary.RDFS 9 | import org.eclipse.rdf4j.repository.Repository 10 | import org.eclipse.rdf4j.repository.sail.SailRepository 11 | import org.eclipse.rdf4j.sail.memory.MemoryStore 12 | 13 | import com.stellmangreene.pbprdf.util.RdfOperations.repositoryImplicitOperations 14 | 15 | /** 16 | * Object that uses the Java annotations in Ontology to build an RDF repository 17 | * that contains statements that describe the ontology 18 | * 19 | * @author andrewstellman 20 | */ 21 | object OntologyRdfRepository { 22 | 23 | /** Repository that contains all of the ontology triples */ 24 | val rep = new SailRepository(new MemoryStore) 25 | 26 | rep.initialize 27 | 28 | addRdfPrefixes(rep) 29 | addRdfClasses 30 | addRdfProperties 31 | 32 | /** Add all of the RDF prefixes to the repository */ 33 | def addRdfPrefixes(rep: Repository) = { 34 | 35 | val ontologyPrefixes: Seq[(Field, OntologyPrefix)] = Ontology.getClass.getDeclaredFields 36 | .map(field => { 37 | field.setAccessible(true) 38 | (field, OntologyAnnotationHelper.getOntologyPrefixAnnotation(field)) 39 | }) 40 | .filter(_._2 != null) 41 | 42 | ontologyPrefixes.foreach(field => { 43 | val prefix = field._2.prefix 44 | val name = field._1.get(Ontology).toString 45 | rep.getConnection.setNamespace(prefix, name) 46 | }) 47 | } 48 | 49 | /** Add all of the RDF classes to the repository */ 50 | private def addRdfClasses = { 51 | 52 | val ontologyClasses: Seq[(Field, OntologyClass)] = Ontology.getClass.getDeclaredFields 53 | .map(field => { 54 | field.setAccessible(true) 55 | (field, OntologyAnnotationHelper.getOntologyClassAnnotation(field)) 56 | }) 57 | .filter(_._2 != null) 58 | 59 | ontologyClasses.foreach(e => { 60 | val (field, ontologyClassAnnotation) = e 61 | val classIri: IRI = field.get(Ontology).asInstanceOf[IRI] 62 | 63 | rep.addTriple(classIri, RDF.TYPE, OWL.CLASS) 64 | rep.addTriple(classIri, RDFS.LABEL, rep.getValueFactory.createLiteral(ontologyClassAnnotation.label)) 65 | 66 | val ontologySubClassOfAnnotation = OntologyAnnotationHelper.getOntologySubClassOfAnnotation(field) 67 | if (ontologySubClassOfAnnotation != null) 68 | ontologySubClassOfAnnotation.subClassOf.foreach(superClass => { 69 | rep.addTriple(classIri, RDFS.SUBCLASSOF, rep.getValueFactory.createIRI(superClass)) 70 | }) 71 | 72 | val comment = OntologyAnnotationHelper.getComment(field) 73 | if (comment != null) 74 | rep.addTriple(classIri, RDFS.COMMENT, rep.getValueFactory.createLiteral(comment)) 75 | }) 76 | 77 | } 78 | 79 | /** Add all of the RDF properties to the repository */ 80 | private def addRdfProperties = { 81 | 82 | val ontologyProperties: Seq[(Field, OntologyProperty)] = Ontology.getClass.getDeclaredFields 83 | .map(field => { 84 | field.setAccessible(true) 85 | (field, OntologyAnnotationHelper.getOntologyPropertyAnnotation(field)) 86 | }) 87 | .filter(_._2 != null) 88 | 89 | ontologyProperties.foreach(e => { 90 | val (field, ontologyPropertyAnnotation) = e 91 | val classIri: IRI = field.get(Ontology).asInstanceOf[IRI] 92 | 93 | rep.addTriple(classIri, RDFS.LABEL, rep.getValueFactory.createLiteral(ontologyPropertyAnnotation.label)) 94 | rep.addTriple(classIri, RDFS.RANGE, rep.getValueFactory.createIRI(ontologyPropertyAnnotation.range)) 95 | 96 | if (ontologyPropertyAnnotation.domain != "") 97 | rep.addTriple(classIri, RDFS.DOMAIN, rep.getValueFactory.createIRI(ontologyPropertyAnnotation.domain)) 98 | 99 | if (!ontologyPropertyAnnotation.domains.isEmpty) 100 | ontologyPropertyAnnotation.domains.foreach(domain => { 101 | rep.addTriple(classIri, RDFS.DOMAIN, rep.getValueFactory.createIRI(domain)) 102 | }) 103 | 104 | if (OntologyAnnotationHelper.isObjectProperty(field)) 105 | rep.addTriple(classIri, RDF.TYPE, OWL.OBJECTPROPERTY) 106 | else 107 | rep.addTriple(classIri, RDF.TYPE, OWL.DATATYPEPROPERTY) 108 | 109 | val comment = OntologyAnnotationHelper.getComment(field) 110 | if (comment != null) 111 | rep.addTriple(classIri, RDFS.COMMENT, rep.getValueFactory.createLiteral(comment)) 112 | }) 113 | 114 | } 115 | 116 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/model/OntologySubClassOf.java: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.model; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | @Retention(RetentionPolicy.RUNTIME) 7 | public @interface OntologySubClassOf { 8 | public String[] subClassOf(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/BlockPlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import org.eclipse.rdf4j.model.Resource 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.eclipse.rdf4j.model.Value 6 | import org.eclipse.rdf4j.repository.Repository 7 | 8 | import com.stellmangreene.pbprdf.GamePeriodInfo 9 | import com.stellmangreene.pbprdf.util.RdfOperations.repositoryImplicitOperations 10 | import com.typesafe.scalalogging.LazyLogging 11 | import com.stellmangreene.pbprdf.model.EntityIriFactory 12 | import com.stellmangreene.pbprdf.model.Ontology 13 | import org.eclipse.rdf4j.model.vocabulary.RDF 14 | 15 | /** 16 | * A play that represents a block 17 | *

18 | * Examples: 19 | * Emma Meesseman blocks Camille Little 's 2-foot jumper 20 | * Krystal Thomas blocks Erin Phillips' 3-foot layup 21 | * 22 | * @param gameIri 23 | * Unique ID of the game 24 | * @param eventNumber 25 | * Sequential number of this event 26 | * @param period 27 | * Period this occurred in (overtime starts with period 5) 28 | * @param team 29 | * Name of the team 30 | * @param play 31 | * Description of the play (eg. "Alyssa Thomas makes free throw 2 of 2") 32 | * @param score 33 | * Game score ("10-4") - CURRENTLY IGNORED 34 | * @param gamePeriodInfo 35 | * GamePeriodInfo for converting game time to seconds 36 | * 37 | * @author andrewstellman 38 | */ 39 | class BlockPlay(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 40 | extends Play(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 41 | with LazyLogging { 42 | 43 | override def addRdf(rep: Repository) = { 44 | val triples: Set[(Resource, IRI, Value)] = 45 | play match { 46 | case BlockPlay.playByPlayRegex(blockedBy, shotBy, shotType) => { 47 | Set( 48 | (eventIri, RDF.TYPE, Ontology.SHOT), 49 | (eventIri, RDF.TYPE, Ontology.BLOCK), 50 | (eventIri, Ontology.SHOT_BY, EntityIriFactory.getPlayerIri(shotBy)), 51 | (eventIri, Ontology.SHOT_BLOCKED_BY, EntityIriFactory.getPlayerIri(blockedBy))) 52 | } 53 | case _ => { logger.warn(s"Unrecognized block play: ${play}"); Set() } 54 | } 55 | 56 | if (!triples.isEmpty) 57 | rep.addTriples(triples) 58 | 59 | super.addRdf(rep) 60 | } 61 | 62 | } 63 | 64 | /** 65 | * Companion object that defines a regex that can be used to check this play against a description 66 | */ 67 | object BlockPlay extends PlayMatcher { 68 | 69 | val playByPlayRegex = """^(.*) blocks (.*)'s? (.*)$""".r 70 | 71 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/DelayOfGamePlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import org.eclipse.rdf4j.model.Resource 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.eclipse.rdf4j.model.Value 6 | import org.eclipse.rdf4j.repository.Repository 7 | import org.eclipse.rdf4j.model.vocabulary.RDF 8 | 9 | import com.stellmangreene.pbprdf.GamePeriodInfo 10 | import com.stellmangreene.pbprdf.util.RdfOperations.repositoryImplicitOperations 11 | import com.typesafe.scalalogging.LazyLogging 12 | import com.stellmangreene.pbprdf.model.Ontology 13 | 14 | /** 15 | * A play that represents a delay of game violation 16 | *

17 | * Examples: 18 | * Los Angeles delay of game violation 19 | * delay techfoul 20 | * 21 | * @param gameIri 22 | * Unique ID of the game 23 | * @param eventNumber 24 | * Sequential number of this event 25 | * @param period 26 | * Period this occurred in (overtime starts with period 5) 27 | * @param team 28 | * Name of the team 29 | * @param play 30 | * Description of the play (eg. "Alyssa Thomas makes free throw 2 of 2") 31 | * @param score 32 | * Game score ("10-4") - CURRENTLY IGNORED 33 | * @param gamePeriodInfo 34 | * GamePeriodInfo for converting game time to seconds 35 | * 36 | * 37 | * @author andrewstellman 38 | */ 39 | class DelayOfGamePlay(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 40 | extends Play(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 41 | with LazyLogging { 42 | 43 | override def addRdf(rep: Repository) = { 44 | val triples: Set[(Resource, IRI, Value)] = 45 | play match { 46 | case DelayOfGamePlay.playByPlayRegex(matchingText) => { 47 | Set( 48 | (eventIri, RDF.TYPE, Ontology.TECHNICAL_FOUL), 49 | (eventIri, Ontology.IS_DELAY_OF_GAME, rep.getValueFactory.createLiteral(true))) 50 | } 51 | 52 | case _ => { logger.warn(s"Unrecognized delay of game play: ${play}"); Set() } 53 | } 54 | 55 | if (!triples.isEmpty) 56 | rep.addTriples(triples) 57 | 58 | super.addRdf(rep) 59 | } 60 | 61 | } 62 | 63 | /** 64 | * Companion object that defines a regex that can be used to check this play against a description 65 | */ 66 | object DelayOfGamePlay extends PlayMatcher { 67 | 68 | val playByPlayRegex = """.*(delay techfoul|delay of game violation).*""".r 69 | 70 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/DoubleTechnicalFoulPlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import org.eclipse.rdf4j.model.Resource 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.eclipse.rdf4j.model.Value 6 | import org.eclipse.rdf4j.model.vocabulary.RDF 7 | import org.eclipse.rdf4j.repository.Repository 8 | 9 | import com.stellmangreene.pbprdf.GamePeriodInfo 10 | import com.stellmangreene.pbprdf.model.EntityIriFactory 11 | import com.stellmangreene.pbprdf.model.Ontology 12 | import com.stellmangreene.pbprdf.util.RdfOperations.repositoryImplicitOperations 13 | import com.typesafe.scalalogging.LazyLogging 14 | 15 | /** 16 | * A play that represents a double technical violation 17 | *

18 | * Examples: 19 | * Double technical foul: Erlana Larkins and Lynetta Kizer 20 | * 21 | * @param gameIri 22 | * Unique ID of the game 23 | * @param eventNumber 24 | * Sequential number of this event 25 | * @param period 26 | * Period this occurred in (overtime starts with period 5) 27 | * @param team 28 | * Name of the team 29 | * @param play 30 | * Description of the play (eg. "Alyssa Thomas makes free throw 2 of 2") 31 | * @param score 32 | * Game score ("10-4") - CURRENTLY IGNORED 33 | * @param gamePeriodInfo 34 | * GamePeriodInfo for converting game time to seconds 35 | * 36 | * 37 | * @author andrewstellman 38 | */ 39 | class DoubleTechnicalFoulPlay(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 40 | extends Play(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 41 | with LazyLogging { 42 | 43 | override def addRdf(rep: Repository) = { 44 | val triples: Set[(Resource, IRI, Value)] = 45 | play match { 46 | case DoubleTechnicalFoulPlay.playByPlayRegex(committedBy1, committedBy2) => { 47 | Set( 48 | (eventIri, RDF.TYPE, Ontology.TECHNICAL_FOUL), 49 | (eventIri, Ontology.FOUL_COMMITTED_BY, EntityIriFactory.getPlayerIri(committedBy1)), 50 | (eventIri, Ontology.FOUL_COMMITTED_BY, EntityIriFactory.getPlayerIri(committedBy2))) 51 | } 52 | 53 | case _ => { logger.warn(s"Unrecognized double technical foul play: ${play}"); Set() } 54 | } 55 | 56 | if (!triples.isEmpty) 57 | rep.addTriples(triples) 58 | 59 | super.addRdf(rep) 60 | } 61 | 62 | } 63 | 64 | /** 65 | * Companion object that defines a regex that can be used to check this play against a description 66 | */ 67 | object DoubleTechnicalFoulPlay extends PlayMatcher { 68 | 69 | val playByPlayRegex = """^Double technical foul: (.*) and (.*)$""".r 70 | 71 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/EjectionPlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import org.eclipse.rdf4j.model.Resource 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.eclipse.rdf4j.model.Value 6 | import org.eclipse.rdf4j.repository.Repository 7 | import org.eclipse.rdf4j.model.vocabulary.RDF 8 | 9 | import com.stellmangreene.pbprdf.model.EntityIriFactory 10 | import com.stellmangreene.pbprdf.model.Ontology 11 | import com.typesafe.scalalogging.LazyLogging 12 | import com.stellmangreene.pbprdf.GamePeriodInfo 13 | 14 | import com.stellmangreene.pbprdf.util.RdfOperations._ 15 | 16 | /** 17 | * A play that represents an ejection 18 | *

19 | * Examples: 20 | * Aerial Powers ejected 21 | * 22 | * @param gameIri 23 | * Unique ID of the game 24 | * @param eventNumber 25 | * Sequential number of this event 26 | * @param period 27 | * Period this occurred in (overtime starts with period 5) 28 | * @param team 29 | * Name of the team 30 | * @param play 31 | * Description of the play (eg. "Alyssa Thomas makes free throw 2 of 2") 32 | * @param score 33 | * Game score ("10-4") - CURRENTLY IGNORED 34 | * @param gamePeriodInfo 35 | * GamePeriodInfo for converting game time to seconds 36 | * 37 | * 38 | * @author andrewstellman 39 | */ 40 | class EjectionPlay(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 41 | extends Play(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 42 | with LazyLogging { 43 | 44 | override def addRdf(rep: Repository) = { 45 | val triples: Set[(Resource, IRI, Value)] = 46 | play match { 47 | case EjectionPlay.playByPlayRegex(playerEjected) => { 48 | Set( 49 | (eventIri, RDF.TYPE, Ontology.EJECTION), 50 | (eventIri, Ontology.PLAYER_EJECTED, EntityIriFactory.getPlayerIri(playerEjected))) 51 | } 52 | 53 | case _ => { logger.warn(s"Unrecognized ejection play: ${play}"); Set() } 54 | } 55 | 56 | if (!triples.isEmpty) 57 | rep.addTriples(triples) 58 | 59 | super.addRdf(rep) 60 | } 61 | 62 | } 63 | 64 | /** 65 | * Companion object that defines a regex that can be used to check this play against a description 66 | */ 67 | object EjectionPlay extends PlayMatcher { 68 | 69 | val playByPlayRegex = """^(.+) ejected$""".r 70 | 71 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/EndOfPlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import org.eclipse.rdf4j.model.Resource 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.eclipse.rdf4j.model.Value 6 | import org.eclipse.rdf4j.model.vocabulary.RDF 7 | import org.eclipse.rdf4j.repository.Repository 8 | 9 | import com.stellmangreene.pbprdf.GamePeriodInfo 10 | import com.stellmangreene.pbprdf.model.Ontology 11 | 12 | import com.stellmangreene.pbprdf.util.RdfOperations._ 13 | 14 | import com.typesafe.scalalogging.LazyLogging 15 | 16 | /** 17 | * A play that represents the end of a period or game 18 | *

19 | * Examples: 20 | * End of the 1st Quarter 21 | * End of Game 22 | * 23 | * @param gameIri 24 | * Unique ID of the game 25 | * @param eventNumber 26 | * Sequential number of this event 27 | * @param period 28 | * Period this occurred in (overtime starts with period 5) 29 | * @param team 30 | * Name of the team 31 | * @param play 32 | * Description of the play (eg. "Alyssa Thomas makes free throw 2 of 2") 33 | * @param score 34 | * Game score ("10-4") - CURRENTLY IGNORED 35 | * @param gamePeriodInfo 36 | * GamePeriodInfo for converting game time to seconds 37 | * 38 | * 39 | * @author andrewstellman 40 | */ 41 | class EndOfPlay(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 42 | extends Play(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 43 | with LazyLogging { 44 | 45 | override def addRdf(rep: Repository) = { 46 | logger.debug(s"Parsing timeout from play: ${play}") 47 | 48 | val triples: Set[(Resource, IRI, Value)] = 49 | play match { 50 | case EndOfPlay.playByPlayRegex("Game") => { 51 | Set( 52 | (eventIri, RDF.TYPE, Ontology.END_OF_GAME)) 53 | } 54 | case _ => { 55 | Set( 56 | (eventIri, RDF.TYPE, Ontology.END_OF_PERIOD)) 57 | } 58 | } 59 | 60 | if (!triples.isEmpty) 61 | rep.addTriples(triples) 62 | 63 | super.addRdf(rep) 64 | } 65 | 66 | } 67 | 68 | /** 69 | * Companion object that defines a regex that can be used to check this play against a description 70 | */ 71 | object EndOfPlay extends PlayMatcher { 72 | 73 | val playByPlayRegex = """^End of (.+)$""".r 74 | 75 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/EnterPlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import org.eclipse.rdf4j.model.Resource 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.eclipse.rdf4j.model.Value 6 | import org.eclipse.rdf4j.model.vocabulary.RDF 7 | import org.eclipse.rdf4j.repository.Repository 8 | 9 | import com.stellmangreene.pbprdf.GamePeriodInfo 10 | import com.stellmangreene.pbprdf.model.EntityIriFactory 11 | import com.stellmangreene.pbprdf.model.Ontology 12 | 13 | import com.stellmangreene.pbprdf.util.RdfOperations._ 14 | 15 | import com.typesafe.scalalogging.LazyLogging 16 | 17 | /** 18 | * A play that represents a player entering the game 19 | *

20 | * Examples: 21 | * Natasha Cloud enters the game for Ivory Latta 22 | * 23 | * @param gameIri 24 | * Unique ID of the game 25 | * @param eventNumber 26 | * Sequential number of this event 27 | * @param period 28 | * Period this occurred in (overtime starts with period 5) 29 | * @param team 30 | * Name of the team 31 | * @param play 32 | * Description of the play (eg. "Alyssa Thomas makes free throw 2 of 2") 33 | * @param score 34 | * Game score ("10-4") - CURRENTLY IGNORED 35 | * @param gamePeriodInfo 36 | * GamePeriodInfo for converting game time to seconds 37 | * 38 | * @author andrewstellman 39 | */ 40 | class EnterPlay(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 41 | extends Play(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 42 | with LazyLogging { 43 | 44 | override def addRdf(rep: Repository) = { 45 | val triples: Set[(Resource, IRI, Value)] = 46 | play match { 47 | case EnterPlay.playByPlayRegex(playerEntering, playerExiting) => { 48 | Set( 49 | (eventIri, RDF.TYPE, Ontology.ENTERS), 50 | (eventIri, Ontology.PLAYER_ENTERING, EntityIriFactory.getPlayerIri(playerEntering)), 51 | (eventIri, Ontology.PLAYER_EXITING, EntityIriFactory.getPlayerIri(playerExiting))) 52 | } 53 | 54 | case _ => { 55 | logger.warn(s"Unable to parse entering play: ${play}") 56 | Set() 57 | } 58 | } 59 | 60 | if (!triples.isEmpty) 61 | rep.addTriples(triples) 62 | 63 | super.addRdf(rep) 64 | } 65 | 66 | private val playersEnteringAndExiting = 67 | play match { 68 | case EnterPlay.playByPlayRegex(playerEntering, playerExiting) => { 69 | (Some(playerEntering.trim), Some(playerExiting.trim)) 70 | } 71 | 72 | case _ => (None, None) 73 | } 74 | 75 | /** The player entering the game (None if there's a problem parsing the player) */ 76 | val playerEntering: Option[String] = playersEnteringAndExiting._1 77 | 78 | /** The player exiting the game (None if there's a problem parsing the player) */ 79 | val playerExiting: Option[String] = playersEnteringAndExiting._2 80 | } 81 | 82 | /** 83 | * Companion object that defines a regex that can be used to check this play against a description 84 | */ 85 | object EnterPlay extends PlayMatcher { 86 | 87 | val playByPlayRegex = """^(.*) enters the game for (.*)$""".r 88 | 89 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/FiveSecondViolationPlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import org.eclipse.rdf4j.model.Resource 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.eclipse.rdf4j.model.Value 6 | import org.eclipse.rdf4j.model.vocabulary.RDF 7 | import org.eclipse.rdf4j.repository.Repository 8 | 9 | import com.stellmangreene.pbprdf.model.EntityIriFactory 10 | import com.stellmangreene.pbprdf.model.Ontology 11 | import com.typesafe.scalalogging.LazyLogging 12 | import com.stellmangreene.pbprdf.GamePeriodInfo 13 | 14 | import com.stellmangreene.pbprdf.util.RdfOperations._ 15 | 16 | /** 17 | * A play that represents a five second violation 18 | *

19 | * Examples: 20 | * 5 second violation 21 | * 22 | * @param gameIri 23 | * Unique ID of the game 24 | * @param eventNumber 25 | * Sequential number of this event 26 | * @param period 27 | * Period this occurred in (overtime starts with period 5) 28 | * @param team 29 | * Name of the team 30 | * @param play 31 | * Description of the play (eg. "Alyssa Thomas makes free throw 2 of 2") 32 | * @param score 33 | * Game score ("10-4") - CURRENTLY IGNORED 34 | * @param gamePeriodInfo 35 | * GamePeriodInfo for converting game time to seconds 36 | * 37 | * @author andrewstellman 38 | */ 39 | class FiveSecondViolationPlay(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 40 | extends Play(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 41 | with LazyLogging { 42 | 43 | override def addRdf(rep: Repository) = { 44 | val triples: Set[(Resource, IRI, Value)] = 45 | play match { 46 | case FiveSecondViolationPlay.playByPlayRegex() => { 47 | Set((eventIri, RDF.TYPE, Ontology.FIVE_SECOND_VIOLATION)) 48 | } 49 | 50 | case _ => { logger.warn(s"Unrecognized five second violation play: ${play}"); Set() } 51 | } 52 | 53 | if (!triples.isEmpty) 54 | rep.addTriples(triples) 55 | 56 | super.addRdf(rep) 57 | } 58 | 59 | } 60 | 61 | /** 62 | * Companion object that defines a regex that can be used to check this play against a description 63 | */ 64 | object FiveSecondViolationPlay extends PlayMatcher { 65 | 66 | val playByPlayRegex = """^5 second violation$""".r 67 | 68 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/FoulPlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import org.eclipse.rdf4j.model.Resource 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.eclipse.rdf4j.model.Value 6 | import org.eclipse.rdf4j.model.vocabulary.RDF 7 | import org.eclipse.rdf4j.repository.Repository 8 | 9 | import com.stellmangreene.pbprdf.GamePeriodInfo 10 | import com.stellmangreene.pbprdf.model.EntityIriFactory 11 | import com.stellmangreene.pbprdf.model.Ontology 12 | 13 | import com.stellmangreene.pbprdf.util.RdfOperations._ 14 | 15 | import com.typesafe.scalalogging.LazyLogging 16 | 17 | /** 18 | * A play that represents a foul 19 | *

20 | * Examples: 21 | * Camille Little personal foul (Stefanie Dolson draws the foul) 22 | * Kelsey Bone offensive foul (Stefanie Dolson draws the foul) 23 | * Kayla Thornton shooting foul (Alyssa Thomas draws the foul) 24 | * Kayla Thornton offensive Charge (Jasmine Thomas draws the foul) 25 | * Jantel Lavender loose ball foul (Sylvia Fowles draws the foul) 26 | * Serge Ibaka offensive charge 27 | * Kevin Love personal blocking foul 28 | * 29 | * @param gameIri 30 | * Unique ID of the game 31 | * @param eventNumber 32 | * Sequential number of this event 33 | * @param period 34 | * Period this occurred in (overtime starts with period 5) 35 | * @param team 36 | * Name of the team 37 | * @param play 38 | * Description of the play (eg. "Alyssa Thomas makes free throw 2 of 2") 39 | * @param score 40 | * Game score ("10-4") - CURRENTLY IGNORED 41 | * @param gamePeriodInfo 42 | * GamePeriodInfo for converting game time to seconds 43 | * 44 | * @author andrewstellman 45 | */ 46 | class FoulPlay(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 47 | extends Play(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 48 | with LazyLogging { 49 | 50 | override def addRdf(rep: Repository) = { 51 | val triples: Set[(Resource, IRI, Value)] = 52 | play match { 53 | case FoulPlay.playByPlayRegex(committedBy, foulType, drawnByGroup) => { 54 | 55 | val drawnByRegex = """ +\((.*) draws the foul\)""".r 56 | 57 | val drawnByTriple: Set[(Resource, IRI, Value)] = 58 | drawnByGroup match { 59 | case drawnByRegex(drawnBy) => Set((eventIri, Ontology.FOUL_DRAWN_BY, EntityIriFactory.getPlayerIri(drawnBy))) 60 | case _ => Set() 61 | } 62 | 63 | val isShootingFoulTriple: Set[(Resource, IRI, Value)] = 64 | if (foulType.trim == "shooting foul") 65 | Set((eventIri, Ontology.IS_SHOOTING_FOUL, rep.getValueFactory.createLiteral(true))) 66 | else 67 | Set() 68 | 69 | val isPersonalBlockingFoulTriple: Set[(Resource, IRI, Value)] = 70 | if (foulType.trim == "personal blocking foul") 71 | Set((eventIri, Ontology.IS_PERSONAL_BLOCKING_FOUL, rep.getValueFactory.createLiteral(true))) 72 | else 73 | Set() 74 | 75 | val offensiveTriples: Set[(Resource, IRI, Value)] = 76 | if (foulType.trim == "offensive foul") 77 | Set((eventIri, Ontology.IS_OFFENSIVE, rep.getValueFactory.createLiteral(true))) 78 | else if (foulType.trim.toLowerCase == "offensive charge") { 79 | Set( 80 | (eventIri, Ontology.IS_OFFENSIVE, rep.getValueFactory.createLiteral(true)), 81 | (eventIri, Ontology.IS_CHARGE, rep.getValueFactory.createLiteral(true))) 82 | } else 83 | Set() 84 | 85 | val looseBallTriple: Set[(Resource, IRI, Value)] = 86 | if (foulType.trim == "loose ball foul") 87 | Set((eventIri, Ontology.IS_LOOSE_BALL_FOUL, rep.getValueFactory.createLiteral(true))) 88 | else 89 | Set() 90 | 91 | Set( 92 | (eventIri, RDF.TYPE, Ontology.FOUL), 93 | (eventIri, Ontology.FOUL_COMMITTED_BY, EntityIriFactory.getPlayerIri(committedBy))) ++ 94 | drawnByTriple ++ isShootingFoulTriple ++ isPersonalBlockingFoulTriple ++ offensiveTriples ++ looseBallTriple 95 | } 96 | case _ => { logger.warn(s"Unrecognized foul play: ${play}"); Set() } 97 | } 98 | 99 | if (!triples.isEmpty) 100 | rep.addTriples(triples) 101 | 102 | super.addRdf(rep) 103 | } 104 | 105 | } 106 | 107 | /** 108 | * Companion object that defines a regex that can be used to check this play against a description 109 | */ 110 | object FoulPlay extends PlayMatcher { 111 | 112 | val playByPlayRegex = """^(.*) (personal foul|personal blocking foul|shooting foul|offensive foul|offensive charge|offensive Charge|loose ball foul|personal take foul|shooting block foul|personal block|in.?bound foul|away from play foul|clear path foul|flagrant foul type .)( +\(.* draws the foul\))?$""".r 113 | 114 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/JumpBallPlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import org.eclipse.rdf4j.model.Resource 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.eclipse.rdf4j.model.Value 6 | import org.eclipse.rdf4j.model.vocabulary.RDF 7 | import org.eclipse.rdf4j.repository.Repository 8 | 9 | import com.stellmangreene.pbprdf.GamePeriodInfo 10 | import com.stellmangreene.pbprdf.model.EntityIriFactory 11 | import com.stellmangreene.pbprdf.model.Ontology 12 | 13 | import com.stellmangreene.pbprdf.util.RdfOperations._ 14 | 15 | import com.typesafe.scalalogging.LazyLogging 16 | 17 | /** 18 | * A play that represents a jump ball 19 | *

20 | * Examples: 21 | * Stefanie Dolson vs. Kelsey Bone (Jasmine Thomas gains possession) 22 | * 23 | * @param gameIri 24 | * Unique ID of the game 25 | * @param eventNumber 26 | * Sequential number of this event 27 | * @param period 28 | * Period this occurred in (overtime starts with period 5) 29 | * @param team 30 | * Name of the team 31 | * @param play 32 | * Description of the play (eg. "Alyssa Thomas makes free throw 2 of 2") 33 | * @param score 34 | * Game score ("10-4") - CURRENTLY IGNORED 35 | * @param gamePeriodInfo 36 | * GamePeriodInfo for converting game time to seconds 37 | * 38 | * @author andrewstellman 39 | */ 40 | class JumpBallPlay(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 41 | extends Play(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 42 | with LazyLogging { 43 | 44 | override def addRdf(rep: Repository) = { 45 | val triples: Set[(Resource, IRI, Value)] = 46 | play match { 47 | case JumpBallPlay.playByPlayRegex(awayPlayer, homePlayerAndGainsPossession) => { 48 | logger.debug(s"Parsing jump ball from play: ${play}") 49 | 50 | val gainsPossessionRegex = """(.*) \((.*) gains possession\)""".r 51 | 52 | homePlayerAndGainsPossession match { 53 | case gainsPossessionRegex(homePlayer, gainedPossessionPlayer) => 54 | Set( 55 | (eventIri, RDF.TYPE, Ontology.JUMP_BALL), 56 | (eventIri, Ontology.JUMP_BALL_HOME_PLAYER, EntityIriFactory.getPlayerIri(homePlayer)), 57 | (eventIri, Ontology.JUMP_BALL_AWAY_PLAYER, EntityIriFactory.getPlayerIri(awayPlayer)), 58 | (eventIri, Ontology.JUMP_BALL_GAINED_POSSESSION, EntityIriFactory.getPlayerIri(gainedPossessionPlayer))) 59 | 60 | case _ => Set( 61 | (eventIri, RDF.TYPE, Ontology.JUMP_BALL), 62 | (eventIri, Ontology.JUMP_BALL_HOME_PLAYER, EntityIriFactory.getPlayerIri(homePlayerAndGainsPossession)), 63 | (eventIri, Ontology.JUMP_BALL_AWAY_PLAYER, EntityIriFactory.getPlayerIri(awayPlayer))) 64 | } 65 | } 66 | 67 | case _ => { logger.warn(s"Unrecognized jump ball play: ${play}"); Set() } 68 | } 69 | 70 | if (!triples.isEmpty) 71 | rep.addTriples(triples) 72 | 73 | super.addRdf(rep) 74 | } 75 | 76 | } 77 | 78 | /** 79 | * Companion object that defines a regex that can be used to check this play against a description 80 | */ 81 | object JumpBallPlay extends PlayMatcher { 82 | 83 | val playByPlayRegex = """^(.*) vs. (.*)$""".r 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/Play.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import org.eclipse.rdf4j.model.IRI 4 | import org.eclipse.rdf4j.model.vocabulary.RDF 5 | import org.eclipse.rdf4j.repository.Repository 6 | 7 | import com.stellmangreene.pbprdf.Event 8 | import com.stellmangreene.pbprdf.GamePeriodInfo 9 | import com.stellmangreene.pbprdf.model.EntityIriFactory 10 | import com.stellmangreene.pbprdf.model.Ontology 11 | 12 | import com.stellmangreene.pbprdf.util.RdfOperations._ 13 | 14 | /** 15 | * A Play is an event that matches a play-by-play regex, and can be checked to see if it 16 | * matches a description from a play-by-play 17 | * 18 | * @author andrewstellman 19 | */ 20 | abstract class Play(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 21 | extends Event(gameIri: IRI, eventNumber, period, time, s"${team}: ${play}")(gamePeriodInfo, team, score, play) { 22 | 23 | /** 24 | * Add the type and pbprdf:team triples that every Play event must have 25 | */ 26 | override def addRdf(rep: Repository) { 27 | rep.addTriple(eventIri, RDF.TYPE, Ontology.PLAY) 28 | rep.addTriple(eventIri, Ontology.FOR_TEAM, EntityIriFactory.getTeamIri(team)) 29 | super.addRdf(rep) 30 | } 31 | 32 | def getTeam = team 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/PlayMatcher.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import scala.util.matching.Regex 4 | 5 | /** 6 | * Trait for all play companion objects 7 | * 8 | * @author andrewstellman 9 | */ 10 | trait PlayMatcher { 11 | val playByPlayRegex: Regex 12 | 13 | def matches(play: String) = { 14 | playByPlayRegex.pattern.matcher(play).matches 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/ReboundPlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import org.eclipse.rdf4j.model.Resource 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.eclipse.rdf4j.model.Value 6 | import org.eclipse.rdf4j.model.vocabulary.RDF 7 | import org.eclipse.rdf4j.repository.Repository 8 | 9 | import com.stellmangreene.pbprdf.GamePeriodInfo 10 | import com.stellmangreene.pbprdf.model.EntityIriFactory 11 | import com.stellmangreene.pbprdf.model.Ontology 12 | 13 | import com.stellmangreene.pbprdf.util.RdfOperations._ 14 | 15 | import com.typesafe.scalalogging.LazyLogging 16 | 17 | /** 18 | * A play that represents a rebound 19 | *

20 | * Examples: 21 | * Emma Meesseman defensive rebound 22 | * Tierra Ruffin-Pratt offensive rebound 23 | * Washington offensive team rebound 24 | * Washington defensive team rebound 25 | * 26 | * @param gameIri 27 | * Unique ID of the game 28 | * @param eventNumber 29 | * Sequential number of this event 30 | * @param period 31 | * Period this occurred in (overtime starts with period 5) 32 | * @param team 33 | * Name of the team 34 | * @param play 35 | * Description of the play (eg. "Alyssa Thomas makes free throw 2 of 2") 36 | * @param score 37 | * Game score ("10-4") - CURRENTLY IGNORED 38 | * @param gamePeriodInfo 39 | * GamePeriodInfo for converting game time to seconds 40 | * 41 | * @author andrewstellman 42 | */ 43 | class ReboundPlay(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 44 | extends Play(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 45 | with LazyLogging { 46 | 47 | override def addRdf(rep: Repository) = { 48 | val triples: Set[(Resource, IRI, Value)] = 49 | play match { 50 | case ReboundPlay.playByPlayRegex(reboundedBy, reboundType, isTeam) => { 51 | logger.debug(s"Parsing rebound from play: ${play}") 52 | 53 | val offensiveReboundTriples: Set[(Resource, IRI, Value)] = 54 | if (reboundType.trim == "offensive") 55 | Set((eventIri, Ontology.IS_OFFENSIVE, rep.getValueFactory.createLiteral(true))) 56 | else 57 | Set() 58 | 59 | Set( 60 | (eventIri, RDF.TYPE, Ontology.REBOUND), 61 | (eventIri, Ontology.REBOUNDED_BY, EntityIriFactory.getPlayerIri(reboundedBy))) ++ 62 | offensiveReboundTriples 63 | } 64 | case _ => { logger.warn(s"Unrecognized rebound play: ${play}"); Set() } 65 | } 66 | 67 | if (!triples.isEmpty) 68 | rep.addTriples(triples) 69 | 70 | super.addRdf(rep) 71 | } 72 | 73 | } 74 | 75 | /** 76 | * Companion object that defines a regex that can be used to check this play against a description 77 | */ 78 | object ReboundPlay extends PlayMatcher { 79 | 80 | val playByPlayRegex = """^(.*) (offensive|defensive) (team )?rebound$""".r 81 | 82 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/ShotPlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import org.eclipse.rdf4j.model.Resource 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.eclipse.rdf4j.model.Value 6 | import org.eclipse.rdf4j.model.vocabulary.RDF 7 | import org.eclipse.rdf4j.repository.Repository 8 | 9 | import com.stellmangreene.pbprdf.GamePeriodInfo 10 | import com.stellmangreene.pbprdf.model.EntityIriFactory 11 | import com.stellmangreene.pbprdf.model.Ontology 12 | import com.typesafe.scalalogging.LazyLogging 13 | 14 | import com.stellmangreene.pbprdf.util.RdfOperations._ 15 | 16 | /** 17 | * A play that represents a shot 18 | *

19 | * Examples: 20 | * Kelsey Bone misses jumper 21 | * Stefanie Dolson misses 13-foot jumper 22 | * Emma Meesseman makes 13-foot two point shot 23 | * Jasmine Thomas makes layup (Alex Bentley assists) 24 | * Kara Lawson makes 24-foot three point jumper (Ivory Latta assists) 25 | * Ivory Latta misses finger roll layup 26 | * Natasha Cloud misses free throw 1 of 2 27 | * Alyssa Thomas makes free throw 2 of 2 28 | * Alex Bentley makes technical free throw 29 | * 30 | * @param gameIri 31 | * Unique ID of the game 32 | * @param eventNumber 33 | * Sequential number of this event 34 | * @param period 35 | * Period this occurred in (overtime starts with period 5) 36 | * @param team 37 | * Name of the team 38 | * @param play 39 | * Description of the play (eg. "Alyssa Thomas makes free throw 2 of 2") 40 | * @param score 41 | * Game score ("10-4") - CURRENTLY IGNORED 42 | * @param gamePeriodInfo 43 | * GamePeriodInfo for converting game time to seconds 44 | * 45 | * @author andrewstellman 46 | */ 47 | class ShotPlay(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 48 | extends Play(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 49 | with LazyLogging { 50 | 51 | override def addRdf(rep: Repository) = { 52 | val triples: Set[(Resource, IRI, Value)] = 53 | play match { 54 | case ShotPlay.playByPlayRegex(player, makesMisses, shotType, assists) => { 55 | logger.debug(s"Parsing shot from play: ${play}") 56 | 57 | val made = 58 | if (makesMisses.trim == "makes") 59 | true 60 | else 61 | false 62 | 63 | val pointsTriple: Set[(Resource, IRI, Value)] = 64 | if (shotType.contains("free throw")) 65 | Set((eventIri, Ontology.SHOT_POINTS, rep.getValueFactory.createLiteral(1))) 66 | else if (shotType.contains("three point")) 67 | Set((eventIri, Ontology.SHOT_POINTS, rep.getValueFactory.createLiteral(3))) 68 | else 69 | Set((eventIri, Ontology.SHOT_POINTS, rep.getValueFactory.createLiteral(2))) 70 | 71 | val assistsRegex = """ *\( *(.*) assists\)""".r 72 | 73 | val assistedByTriple: Set[(Resource, IRI, Value)] = 74 | assists match { 75 | case assistsRegex(assistedBy) => 76 | Set((eventIri, Ontology.SHOT_ASSISTED_BY, EntityIriFactory.getPlayerIri(assistedBy))) 77 | case _ => Set() 78 | } 79 | 80 | val shotTypeTriple: Set[(Resource, IRI, Value)] = 81 | if (!shotType.trim.isEmpty) 82 | Set((eventIri, Ontology.SHOT_TYPE, rep.getValueFactory.createLiteral(shotType.trim))) 83 | else 84 | Set() 85 | 86 | Set( 87 | (eventIri, RDF.TYPE, Ontology.SHOT), 88 | (eventIri, Ontology.SHOT_BY, EntityIriFactory.getPlayerIri(player)), 89 | (eventIri, Ontology.SHOT_MADE, rep.getValueFactory.createLiteral(made))) ++ 90 | pointsTriple ++ 91 | shotTypeTriple ++ 92 | assistedByTriple 93 | } 94 | 95 | case _ => { logger.warn(s"Unrecognized shot play: ${play}"); Set() } 96 | } 97 | 98 | if (!triples.isEmpty) 99 | rep.addTriples(triples) 100 | 101 | super.addRdf(rep) 102 | } 103 | 104 | } 105 | 106 | /** 107 | * Companion object that defines a regex that can be used to check this play against a description 108 | */ 109 | object ShotPlay extends PlayMatcher { 110 | 111 | val playByPlayRegex = """^(.*) (makes|misses) ?(.*?)( \(.* assists\))?$""".r 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/TechnicalFoulPlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import org.eclipse.rdf4j.model.Resource 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.eclipse.rdf4j.model.Value 6 | import org.eclipse.rdf4j.model.vocabulary.RDF 7 | import org.eclipse.rdf4j.repository.Repository 8 | 9 | import com.stellmangreene.pbprdf.model.EntityIriFactory 10 | import com.stellmangreene.pbprdf.model.Ontology 11 | import com.typesafe.scalalogging.LazyLogging 12 | import com.stellmangreene.pbprdf.GamePeriodInfo 13 | 14 | import com.stellmangreene.pbprdf.util.RdfOperations._ 15 | 16 | /** 17 | * A play that represents a technical violation 18 | *

19 | * Examples: 20 | * Diana Taurasi technical foul(1st technical foul) 21 | * 22 | * @param gameIri 23 | * Unique ID of the game 24 | * @param eventNumber 25 | * Sequential number of this event 26 | * @param period 27 | * Period this occurred in (overtime starts with period 5) 28 | * @param team 29 | * Name of the team 30 | * @param play 31 | * Description of the play (eg. "Alyssa Thomas makes free throw 2 of 2") 32 | * @param score 33 | * Game score ("10-4") - CURRENTLY IGNORED 34 | * @param gamePeriodInfo 35 | * GamePeriodInfo for converting game time to seconds 36 | * 37 | * @author andrewstellman 38 | */ 39 | class TechnicalFoulPlay(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 40 | extends Play(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 41 | with LazyLogging { 42 | 43 | override def addRdf(rep: Repository) = { 44 | val triples: Set[(Resource, IRI, Value)] = 45 | play match { 46 | case TechnicalFoulPlay.playByPlayRegex(committedBy, foulNumber) => { 47 | val committedByTriple: Set[(Resource, IRI, Value)] = 48 | if (committedBy != null && !committedBy.isEmpty) 49 | Set((eventIri, Ontology.FOUL_COMMITTED_BY, EntityIriFactory.getPlayerIri(committedBy))) 50 | else 51 | Set() 52 | 53 | Set( 54 | (eventIri, RDF.TYPE, Ontology.TECHNICAL_FOUL), 55 | (eventIri, Ontology.TECHNICAL_FOUL_NUMBER, rep.getValueFactory.createLiteral(foulNumber.toInt))) ++ committedByTriple 56 | } 57 | 58 | case _ => { logger.warn(s"Unrecognized technical foul play: ${play}"); Set() } 59 | } 60 | 61 | if (!triples.isEmpty) 62 | rep.addTriples(triples) 63 | 64 | super.addRdf(rep) 65 | } 66 | 67 | } 68 | 69 | /** 70 | * Companion object that defines a regex that can be used to check this play against a description 71 | */ 72 | object TechnicalFoulPlay extends PlayMatcher { 73 | 74 | val playByPlayRegex = """^(.*) ?technical foul.*\((\d+)\D* technical foul\)""".r 75 | 76 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/ThreeSecondViolationPlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import org.eclipse.rdf4j.model.Resource 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.eclipse.rdf4j.model.Value 6 | import org.eclipse.rdf4j.model.vocabulary.RDF 7 | import org.eclipse.rdf4j.repository.Repository 8 | 9 | import com.stellmangreene.pbprdf.model.EntityIriFactory 10 | import com.stellmangreene.pbprdf.model.Ontology 11 | import com.typesafe.scalalogging.LazyLogging 12 | import com.stellmangreene.pbprdf.GamePeriodInfo 13 | 14 | import com.stellmangreene.pbprdf.util.RdfOperations._ 15 | 16 | /** 17 | * A play that represents a three second violation 18 | *

19 | * Examples: 20 | * Kara Lawson defensive 3-seconds (Technical Foul) 21 | * Rudy Gobert defensive 3-seconds (technical foul) 22 | * 23 | * @param gameIri 24 | * Unique ID of the game 25 | * @param eventNumber 26 | * Sequential number of this event 27 | * @param period 28 | * Period this occurred in (overtime starts with period 5) 29 | * @param team 30 | * Name of the team 31 | * @param play 32 | * Description of the play (eg. "Alyssa Thomas makes free throw 2 of 2") 33 | * @param score 34 | * Game score ("10-4") - CURRENTLY IGNORED 35 | * @param gamePeriodInfo 36 | * GamePeriodInfo for converting game time to seconds 37 | * 38 | * @author andrewstellman 39 | */ 40 | class ThreeSecondViolationPlay(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 41 | extends Play(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 42 | with LazyLogging { 43 | 44 | override def addRdf(rep: Repository) = { 45 | val triples: Set[(Resource, IRI, Value)] = 46 | play match { 47 | case ThreeSecondViolationPlay.playByPlayRegex(committedBy) => { 48 | Set( 49 | (eventIri, Ontology.IS_THREE_SECOND, rep.getValueFactory.createLiteral(true)), 50 | (eventIri, RDF.TYPE, Ontology.TECHNICAL_FOUL), 51 | (eventIri, Ontology.FOUL_COMMITTED_BY, EntityIriFactory.getPlayerIri(committedBy))) 52 | } 53 | 54 | case _ => { logger.warn(s"Unrecognized three second violation play: ${play}"); Set() } 55 | } 56 | 57 | if (!triples.isEmpty) 58 | rep.addTriples(triples) 59 | 60 | super.addRdf(rep) 61 | } 62 | 63 | } 64 | 65 | /** 66 | * Companion object that defines a regex that can be used to check this play against a description 67 | */ 68 | object ThreeSecondViolationPlay extends PlayMatcher { 69 | 70 | val playByPlayRegex = """(?i)^(.*) defensive 3-seconds +\(Technical Foul\)$""".r 71 | 72 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/TimeoutPlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import org.eclipse.rdf4j.model.Resource 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.eclipse.rdf4j.model.Value 6 | import org.eclipse.rdf4j.model.vocabulary.RDF 7 | import org.eclipse.rdf4j.repository.Repository 8 | 9 | import com.stellmangreene.pbprdf.model.EntityIriFactory 10 | import com.stellmangreene.pbprdf.model.Ontology 11 | import com.typesafe.scalalogging.LazyLogging 12 | import com.stellmangreene.pbprdf.GamePeriodInfo 13 | 14 | import com.stellmangreene.pbprdf.util.RdfOperations._ 15 | 16 | /** 17 | * A play that represents a timeout 18 | *

19 | * Examples: 20 | * Connecticut Full timeout 21 | * Washington 20 Sec. timeout 22 | * Official timeout 23 | * 24 | * @param gameIri 25 | * Unique ID of the game 26 | * @param eventNumber 27 | * Sequential number of this event 28 | * @param period 29 | * Period this occurred in (overtime starts with period 5) 30 | * @param team 31 | * Name of the team 32 | * @param play 33 | * Description of the play (eg. "Alyssa Thomas makes free throw 2 of 2") 34 | * @param score 35 | * Game score ("10-4") - CURRENTLY IGNORED 36 | * @param gamePeriodInfo 37 | * GamePeriodInfo for converting game time to seconds 38 | * 39 | * @author andrewstellman 40 | */ 41 | class TimeoutPlay(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 42 | extends Play(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 43 | with LazyLogging { 44 | 45 | override def addRdf(rep: Repository) = { 46 | logger.debug(s"Parsing timeout from play: ${play}") 47 | 48 | val triples: Set[(Resource, IRI, Value)] = 49 | play match { 50 | case TimeoutPlay.playByPlayRegex("Official") => { 51 | Set( 52 | (eventIri, RDF.TYPE, Ontology.TIMEOUT), 53 | (eventIri, Ontology.IS_OFFICIAL, rep.getValueFactory.createLiteral(true))) 54 | } 55 | case TimeoutPlay.playByPlayRegex(description) if (description.endsWith("Full")) => { 56 | Set( 57 | (eventIri, RDF.TYPE, Ontology.TIMEOUT), 58 | (eventIri, Ontology.TIMEOUT_DURATION, rep.getValueFactory.createLiteral("Full"))) 59 | } 60 | case TimeoutPlay.playByPlayRegex(description) if (description.endsWith("20 Sec.")) => { 61 | Set( 62 | (eventIri, RDF.TYPE, Ontology.TIMEOUT), 63 | (eventIri, Ontology.TIMEOUT_DURATION, rep.getValueFactory.createLiteral("20 Sec."))) 64 | } 65 | case _ => { logger.warn(s"Unrecognized timeout play: ${play}"); Set() } 66 | } 67 | 68 | if (!triples.isEmpty) 69 | rep.addTriples(triples) 70 | 71 | super.addRdf(rep) 72 | } 73 | 74 | } 75 | 76 | /** 77 | * Companion object that defines a regex that can be used to check this play against a description 78 | */ 79 | object TimeoutPlay extends PlayMatcher { 80 | 81 | val playByPlayRegex = """^(.+) timeout$""".r 82 | 83 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/plays/TurnoverPlay.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays 2 | 3 | import org.eclipse.rdf4j.model.Resource 4 | import org.eclipse.rdf4j.model.IRI 5 | import org.eclipse.rdf4j.model.Value 6 | import org.eclipse.rdf4j.model.vocabulary.RDF 7 | import org.eclipse.rdf4j.repository.Repository 8 | 9 | import com.stellmangreene.pbprdf.model.EntityIriFactory 10 | import com.stellmangreene.pbprdf.model.Ontology 11 | import com.typesafe.scalalogging.LazyLogging 12 | import com.stellmangreene.pbprdf.GamePeriodInfo 13 | 14 | import com.stellmangreene.pbprdf.util.RdfOperations._ 15 | 16 | /** 17 | * A play that represents a turnover 18 | *

19 | * Examples: 20 | * Kelsey Bone lost ball turnover (Emma Meesseman steals) 21 | * Kelsey Bone turnover 22 | * shot clock turnover 23 | * Shekinna Stricklen traveling 24 | * Stefanie Dolson lost ball turnover (Kelsey Bone steals) 25 | * Camille Little bad pass 26 | * Ivory Latta bad pass (Kelsey Bone steals) 27 | * Kara Lawson kicked ball violation 28 | * 29 | * @param gameIri 30 | * Unique ID of the game 31 | * @param eventNumber 32 | * Sequential number of this event 33 | * @param period 34 | * Period this occurred in (overtime starts with period 5) 35 | * @param team 36 | * Name of the team 37 | * @param play 38 | * Description of the play (eg. "Alyssa Thomas makes free throw 2 of 2") 39 | * @param score 40 | * Game score ("10-4") - CURRENTLY IGNORED 41 | * @param gamePeriodInfo 42 | * GamePeriodInfo for converting game time to seconds 43 | * 44 | * @author andrewstellman 45 | */ 46 | class TurnoverPlay(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 47 | extends Play(gameIri: IRI, eventNumber: Int, period: Int, time: String, team: String, play: String, score: String, gamePeriodInfo: GamePeriodInfo) 48 | with LazyLogging { 49 | 50 | override def addRdf(rep: Repository) = { 51 | val triples: Set[(Resource, IRI, Value)] = 52 | play match { 53 | case TurnoverPlay.playByPlayRegex(player, turnoverType, steals) => { 54 | 55 | val turnoverPlayerTriple: Set[(Resource, IRI, Value)] = 56 | if (!player.trim.isEmpty) 57 | if (player.trim == "shot clock") 58 | Set((eventIri, Ontology.TURNOVER_TYPE, rep.getValueFactory.createLiteral("shot clock"))) 59 | else 60 | Set( 61 | (eventIri, Ontology.TURNED_OVER_BY, EntityIriFactory.getPlayerIri(player)), 62 | (eventIri, Ontology.TURNOVER_TYPE, rep.getValueFactory.createLiteral(turnoverType.trim.toLowerCase))) 63 | else 64 | Set() 65 | 66 | val stealsRegex = """ ?\((.*) steals\)""".r 67 | 68 | val stolenByTriple: Set[(Resource, IRI, Value)] = 69 | steals match { 70 | case stealsRegex(stolenByPlayer) => 71 | Set((eventIri, Ontology.STOLEN_BY, EntityIriFactory.getPlayerIri(stolenByPlayer))) 72 | case _ => Set() 73 | } 74 | 75 | Set((eventIri, RDF.TYPE, Ontology.TURNOVER)) ++ turnoverPlayerTriple ++ stolenByTriple 76 | } 77 | 78 | case _ => { logger.warn(s"Unrecognized turnover play: ${play}"); Set() } 79 | } 80 | 81 | if (!triples.isEmpty) 82 | rep.addTriples(triples) 83 | 84 | super.addRdf(rep) 85 | } 86 | 87 | } 88 | 89 | /** 90 | * Companion object that defines a regex that can be used to check this play against a description 91 | */ 92 | object TurnoverPlay extends PlayMatcher { 93 | 94 | val playByPlayRegex = """(?i)^(.*?) +(turnover|lost ball turnover|traveling|turnover|bad pass|kicked ball violation|lane violation|turnover \(lane violation\)|double lane violation|jump ball violation|defensive goaltending violation|Out-of-Bounds Bad Pass)( ?.*)$""".r 95 | 96 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/util/RdfOperations.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.util 2 | 3 | import scala.language.implicitConversions 4 | import java.io.FileOutputStream 5 | import com.stellmangreene.pbprdf.model.Ontology 6 | import com.typesafe.scalalogging.LazyLogging 7 | import org.eclipse.rdf4j.query.TupleQueryResult 8 | import org.eclipse.rdf4j.rio.RDFFormat 9 | import org.eclipse.rdf4j.common.iteration.Iteration 10 | import org.eclipse.rdf4j.rio.Rio 11 | import org.eclipse.rdf4j.model.impl.LinkedHashModel 12 | import org.eclipse.rdf4j.common.iteration.Iterations 13 | import org.eclipse.rdf4j.model.IRI 14 | import org.eclipse.rdf4j.model.Value 15 | import org.eclipse.rdf4j.model.Resource 16 | import org.eclipse.rdf4j.repository.Repository 17 | import org.eclipse.rdf4j.model.vocabulary.RDFS 18 | import org.eclipse.rdf4j.model.vocabulary.RDF 19 | import org.eclipse.rdf4j.query.QueryLanguage 20 | import org.eclipse.rdf4j.model.vocabulary.XMLSchema 21 | 22 | // TODO: Move this into a separate project, publish it to Maven Central, and add it as a dependency (including the ontology annotatins and ontology file generation 23 | 24 | /** 25 | * Define implicit operations to perform RDF functions on rdf4j repositories and collections 26 | * 27 | * @author andrewstellman 28 | */ 29 | object RdfOperations { 30 | 31 | /** 32 | * Define implicit Repository operations 33 | */ 34 | implicit def repositoryImplicitOperations(repository: Repository) = new RepositoryImplicitOperations(repository) 35 | 36 | /** 37 | * Define implicit Iteration operations 38 | */ 39 | implicit def iterationImplicitOperations[T, X <: Exception](i: Iteration[T, X]) = new IterationImplicitOperations(i) 40 | 41 | /** 42 | * Implicit Repository operations to help execute SPARQL queries 43 | */ 44 | class RepositoryImplicitOperations(repository: Repository) extends LazyLogging { 45 | 46 | /** 47 | * Returns all of the statements in the repository 48 | */ 49 | def statements = { 50 | val conn = repository.getConnection 51 | conn.getStatements(null, null, null, true).asIterator 52 | } 53 | 54 | /** 55 | * Write all of the statements in the repository to a file or System.out 56 | * 57 | * @param outputFile 58 | * The name of the file to write to, or None to write to System.out 59 | * 60 | * @param format 61 | * The format to write (defaults to Turtle) 62 | */ 63 | def writeAllStatements(outputFile: Option[String], rdfFormat: RDFFormat = RDFFormat.TURTLE) = { 64 | val conn = repository.getConnection 65 | var statements = conn.getStatements(null, null, null, true) 66 | 67 | var model = Iterations.addAll(statements, new LinkedHashModel) 68 | model.setNamespace("rdf", RDF.NAMESPACE) 69 | model.setNamespace("rdfs", RDFS.NAMESPACE) 70 | model.setNamespace("xsd", XMLSchema.NAMESPACE) 71 | model.setNamespace("pbprdf", Ontology.NAMESPACE) 72 | 73 | if (outputFile.isDefined) { 74 | val outputStream = new FileOutputStream(outputFile.get) 75 | logger.info(s"Writing Turtle to ${outputFile.get}") 76 | Rio.write(model, outputStream, rdfFormat) 77 | } else { 78 | logger.info("Writing Turtle to standard output") 79 | Rio.write(model, System.out, rdfFormat) 80 | } 81 | } 82 | 83 | /** 84 | * Execute a SPARQL query and return a result 85 | * 86 | * @param query 87 | * Query to execute 88 | * 89 | * @return TupleQueryResult with the query results 90 | */ 91 | def executeQuery(query: String): TupleQueryResult = { 92 | val conn = repository.getConnection 93 | val tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, query) 94 | tupleQuery.evaluate 95 | } 96 | 97 | /** 98 | * Add a triple to the repository 99 | * 100 | * @param subject 101 | * The statement's subject. 102 | * @param predicate 103 | * The statement's predicate. 104 | * @param object 105 | * The statement's object. 106 | * @throws RepositoryException 107 | * If the data could not be added to the repository, for example 108 | * because the repository is not writable. 109 | */ 110 | def addTriple(subject: Resource, predicate: IRI, `object`: Value): Unit = { 111 | val connection = repository.getConnection 112 | connection.add(subject, predicate, `object`) 113 | } 114 | 115 | /** 116 | * Add a triple to the repository 117 | * 118 | * @param subject 119 | * The statement's subject. 120 | * @param predicate 121 | * The statement's predicate. 122 | * @param object 123 | * The statement's object. 124 | * @param context 125 | * The context to add the data to 126 | * @throws RepositoryException 127 | * If the data could not be added to the repository, for example 128 | * because the repository is not writable. 129 | */ 130 | def addTriple(subject: Resource, predicate: IRI, `object`: Value, context: Resource): Unit = { 131 | val connection = repository.getConnection 132 | connection.add(subject, predicate, `object`, context) 133 | } 134 | 135 | /** 136 | * Add a set of triples to the repository 137 | * 138 | * @param triples 139 | * Triples to add. 140 | * @throws RepositoryException 141 | * If the data could not be added to the repository, for example 142 | * because the repository is not writable. 143 | */ 144 | def addTriples(triples: Set[(Resource, IRI, Value)]): Unit = { 145 | val connection = repository.getConnection 146 | triples.foreach(triple => connection.add(triple._1, triple._2, triple._3)) 147 | } 148 | 149 | /** 150 | * Add a set of triples to the repository 151 | * 152 | * @param triples 153 | * Triples to add. 154 | * @param context 155 | * The context to add the data to 156 | * @throws RepositoryException 157 | * If the data could not be added to the repository, for example 158 | * because the repository is not writable. 159 | */ 160 | def addTriples(triples: Set[(Resource, IRI, Value)], context: Resource): Unit = { 161 | val connection = repository.getConnection 162 | triples.foreach(triple => connection.add(triple._1, triple._2, triple._3, context)) 163 | } 164 | } 165 | 166 | /** 167 | * Implicit Iteration[T, X] functions to help process RDF Iteration objects (like ReposotiryResult[Statement] collections) 168 | */ 169 | protected class IterationImplicitOperations[T, X <: Exception](iteration: Iteration[T, X]) { 170 | private class IterationWrapper[T, X <: Exception](iteration: Iteration[T, X]) extends Iterator[T] { 171 | def hasNext: Boolean = iteration.hasNext 172 | def next(): T = iteration.next 173 | } 174 | 175 | private val iterator = for (statement <- new IterationWrapper[T, X](iteration)) yield statement 176 | 177 | /** 178 | * Converts this Iteration to an Iterator. May be lazy and unevaluated, and can be traversed only once. 179 | * 180 | * @returns an Iterator that returns all elements of this Iteration. 181 | */ 182 | def asIterator(): Iterator[T] = { 183 | iterator 184 | } 185 | 186 | /** 187 | * Applies a function to this Iteration 188 | * 189 | * @param f 190 | * the function that is applied for its side-effect to every element. The result of function f is discarded. 191 | * 192 | */ 193 | def foreach[U](f: T => U): Unit = { 194 | iterator.foreach(f) 195 | } 196 | 197 | /** 198 | * Creates a new iterator that maps all produced values of this Iteration to new values using a transformation function 199 | * 200 | * @param f 201 | * the transformation function 202 | * 203 | * @return a new iterator which transforms every value produced by this iterator by applying the function f to it. 204 | */ 205 | def map[U](f: T => U): Iterator[U] = { 206 | iterator.map(f) 207 | } 208 | 209 | /** 210 | * Converts this Iteration to a sequence. As with toIterable, it's lazy in this default implementation, as this TraversableOnce may be lazy and unevaluated. 211 | * 212 | * @returns a sequence containing all elements of this Iteration. 213 | */ 214 | def toSeq(): Seq[T] = { 215 | iterator.toSeq 216 | } 217 | 218 | } 219 | 220 | } -------------------------------------------------------------------------------- /src/main/scala/com/stellmangreene/pbprdf/util/XmlHelper.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.util 2 | 3 | import java.io.InputStream 4 | import java.io.StringWriter 5 | import scala.xml.XML 6 | import org.ccil.cowan.tagsoup.Parser 7 | import org.ccil.cowan.tagsoup.XMLWriter 8 | import org.xml.sax.InputSource 9 | import scala.xml.Elem 10 | import scala.xml.NodeSeq 11 | 12 | /** 13 | * Helper object to parse XML 14 | *

15 | * Uses tagsoup to parse HTML from ESPN.com that breaks scala.xml.XML by default 16 | * See also http://scala-language.1934581.n4.nabble.com/How-to-use-TagSoup-with-Scala-XML-td1940874.html 17 | * 18 | * @author andrewstellman 19 | */ 20 | object XmlHelper { 21 | 22 | /** 23 | * Parse the root element from an InputStream with XML 24 | * @param xmlStream InputStream with the XML data 25 | * @return Scala XML Elem element 26 | */ 27 | def parseXml(xmlStream: InputStream): Elem = { 28 | val parser = new Parser() 29 | val writer = new StringWriter() 30 | 31 | parser.setContentHandler(new XMLWriter(writer)) 32 | val source = new InputSource(xmlStream) 33 | 34 | parser.parse(source) 35 | 36 | parser.setContentHandler(new XMLWriter(writer)) 37 | XML.loadString(writer.toString()) 38 | } 39 | 40 | /** 41 | * Get children of an element by class and tag 42 | * @param clazz Class to look for 43 | * @param tag Tag to look for 44 | * @return Some(NodeSeq) with the matching nodes, or None if not found 45 | */ 46 | def getElemByClassAndTag(elem: NodeSeq, clazz: String, tag: String): Option[NodeSeq] = { 47 | elem 48 | .find(_.attribute("class").mkString == clazz) 49 | .map(_ \\ tag) 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/model/test/OntologyRdfRepositorySpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.model.test 2 | 3 | import scala.language.postfixOps 4 | 5 | import org.scalatest.FlatSpec 6 | import org.scalatest.Matchers 7 | 8 | import com.stellmangreene.pbprdf.model.OntologyRdfRepository 9 | 10 | import com.stellmangreene.pbprdf.util.RdfOperations._ 11 | 12 | /** 13 | * @author andrewstellman 14 | */ 15 | class OntologyRdfRepositorySpec extends FlatSpec with Matchers { 16 | 17 | behavior of "OntologyRdfRepository" 18 | 19 | it should "read classes" in { 20 | 21 | OntologyRdfRepository.rep.executeQuery("SELECT * { ?p ?o}") 22 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 23 | .toSet should be( 24 | Set( 25 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://www.w3.org/2002/07/owl#Class", 26 | "http://www.w3.org/2000/01/rdf-schema#label -> A game")) 27 | 28 | OntologyRdfRepository.rep.executeQuery("SELECT * { ?p ?o}") 29 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 30 | .toSet should be( 31 | Set( 32 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://www.w3.org/2002/07/owl#Class", 33 | "http://www.w3.org/2000/01/rdf-schema#subClassOf -> http://stellman-greene.com/pbprdf#Event", 34 | "http://www.w3.org/2000/01/rdf-schema#subClassOf -> http://stellman-greene.com/pbprdf#Play", 35 | "http://www.w3.org/2000/01/rdf-schema#label -> A turnover")) 36 | } 37 | 38 | it should "read properties" in { 39 | 40 | OntologyRdfRepository.rep.executeQuery("SELECT * { ?p ?o}") 41 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 42 | .toSet should be( 43 | Set( 44 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://www.w3.org/2002/07/owl#ObjectProperty", 45 | "http://www.w3.org/2000/01/rdf-schema#domain -> http://stellman-greene.com/pbprdf#Roster", 46 | "http://www.w3.org/2000/01/rdf-schema#range -> http://stellman-greene.com/pbprdf#Player", 47 | "http://www.w3.org/2000/01/rdf-schema#label -> A player on a roster")) 48 | 49 | OntologyRdfRepository.rep.executeQuery("SELECT * { ?p ?o}") 50 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 51 | .toSet should be( 52 | Set( 53 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://www.w3.org/2002/07/owl#DatatypeProperty", 54 | "http://www.w3.org/2000/01/rdf-schema#domain -> http://stellman-greene.com/pbprdf#Event", 55 | "http://www.w3.org/2000/01/rdf-schema#range -> http://www.w3.org/2001/XMLSchema#int", 56 | "http://www.w3.org/2000/01/rdf-schema#comment -> Regulation periods are 1 through 4, overtime periods start at 5", 57 | "http://www.w3.org/2000/01/rdf-schema#label -> The period the event occurred in")) 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/plays/test/BlockPlaySpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays.test 2 | 3 | import org.eclipse.rdf4j.repository.sail.SailRepository 4 | import org.eclipse.rdf4j.sail.memory.MemoryStore 5 | 6 | import org.scalatest.FlatSpec 7 | import org.scalatest.Matchers 8 | 9 | import com.stellmangreene.pbprdf.GamePeriodInfo 10 | import com.stellmangreene.pbprdf.plays.BlockPlay 11 | import com.stellmangreene.pbprdf.test.TestIri 12 | 13 | import com.stellmangreene.pbprdf.util.RdfOperations._ 14 | 15 | /** 16 | * Test the BlockPlay class 17 | * 18 | * @author andrewstellman 19 | */ 20 | class BlockPlaySpec extends FlatSpec with Matchers { 21 | 22 | behavior of "BlockPlay" 23 | 24 | // As long as each event has unique game and event IDs, they can all go into the same repository 25 | val rep = new SailRepository(new MemoryStore) 26 | rep.initialize 27 | 28 | it should "parse block triples" in { 29 | var testIri = TestIri.create("400610636") 30 | new BlockPlay(testIri, 108, 2, "7:48", "Mystics", "Tayler Hill blocks Jasmine Thomas's layup", "23-26", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 31 | 32 | rep.executeQuery("SELECT * { ?p ?o }") 33 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 34 | .toSet should be( 35 | Set( 36 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 37 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Shot", 38 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 39 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Block", 40 | "http://stellman-greene.com/pbprdf#awayScore -> 23", 41 | "http://stellman-greene.com/pbprdf#homeScore -> 26", 42 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 43 | "http://stellman-greene.com/pbprdf#period -> 2", 44 | "http://stellman-greene.com/pbprdf#time -> 7:48", 45 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 732", 46 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 468", 47 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Mystics", 48 | "http://stellman-greene.com/pbprdf#shotBy -> http://stellman-greene.com/pbprdf/players/Jasmine_Thomas", 49 | "http://stellman-greene.com/pbprdf#shotBlockedBy -> http://stellman-greene.com/pbprdf/players/Tayler_Hill", 50 | "http://www.w3.org/2000/01/rdf-schema#label -> Mystics: Tayler Hill blocks Jasmine Thomas's layup")) 51 | 52 | var testIri2 = TestIri.create("400610636") 53 | new BlockPlay(testIri, 53, 1, "2:37", "Mercury", "Krystal Thomas blocks Erin Phillips' 3-foot layup", "19-23", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 54 | 55 | rep.executeQuery("SELECT * { ?p ?o }") 56 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 57 | .toSet should be( 58 | Set( 59 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 60 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Shot", 61 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 62 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Block", 63 | "http://stellman-greene.com/pbprdf#awayScore -> 19", 64 | "http://stellman-greene.com/pbprdf#homeScore -> 23", 65 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri2.stringValue}", 66 | "http://stellman-greene.com/pbprdf#period -> 1", 67 | "http://stellman-greene.com/pbprdf#time -> 2:37", 68 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 443", 69 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 157", 70 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Mercury", 71 | "http://stellman-greene.com/pbprdf#shotBy -> http://stellman-greene.com/pbprdf/players/Erin_Phillips", 72 | "http://stellman-greene.com/pbprdf#shotBlockedBy -> http://stellman-greene.com/pbprdf/players/Krystal_Thomas", 73 | "http://www.w3.org/2000/01/rdf-schema#label -> Mercury: Krystal Thomas blocks Erin Phillips' 3-foot layup")) 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/plays/test/DelayOfGamePlaySpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays.test 2 | 3 | import org.eclipse.rdf4j.repository.sail.SailRepository 4 | import org.eclipse.rdf4j.sail.memory.MemoryStore 5 | import org.scalatest.FlatSpec 6 | import org.scalatest.Matchers 7 | 8 | import com.stellmangreene.pbprdf.plays.DelayOfGamePlay 9 | import com.stellmangreene.pbprdf.test.TestIri 10 | import com.stellmangreene.pbprdf.GamePeriodInfo 11 | 12 | import com.stellmangreene.pbprdf.util.RdfOperations._ 13 | 14 | /** 15 | * Test the DelayOfGamePlay class 16 | * 17 | * @author andrewstellman 18 | */ 19 | class DelayOfGamePlaySpec extends FlatSpec with Matchers { 20 | 21 | behavior of "DelayOfGamePlay" 22 | 23 | // As long as each event has unique game and event IDs, they can all go into the same repository 24 | val rep = new SailRepository(new MemoryStore) 25 | rep.initialize 26 | 27 | it should "parse a delay of game violation" in { 28 | val testIri = TestIri.create("400610739") 29 | 30 | new DelayOfGamePlay(testIri, 86, 2, "10:00", "Sparks", "Los Angeles delay of game violation", "15-22", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 31 | new DelayOfGamePlay(testIri, 295, 3, "1:39", "Sparks", "delay techfoul", "54-56", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 32 | 33 | rep.executeQuery("SELECT * { ?p ?o }") 34 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 35 | .toSet should be( 36 | Set( 37 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 38 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 39 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#TechnicalFoul", 40 | "http://stellman-greene.com/pbprdf#awayScore -> 15", 41 | "http://stellman-greene.com/pbprdf#homeScore -> 22", 42 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 43 | "http://stellman-greene.com/pbprdf#period -> 2", 44 | "http://stellman-greene.com/pbprdf#time -> 10:00", 45 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 600", 46 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 600", 47 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Sparks", 48 | "http://stellman-greene.com/pbprdf#isDelayOfGame -> true", 49 | "http://www.w3.org/2000/01/rdf-schema#label -> Sparks: Los Angeles delay of game violation")) 50 | 51 | rep.executeQuery("SELECT * { ?p ?o }") 52 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 53 | .toSet should be( 54 | Set( 55 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 56 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 57 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#TechnicalFoul", 58 | "http://stellman-greene.com/pbprdf#awayScore -> 54", 59 | "http://stellman-greene.com/pbprdf#homeScore -> 56", 60 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 61 | "http://stellman-greene.com/pbprdf#period -> 3", 62 | "http://stellman-greene.com/pbprdf#time -> 1:39", 63 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 1701", 64 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 99", 65 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Sparks", 66 | "http://stellman-greene.com/pbprdf#isDelayOfGame -> true", 67 | "http://www.w3.org/2000/01/rdf-schema#label -> Sparks: delay techfoul")) 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/plays/test/EjectionPlaySpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays.test 2 | 3 | import org.eclipse.rdf4j.repository.sail.SailRepository 4 | import org.eclipse.rdf4j.sail.memory.MemoryStore 5 | import org.scalatest.FlatSpec 6 | import org.scalatest.Matchers 7 | 8 | import com.stellmangreene.pbprdf.GamePeriodInfo 9 | import com.stellmangreene.pbprdf.plays.EjectionPlay 10 | import com.stellmangreene.pbprdf.test.TestIri 11 | 12 | import com.stellmangreene.pbprdf.util.RdfOperations._ 13 | 14 | /** 15 | * Test the EjectionPlay class 16 | * 17 | * @author andrewstellman 18 | */ 19 | class EjectionPlaySpec extends FlatSpec with Matchers { 20 | 21 | behavior of "EjectionPlay" 22 | 23 | // As long as each event has unique game and event IDs, they can all go into the same repository 24 | val rep = new SailRepository(new MemoryStore) 25 | rep.initialize 26 | 27 | it should "parse a five-second violation" in { 28 | 29 | val testIri = TestIri.create("400981090") 30 | new EjectionPlay(testIri, 367, 4, "1:10", "Wings", "Aerial Powers ejected", "71-76", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 31 | 32 | rep.executeQuery("SELECT * { ?p ?o }") 33 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 34 | .toSet should be( 35 | Set( 36 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 37 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 38 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Ejection", 39 | "http://stellman-greene.com/pbprdf#awayScore -> 71", 40 | "http://stellman-greene.com/pbprdf#homeScore -> 76", 41 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 42 | "http://stellman-greene.com/pbprdf#period -> 4", 43 | "http://stellman-greene.com/pbprdf#time -> 1:10", 44 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 2330", 45 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 70", 46 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Wings", 47 | "http://stellman-greene.com/pbprdf#PlayerEjected -> http://stellman-greene.com/pbprdf/players/Aerial_Powers", 48 | "http://www.w3.org/2000/01/rdf-schema#label -> Wings: Aerial Powers ejected")) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/plays/test/EndOfPlaySpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays.test 2 | 3 | import org.eclipse.rdf4j.repository.sail.SailRepository 4 | import org.eclipse.rdf4j.sail.memory.MemoryStore 5 | import org.scalatest.FlatSpec 6 | import org.scalatest.Matchers 7 | 8 | import com.stellmangreene.pbprdf.plays.EndOfPlay 9 | import com.stellmangreene.pbprdf.test.TestIri 10 | import com.stellmangreene.pbprdf.GamePeriodInfo 11 | 12 | import com.stellmangreene.pbprdf.util.RdfOperations._ 13 | 14 | /** 15 | * Test the TurnoverPlay class 16 | * 17 | * @author andrewstellman 18 | */ 19 | class EndOfPlaySpec extends FlatSpec with Matchers { 20 | 21 | behavior of "EndOfPlay" 22 | 23 | // As long as each event has unique game and event IDs, they can all go into the same repository 24 | val rep = new SailRepository(new MemoryStore) 25 | rep.initialize 26 | 27 | it should "parse an official timeout" in { 28 | val testIri = TestIri.create("400610636") 29 | val play = new EndOfPlay(testIri, 125, 2, "0.0", "Mystics", "End of the 2nd Quarter", "44-40", GamePeriodInfo.WNBAPeriodInfo) 30 | play.addRdf(rep) 31 | 32 | val statements = rep 33 | .executeQuery("SELECT * { ?p ?o }") 34 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 35 | .toSet 36 | 37 | statements should be( 38 | Set( 39 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 40 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 41 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#EndOfPeriod", 42 | "http://stellman-greene.com/pbprdf#awayScore -> 44", 43 | "http://stellman-greene.com/pbprdf#homeScore -> 40", 44 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 45 | "http://stellman-greene.com/pbprdf#period -> 2", 46 | "http://stellman-greene.com/pbprdf#time -> 0.0", 47 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 1200", 48 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 0", 49 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Mystics", 50 | "http://www.w3.org/2000/01/rdf-schema#label -> Mystics: End of the 2nd Quarter")) 51 | 52 | } 53 | 54 | it should "parse a full timeout" in { 55 | val testIri = TestIri.create("400610636") 56 | val play = new EndOfPlay(testIri, 391, 4, "0.0", "Mystics", "End of Game", "73-68", GamePeriodInfo.WNBAPeriodInfo) 57 | play.addRdf(rep) 58 | 59 | val statements = rep 60 | .executeQuery("SELECT * { ?p ?o }") 61 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 62 | .toSet 63 | 64 | statements should be( 65 | Set( 66 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 67 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 68 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#EndOfGame", 69 | "http://stellman-greene.com/pbprdf#awayScore -> 73", 70 | "http://stellman-greene.com/pbprdf#homeScore -> 68", 71 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 72 | "http://stellman-greene.com/pbprdf#period -> 4", 73 | "http://stellman-greene.com/pbprdf#time -> 0.0", 74 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 2400", 75 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 0", 76 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Mystics", 77 | "http://www.w3.org/2000/01/rdf-schema#label -> Mystics: End of Game")) 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/plays/test/EnterPlaySpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays.test 2 | 3 | import org.eclipse.rdf4j.repository.sail.SailRepository 4 | import org.eclipse.rdf4j.sail.memory.MemoryStore 5 | import org.scalatest.FlatSpec 6 | import org.scalatest.Matchers 7 | 8 | import com.stellmangreene.pbprdf.test.TestIri 9 | import com.stellmangreene.pbprdf.GamePeriodInfo 10 | import com.stellmangreene.pbprdf.plays.EnterPlay 11 | 12 | import com.stellmangreene.pbprdf.util.RdfOperations._ 13 | 14 | /** 15 | * Test the EnterPlay class 16 | * 17 | * @author andrewstellman 18 | */ 19 | class EnterPlaySpec extends FlatSpec with Matchers { 20 | 21 | behavior of "EnterPlay" 22 | 23 | // As long as each event has unique game and event IDs, they can all go into the same repository 24 | val rep = new SailRepository(new MemoryStore) 25 | rep.initialize 26 | 27 | it should "parse enter triples" in { 28 | 29 | var rep = new SailRepository(new MemoryStore) 30 | rep.initialize 31 | 32 | var testIri = TestIri.create("400610636") 33 | new EnterPlay(testIri, 101, 1, "8:00", "Sun", "Kelly Faris enters the game for Alyssa Thomas", "21-26", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 34 | 35 | rep.executeQuery("SELECT * { ?p ?o }") 36 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 37 | .toSet should be( 38 | Set( 39 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 40 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 41 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Enters", 42 | "http://stellman-greene.com/pbprdf#awayScore -> 21", 43 | "http://stellman-greene.com/pbprdf#homeScore -> 26", 44 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 45 | "http://stellman-greene.com/pbprdf#period -> 1", 46 | "http://stellman-greene.com/pbprdf#time -> 8:00", 47 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 120", 48 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 480", 49 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Sun", 50 | "http://stellman-greene.com/pbprdf#playerEntering -> http://stellman-greene.com/pbprdf/players/Kelly_Faris", 51 | "http://stellman-greene.com/pbprdf#playerExiting -> http://stellman-greene.com/pbprdf/players/Alyssa_Thomas", 52 | "http://www.w3.org/2000/01/rdf-schema#label -> Sun: Kelly Faris enters the game for Alyssa Thomas")) 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/plays/test/FiveSecondViolationPlaySpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays.test 2 | 3 | import org.eclipse.rdf4j.repository.sail.SailRepository 4 | import org.eclipse.rdf4j.sail.memory.MemoryStore 5 | import org.scalatest.FlatSpec 6 | import org.scalatest.Matchers 7 | 8 | import com.stellmangreene.pbprdf.GamePeriodInfo 9 | import com.stellmangreene.pbprdf.plays.FiveSecondViolationPlay 10 | import com.stellmangreene.pbprdf.test.TestIri 11 | 12 | import com.stellmangreene.pbprdf.util.RdfOperations._ 13 | 14 | /** 15 | * Test the FiveSecondViolationPlay class 16 | * 17 | * @author andrewstellman 18 | */ 19 | class FiveSecondViolationPlaySpec extends FlatSpec with Matchers { 20 | 21 | behavior of "FiveSecondViolationPlay" 22 | 23 | // As long as each event has unique game and event IDs, they can all go into the same repository 24 | val rep = new SailRepository(new MemoryStore) 25 | rep.initialize 26 | 27 | it should "parse a five-second violation" in { 28 | 29 | val testIri = TestIri.create("400975630") 30 | new FiveSecondViolationPlay(testIri, 70, 1, "2:39", "Magic", "5 second violation", "18-19", GamePeriodInfo.NBAPeriodInfo).addRdf(rep) 31 | 32 | rep.executeQuery("SELECT * { ?p ?o }") 33 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 34 | .toSet should be( 35 | Set( 36 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 37 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 38 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#FiveSecondViolation", 39 | "http://stellman-greene.com/pbprdf#awayScore -> 18", 40 | "http://stellman-greene.com/pbprdf#homeScore -> 19", 41 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 42 | "http://stellman-greene.com/pbprdf#period -> 1", 43 | "http://stellman-greene.com/pbprdf#time -> 2:39", 44 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 561", 45 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 159", 46 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Magic", 47 | "http://www.w3.org/2000/01/rdf-schema#label -> Magic: 5 second violation")) 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/plays/test/JumpBallPlaySpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays.test 2 | 3 | import org.eclipse.rdf4j.repository.sail.SailRepository 4 | import org.eclipse.rdf4j.sail.memory.MemoryStore 5 | import org.scalatest.FlatSpec 6 | import org.scalatest.Matchers 7 | 8 | import com.stellmangreene.pbprdf.GamePeriodInfo 9 | import com.stellmangreene.pbprdf.plays.JumpBallPlay 10 | import com.stellmangreene.pbprdf.test.TestIri 11 | 12 | import com.stellmangreene.pbprdf.util.RdfOperations._ 13 | 14 | /** 15 | * Test the JumpBallPlay class 16 | * 17 | * @author andrewstellman 18 | */ 19 | class JumpBallPlaySpec extends FlatSpec with Matchers { 20 | 21 | behavior of "JumpBallPlay" 22 | 23 | // As long as each event has unique game and event IDs, they can all go into the same repository 24 | val rep = new SailRepository(new MemoryStore) 25 | rep.initialize 26 | 27 | it should "parse jump ball triples" in { 28 | val testIri = TestIri.create("400610736") 29 | val play = new JumpBallPlay(testIri, 426, 5, "4:58", "Mercury", "Elena Delle Donne vs. Brittney Griner (DeWanna Bonner gains possession)", "78-78", GamePeriodInfo.WNBAPeriodInfo) 30 | play.addRdf(rep) 31 | 32 | val statements = rep 33 | .executeQuery("SELECT * { ?p ?o }") 34 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 35 | .toSet 36 | 37 | statements should be( 38 | Set( 39 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 40 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 41 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#JumpBall", 42 | "http://stellman-greene.com/pbprdf#awayScore -> 78", 43 | "http://stellman-greene.com/pbprdf#homeScore -> 78", 44 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 45 | "http://stellman-greene.com/pbprdf#period -> 5", 46 | "http://stellman-greene.com/pbprdf#time -> 4:58", 47 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 2402", 48 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 298", 49 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Mercury", 50 | "http://stellman-greene.com/pbprdf#jumpBallHomePlayer -> http://stellman-greene.com/pbprdf/players/Brittney_Griner", 51 | "http://stellman-greene.com/pbprdf#jumpBallAwayPlayer -> http://stellman-greene.com/pbprdf/players/Elena_Delle_Donne", 52 | "http://stellman-greene.com/pbprdf#jumpBallGainedPossession -> http://stellman-greene.com/pbprdf/players/DeWanna_Bonner", 53 | "http://www.w3.org/2000/01/rdf-schema#label -> Mercury: Elena Delle Donne vs. Brittney Griner (DeWanna Bonner gains possession)")) 54 | 55 | val testIri2 = TestIri.create("400610636") 56 | val play2 = new JumpBallPlay(testIri2, 1, 1, "10:00", "Sun", "Stefanie Dolson vs. Kelsey Bone", "0-0", GamePeriodInfo.WNBAPeriodInfo) 57 | play2.addRdf(rep) 58 | 59 | val statements2 = rep 60 | .executeQuery("SELECT * { ?p ?o }") 61 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 62 | .toSet 63 | 64 | statements2 should be( 65 | Set( 66 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 67 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 68 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#JumpBall", 69 | "http://stellman-greene.com/pbprdf#awayScore -> 0", 70 | "http://stellman-greene.com/pbprdf#homeScore -> 0", 71 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri2.stringValue}", 72 | "http://stellman-greene.com/pbprdf#period -> 1", 73 | "http://stellman-greene.com/pbprdf#time -> 10:00", 74 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 0", 75 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 600", 76 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Sun", 77 | "http://stellman-greene.com/pbprdf#jumpBallHomePlayer -> http://stellman-greene.com/pbprdf/players/Kelsey_Bone", 78 | "http://stellman-greene.com/pbprdf#jumpBallAwayPlayer -> http://stellman-greene.com/pbprdf/players/Stefanie_Dolson", 79 | "http://www.w3.org/2000/01/rdf-schema#label -> Sun: Stefanie Dolson vs. Kelsey Bone")) 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/plays/test/ReboundPlaySpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays.test 2 | 3 | import org.eclipse.rdf4j.repository.sail.SailRepository 4 | import org.eclipse.rdf4j.sail.memory.MemoryStore 5 | import org.scalatest.FlatSpec 6 | import org.scalatest.Matchers 7 | 8 | import com.stellmangreene.pbprdf.GamePeriodInfo 9 | import com.stellmangreene.pbprdf.plays.ReboundPlay 10 | import com.stellmangreene.pbprdf.test.TestIri 11 | 12 | import com.stellmangreene.pbprdf.util.RdfOperations._ 13 | 14 | /** 15 | * Test the ReboundPlay class 16 | * 17 | * @author andrewstellman 18 | */ 19 | class ReboundPlaySpec extends FlatSpec with Matchers { 20 | 21 | behavior of "ReboundPlay" 22 | 23 | // As long as each event has unique game and event IDs, they can all go into the same repository 24 | val rep = new SailRepository(new MemoryStore) 25 | rep.initialize 26 | 27 | it should "parse rebound triples" in { 28 | val testIri = TestIri.create("400610636") 29 | val play = new ReboundPlay(testIri, 125, 2, "4:02", "Mystics", "Emma Meesseman offensive rebound", "31-30", GamePeriodInfo.WNBAPeriodInfo) 30 | play.addRdf(rep) 31 | 32 | val statements = rep 33 | .executeQuery("SELECT * { ?p ?o }") 34 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 35 | .toSet 36 | 37 | statements should be( 38 | Set( 39 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 40 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Rebound", 41 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 42 | "http://stellman-greene.com/pbprdf#awayScore -> 31", 43 | "http://stellman-greene.com/pbprdf#homeScore -> 30", 44 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 45 | "http://stellman-greene.com/pbprdf#period -> 2", 46 | "http://stellman-greene.com/pbprdf#time -> 4:02", 47 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 958", 48 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 242", 49 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Mystics", 50 | "http://stellman-greene.com/pbprdf#reboundedBy -> http://stellman-greene.com/pbprdf/players/Emma_Meesseman", 51 | "http://stellman-greene.com/pbprdf#isOffensive -> true", 52 | "http://www.w3.org/2000/01/rdf-schema#label -> Mystics: Emma Meesseman offensive rebound")) 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/plays/test/ShotPlaySpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays.test 2 | 3 | import org.eclipse.rdf4j.repository.sail.SailRepository 4 | import org.eclipse.rdf4j.sail.memory.MemoryStore 5 | import org.scalatest.FlatSpec 6 | import org.scalatest.Matchers 7 | 8 | import com.stellmangreene.pbprdf.GamePeriodInfo 9 | import com.stellmangreene.pbprdf.plays.ShotPlay 10 | import com.stellmangreene.pbprdf.test.TestIri 11 | 12 | import com.stellmangreene.pbprdf.util.RdfOperations._ 13 | 14 | /** 15 | * Test the ShotPlay class 16 | * 17 | * @author andrewstellman 18 | */ 19 | class ShotPlaySpec extends FlatSpec with Matchers { 20 | 21 | behavior of "ShotPlay" 22 | 23 | // As long as each event has unique game and event IDs, they can all go into the same repository 24 | val rep = new SailRepository(new MemoryStore) 25 | rep.initialize 26 | 27 | val testIri = TestIri.create("400610636") 28 | 29 | it should "parse shots" in { 30 | new ShotPlay(testIri, 4, 1, "9:18", "Mystics", "Stefanie Dolson misses 13-foot jumper", "0-0", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 31 | 32 | rep.executeQuery("SELECT * { ?p ?o }") 33 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 34 | .toSet should be( 35 | Set( 36 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 37 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 38 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Shot", 39 | "http://stellman-greene.com/pbprdf#awayScore -> 0", 40 | "http://stellman-greene.com/pbprdf#homeScore -> 0", 41 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 42 | "http://stellman-greene.com/pbprdf#period -> 1", 43 | "http://stellman-greene.com/pbprdf#time -> 9:18", 44 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 42", 45 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 558", 46 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Mystics", 47 | "http://stellman-greene.com/pbprdf#shotPoints -> 2", 48 | "http://stellman-greene.com/pbprdf#shotBy -> http://stellman-greene.com/pbprdf/players/Stefanie_Dolson", 49 | "http://stellman-greene.com/pbprdf#shotType -> 13-foot jumper", 50 | "http://stellman-greene.com/pbprdf#shotMade -> false", 51 | "http://www.w3.org/2000/01/rdf-schema#label -> Mystics: Stefanie Dolson misses 13-foot jumper")) 52 | 53 | new ShotPlay(testIri, 5, 1, "9:15", "Sun", "Kelsey Bone misses", "0-0", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 54 | 55 | rep.executeQuery("SELECT * { ?p ?o }") 56 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 57 | .toSet should be( 58 | Set( 59 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 60 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 61 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Shot", 62 | "http://stellman-greene.com/pbprdf#awayScore -> 0", 63 | "http://stellman-greene.com/pbprdf#homeScore -> 0", 64 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 65 | "http://stellman-greene.com/pbprdf#period -> 1", 66 | "http://stellman-greene.com/pbprdf#time -> 9:15", 67 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 45", 68 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 555", 69 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Sun", 70 | "http://stellman-greene.com/pbprdf#shotBy -> http://stellman-greene.com/pbprdf/players/Kelsey_Bone", 71 | "http://stellman-greene.com/pbprdf#shotPoints -> 2", 72 | "http://stellman-greene.com/pbprdf#shotMade -> false", 73 | "http://www.w3.org/2000/01/rdf-schema#label -> Sun: Kelsey Bone misses")) 74 | 75 | } 76 | 77 | it should "parse assisted shots" in { 78 | new ShotPlay(testIri, 8, 2, "9:11", "Mystics", "Ivory Latta makes 24-foot three point jumper (Tierra Ruffin-Pratt assists)", "3-0", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 79 | 80 | rep.executeQuery("SELECT * { ?p ?o }") 81 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 82 | .toSet should be( 83 | Set( 84 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 85 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Shot", 86 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 87 | "http://stellman-greene.com/pbprdf#awayScore -> 3", 88 | "http://stellman-greene.com/pbprdf#homeScore -> 0", 89 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 90 | "http://stellman-greene.com/pbprdf#period -> 2", 91 | "http://stellman-greene.com/pbprdf#time -> 9:11", 92 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 649", 93 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 551", 94 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Mystics", 95 | "http://stellman-greene.com/pbprdf#shotBy -> http://stellman-greene.com/pbprdf/players/Ivory_Latta", 96 | "http://stellman-greene.com/pbprdf#shotAssistedBy -> http://stellman-greene.com/pbprdf/players/Tierra_Ruffin-Pratt", 97 | "http://stellman-greene.com/pbprdf#shotType -> 24-foot three point jumper", 98 | "http://stellman-greene.com/pbprdf#shotMade -> true", 99 | "http://stellman-greene.com/pbprdf#shotPoints -> 3", 100 | "http://www.w3.org/2000/01/rdf-schema#label -> Mystics: Ivory Latta makes 24-foot three point jumper (Tierra Ruffin-Pratt assists)")) 101 | } 102 | 103 | it should "parse the correct number of points for free throws" in { 104 | new ShotPlay(testIri, 88, 2, "9:15", "Sun", "Alyssa Thomas makes free throw 2 of 2", "18-26", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 105 | 106 | rep.executeQuery("SELECT * { ?p ?o }") 107 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 108 | .toSet should be( 109 | Set( 110 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 111 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 112 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Shot", 113 | "http://stellman-greene.com/pbprdf#awayScore -> 18", 114 | "http://stellman-greene.com/pbprdf#homeScore -> 26", 115 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 116 | "http://stellman-greene.com/pbprdf#period -> 2", 117 | "http://stellman-greene.com/pbprdf#time -> 9:15", 118 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 645", 119 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 555", 120 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Sun", 121 | "http://stellman-greene.com/pbprdf#shotBy -> http://stellman-greene.com/pbprdf/players/Alyssa_Thomas", 122 | "http://stellman-greene.com/pbprdf#shotType -> free throw 2 of 2", 123 | "http://stellman-greene.com/pbprdf#shotMade -> true", 124 | "http://stellman-greene.com/pbprdf#shotPoints -> 1", 125 | "http://www.w3.org/2000/01/rdf-schema#label -> Sun: Alyssa Thomas makes free throw 2 of 2")) 126 | } 127 | 128 | it should "parse missed three point shots" in { 129 | new ShotPlay(testIri, 20, 1, "7:35", "Mystics", "Jasmine Thomas misses 26-foot three point jumper", "5-2", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 130 | 131 | rep.executeQuery("SELECT * { ?p ?o }") 132 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 133 | .toSet should be( 134 | Set( 135 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 136 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Shot", 137 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 138 | "http://stellman-greene.com/pbprdf#awayScore -> 5", 139 | "http://stellman-greene.com/pbprdf#homeScore -> 2", 140 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 141 | "http://stellman-greene.com/pbprdf#period -> 1", 142 | "http://stellman-greene.com/pbprdf#time -> 7:35", 143 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 145", 144 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 455", 145 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Mystics", 146 | "http://stellman-greene.com/pbprdf#shotBy -> http://stellman-greene.com/pbprdf/players/Jasmine_Thomas", 147 | "http://stellman-greene.com/pbprdf#shotType -> 26-foot three point jumper", 148 | "http://stellman-greene.com/pbprdf#shotMade -> false", 149 | "http://stellman-greene.com/pbprdf#shotPoints -> 3", 150 | "http://www.w3.org/2000/01/rdf-schema#label -> Mystics: Jasmine Thomas misses 26-foot three point jumper")) 151 | 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/plays/test/TechnicalFoulPlaySpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays.test 2 | 3 | import org.eclipse.rdf4j.repository.sail.SailRepository 4 | import org.eclipse.rdf4j.sail.memory.MemoryStore 5 | import org.scalatest.FlatSpec 6 | import org.scalatest.Matchers 7 | 8 | import com.stellmangreene.pbprdf.GamePeriodInfo 9 | import com.stellmangreene.pbprdf.plays.DoubleTechnicalFoulPlay 10 | import com.stellmangreene.pbprdf.plays.TechnicalFoulPlay 11 | import com.stellmangreene.pbprdf.test.TestIri 12 | 13 | import com.stellmangreene.pbprdf.util.RdfOperations._ 14 | 15 | /** 16 | * Test the TechnicalFoulPlay class 17 | * 18 | * @author andrewstellman 19 | */ 20 | class TechnicalFoulPlaySpec extends FlatSpec with Matchers { 21 | 22 | behavior of "TechnicalFoulPlay" 23 | 24 | // As long as each event has unique game and event IDs, they can all go into the same repository 25 | val rep = new SailRepository(new MemoryStore) 26 | rep.initialize 27 | 28 | it should "parse a technical foul" in { 29 | val testIri = TestIri.create("400496779") 30 | new TechnicalFoulPlay(testIri, 93, 2, "7:37", "Mercury", "Diana Taurasi technical foul(1st technical foul)", "21-28", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 31 | 32 | rep.executeQuery("SELECT * { ?p ?o }") 33 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 34 | .toSet should be( 35 | Set( 36 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 37 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 38 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#TechnicalFoul", 39 | "http://stellman-greene.com/pbprdf#awayScore -> 21", 40 | "http://stellman-greene.com/pbprdf#homeScore -> 28", 41 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 42 | "http://stellman-greene.com/pbprdf#period -> 2", 43 | "http://stellman-greene.com/pbprdf#time -> 7:37", 44 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 743", 45 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 457", 46 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Mercury", 47 | "http://stellman-greene.com/pbprdf#foulCommittedBy -> http://stellman-greene.com/pbprdf/players/Diana_Taurasi", 48 | "http://stellman-greene.com/pbprdf#technicalFoulNumber -> 1", 49 | "http://www.w3.org/2000/01/rdf-schema#label -> Mercury: Diana Taurasi technical foul(1st technical foul)")) 50 | 51 | } 52 | 53 | it should "parse a technical foul with no player specified" in { 54 | val testIri = TestIri.create("400496779") 55 | new TechnicalFoulPlay(testIri, 152, 2, "1:03", "Mercury", "technical foul(2nd technical foul)", "37-32", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 56 | 57 | rep.executeQuery("SELECT * { ?p ?o }") 58 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 59 | .toSet should be( 60 | Set( 61 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 62 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 63 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#TechnicalFoul", 64 | "http://stellman-greene.com/pbprdf#awayScore -> 37", 65 | "http://stellman-greene.com/pbprdf#homeScore -> 32", 66 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 67 | "http://stellman-greene.com/pbprdf#period -> 2", 68 | "http://stellman-greene.com/pbprdf#time -> 1:03", 69 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 1137", 70 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 63", 71 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Mercury", 72 | "http://stellman-greene.com/pbprdf#technicalFoulNumber -> 2", 73 | "http://www.w3.org/2000/01/rdf-schema#label -> Mercury: technical foul(2nd technical foul)")) 74 | 75 | } 76 | 77 | it should "parse a double technical foul" in { 78 | val testIri = TestIri.create("400445797") 79 | new DoubleTechnicalFoulPlay(testIri, 231, 3, "4:22", "Shock", "Double technical foul: Marissa Coleman and Glory Johnson", "47-41", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 80 | 81 | rep.executeQuery("SELECT * { ?p ?o }") 82 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 83 | .toSet should be( 84 | Set( 85 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 86 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 87 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#TechnicalFoul", 88 | "http://stellman-greene.com/pbprdf#awayScore -> 47", 89 | "http://stellman-greene.com/pbprdf#homeScore -> 41", 90 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 91 | "http://stellman-greene.com/pbprdf#period -> 3", 92 | "http://stellman-greene.com/pbprdf#time -> 4:22", 93 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 1538", 94 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 262", 95 | "http://stellman-greene.com/pbprdf#foulCommittedBy -> http://stellman-greene.com/pbprdf/players/Marissa_Coleman", 96 | "http://stellman-greene.com/pbprdf#foulCommittedBy -> http://stellman-greene.com/pbprdf/players/Glory_Johnson", 97 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Shock", 98 | "http://www.w3.org/2000/01/rdf-schema#label -> Shock: Double technical foul: Marissa Coleman and Glory Johnson")) 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/plays/test/ThreeSecondViolationPlaySpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays.test 2 | 3 | import org.eclipse.rdf4j.repository.sail.SailRepository 4 | import org.eclipse.rdf4j.sail.memory.MemoryStore 5 | import org.scalatest.FlatSpec 6 | import org.scalatest.Matchers 7 | 8 | import com.stellmangreene.pbprdf.GamePeriodInfo 9 | import com.stellmangreene.pbprdf.plays.ThreeSecondViolationPlay 10 | import com.stellmangreene.pbprdf.test.TestIri 11 | 12 | import com.stellmangreene.pbprdf.util.RdfOperations._ 13 | 14 | /** 15 | * Test the ThreeSecondViolationPlay class 16 | * 17 | * @author andrewstellman 18 | */ 19 | class ThreeSecondViolationPlaySpec extends FlatSpec with Matchers { 20 | 21 | behavior of "ThreeSecondViolationPlay" 22 | 23 | // As long as each event has unique game and event IDs, they can all go into the same repository 24 | val rep = new SailRepository(new MemoryStore) 25 | rep.initialize 26 | 27 | it should "parse a three-second violation" in { 28 | val testIri = TestIri.create("400610636") 29 | new ThreeSecondViolationPlay(testIri, 146, 1, "4:07", "Sun", "Kara Lawson defensive 3-seconds (Technical Foul)", "33-31", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 30 | 31 | rep.executeQuery("SELECT * { ?p ?o }") 32 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 33 | .toSet should be( 34 | Set( 35 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 36 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 37 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#TechnicalFoul", 38 | "http://stellman-greene.com/pbprdf#awayScore -> 33", 39 | "http://stellman-greene.com/pbprdf#homeScore -> 31", 40 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 41 | "http://stellman-greene.com/pbprdf#period -> 1", 42 | "http://stellman-greene.com/pbprdf#time -> 4:07", 43 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 353", 44 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 247", 45 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Sun", 46 | "http://stellman-greene.com/pbprdf#foulCommittedBy -> http://stellman-greene.com/pbprdf/players/Kara_Lawson", 47 | "http://stellman-greene.com/pbprdf#isThreeSecond -> true", 48 | "http://www.w3.org/2000/01/rdf-schema#label -> Sun: Kara Lawson defensive 3-seconds (Technical Foul)")) 49 | 50 | val testIri2 = TestIri.create("401031640") 51 | new ThreeSecondViolationPlay(testIri2, 20, 1, "8:28", "Jazz", "Rudy Gobert defensive 3-seconds (technical foul)", "12-5", GamePeriodInfo.NBAPeriodInfo).addRdf(rep) 52 | 53 | rep.executeQuery("SELECT * { ?p ?o }") 54 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 55 | .toSet should be( 56 | Set( 57 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 58 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 59 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#TechnicalFoul", 60 | "http://stellman-greene.com/pbprdf#awayScore -> 12", 61 | "http://stellman-greene.com/pbprdf#homeScore -> 5", 62 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri2.stringValue}", 63 | "http://stellman-greene.com/pbprdf#period -> 1", 64 | "http://stellman-greene.com/pbprdf#time -> 8:28", 65 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 212", 66 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 508", 67 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Jazz", 68 | "http://stellman-greene.com/pbprdf#foulCommittedBy -> http://stellman-greene.com/pbprdf/players/Rudy_Gobert", 69 | "http://stellman-greene.com/pbprdf#isThreeSecond -> true", 70 | "http://www.w3.org/2000/01/rdf-schema#label -> Jazz: Rudy Gobert defensive 3-seconds (technical foul)")) 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/plays/test/TimeoutPlaySpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays.test 2 | 3 | import org.eclipse.rdf4j.repository.sail.SailRepository 4 | import org.eclipse.rdf4j.sail.memory.MemoryStore 5 | import org.scalatest.FlatSpec 6 | import org.scalatest.Matchers 7 | 8 | import com.stellmangreene.pbprdf.GamePeriodInfo 9 | import com.stellmangreene.pbprdf.plays.TimeoutPlay 10 | import com.stellmangreene.pbprdf.test.TestIri 11 | 12 | import com.stellmangreene.pbprdf.util.RdfOperations._ 13 | 14 | /** 15 | * Test the TimeoutPlay class 16 | * 17 | * @author andrewstellman 18 | */ 19 | class TimeoutPlaySpec extends FlatSpec with Matchers { 20 | 21 | behavior of "TimeoutPlay" 22 | 23 | // As long as each event has unique game and event IDs, they can all go into the same repository 24 | val rep = new SailRepository(new MemoryStore) 25 | rep.initialize 26 | 27 | it should "parse an official timeout" in { 28 | val testIri = TestIri.create("400610636") 29 | val play = new TimeoutPlay(testIri, 125, 2, "4:56", "Mystics", "Official timeout", "10-9", GamePeriodInfo.WNBAPeriodInfo) 30 | play.addRdf(rep) 31 | 32 | val statements = rep 33 | .executeQuery("SELECT * { ?p ?o }") 34 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 35 | .toSet 36 | 37 | statements should be( 38 | Set( 39 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 40 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Timeout", 41 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 42 | "http://stellman-greene.com/pbprdf#awayScore -> 10", 43 | "http://stellman-greene.com/pbprdf#homeScore -> 9", 44 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 45 | "http://stellman-greene.com/pbprdf#period -> 2", 46 | "http://stellman-greene.com/pbprdf#time -> 4:56", 47 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 904", 48 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 296", 49 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Mystics", 50 | "http://stellman-greene.com/pbprdf#isOfficial -> true", 51 | "http://www.w3.org/2000/01/rdf-schema#label -> Mystics: Official timeout")) 52 | } 53 | 54 | it should "parse a full timeout" in { 55 | val testIri = TestIri.create("400610636") 56 | val play = new TimeoutPlay(testIri, 327, 2, "7:05", "Sun", "Connecticut Full timeout", "25-26", GamePeriodInfo.WNBAPeriodInfo) 57 | play.addRdf(rep) 58 | 59 | val statements = rep 60 | .executeQuery("SELECT * { ?p ?o }") 61 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 62 | .toSet 63 | 64 | statements should be( 65 | Set( 66 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 67 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Timeout", 68 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 69 | "http://stellman-greene.com/pbprdf#awayScore -> 25", 70 | "http://stellman-greene.com/pbprdf#homeScore -> 26", 71 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 72 | "http://stellman-greene.com/pbprdf#period -> 2", 73 | "http://stellman-greene.com/pbprdf#time -> 7:05", 74 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 775", 75 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 425", 76 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Sun", 77 | "http://stellman-greene.com/pbprdf#timeoutDuration -> Full", 78 | "http://www.w3.org/2000/01/rdf-schema#label -> Sun: Connecticut Full timeout")) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/plays/test/TurnoverPlaySpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.plays.test 2 | 3 | import org.eclipse.rdf4j.repository.sail.SailRepository 4 | import org.eclipse.rdf4j.sail.memory.MemoryStore 5 | import org.scalatest.FlatSpec 6 | import org.scalatest.Matchers 7 | 8 | import com.stellmangreene.pbprdf.GamePeriodInfo 9 | import com.stellmangreene.pbprdf.plays.TurnoverPlay 10 | import com.stellmangreene.pbprdf.test.TestIri 11 | 12 | import com.stellmangreene.pbprdf.util.RdfOperations._ 13 | 14 | /** 15 | * Test the TurnoverPlay class 16 | * 17 | * @author andrewstellman 18 | */ 19 | class TurnoverPlaySpec extends FlatSpec with Matchers { 20 | 21 | behavior of "TurnoverPlay" 22 | 23 | // As long as each event has unique game and event IDs, they can all go into the same repository 24 | val rep = new SailRepository(new MemoryStore) 25 | rep.initialize 26 | 27 | val testIri = TestIri.create("400610636") 28 | 29 | it should "parse a turnover" in { 30 | new TurnoverPlay(testIri, 167, 1, "1:05", "Mystics", "Kayla Thornton turnover", "40-38", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 31 | 32 | rep.executeQuery("SELECT * { ?p ?o }") 33 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 34 | .toSet should be( 35 | Set( 36 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 37 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 38 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Turnover", 39 | "http://stellman-greene.com/pbprdf#awayScore -> 40", 40 | "http://stellman-greene.com/pbprdf#homeScore -> 38", 41 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 42 | "http://stellman-greene.com/pbprdf#period -> 1", 43 | "http://stellman-greene.com/pbprdf#time -> 1:05", 44 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 65", 45 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 535", 46 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Mystics", 47 | "http://stellman-greene.com/pbprdf#turnoverType -> turnover", 48 | "http://stellman-greene.com/pbprdf#turnedOverBy -> http://stellman-greene.com/pbprdf/players/Kayla_Thornton", 49 | "http://www.w3.org/2000/01/rdf-schema#label -> Mystics: Kayla Thornton turnover")) 50 | } 51 | 52 | it should "parse a lost ball turnover" in { 53 | new TurnoverPlay(testIri, 17, 1, "8:00", "Sun", "Tierra Ruffin-Pratt lost ball turnover (Alex Bentley steals)", "5-0", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 54 | 55 | rep.executeQuery("SELECT * { ?p ?o }") 56 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 57 | .toSet should be( 58 | Set( 59 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 60 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 61 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Turnover", 62 | "http://stellman-greene.com/pbprdf#awayScore -> 5", 63 | "http://stellman-greene.com/pbprdf#homeScore -> 0", 64 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 65 | "http://stellman-greene.com/pbprdf#period -> 1", 66 | "http://stellman-greene.com/pbprdf#time -> 8:00", 67 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 120", 68 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 480", 69 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Sun", 70 | "http://stellman-greene.com/pbprdf#turnoverType -> lost ball turnover", 71 | "http://stellman-greene.com/pbprdf#turnedOverBy -> http://stellman-greene.com/pbprdf/players/Tierra_Ruffin-Pratt", 72 | "http://stellman-greene.com/pbprdf#stolenBy -> http://stellman-greene.com/pbprdf/players/Alex_Bentley", 73 | "http://www.w3.org/2000/01/rdf-schema#label -> Sun: Tierra Ruffin-Pratt lost ball turnover (Alex Bentley steals)")) 74 | } 75 | 76 | it should "parse a shot clock violation" in { 77 | new TurnoverPlay(testIri, 84, 1, "9:36", "Sun", "shot clock turnover", "18-24", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 78 | 79 | rep.executeQuery("SELECT * { ?p ?o }") 80 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 81 | .toSet should be( 82 | Set( 83 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 84 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 85 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Turnover", 86 | "http://stellman-greene.com/pbprdf#awayScore -> 18", 87 | "http://stellman-greene.com/pbprdf#homeScore -> 24", 88 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 89 | "http://stellman-greene.com/pbprdf#period -> 1", 90 | "http://stellman-greene.com/pbprdf#time -> 9:36", 91 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 24", 92 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 576", 93 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Sun", 94 | "http://stellman-greene.com/pbprdf#turnoverType -> shot clock", 95 | "http://www.w3.org/2000/01/rdf-schema#label -> Sun: shot clock turnover")) 96 | } 97 | 98 | it should "parse a bad pass" in { 99 | new TurnoverPlay(testIri, 195, 2, "6:54", "Sun", "Alex Bentley bad pass", "52-40", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 100 | 101 | rep.executeQuery("SELECT * { ?p ?o }") 102 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 103 | .toSet should be( 104 | Set( 105 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 106 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 107 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Turnover", 108 | "http://stellman-greene.com/pbprdf#awayScore -> 52", 109 | "http://stellman-greene.com/pbprdf#homeScore -> 40", 110 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 111 | "http://stellman-greene.com/pbprdf#period -> 2", 112 | "http://stellman-greene.com/pbprdf#time -> 6:54", 113 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 786", 114 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 414", 115 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Sun", 116 | "http://stellman-greene.com/pbprdf#turnoverType -> bad pass", 117 | "http://stellman-greene.com/pbprdf#turnedOverBy -> http://stellman-greene.com/pbprdf/players/Alex_Bentley", 118 | "http://www.w3.org/2000/01/rdf-schema#label -> Sun: Alex Bentley bad pass")) 119 | } 120 | 121 | it should "parse a bad pass and steal" in { 122 | new TurnoverPlay(testIri, 366, 4, "8:04", "Mystics", "Ivory Latta bad pass (Kelsey Bone steals)", "69-66", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 123 | 124 | rep.executeQuery("SELECT * { ?p ?o }") 125 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 126 | .toSet should be( 127 | Set( 128 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 129 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 130 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Turnover", 131 | "http://stellman-greene.com/pbprdf#awayScore -> 69", 132 | "http://stellman-greene.com/pbprdf#homeScore -> 66", 133 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 134 | "http://stellman-greene.com/pbprdf#period -> 4", 135 | "http://stellman-greene.com/pbprdf#time -> 8:04", 136 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 1916", 137 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 484", 138 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Mystics", 139 | "http://stellman-greene.com/pbprdf#turnoverType -> bad pass", 140 | "http://stellman-greene.com/pbprdf#turnedOverBy -> http://stellman-greene.com/pbprdf/players/Ivory_Latta", 141 | "http://stellman-greene.com/pbprdf#stolenBy -> http://stellman-greene.com/pbprdf/players/Kelsey_Bone", 142 | "http://www.w3.org/2000/01/rdf-schema#label -> Mystics: Ivory Latta bad pass (Kelsey Bone steals)")) 143 | } 144 | 145 | it should "parse a traveling violation" in { 146 | new TurnoverPlay(testIri, 204, 2, "1:09", "Sun", "Kelsey Bone traveling", "52-42", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 147 | 148 | rep.executeQuery("SELECT * { ?p ?o }") 149 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 150 | .toSet should be( 151 | Set( 152 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 153 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 154 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Turnover", 155 | "http://stellman-greene.com/pbprdf#awayScore -> 52", 156 | "http://stellman-greene.com/pbprdf#homeScore -> 42", 157 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 158 | "http://stellman-greene.com/pbprdf#period -> 2", 159 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 1131", 160 | "http://stellman-greene.com/pbprdf#time -> 1:09", 161 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 69", 162 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Sun", 163 | "http://stellman-greene.com/pbprdf#turnedOverBy -> http://stellman-greene.com/pbprdf/players/Kelsey_Bone", 164 | "http://stellman-greene.com/pbprdf#turnoverType -> traveling", 165 | "http://www.w3.org/2000/01/rdf-schema#label -> Sun: Kelsey Bone traveling")) 166 | } 167 | 168 | it should "parse a kicked ball violation" in { 169 | new TurnoverPlay(testIri, 337, 3, "4:16", "Sun", "Kara Lawson kicked ball violation", "63-61", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 170 | 171 | rep.executeQuery("SELECT * { ?p ?o }") 172 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 173 | .toSet should be( 174 | Set( 175 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 176 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 177 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Turnover", 178 | "http://stellman-greene.com/pbprdf#awayScore -> 63", 179 | "http://stellman-greene.com/pbprdf#homeScore -> 61", 180 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 181 | "http://stellman-greene.com/pbprdf#period -> 3", 182 | "http://stellman-greene.com/pbprdf#time -> 4:16", 183 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 1544", 184 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 256", 185 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Sun", 186 | "http://stellman-greene.com/pbprdf#turnoverType -> kicked ball violation", 187 | "http://stellman-greene.com/pbprdf#turnedOverBy -> http://stellman-greene.com/pbprdf/players/Kara_Lawson", 188 | "http://www.w3.org/2000/01/rdf-schema#label -> Sun: Kara Lawson kicked ball violation")) 189 | } 190 | 191 | it should "parse an out-of-bounds bad pass turnover" in { 192 | val testIri2 = TestIri.create("400610636") 193 | new TurnoverPlay(testIri2, 345, 4, "2:38", "Sparks", "Candace Parker Out-of-Bounds Bad Pass Turnover", "67-70", GamePeriodInfo.WNBAPeriodInfo).addRdf(rep) 194 | 195 | rep.executeQuery("SELECT * { ?p ?o }") 196 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 197 | .toSet should be( 198 | Set( 199 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 200 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Play", 201 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Turnover", 202 | "http://stellman-greene.com/pbprdf#awayScore -> 67", 203 | "http://stellman-greene.com/pbprdf#homeScore -> 70", 204 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 205 | "http://stellman-greene.com/pbprdf#period -> 4", 206 | "http://stellman-greene.com/pbprdf#time -> 2:38", 207 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 2242", 208 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 158", 209 | "http://stellman-greene.com/pbprdf#forTeam -> http://stellman-greene.com/pbprdf/teams/Sparks", 210 | "http://stellman-greene.com/pbprdf#turnoverType -> out-of-bounds bad pass", 211 | "http://stellman-greene.com/pbprdf#turnedOverBy -> http://stellman-greene.com/pbprdf/players/Candace_Parker", 212 | "http://www.w3.org/2000/01/rdf-schema#label -> Sparks: Candace Parker Out-of-Bounds Bad Pass Turnover")) 213 | 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/test/EventSpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.test 2 | 3 | import org.eclipse.rdf4j.repository.sail.SailRepository 4 | import org.eclipse.rdf4j.sail.memory.MemoryStore 5 | import org.scalatest.FlatSpec 6 | import org.scalatest.Matchers 7 | 8 | import com.stellmangreene.pbprdf.Event 9 | import com.stellmangreene.pbprdf.GamePeriodInfo 10 | 11 | import com.stellmangreene.pbprdf.util.RdfOperations._ 12 | 13 | /** 14 | * Test the Event class 15 | * 16 | * @author andrewstellman 17 | */ 18 | class EventSpec extends FlatSpec with Matchers { 19 | 20 | behavior of "an instance of Event" 21 | 22 | it should "generate the event IRI" in { 23 | val event = Event(TestIri.create("400610636"), 38, 1, "4:56", "Official timeout")(GamePeriodInfo.WNBAPeriodInfo, "", "", "") 24 | event.eventIri.stringValue should be("http://stellman-greene.com/pbprdf/400610636/38") 25 | } 26 | 27 | it should "populate the event fields" in { 28 | val event = Event(TestIri.create("400610636"), 38, 1, "4:56", "Official timeout")(GamePeriodInfo.WNBAPeriodInfo, "", "", "") 29 | event.eventNumber should be(38) 30 | event.period should be(1) 31 | event.time should be("4:56") 32 | } 33 | 34 | it should "generate RDF for an unmatched event" in { 35 | var rep = new SailRepository(new MemoryStore) 36 | rep.initialize 37 | 38 | val testIri = TestIri.create("400610636") 39 | Event(testIri, 38, 1, "4:56", "Unmatched event")(GamePeriodInfo.WNBAPeriodInfo, "", "", "").addRdf(rep) 40 | Event(testIri, 119, 2, "7:05", "Unmatched event 2")(GamePeriodInfo.WNBAPeriodInfo, "", "", "").addRdf(rep) 41 | 42 | rep 43 | .executeQuery("SELECT * { ?p ?o }") 44 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 45 | .toSet should be( 46 | Set( 47 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 48 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 49 | "http://stellman-greene.com/pbprdf#period -> 1", 50 | "http://stellman-greene.com/pbprdf#time -> 4:56", 51 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 304", 52 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 296", 53 | "http://www.w3.org/2000/01/rdf-schema#label -> Unmatched event")) 54 | 55 | rep 56 | .executeQuery("SELECT * { ?p ?o }") 57 | .map(statement => (s"${statement.getValue("p").stringValue} -> ${statement.getValue("o").stringValue}")) 58 | .toSet should be( 59 | Set( 60 | "http://www.w3.org/1999/02/22-rdf-syntax-ns#type -> http://stellman-greene.com/pbprdf#Event", 61 | s"http://stellman-greene.com/pbprdf#inGame -> ${testIri.stringValue}", 62 | "http://stellman-greene.com/pbprdf#period -> 2", 63 | "http://stellman-greene.com/pbprdf#time -> 7:05", 64 | "http://stellman-greene.com/pbprdf#secondsIntoGame -> 775", 65 | "http://stellman-greene.com/pbprdf#secondsLeftInPeriod -> 425", 66 | "http://www.w3.org/2000/01/rdf-schema#label -> Unmatched event 2")) 67 | } 68 | 69 | it should "calculate the correct times for the start, middle, and end of a period" in { 70 | var rep = new SailRepository(new MemoryStore) 71 | rep.initialize 72 | 73 | Event(TestIri.create("X"), 1, 1, "10:00", "1st quarter")(GamePeriodInfo.WNBAPeriodInfo, "", "", "").addRdf(rep) 74 | Event(TestIri.create("X"), 25, 1, "0:00", "end of 1st quarter")(GamePeriodInfo.WNBAPeriodInfo, "", "", "").addRdf(rep) 75 | Event(TestIri.create("X"), 50, 2, "10:00", "2nd quarter")(GamePeriodInfo.WNBAPeriodInfo, "", "", "").addRdf(rep) 76 | Event(TestIri.create("X"), 75, 2, "5:00", "halfway through 2nd quarter")(GamePeriodInfo.WNBAPeriodInfo, "", "", "").addRdf(rep) 77 | Event(TestIri.create("X"), 100, 3, "10:00", "3rd quarter")(GamePeriodInfo.WNBAPeriodInfo, "", "", "").addRdf(rep) 78 | Event(TestIri.create("X"), 150, 4, "10:00", "4th quarter")(GamePeriodInfo.WNBAPeriodInfo, "", "", "").addRdf(rep) 79 | Event(TestIri.create("X"), 175, 4, "0:00", "end of 4th quarter")(GamePeriodInfo.WNBAPeriodInfo, "", "", "").addRdf(rep) 80 | Event(TestIri.create("X"), 200, 5, "5:00", "first overtime")(GamePeriodInfo.WNBAPeriodInfo, "", "", "").addRdf(rep) 81 | Event(TestIri.create("X"), 225, 5, "0:00", "end of first overtime")(GamePeriodInfo.WNBAPeriodInfo, "", "", "").addRdf(rep) 82 | Event(TestIri.create("X"), 250, 6, "5:00", "second overtime")(GamePeriodInfo.WNBAPeriodInfo, "", "", "").addRdf(rep) 83 | Event(TestIri.create("X"), 275, 6, "2:30", "halfway through second overtime")(GamePeriodInfo.WNBAPeriodInfo, "", "", "").addRdf(rep) 84 | Event(TestIri.create("X"), 300, 7, "5:00", "third overtime")(GamePeriodInfo.WNBAPeriodInfo, "", "", "").addRdf(rep) 85 | Event(TestIri.create("X"), 350, 7, "0:00", "end of third overtime")(GamePeriodInfo.WNBAPeriodInfo, "", "", "").addRdf(rep) 86 | 87 | rep 88 | .executeQuery(""" 89 | SELECT * { 90 | ?s ?secondsIntoGame . 91 | ?s ?label . 92 | }""") 93 | .map(statement => (s"${statement.getValue("label").stringValue} -> ${statement.getValue("secondsIntoGame").stringValue}")) 94 | .toSet should be( 95 | Set( 96 | "1st quarter -> 0", 97 | "end of 1st quarter -> 600", 98 | "2nd quarter -> 600", 99 | "halfway through 2nd quarter -> 900", 100 | "3rd quarter -> 1200", 101 | "4th quarter -> 1800", 102 | "end of 4th quarter -> 2400", 103 | "first overtime -> 2400", 104 | "end of first overtime -> 2700", 105 | "second overtime -> 2700", 106 | "halfway through second overtime -> 2850", 107 | "third overtime -> 3000", 108 | "end of third overtime -> 3300")) 109 | } 110 | 111 | it should "add previous and next triples" in { 112 | val rep = new SailRepository(new MemoryStore) 113 | rep.initialize 114 | 115 | val testIri = TestIri.create("12345678") 116 | 117 | val events = Seq( 118 | Event(testIri, 1, 1, "9:59", "First event Q1")(GamePeriodInfo.WNBAPeriodInfo, "", "", ""), 119 | Event(testIri, 2, 2, "7:30", "Second event Q2")(GamePeriodInfo.WNBAPeriodInfo, "", "", ""), 120 | Event(testIri, 3, 3, "5:00", "Third event Q3")(GamePeriodInfo.WNBAPeriodInfo, "", "", ""), 121 | Event(testIri, 4, 4, "2:30", "Fourth event Q4")(GamePeriodInfo.WNBAPeriodInfo, "", "", ""), 122 | Event(testIri, 5, 5, "1.0", "Last event Q5")(GamePeriodInfo.WNBAPeriodInfo, "", "", "")) 123 | 124 | Event.addPreviousAndNextTriples(rep, events) 125 | 126 | def stripSO(s: String) = s.replaceAll("^http://stellman-greene.com/pbprdf/12345678/", "").toInt 127 | def stripP(s: String) = s.replaceAll("^http://stellman-greene.com/pbprdf#", "") 128 | 129 | val statements = rep.statements.toSeq 130 | .map(s => (stripSO(s.getSubject.stringValue), stripP(s.getPredicate.stringValue), stripSO(s.getObject.stringValue))) 131 | .toSet 132 | 133 | statements.size should be(13) 134 | statements should contain(1, "eventNumber", 1) 135 | statements should contain(2, "eventNumber", 2) 136 | statements should contain(3, "eventNumber", 3) 137 | statements should contain(4, "eventNumber", 4) 138 | statements should contain(5, "eventNumber", 5) 139 | 140 | statements should contain(1, "nextEvent", 2) 141 | statements should contain(2, "previousEvent", 1) 142 | statements should contain(2, "nextEvent", 3) 143 | statements should contain(3, "previousEvent", 2) 144 | statements should contain(3, "nextEvent", 4) 145 | statements should contain(4, "previousEvent", 3) 146 | statements should contain(4, "nextEvent", 5) 147 | statements should contain(5, "previousEvent", 4) 148 | } 149 | 150 | it should "add seconds since previous and until next event" in { 151 | val rep = new SailRepository(new MemoryStore) 152 | rep.initialize 153 | 154 | val testIri = TestIri.create("12345678") 155 | 156 | val events = Seq( 157 | Event(testIri, 1, 1, "2:00", "First event Q1")(GamePeriodInfo.WNBAPeriodInfo, "", "", ""), 158 | Event(testIri, 2, 1, "1:43", "Second event Q1")(GamePeriodInfo.WNBAPeriodInfo, "", "", ""), 159 | Event(testIri, 3, 1, "1:25", "Third event Q1")(GamePeriodInfo.WNBAPeriodInfo, "", "", ""), 160 | Event(testIri, 4, 2, "3:15", "First event Q2")(GamePeriodInfo.WNBAPeriodInfo, "", "", ""), 161 | Event(testIri, 5, 2, "3:01", "Second event Q2")(GamePeriodInfo.WNBAPeriodInfo, "", "", ""), 162 | Event(testIri, 6, 2, "2:59", "Third event Q2")(GamePeriodInfo.WNBAPeriodInfo, "", "", ""), 163 | Event(testIri, 7, 2, "2:59", "Fourth event Q2")(GamePeriodInfo.WNBAPeriodInfo, "", "", ""), 164 | Event(testIri, 8, 2, "1:01", "Fifth event Q2")(GamePeriodInfo.WNBAPeriodInfo, "", "", ""), 165 | Event(testIri, 9, 2, "59.7", "Sixth event Q2")(GamePeriodInfo.WNBAPeriodInfo, "", "", ""), 166 | Event(testIri, 10, 2, "37.2", "Seventh event Q2")(GamePeriodInfo.WNBAPeriodInfo, "", "", ""), 167 | Event(testIri, 11, 2, "0.0", "Eighth event Q2")(GamePeriodInfo.WNBAPeriodInfo, "", "", "")) 168 | 169 | Event.addPreviousAndNextTriples(rep, events) 170 | 171 | def stripSO(s: String) = s.replaceAll("^http://stellman-greene.com/pbprdf/12345678/", "").toInt 172 | def stripP(s: String) = s.replaceAll("^http://stellman-greene.com/pbprdf#", "") 173 | 174 | val statements = rep.statements.toSeq 175 | .map(s => (stripSO(s.getSubject.stringValue), stripP(s.getPredicate.stringValue), stripSO(s.getObject.stringValue))) 176 | .filter(_._2.contains("seconds")) 177 | .toSet 178 | 179 | statements.size should be(18) 180 | statements should contain(1, "secondsUntilNextEvent", 17) 181 | 182 | statements should contain(2, "secondsSincePreviousEvent", 17) 183 | statements should contain(2, "secondsUntilNextEvent", 18) 184 | 185 | statements should contain(3, "secondsSincePreviousEvent", 18) 186 | 187 | statements should contain(4, "secondsUntilNextEvent", 14) 188 | 189 | statements should contain(5, "secondsSincePreviousEvent", 14) 190 | statements should contain(5, "secondsUntilNextEvent", 2) 191 | 192 | statements should contain(6, "secondsSincePreviousEvent", 2) 193 | statements should contain(6, "secondsUntilNextEvent", 0) 194 | 195 | statements should contain(7, "secondsSincePreviousEvent", 0) 196 | statements should contain(7, "secondsUntilNextEvent", 118) 197 | 198 | statements should contain(8, "secondsSincePreviousEvent", 118) 199 | statements should contain(8, "secondsUntilNextEvent", 2) 200 | 201 | statements should contain(9, "secondsSincePreviousEvent", 2) 202 | statements should contain(9, "secondsUntilNextEvent", 22) 203 | 204 | statements should contain(10, "secondsSincePreviousEvent", 22) 205 | statements should contain(10, "secondsUntilNextEvent", 37) 206 | 207 | statements should contain(11, "secondsSincePreviousEvent", 37) 208 | } 209 | } -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/test/GamePeriodInfoSpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.test 2 | 3 | import org.scalatest._ 4 | import com.stellmangreene.pbprdf._ 5 | 6 | class GamePeriodInfoSpec extends FlatSpec with Matchers { 7 | 8 | behavior of "GamePeriodInfo" 9 | 10 | it should "convert a clock to seconds left in WNBA regulation" in { 11 | GamePeriodInfo.WNBAPeriodInfo.clockToSecondsLeft(1, "10:00") should be(Some(EventTimes(0, 600))) 12 | GamePeriodInfo.WNBAPeriodInfo.clockToSecondsLeft(1, "9:59") should be(Some(EventTimes(1, 599))) 13 | GamePeriodInfo.WNBAPeriodInfo.clockToSecondsLeft(1, "27.3") should be(Some(EventTimes(573, 27))) 14 | GamePeriodInfo.WNBAPeriodInfo.clockToSecondsLeft(2, "4:02") should be(Some(EventTimes(958, 242))) 15 | GamePeriodInfo.WNBAPeriodInfo.clockToSecondsLeft(3, "4:22") should be(Some(EventTimes(1538, 262))) 16 | GamePeriodInfo.WNBAPeriodInfo.clockToSecondsLeft(3, "1:05") should be(Some(EventTimes(1735, 65))) 17 | GamePeriodInfo.WNBAPeriodInfo.clockToSecondsLeft(4, "8:04") should be(Some(EventTimes(1916, 484))) 18 | GamePeriodInfo.WNBAPeriodInfo.clockToSecondsLeft(4, "3.6") should be(Some(EventTimes(2397, 3))) 19 | } 20 | 21 | it should "convert a clock to seconds left in NBA regulation" in { 22 | GamePeriodInfo.NBAPeriodInfo.clockToSecondsLeft(1, "12:00") should be(Some(EventTimes(0, 720))) 23 | GamePeriodInfo.NBAPeriodInfo.clockToSecondsLeft(1, "10:00") should be(Some(EventTimes(120, 600))) 24 | GamePeriodInfo.NBAPeriodInfo.clockToSecondsLeft(1, "8:28") should be(Some(EventTimes(212, 508))) 25 | GamePeriodInfo.NBAPeriodInfo.clockToSecondsLeft(1, "37.2") should be(Some(EventTimes(683, 37))) 26 | GamePeriodInfo.NBAPeriodInfo.clockToSecondsLeft(3, "37.2") should be(Some(EventTimes(2123, 37))) 27 | GamePeriodInfo.NBAPeriodInfo.clockToSecondsLeft(4, "12:00") should be(Some(EventTimes(2160, 720))) 28 | GamePeriodInfo.NBAPeriodInfo.clockToSecondsLeft(4, "8:28") should be(Some(EventTimes(2372, 508))) 29 | } 30 | 31 | it should "convert a clock to seconds left in WNBA overtime" in { 32 | GamePeriodInfo.WNBAPeriodInfo.clockToSecondsLeft(5, "5:00") should be(Some(EventTimes(2400, 300))) 33 | GamePeriodInfo.WNBAPeriodInfo.clockToSecondsLeft(5, "4:59") should be(Some(EventTimes(2401, 299))) 34 | GamePeriodInfo.WNBAPeriodInfo.clockToSecondsLeft(5, "1:02") should be(Some(EventTimes(2638, 62))) 35 | GamePeriodInfo.WNBAPeriodInfo.clockToSecondsLeft(7, "5:00") should be(Some(EventTimes(3000, 300))) 36 | GamePeriodInfo.WNBAPeriodInfo.clockToSecondsLeft(7, "30.4") should be(Some(EventTimes(3270, 30))) 37 | GamePeriodInfo.WNBAPeriodInfo.clockToSecondsLeft(7, "1.2") should be(Some(EventTimes(3299, 1))) 38 | } 39 | 40 | it should "convert a clock to seconds left in NBA overtime" in { 41 | GamePeriodInfo.NBAPeriodInfo.clockToSecondsLeft(5, "5:00") should be(Some(EventTimes(2880, 300))) 42 | GamePeriodInfo.NBAPeriodInfo.clockToSecondsLeft(5, "4:59") should be(Some(EventTimes(2881, 299))) 43 | GamePeriodInfo.NBAPeriodInfo.clockToSecondsLeft(5, "1:02") should be(Some(EventTimes(3118, 62))) 44 | GamePeriodInfo.NBAPeriodInfo.clockToSecondsLeft(6, "5:00") should be(Some(EventTimes(3180, 300))) 45 | GamePeriodInfo.NBAPeriodInfo.clockToSecondsLeft(7, "5:00") should be(Some(EventTimes(3480, 300))) 46 | GamePeriodInfo.NBAPeriodInfo.clockToSecondsLeft(7, "30.4") should be(Some(EventTimes(3750, 30))) 47 | GamePeriodInfo.NBAPeriodInfo.clockToSecondsLeft(7, "1.2") should be(Some(EventTimes(3779, 1))) 48 | } 49 | 50 | it should "handle in invalid clock" in { 51 | GamePeriodInfo.WNBAPeriodInfo.clockToSecondsLeft(1, "XYZ") should be(None) 52 | GamePeriodInfo.WNBAPeriodInfo.clockToSecondsLeft(1, "") should be(None) 53 | } 54 | } -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/test/TestIri.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.test 2 | 3 | import org.eclipse.rdf4j.model.impl.SimpleValueFactory 4 | import org.eclipse.rdf4j.model.IRI 5 | 6 | object TestIri { 7 | def create(s: String): IRI = SimpleValueFactory.getInstance().createIRI(s"http://stellman-greene.com/pbprdf/${s}") 8 | } -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/util/test/RdfOperationsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.util.test 2 | 3 | import java.io.ByteArrayOutputStream 4 | import java.io.PrintStream 5 | 6 | import scala.language.postfixOps 7 | 8 | import org.eclipse.rdf4j.model.vocabulary.OWL 9 | import org.eclipse.rdf4j.model.vocabulary.RDF 10 | import org.eclipse.rdf4j.model.vocabulary.RDFS 11 | import org.eclipse.rdf4j.repository.Repository 12 | import org.eclipse.rdf4j.repository.sail.SailRepository 13 | import org.eclipse.rdf4j.sail.memory.MemoryStore 14 | import org.scalatest.BeforeAndAfterEach 15 | import org.scalatest.FlatSpec 16 | import org.scalatest.Matchers 17 | 18 | import com.stellmangreene.pbprdf.util.RdfOperations._ 19 | import org.eclipse.rdf4j.rio.RDFFormat 20 | 21 | /** 22 | * Unit tests for the RdfOperations trait that provides implicit RDF operations 23 | * for rdf4j repositories and Aduna Iterators such as TupleQueryResult objects 24 | * 25 | * @author andrewstellman 26 | */ 27 | class RdfOperationsSpec extends FlatSpec with Matchers with BeforeAndAfterEach { 28 | 29 | behavior of "RdfOperations" 30 | 31 | private var rep: Repository = null 32 | 33 | override def beforeEach() = { 34 | rep = new SailRepository(new MemoryStore) 35 | rep.initialize 36 | 37 | // Use the implicit functions to add triples 38 | 39 | rep.addTriple(rep.getValueFactory.createIRI("test:entity1"), RDF.TYPE, OWL.THING) 40 | rep.addTriples(Set( 41 | (rep.getValueFactory.createIRI("test:entity1"), RDFS.LABEL, rep.getValueFactory.createLiteral("This is a label")), 42 | (rep.getValueFactory.createIRI("test:entity1"), RDFS.COMMENT, rep.getValueFactory.createLiteral("This is a comment")))) 43 | 44 | // Use the implicit functions to add triples with a context 45 | 46 | val context = rep.getValueFactory.createIRI("test:context") 47 | rep.addTriple(rep.getValueFactory.createIRI("test:entity2"), RDF.TYPE, OWL.THING, context) 48 | rep.addTriples( 49 | Set( 50 | (rep.getValueFactory.createIRI("test:entity2"), rep.getValueFactory.createIRI("test:predicate#is"), rep.getValueFactory.createLiteral(true)), 51 | (rep.getValueFactory.createIRI("test:entity2"), RDFS.LABEL, rep.getValueFactory.createLiteral("This is a second label")), 52 | (rep.getValueFactory.createIRI("test:entity2"), RDFS.COMMENT, rep.getValueFactory.createLiteral("This is a second comment"))), 53 | context) 54 | } 55 | 56 | it should "execute a SPARQL query and iterate over the results" in { 57 | val results = rep.executeQuery(""" 58 | SELECT ?label ?comment 59 | FROM NAMED 60 | WHERE { 61 | GRAPH ?context { 62 | OPTIONAL { ?s rdfs:label ?label } 63 | OPTIONAL { ?s rdfs:comment ?comment } 64 | } 65 | } 66 | """) 67 | 68 | results.map(bindingSet => s"label=${bindingSet.getValue("label")} comment=${bindingSet.getValue("comment")}").toSeq should be( 69 | Seq("""label="This is a second label"^^ comment="This is a second comment"^^""")) 70 | } 71 | 72 | it should "treat Aduna iterations (like TupleQueryResult objects) as an iterator that can only be traversed once" in { 73 | val results = rep.executeQuery("SELECT * { ?s ?p ?o }") 74 | results.asIterator.isTraversableAgain should be(false) 75 | 76 | // The iterator can be used once 77 | results.asIterator.toSeq.size should be(7) 78 | 79 | // This is a true iterator: the first iteration is lazy, and traversing it a second time will yield no results 80 | results.asIterator.toSeq.isEmpty should be(true) 81 | 82 | // The toSeq, foreach, and map functions are just conveniences that call asIterator 83 | results.toSeq.isEmpty should be(true) 84 | results.map(e => e).isEmpty should be(true) 85 | } 86 | 87 | it should "write all of the statements in the repository to an RDF format" in { 88 | // Redirect stdout to a stream 89 | var myOut = new ByteArrayOutputStream 90 | System.setOut(new PrintStream(myOut)); 91 | 92 | // Write all statements to stdout 93 | rep.writeAllStatements(None, RDFFormat.NQUADS) 94 | 95 | // Verify that stdout contains all of the expected nquads 96 | var standardOutput = myOut.toString 97 | standardOutput.split("\n") 98 | .toSet 99 | .filter(s => !s.contains("Writing Turtle to standard output") && !s.contains("WARN")) /* remove the log messages */ should be( 100 | Set( 101 | """ "This is a label" .""", 102 | """ "This is a comment" .""", 103 | """ .""", 104 | """ "This is a second label" .""", 105 | """ "This is a second comment" .""", 106 | """ "true"^^ .""", 107 | """ .""")) 108 | } 109 | 110 | it should "get an iterator with all of the statements in the repository" in { 111 | val statements = rep.statements.toSet 112 | statements.map(_.toString) should be( 113 | Set( 114 | """(test:entity1, http://www.w3.org/2000/01/rdf-schema#comment, "This is a comment"^^) [null]""", 115 | """(test:entity1, http://www.w3.org/1999/02/22-rdf-syntax-ns#type, http://www.w3.org/2002/07/owl#Thing) [null]""", 116 | """(test:entity1, http://www.w3.org/2000/01/rdf-schema#label, "This is a label"^^) [null]""", 117 | """(test:entity2, test:predicate#is, "true"^^) [test:context]""", 118 | """(test:entity2, http://www.w3.org/2000/01/rdf-schema#comment, "This is a second comment"^^) [test:context]""", 119 | """(test:entity2, http://www.w3.org/2000/01/rdf-schema#label, "This is a second label"^^) [test:context]""", 120 | """(test:entity2, http://www.w3.org/1999/02/22-rdf-syntax-ns#type, http://www.w3.org/2002/07/owl#Thing) [test:context]""")) 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/test/scala/com/stellmangreene/pbprdf/util/test/XmlHelperSpec.scala: -------------------------------------------------------------------------------- 1 | package com.stellmangreene.pbprdf.util.test 2 | 3 | import java.io.FileInputStream 4 | import org.scalatest._ 5 | import com.stellmangreene.pbprdf.util.XmlHelper 6 | import scala.language.postfixOps 7 | import better.files._ 8 | 9 | /** 10 | * @author andrewstellman 11 | */ 12 | class XmlHelperSpec extends FlatSpec with Matchers { 13 | 14 | behavior of "XmlHelper" 15 | 16 | it should "return the root element with valid XML" in { 17 | val xml = "src/test/resources/com/stellmangreene/pbprdf/test/htmldata/400610636.html".toFile.newInputStream 18 | val rootElem = XmlHelper.parseXml(xml) 19 | 20 | ((rootElem \\ "title") text) should be("Washington vs. Connecticut - Play-By-Play - June 5, 2015 - ESPN") 21 | 22 | (rootElem \\ "body" \\ "div") 23 | .filter(_.attribute("id").isDefined) 24 | .map(_.attribute("id").get.mkString) should be( 25 | List("fb-root", "global-viewport", "header-wrapper", "fullbtn", "global-search", "custom-nav", "gamepackage-header-wrap", "gamepackage-matchup-wrap", 26 | "gamepackage-linescore-wrap", "gamepackage-links-wrap", "gamepackage-wrap", "gamepackage-content-wrap", "gamepackage-column-wrap", "gamepackage-shot-chart", 27 | "chart1", "accordion-1", "gamepackage-play-by-play", "gamepackage-qtrs-wrap", "gp-quarter-1", "gp-quarter-2", "gp-quarter-3", "gp-quarter-4", 28 | "gamepackage-outbrain", "gamepackage-shop", "gamepackage-ad", "gamepackage-cliplist", "gamepackage-news", "gamepackage-season-series")) 29 | } 30 | 31 | it should "get elements by class and tag" in { 32 | val xml = "src/test/resources/com/stellmangreene/pbprdf/test/htmldata/400610636-gameinfo.html".toFile.newInputStream 33 | val rootElem = XmlHelper.parseXml(xml) 34 | 35 | val divs = (rootElem \\ "body" \\ "div") 36 | val gameTimeLocationDivs = XmlHelper.getElemByClassAndTag(divs, "game-date-time", "span") 37 | gameTimeLocationDivs.get.size should be(3) 38 | gameTimeLocationDivs.get.mkString.contains("""""") should be(true) 39 | } 40 | 41 | } 42 | --------------------------------------------------------------------------------