From 19c1bbfd796a2e8186793a8c2648d43bca702b2d Mon Sep 17 00:00:00 2001 From: Ioanna Kokkini Date: Tue, 24 Sep 2024 12:05:31 +0100 Subject: [PATCH] Substitute cricket match description for certain dismissal types We would like to be able to use the full name of a bowler or a catcher if two players in the same team share the same surname. We also want to leave the description as it is if there is no need for subsitution, e.g. when dismissal type is "Run Out" Co-authored-by: Roberto Tyley <52038+rtyley@users.noreply.github.com> --- sport/app/cricket/feed/cricketModel.scala | 6 +- .../feed/cricketPaDeserialisation.scala | 83 ++++--- .../feed/CricketPaDeserialisationTest.scala | 207 +++--------------- 3 files changed, 85 insertions(+), 211 deletions(-) diff --git a/sport/app/cricket/feed/cricketModel.scala b/sport/app/cricket/feed/cricketModel.scala index 4e882b3c2de..5712a6b096b 100644 --- a/sport/app/cricket/feed/cricketModel.scala +++ b/sport/app/cricket/feed/cricketModel.scala @@ -1,6 +1,8 @@ package cricketModel +import cricket.feed.PlayerNames import play.api.libs.json._ + import java.time.LocalDateTime case class Player(id: String, name: String, firstName: String, lastName: String, initials: String) @@ -8,7 +10,9 @@ case class Player(id: String, name: String, firstName: String, lastName: String, object Player { implicit val writes: OWrites[Player] = Json.writes[Player] } -case class Team(name: String, id: String, home: Boolean, lineup: List[String], players: List[Player]) +case class Team(name: String, id: String, home: Boolean, lineup: List[String], players: List[Player]) { + lazy val uniquePlayerNames = PlayerNames.uniqueNames(players) +} object Team { implicit val writes: OWrites[Team] = Json.writes[Team] diff --git a/sport/app/cricket/feed/cricketPaDeserialisation.scala b/sport/app/cricket/feed/cricketPaDeserialisation.scala index ba7f5999e39..70877c54e8d 100644 --- a/sport/app/cricket/feed/cricketPaDeserialisation.scala +++ b/sport/app/cricket/feed/cricketPaDeserialisation.scala @@ -1,9 +1,10 @@ package conf.cricketPa +import com.madgag.scala.collection.decorators.MapDecorator import common.Chronos import cricket.feed.PlayerNames -import xml.{NodeSeq, XML} +import xml.{Node, NodeSeq, XML} import scala.language.postfixOps import cricketModel._ @@ -71,16 +72,15 @@ object Parser { }.toList - private def parseTeamLineup(lineup: NodeSeq): List[Player] = - lineup.map { player => - Player( - id = (player \ "id").text, - name = (player \ "name").text, - firstName = (player \ "firstName").text, - lastName = (player \ "lastName").text, - initials = (player \ "initials").text, - ) - }.toList + private def parsePlayer(player: Node): Player = Player( + id = (player \ "id").text, + name = (player \ "name").text, + firstName = (player \ "firstName").text, + lastName = (player \ "lastName").text, + initials = (player \ "initials").text, + ) + + private def parseTeamLineup(lineup: NodeSeq): List[Player] = lineup.map(parsePlayer).toList private def getStatistic(statistics: NodeSeq, statistic: String): String = (statistics \ "statistic").find(node => (node \ "@type").text == statistic).map(_.text).getOrElse("") @@ -91,7 +91,8 @@ object Parser { val inningsOrder = (singleInnings \ "@order").text.toInt val battingTeam = (singleInnings \ "batting" \ "team" \ "name").text - val bowlingTeam = teams.find(_.name == (singleInnings \ "bowling" \ "team" \ "name)").text) + val bowlingTeamName = (singleInnings \ "bowling" \ "team" \ "name").text + val bowlingTeam = teams.find(_.name == bowlingTeamName) Innings( inningsOrder, @@ -115,32 +116,48 @@ object Parser { .toList .sortBy(_.order) - def descriptionWithUniqueNames( - bowlingTeam: Option[Team], - catcherId: String, - bowlerId: String, - description: String, - ): String = { - val players = bowlingTeam.map(team => PlayerNames.uniqueNames(team.players)).get - - description - .replaceFirst("c\\s+\\w+\\s?", s"c ${players.getOrElse(catcherId, "")} ") - .replaceFirst("st\\s+\\w+\\s?", s"st ${players.getOrElse(catcherId, "")} ") - .replaceFirst("b\\s+\\w+\\s?", s"b ${players.getOrElse(bowlerId, "")}") + case class Dismissal(items: Seq[(String, String)]) { + + def description(dismissal: NodeSeq, f: Player => String): String = { + for { + (nodeName, prefix) <- items + } yield { + s"$prefix ${f(parsePlayer((dismissal \ nodeName \ "player").head))}" + } + }.mkString(" ") + + } + + val dismissalTypes: Map[String, Dismissal] = Map( + "caught" -> Seq("caughtBy" -> "st", "bowledBy" -> "b"), // c Rathnayake b de Silva + "caught-sub" -> Seq("bowledBy" -> "c Sub b"), // c Sub b Kumara + "caught-and-bowled" -> Seq("caughtBy" -> "c & b"), // c & b Woakes + "stumped" -> Seq("caughtBy" -> "st", "bowledBy" -> "b"), // st Ambrose b Patel + "run-out" -> Seq("caughtBy" -> "Run Out"), // Run Out Stone + "lbw" -> Seq("bowledBy" -> "lbw b"), // lbw b Stone + "bowled" -> Seq("bowledBy" -> "b"), // b Kumara + ).mapV(Dismissal) + + def parseDismissal(dismissal: NodeSeq, bowlingTeamOpt: Option[Team]): String = { + val description = (dismissal \ "description").text + ( + for { + bowlingTeam <- bowlingTeamOpt + dismissalDescriber <- dismissalTypes.get(dismissal \@ "type") + if description == dismissalDescriber.description(dismissal, _.lastName) + } yield { + dismissalDescriber.description( + dismissal, + player => bowlingTeam.uniquePlayerNames.getOrElse(player.id, player.name), + ) + } + ).getOrElse(description) } private def parseInningsBatters(batters: NodeSeq, bowlingTeam: Option[Team]): List[InningsBatter] = { batters .map { batter => - val dismissalDescription = (batter \ "dismissal" \ "description").text - val catcherId = - if (dismissalDescription.contains("c") || dismissalDescription.contains("st")) - (batter \ "dismissal" \ "caughtBy" \ "player" \ "@id").text - else "" - val bowlerId = - if (dismissalDescription.contains("b")) (batter \ "dismissal" \ "bowledBy" \ "player" \ "@id").text else "" - InningsBatter( (batter \ "player" \ "name").text, (batter \ "@order").text.toInt, @@ -149,7 +166,7 @@ object Parser { getStatistic(batter, "fours") toInt, getStatistic(batter, "sixes") toInt, (batter \ "status").text == "batted", - descriptionWithUniqueNames(bowlingTeam, dismissalDescription, catcherId, bowlerId), + parseDismissal(batter \ "dismissal", bowlingTeam), getStatistic(batter, "on-strike").toInt > 0, getStatistic(batter, "runs-scored").toInt > 0, ) diff --git a/sport/test/cricket/feed/CricketPaDeserialisationTest.scala b/sport/test/cricket/feed/CricketPaDeserialisationTest.scala index c8a391d693c..e3331797e42 100644 --- a/sport/test/cricket/feed/CricketPaDeserialisationTest.scala +++ b/sport/test/cricket/feed/CricketPaDeserialisationTest.scala @@ -1,184 +1,37 @@ package cricket.feed -import conf.cricketPa.Parser.descriptionWithUniqueNames -import cricketModel.{Player, Team} +import org.apache.pekko.actor.{ActorSystem => PekkoActorSystem} +import conf.cricketPa.PaFeed +import org.scalatest.{BeforeAndAfterAll, DoNotDiscover} +import org.scalatest.concurrent.{IntegrationPatience, ScalaFutures} import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import test.{ + ConfiguredTestSuite, + WithMaterializer, + WithTestApplicationContext, + WithTestExecutionContext, + WithTestWsClient, +} -class CricketPaDeserialisationTest extends AnyFlatSpec with Matchers { - - val teamWithPlayersWithUniqueSurnames = Team( - name = "Sri Lanka", - id = "0cbc23be-e7cc-9574-611a-06561460eb8b", - home = false, - lineup = List("Asitha Fernando", "Dimuth Karunaratne", "Kusal Mendis"), - players = List( - Player( - id = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", - name = "Asitha Fernando", - firstName = "Asitha", - lastName = "Fernando", - initials = "A M", - ), - Player( - id = "c32cd9c7-d38a-93e7-e874-f5f4a5197812", - name = "Dimuth Karunaratne", - firstName = "Frank", - lastName = "Karunaratne", - initials = "F D M", - ), - Player( - id = "b96e5130-0348-9659-e3c6-ba887f306eeb", - name = "Kusal Mendis", - firstName = "Balapuwaduge", - lastName = "Mendis", - initials = "B K G", - ), - Player( - id = "46a55cb7-49f5-e9f7-7560-c7110b0f68ec", - name = "Dinesh Chandimal", - firstName = "Lokuge", - lastName = "Chandimal", - initials = "L D", - ), - ), - ) - - val teamWithPlayersWithTheSameSurname = Team( - name = "Sri Lanka", - id = "0cbc23be-e7cc-9574-611a-06561460eb8b", - home = false, - lineup = List("Asitha Fernando", "Dimuth Karunaratne", "Nishan Madushka"), - players = List( - Player( - id = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", - name = "Asitha Fernando", - firstName = "Asitha", - lastName = "Fernando", - initials = "A M", - ), - Player( - id = "c32cd9c7-d38a-93e7-e874-f5f4a5197812", - name = "Dimuth Karunaratne", - firstName = "Frank", - lastName = "Karunaratne", - initials = "F D M", - ), - Player( - id = "d29c8d1c-29b4-517e-5b62-1277065801b2", - name = "Nishan Madushka", - firstName = "Kottasinghakkarage", - lastName = "Fernando", - initials = "K N M", - ), - Player( - id = "46a55cb7-49f5-e9f7-7560-c7110b0f68ec", - name = "Dinesh Chandimal", - firstName = "Lokuge", - lastName = "Chandimal", - initials = "L D", - ), - ), - ) - - "descriptionWithUniqueNames" should "include catcher's and bowler's surname if this is enough to determine their unique name when dismissal type is caught" in { - - descriptionWithUniqueNames( - bowlingTeam = Some(teamWithPlayersWithUniqueSurnames), - catcherId = "46a55cb7-49f5-e9f7-7560-c7110b0f68ec", - bowlerId = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", - description = "c Chandimal b Fernando", - ) shouldEqual "c Chandimal b Fernando" - - } - - it should "include catcher's first name and surname if other player with the same surname exists in the same team when dismissal type is caught" in { - descriptionWithUniqueNames( - bowlingTeam = Some(teamWithPlayersWithTheSameSurname), - catcherId = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", - bowlerId = "46a55cb7-49f5-e9f7-7560-c7110b0f68ec", - description = "c Chandimal b Fernando", - ) shouldEqual "c Asitha Fernando b Chandimal" - } - - it should "include bowler's first name and surname if other player with the same surname exists in the same team when dismissal type is caught" in { - descriptionWithUniqueNames( - bowlingTeam = Some(teamWithPlayersWithTheSameSurname), - catcherId = "46a55cb7-49f5-e9f7-7560-c7110b0f68ec", - bowlerId = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", - description = "c Chandimal b Fernando", - ) shouldEqual "c Chandimal b Asitha Fernando" - } - - it should "include bowler's surname if this is enough to determine their unique name when dismissal type is bowled" in { - descriptionWithUniqueNames( - bowlingTeam = Some(teamWithPlayersWithUniqueSurnames), - catcherId = "", - bowlerId = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", - description = "b Fernando", - ) shouldEqual "b Fernando" - } - - it should "include bowler's first name and surname if other player with the same surname exists in the same team when dismissal type is bowled" in { - descriptionWithUniqueNames( - bowlingTeam = Some(teamWithPlayersWithTheSameSurname), - catcherId = "", - bowlerId = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", - description = "b Fernando", - ) shouldEqual "b Asitha Fernando" - } - - it should "include catcher's surname if this is enough to determine their unique name when dismissal type is stumped" in { - descriptionWithUniqueNames( - bowlingTeam = Some(teamWithPlayersWithUniqueSurnames), - catcherId = "46a55cb7-49f5-e9f7-7560-c7110b0f68ec", - bowlerId = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", - description = "st Chandimal b Fernando", - ) shouldEqual "st Chandimal b Fernando" - } - - it should "include catcher's first name and surname if other player with the same surname exists in the same team when dismissal type is stumped" in { - descriptionWithUniqueNames( - bowlingTeam = Some(teamWithPlayersWithTheSameSurname), - catcherId = "46a55cb7-49f5-e9f7-7560-c7110b0f68ec", - bowlerId = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", - description = "st Chandimal b Fernando", - ) shouldEqual "st Chandimal b Asitha Fernando" - } - - it should "include bowler's surname if this is enough to determine their unique name when dismissal type is lbw" in { - descriptionWithUniqueNames( - bowlingTeam = Some(teamWithPlayersWithUniqueSurnames), - catcherId = "", - bowlerId = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", - description = "lbw b Fernando", - ) shouldEqual "lbw b Fernando" - } - - it should "include bowler's first name and surname if other player with the same surname exists in the same team when dismissal type is lbw" in { - descriptionWithUniqueNames( - bowlingTeam = Some(teamWithPlayersWithTheSameSurname), - catcherId = "", - bowlerId = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", - description = "lbw b Fernando", - ) shouldEqual "lbw b Asitha Fernando" - } - - it should "be the same as initial description when dismissal type is not-out" in { - descriptionWithUniqueNames( - bowlingTeam = Some(teamWithPlayersWithTheSameSurname), - catcherId = "", - bowlerId = "", - description = "Not Out", - ) shouldEqual "Not Out" - } - - it should "be the same as initial description when dismissal type is yet-to-bat" in { - descriptionWithUniqueNames( - bowlingTeam = Some(teamWithPlayersWithTheSameSurname), - catcherId = "", - bowlerId = "", - description = "Yet to Bat", - ) shouldEqual "Yet to Bat" +class CricketPaDeserialisationTest + extends AnyFlatSpec + with Matchers + with ConfiguredTestSuite + with BeforeAndAfterAll + with WithMaterializer + with WithTestWsClient + with WithTestApplicationContext + with WithTestExecutionContext + with ScalaFutures + with IntegrationPatience { + val actorSystem = PekkoActorSystem() + val paFeed = new PaFeed(wsClient, actorSystem, materializer) + + whenReady(paFeed.getMatch("39145392-3f2e-8022-35f3-eac0b0654610")) { cricketMatch => + { + cricketMatch.innings.head.batters.head.howOut shouldBe "" + + } } }