From 38582a82fdbf866dfe2e38683f1c16573e3b8e0f Mon Sep 17 00:00:00 2001 From: Ioanna Kokkini Date: Fri, 6 Sep 2024 13:35:02 +0100 Subject: [PATCH 1/8] Create a map of cricker players id and unique name We need to be able to distinguish between bowlers and catchers that have the same surname. This is a first step to be able to know their unique name. Co-authored-by: Roberto Tyley <52038+rtyley@users.noreply.github.com> --- sport/app/cricket/feed/PlayerNames.scala | 13 +++++++++++++ sport/app/cricket/feed/cricketModel.scala | 7 ++++++- .../cricket/feed/cricketPaDeserialisation.scala | 15 ++++++++++++--- 3 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 sport/app/cricket/feed/PlayerNames.scala diff --git a/sport/app/cricket/feed/PlayerNames.scala b/sport/app/cricket/feed/PlayerNames.scala new file mode 100644 index 000000000000..9aa240025b6e --- /dev/null +++ b/sport/app/cricket/feed/PlayerNames.scala @@ -0,0 +1,13 @@ +package cricket.feed + +import cricketModel.Player + +object PlayerNames { + + def uniqueNames(players: List[Player]) = (for { + playersGroupedByName <- players.groupBy(_.lastName).values + player <- playersGroupedByName + } yield { + player.id -> { if (playersGroupedByName.size > 1) s"${player.name} ${player.lastName}" else player.lastName } + }).toMap +} diff --git a/sport/app/cricket/feed/cricketModel.scala b/sport/app/cricket/feed/cricketModel.scala index e9e74ce69eeb..4e882b3c2de1 100644 --- a/sport/app/cricket/feed/cricketModel.scala +++ b/sport/app/cricket/feed/cricketModel.scala @@ -3,7 +3,12 @@ package cricketModel import play.api.libs.json._ import java.time.LocalDateTime -case class Team(name: String, id: String, home: Boolean, lineup: List[String]) +case class Player(id: String, name: String, firstName: String, lastName: String, initials: 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]) 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 6864e682a1de..6382ca14c94a 100644 --- a/sport/app/cricket/feed/cricketPaDeserialisation.scala +++ b/sport/app/cricket/feed/cricketPaDeserialisation.scala @@ -64,12 +64,21 @@ object Parser { private def parseTeams(teams: NodeSeq): List[Team] = teams.map { team => - Team((team \ "name").text, (team \ "@id").text, (team \ "home").text == "true", parseTeamLineup(team \ "player")) + val players = parseTeamLineup(team \ "player") + Team((team \ "name").text, (team \ "@id").text, (team \ "home").text == "true", players.map(_.name), players) }.toList - private def parseTeamLineup(lineup: NodeSeq): List[String] = - lineup.map { player => (player \ "name").text }.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 getStatistic(statistics: NodeSeq, statistic: String): String = (statistics \ "statistic").find(node => (node \ "@type").text == statistic).map(_.text).getOrElse("") From 993b69e3f1f70f978c3010f5839cae0e5a8b3630 Mon Sep 17 00:00:00 2001 From: Ioanna Kokkini Date: Tue, 10 Sep 2024 18:29:47 +0100 Subject: [PATCH 2/8] Add unit tests for PlayerNames.uniqueNames --- sport/app/cricket/feed/PlayerNames.scala | 2 +- sport/test/cricket/feed/PlayerNamesTest.scala | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 sport/test/cricket/feed/PlayerNamesTest.scala diff --git a/sport/app/cricket/feed/PlayerNames.scala b/sport/app/cricket/feed/PlayerNames.scala index 9aa240025b6e..4a80d2202b14 100644 --- a/sport/app/cricket/feed/PlayerNames.scala +++ b/sport/app/cricket/feed/PlayerNames.scala @@ -8,6 +8,6 @@ object PlayerNames { playersGroupedByName <- players.groupBy(_.lastName).values player <- playersGroupedByName } yield { - player.id -> { if (playersGroupedByName.size > 1) s"${player.name} ${player.lastName}" else player.lastName } + player.id -> { if (playersGroupedByName.size > 1) s"${player.firstName} ${player.lastName}" else player.lastName } }).toMap } diff --git a/sport/test/cricket/feed/PlayerNamesTest.scala b/sport/test/cricket/feed/PlayerNamesTest.scala new file mode 100644 index 000000000000..ad1626e6a41c --- /dev/null +++ b/sport/test/cricket/feed/PlayerNamesTest.scala @@ -0,0 +1,29 @@ +package cricket.feed + +import cricketModel.Player +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class PlayerNamesTest extends AnyFlatSpec with Matchers { + + "uniqueNames" should "be determined by lastName if players have different last names" in { + val player1 = Player(id = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", name = "Asitha Fernando", firstName = "Asitha", lastName = "Fernando", initials = "A M") + val player2 = Player(id = "c32cd9c7-d38a-93e7-e874-f5f4a5197812", name = "Dimuth Karunaratne", firstName= "Frank", lastName = "Karunaratne", initials = "F D M") + val player3 = Player(id = "b96e5130-0348-9659-e3c6-ba887f306eeb", name = "Kusal Mendis", firstName = "Balapuwaduge", lastName = "Mendis", initials = "B K G") + + PlayerNames.uniqueNames(List(player1, player2, player3)).values.toList should contain ("Fernando") + PlayerNames.uniqueNames(List(player1, player2, player3)).values.toList should contain ("Mendis") + PlayerNames.uniqueNames(List(player1, player2, player3)).values.toList should contain ("Karunaratne") + } + + "uniqueNames" should "be determined by full name and lastName if players have the same last name" in { + val player1 = Player(id = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", name = "Asitha Fernando", firstName = "Asitha", lastName = "Fernando", initials = "A M") + val player2 = Player(id = "c32cd9c7-d38a-93e7-e874-f5f4a5197812", name = "Dimuth Karunaratne", firstName= "Frank", lastName = "Karunaratne", initials = "F D M") + val player3 = Player(id = "d29c8d1c-29b4-517e-5b62-1277065801b2", name = "Nishan Madushka", firstName = "Kottasinghakkarage", lastName = "Fernando", initials = "K N M") + + PlayerNames.uniqueNames(List(player1, player2, player3)).values.toList should contain("Asitha Fernando") + PlayerNames.uniqueNames(List(player1, player2, player3)).values.toList should contain("Karunaratne") + PlayerNames.uniqueNames(List(player1, player2, player3)).values.toList should contain("Kottasinghakkarage Fernando") + } +} + From 22bf3280b6f5c4137636f481199d7c25eb8bb9a3 Mon Sep 17 00:00:00 2001 From: Ioanna Kokkini Date: Wed, 11 Sep 2024 16:28:13 +0100 Subject: [PATCH 3/8] PlayerNamesTest --- sport/test/cricket/feed/PlayerNamesTest.scala | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/sport/test/cricket/feed/PlayerNamesTest.scala b/sport/test/cricket/feed/PlayerNamesTest.scala index ad1626e6a41c..7d2235929d18 100644 --- a/sport/test/cricket/feed/PlayerNamesTest.scala +++ b/sport/test/cricket/feed/PlayerNamesTest.scala @@ -7,23 +7,58 @@ import org.scalatest.matchers.should.Matchers class PlayerNamesTest extends AnyFlatSpec with Matchers { "uniqueNames" should "be determined by lastName if players have different last names" in { - val player1 = Player(id = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", name = "Asitha Fernando", firstName = "Asitha", lastName = "Fernando", initials = "A M") - val player2 = Player(id = "c32cd9c7-d38a-93e7-e874-f5f4a5197812", name = "Dimuth Karunaratne", firstName= "Frank", lastName = "Karunaratne", initials = "F D M") - val player3 = Player(id = "b96e5130-0348-9659-e3c6-ba887f306eeb", name = "Kusal Mendis", firstName = "Balapuwaduge", lastName = "Mendis", initials = "B K G") + val player1 = Player( + id = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", + name = "Asitha Fernando", + firstName = "Asitha", + lastName = "Fernando", + initials = "A M", + ) + val player2 = Player( + id = "c32cd9c7-d38a-93e7-e874-f5f4a5197812", + name = "Dimuth Karunaratne", + firstName = "Frank", + lastName = "Karunaratne", + initials = "F D M", + ) + val player3 = Player( + id = "b96e5130-0348-9659-e3c6-ba887f306eeb", + name = "Kusal Mendis", + firstName = "Balapuwaduge", + lastName = "Mendis", + initials = "B K G", + ) - PlayerNames.uniqueNames(List(player1, player2, player3)).values.toList should contain ("Fernando") - PlayerNames.uniqueNames(List(player1, player2, player3)).values.toList should contain ("Mendis") - PlayerNames.uniqueNames(List(player1, player2, player3)).values.toList should contain ("Karunaratne") + PlayerNames.uniqueNames(List(player1, player2, player3)).values.toList should contain("Fernando") + PlayerNames.uniqueNames(List(player1, player2, player3)).values.toList should contain("Mendis") + PlayerNames.uniqueNames(List(player1, player2, player3)).values.toList should contain("Karunaratne") } - "uniqueNames" should "be determined by full name and lastName if players have the same last name" in { - val player1 = Player(id = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", name = "Asitha Fernando", firstName = "Asitha", lastName = "Fernando", initials = "A M") - val player2 = Player(id = "c32cd9c7-d38a-93e7-e874-f5f4a5197812", name = "Dimuth Karunaratne", firstName= "Frank", lastName = "Karunaratne", initials = "F D M") - val player3 = Player(id = "d29c8d1c-29b4-517e-5b62-1277065801b2", name = "Nishan Madushka", firstName = "Kottasinghakkarage", lastName = "Fernando", initials = "K N M") + it should "be determined by full name and lastName if players have the same last name" in { + val player1 = Player( + id = "ae5e0dbf-d6af-70ec-76ef-1f8e83230405", + name = "Asitha Fernando", + firstName = "Asitha", + lastName = "Fernando", + initials = "A M", + ) + val player2 = Player( + id = "c32cd9c7-d38a-93e7-e874-f5f4a5197812", + name = "Dimuth Karunaratne", + firstName = "Frank", + lastName = "Karunaratne", + initials = "F D M", + ) + val player3 = Player( + id = "d29c8d1c-29b4-517e-5b62-1277065801b2", + name = "Nishan Madushka", + firstName = "Kottasinghakkarage", + lastName = "Fernando", + initials = "K N M", + ) PlayerNames.uniqueNames(List(player1, player2, player3)).values.toList should contain("Asitha Fernando") PlayerNames.uniqueNames(List(player1, player2, player3)).values.toList should contain("Karunaratne") PlayerNames.uniqueNames(List(player1, player2, player3)).values.toList should contain("Kottasinghakkarage Fernando") } } - From a7804a1e5c93f228633d5045cf22b2cf3387e799 Mon Sep 17 00:00:00 2001 From: Ioanna Kokkini Date: Wed, 11 Sep 2024 16:29:16 +0100 Subject: [PATCH 4/8] Pass variables --- .../cricket/feed/cricketPaDeserialisation.scala | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sport/app/cricket/feed/cricketPaDeserialisation.scala b/sport/app/cricket/feed/cricketPaDeserialisation.scala index 6382ca14c94a..60e89e86909c 100644 --- a/sport/app/cricket/feed/cricketPaDeserialisation.scala +++ b/sport/app/cricket/feed/cricketPaDeserialisation.scala @@ -1,6 +1,7 @@ package conf.cricketPa import common.Chronos +import cricket.feed.PlayerNames import xml.{NodeSeq, XML} import scala.language.postfixOps @@ -17,10 +18,11 @@ object Parser { val matchDetail = parseMatchDetail(matchData) val lineupData = XML.loadString(lineups) val scorecardData = XML.loadString(scorecard) + val teams = parseTeams(lineupData \ "match" \ "team") Match( - parseTeams(lineupData \ "match" \ "team"), - parseInnings(scorecardData \ "match" \ "innings"), + teams, + parseInnings(scorecardData \ "match" \ "innings", teams), matchDetail.competitionName, matchDetail.venueName, matchDetail.result, @@ -83,11 +85,14 @@ object Parser { private def getStatistic(statistics: NodeSeq, statistic: String): String = (statistics \ "statistic").find(node => (node \ "@type").text == statistic).map(_.text).getOrElse("") - private def parseInnings(innings: NodeSeq): List[Innings] = + private def parseInnings(innings: NodeSeq, teams: List[Team]): List[Innings] = innings .map { singleInnings => val inningsOrder = (singleInnings \ "@order").text.toInt val battingTeam = (singleInnings \ "batting" \ "team" \ "name").text + + val bowlingTeam = teams.find(_.name == (singleInnings \ "bowling" \ "team" \ "name)").text) + Innings( inningsOrder, battingTeam, @@ -96,7 +101,7 @@ object Parser { getStatistic(singleInnings, "declared") == "true", getStatistic(singleInnings, "forefeited") == "true", inningsDescription(inningsOrder, battingTeam), - parseInningsBatters(singleInnings \ "batting" \ "batter"), + parseInningsBatters(singleInnings \ "batting" \ "batter", bowlingTeam), parseInningsBowlers(singleInnings \ "bowling" \ "bowler"), parseInningsWickets(singleInnings \ "fallenWicket"), getStatistic(singleInnings, "extra-byes").toInt, @@ -110,7 +115,7 @@ object Parser { .toList .sortBy(_.order) - private def parseInningsBatters(batters: NodeSeq): List[InningsBatter] = + private def parseInningsBatters(batters: NodeSeq, bowlingTeam: Option[Team]): List[InningsBatter] = batters .map { batter => InningsBatter( From 4081b7b91c8d72c9a52cdca87ac5df5a78c6dbbc Mon Sep 17 00:00:00 2001 From: Ioanna Kokkini Date: Wed, 11 Sep 2024 16:55:51 +0100 Subject: [PATCH 5/8] Get catcher and bowler ids --- .../feed/cricketPaDeserialisation.scala | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/sport/app/cricket/feed/cricketPaDeserialisation.scala b/sport/app/cricket/feed/cricketPaDeserialisation.scala index 60e89e86909c..61d63c4251ce 100644 --- a/sport/app/cricket/feed/cricketPaDeserialisation.scala +++ b/sport/app/cricket/feed/cricketPaDeserialisation.scala @@ -115,9 +115,32 @@ object Parser { .toList .sortBy(_.order) - private def parseInningsBatters(batters: NodeSeq, bowlingTeam: Option[Team]): List[InningsBatter] = +// 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, "")}") +// } + + 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, @@ -127,12 +150,14 @@ object Parser { getStatistic(batter, "sixes") toInt, (batter \ "status").text == "batted", (batter \ "dismissal" \ "description").text, +// descriptionWithUniqueNames(bowlingTeam, dismissalDescription, catcherId, bowlerId), getStatistic(batter, "on-strike").toInt > 0, getStatistic(batter, "runs-scored").toInt > 0, ) } .toList .sortBy(_.order) + } private def parseInningsBowlers(bowlers: NodeSeq): List[InningsBowler] = bowlers From 4102e0d8abfcb598d6c0085539a1dd6befdaac96 Mon Sep 17 00:00:00 2001 From: Ioanna Kokkini Date: Wed, 11 Sep 2024 17:03:17 +0100 Subject: [PATCH 6/8] Add descriptionWithUniqueNames method --- .../feed/cricketPaDeserialisation.scala | 26 +-- .../feed/CricketPaDeserialisationTest.scala | 184 ++++++++++++++++++ 2 files changed, 197 insertions(+), 13 deletions(-) create mode 100644 sport/test/cricket/feed/CricketPaDeserialisationTest.scala diff --git a/sport/app/cricket/feed/cricketPaDeserialisation.scala b/sport/app/cricket/feed/cricketPaDeserialisation.scala index 61d63c4251ce..59daea4edd6d 100644 --- a/sport/app/cricket/feed/cricketPaDeserialisation.scala +++ b/sport/app/cricket/feed/cricketPaDeserialisation.scala @@ -115,19 +115,19 @@ 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, "")}") -// } + 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, "")}") + } private def parseInningsBatters(batters: NodeSeq, bowlingTeam: Option[Team]): List[InningsBatter] = { diff --git a/sport/test/cricket/feed/CricketPaDeserialisationTest.scala b/sport/test/cricket/feed/CricketPaDeserialisationTest.scala new file mode 100644 index 000000000000..c8a391d693c3 --- /dev/null +++ b/sport/test/cricket/feed/CricketPaDeserialisationTest.scala @@ -0,0 +1,184 @@ +package cricket.feed + +import conf.cricketPa.Parser.descriptionWithUniqueNames +import cricketModel.{Player, Team} +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +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" + } +} From 12a10ae40e0291d465770b891549a00ab49fb487 Mon Sep 17 00:00:00 2001 From: Ioanna Kokkini Date: Wed, 11 Sep 2024 17:14:47 +0100 Subject: [PATCH 7/8] Call descriptionWithUniqueNames --- sport/app/cricket/feed/cricketPaDeserialisation.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sport/app/cricket/feed/cricketPaDeserialisation.scala b/sport/app/cricket/feed/cricketPaDeserialisation.scala index 59daea4edd6d..ba7f5999e396 100644 --- a/sport/app/cricket/feed/cricketPaDeserialisation.scala +++ b/sport/app/cricket/feed/cricketPaDeserialisation.scala @@ -149,8 +149,7 @@ object Parser { getStatistic(batter, "fours") toInt, getStatistic(batter, "sixes") toInt, (batter \ "status").text == "batted", - (batter \ "dismissal" \ "description").text, -// descriptionWithUniqueNames(bowlingTeam, dismissalDescription, catcherId, bowlerId), + descriptionWithUniqueNames(bowlingTeam, dismissalDescription, catcherId, bowlerId), getStatistic(batter, "on-strike").toInt > 0, getStatistic(batter, "runs-scored").toInt > 0, ) From c69455662c73a5a672b49837e73db8fc65982ca4 Mon Sep 17 00:00:00 2001 From: Ioanna Kokkini Date: Tue, 24 Sep 2024 12:05:31 +0100 Subject: [PATCH 8/8] 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 4e882b3c2de1..5712a6b096ba 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 ba7f5999e396..70877c54e8da 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 c8a391d693c3..c4b3df8feece 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" +@DoNotDiscover 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 "" + + } } }