Skip to content

Commit

Permalink
Substitute cricket match description for certain dismissal types
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
ioannakok and rtyley committed Sep 24, 2024
1 parent 12a10ae commit 19c1bbf
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 211 deletions.
6 changes: 5 additions & 1 deletion sport/app/cricket/feed/cricketModel.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
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)

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]
Expand Down
83 changes: 50 additions & 33 deletions sport/app/cricket/feed/cricketPaDeserialisation.scala
Original file line number Diff line number Diff line change
@@ -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._

Expand Down Expand Up @@ -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("")
Expand All @@ -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,
Expand All @@ -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 &amp; 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,
Expand All @@ -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,
)
Expand Down
207 changes: 30 additions & 177 deletions sport/test/cricket/feed/CricketPaDeserialisationTest.scala
Original file line number Diff line number Diff line change
@@ -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 ""

}
}
}

0 comments on commit 19c1bbf

Please sign in to comment.