Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 29 additions & 24 deletions docs/03-dev-howtos/23-sport-tournaments.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
Sport Tournaments
=================
# Sport Tournaments

Every now and then sport happens. In fact, it happens fairly frequently. But sometimes (about once a year), BIG sport
happens. BIG sport comes in the form of delightful events such as a [football world cup](https://www.theguardian.com/football/womens-world-cup-2019)
or a [cricket world cup](https://www.theguardian.com/sport/live/2019/jun/05/south-africa-v-india-cricket-world-cup-2019-live).
For these events we often provide live scores, tournament [spider diagrams](https://www.theguardian.com/football/womens-world-cup-2019/overview),
[fixtures tables](https://www.theguardian.com/football/fixtures) and other lovely things. (In fact, we provide some of this
stuff all the time for the various football leagues.)
[fixtures tables](https://www.theguardian.com/football/fixtures) and other lovely things. (In fact, we provide some of this stuff all the time for the various football leagues.)

So the time has come to add a new competition, and you are the bold developer taking up the mantle to deliver automated
sports results across the guardian. How to begin?
So the time has come to add a new competition, and you are the bold developer taking up the mantle to deliver automated
sports results across the guardian. How to begin?

In general the sport routes file is a good place to start to get an idea of how this stuff fits together and what controller
is responsible for the pages you're trying to sort out. We don't tend to remove routes after a competition is finished -
Expand All @@ -27,33 +26,41 @@ out of the config. There's also a PA api explorer and some docs of the various e

More specifically, there's some stuff that you'll often end up doing when sorting out a new tournament.

*Football*
## Football

We've got more football logic than for any of the other sports. You can find all the lovely football stuff we do all year
round by clicking through the nav on the [football section](https://www.theguardian.com/football/live). Tasks you'll need to do:

- Add the competition to Competitions.scala
- Add the tournament badge to static/public/images/badges
- Add the competition to `tableOrder` in LeagueTableController
- Update CompetitionStages.scala with the different matches of the tournament (this is for the spider diagram). NOTE:
ordering is important here, make sure you've got the right teams playing each other. Here's an example https://github.com/guardian/frontend/pull/21396

- Add the competition to Competitions.scala
- Add the tournament badge to static/public/images/badges
- Add the competition to `tableOrder` in LeagueTableController
- Update CompetitionStages.scala with the different matches of the tournament (this is for the spider diagram). NOTE:
ordering is important here, make sure you've got the right teams playing each other. Here's an example https://github.com/guardian/frontend/pull/21396

After doing the above...

- Verify that data is being successfully fetched from PA - you should see 'refreshing results for <competition pa id>'
in the logs - check for any errors. The logic for fetching data is in agent.scala. It may be that we need to ask PA to
switch on the data feed for that competition.
- Check the spider diagram looks right. We do some string matching in knockoutSpider.scala.html to pick up the 3rd place
play offs. If PA decides to use a slightly different name for that round then our string match will need to be updated
to get the matches showing up in the right place on the diagram.
- Check any team name cleaning is being done - see https://github.com/guardian/frontend/pull/21542, https://github.com/guardian/frontend/pull/21609
and https://github.com/guardian/frontend/pull/21500
- Verify that data is being successfully fetched from PA - you should see 'refreshing results for <competition pa id>'
in the logs - check for any errors. The logic for fetching data is in agent.scala. It may be that we need to ask PA to
switch on the data feed for that competition.
- Check the spider diagram looks right. We do some string matching in knockoutSpider.scala.html to pick up the 3rd place
play offs. If PA decides to use a slightly different name for that round then our string match will need to be updated
to get the matches showing up in the right place on the diagram.
- Check any team name cleaning is being done - see https://github.com/guardian/frontend/pull/21542, https://github.com/guardian/frontend/pull/21609
and https://github.com/guardian/frontend/pull/21500

As of writing this the email for contacting PA customer.services@pressassociation.com

### Gotchas

#### Why is the spider diagram not rendering?
Typically, PA will populate knockout fixtures during the competition. This means that the spider diagram won't render until PA has populated the fixtures.
If this is the case, it is necessary to request that PA supply placeholder teams, sometimes referred to in code as "ghost data" (eg `group A winner v group B runner up` etc)
for the knockout fixtures to get the spider diagram rendering before the tournament starts. This is something that can be requested from PA customer services.
NB - Once the placeholder teams are confirmed, the original placeholder will be removed, and a new fixture ID including the two confirmed teams will be created.
This is OK as the spider diagram currently relies on date, rather than fixture ID, to place the matches in the right place on the diagram.

## Other sports

*Other sports*
[Football isn't the only sport](https://elt.oup.com/elt/students/englishfile/dyslexicfriendlytexts/a002209_english_file_3e_beginner_reading_text_file_7a.pdf?cc=us&selLanguage=en)
it turns out, who'd have thought it. Cricket also exists! All we offer for cricket is live scores that get injected into
live blogs with the appropriate team tags on them. These are set up in CricketTeams.scala - as of writing this we only
Expand All @@ -63,5 +70,3 @@ for details.

Rugby is also a thing, as of writing this the code is broken due to being designed for an API we no longer have access
to. Watch this space though - it might be getting a makeover (so have a look in PR history for rugby related things!)


Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class LeagueTableController(
"Women's Champions League",
"Europa League",
"World Cup 2026 qualifying",
"World Cup 2026",
"Euro 2024",
"Nations League",
"Women's Nations League",
Expand Down
15 changes: 14 additions & 1 deletion sport/app/football/controllers/WallchartController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,20 @@ class WallchartController(

def renderIndividualGroupTableEmbed(competitionTag: String, groupIds: String): Action[AnyContent] = {
def convertGroupIdToInt(groupLetter: String): Option[Int] = {
val groupIdMap = Map("a" -> 1, "b" -> 2, "c" -> 3, "d" -> 4, "e" -> 5, "f" -> 6, "g" -> 7, "h" -> 8)
val groupIdMap = Map(
"a" -> 1,
"b" -> 2,
"c" -> 3,
"d" -> 4,
"e" -> 5,
"f" -> 6,
"g" -> 7,
"h" -> 8,
"i" -> 9,
"j" -> 10,
"k" -> 11,
"l" -> 12,
)
groupIdMap.get(groupLetter)
}

Expand Down
2 changes: 1 addition & 1 deletion sport/app/football/feed/Competitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ object CompetitionsProvider {
"World Cup 2026",
"Internationals",
showInTeamsList = true,
tableDividers = List(2),
tableDividers = List(2, 3),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we write anywhere by the table the 3rd place rules?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not, no. I don't believe we have this on any of our leagues. It would be a good thing to have.

startDate = Some(LocalDate.of(2025, 12, 2)),
),
Competition(
Expand Down
57 changes: 37 additions & 20 deletions sport/app/football/model/CompetitionStages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -123,39 +123,56 @@ object KnockoutSpider {
*/

val orderings: Map[String, List[ZonedDateTime]] = Map(
// world cup 2022
// world cup 2026
"700" -> List(
// Data from https://en.wikipedia.org/wiki/2022_FIFA_World_Cup_knockout_stage & PA API
// Data from https://en.wikipedia.org/wiki/2026_FIFA_World_Cup_knockout_stage & PA API
// ----
// Rounds of 16
// ----
ZonedDateTime.of(2022, 12, 3, 15, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 16 - Match 49
ZonedDateTime.of(2022, 12, 3, 19, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 16 - Match 50
// Rounds of 32
// ----
ZonedDateTime.of(2022, 12, 5, 15, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 16 - Match 53
ZonedDateTime.of(2022, 12, 5, 19, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 16 - Match 54
ZonedDateTime.of(2026, 6, 28, 20, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 32 - Match 73
ZonedDateTime.of(2026, 6, 29, 21, 30, 0, 0, ZoneId.of("Europe/London")), // Round of 32 - Match 74
ZonedDateTime.of(2026, 6, 30, 2, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 32 - Match 75
ZonedDateTime.of(2026, 6, 29, 18, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 32 - Match 76
ZonedDateTime.of(2026, 6, 30, 22, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 32 - Match 77
ZonedDateTime.of(2026, 6, 30, 18, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 32 - Match 78
ZonedDateTime.of(2026, 7, 1, 2, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 32 - Match 79
ZonedDateTime.of(2026, 7, 1, 17, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 32 - Match 80
ZonedDateTime.of(2026, 7, 2, 1, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 32 - Match 81
ZonedDateTime.of(2026, 7, 1, 21, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 32 - Match 82
ZonedDateTime.of(2026, 7, 3, 0, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 32 - Match 83
ZonedDateTime.of(2026, 7, 2, 20, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 32 - Match 84
ZonedDateTime.of(2026, 7, 3, 4, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 32 - Match 85
ZonedDateTime.of(2026, 7, 3, 23, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 32 - Match 86
ZonedDateTime.of(2026, 7, 4, 2, 30, 0, 0, ZoneId.of("Europe/London")), // Round of 32 - Match 87
ZonedDateTime.of(2026, 7, 3, 19, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 32 - Match 88
// ----
ZonedDateTime.of(2022, 12, 4, 19, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 16 - Match 52
ZonedDateTime.of(2022, 12, 4, 15, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 16 - Match 51
// Rounds of 16
// ----
ZonedDateTime.of(2022, 12, 6, 15, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 16 - Match 55
ZonedDateTime.of(2022, 12, 6, 19, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 16 - Match 56
ZonedDateTime.of(2026, 7, 4, 22, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 16 - Match 89
ZonedDateTime.of(2026, 7, 4, 18, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 16 - Match 90
ZonedDateTime.of(2026, 7, 5, 21, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 16 - Match 91
ZonedDateTime.of(2026, 7, 6, 1, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 16 - Match 92
ZonedDateTime.of(2026, 7, 6, 20, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 16 - Match 93
ZonedDateTime.of(2026, 7, 7, 1, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 16 - Match 94
ZonedDateTime.of(2026, 7, 7, 17, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 16 - Match 95
ZonedDateTime.of(2026, 7, 7, 21, 0, 0, 0, ZoneId.of("Europe/London")), // Round of 16 - Match 96
// ----
// Quarter Finals
// ----
ZonedDateTime.of(2022, 12, 9, 19, 0, 0, 0, ZoneId.of("Europe/London")), // Quarter Finals - Match 57
ZonedDateTime.of(2022, 12, 9, 15, 0, 0, 0, ZoneId.of("Europe/London")), // Quarter Finals - Match 58
ZonedDateTime.of(2022, 12, 10, 19, 0, 0, 0, ZoneId.of("Europe/London")), // Quarter Finals - Match 59
ZonedDateTime.of(2022, 12, 10, 15, 0, 0, 0, ZoneId.of("Europe/London")), // Quarter Finals - Match 60
ZonedDateTime.of(2026, 7, 9, 21, 0, 0, 0, ZoneId.of("Europe/London")), // Quarter Finals - Match 97
ZonedDateTime.of(2026, 7, 10, 20, 0, 0, 0, ZoneId.of("Europe/London")), // Quarter Finals - Match 98
ZonedDateTime.of(2026, 7, 11, 22, 0, 0, 0, ZoneId.of("Europe/London")), // Quarter Finals - Match 99
ZonedDateTime.of(2026, 7, 12, 2, 0, 0, 0, ZoneId.of("Europe/London")), // Quarter Finals - Match 100
// ----
// Semi Finals
// ----
ZonedDateTime.of(2022, 12, 13, 19, 0, 0, 0, ZoneId.of("Europe/London")), // Semi Finals - Match 61
ZonedDateTime.of(2022, 12, 14, 19, 0, 0, 0, ZoneId.of("Europe/London")), // Semi Finals - Match 62
ZonedDateTime.of(2026, 7, 14, 20, 0, 0, 0, ZoneId.of("Europe/London")), // Semi Finals - Match 101
ZonedDateTime.of(2026, 7, 15, 20, 0, 0, 0, ZoneId.of("Europe/London")), // Semi Finals - Match 102
// ----
// Final
// Third-place and Final
// ----
ZonedDateTime.of(2022, 12, 18, 15, 0, 0, 0, ZoneId.of("Europe/London")), // Semi Finals - Match 64
ZonedDateTime.of(2026, 7, 18, 22, 0, 0, 0, ZoneId.of("Europe/London")), // Final - Match 103
ZonedDateTime.of(2026, 7, 19, 20, 0, 0, 0, ZoneId.of("Europe/London")), // Final - Match 104
),
// women world cup 2023
"870" -> List(
Expand Down
Loading
Loading