Skip to content

Commit

Permalink
FIX: proper support for 2024 sprint format (#570)
Browse files Browse the repository at this point in the history
(cherry picked from commit ac37e23)
  • Loading branch information
theOehrly committed Apr 17, 2024
1 parent e0b845e commit 84f2a0d
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 35 deletions.
142 changes: 110 additions & 32 deletions fastf1/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
- ``EventFormat`` | :class:`str` |
The format of the event. One of 'conventional', 'sprint',
'sprint_shootout', 'testing'.
'sprint_shootout', 'sprint_qualifying', 'testing'. See :ref:`event-formats`
- ``Session*`` | :class:`str` |
The name of the session. One of 'Practice 1', 'Practice 2', 'Practice 3',
Expand Down Expand Up @@ -72,7 +72,7 @@
.................
FastF1 provides its own event schedule for the 2018 season and all later
seasons. The schedule for the all seasons before 2018 is built using data from
seasons. The schedule for all seasons before 2018 is built using data from
the Ergast API. Only limited data is available for these seasons. Usage of the
Ergast API can be enforced for all seasons by setting ``backend='ergast'``,
in which case the same limitations apply for the more recent seasons too.
Expand All @@ -90,14 +90,39 @@
supported if usage of the Ergast API is enforced.
Event Schedule
..............
.. _event-formats:
- 'conventional': Practice 1, Practice 2, Practice 3, Qualifying, Race
- 'sprint': Practice 1, Qualifying, Practice 2, Sprint, Race
- 'sprint_shootout': Practice 1, Qualifying, Sprint Shootout, Sprint, Race
- 'testing': no fixed session order; usually three practice sessions on
three separate days
Event Formats
.............
- **'conventional': Practice 1, Practice 2, Practice 3, Qualifying, Race**
- **'sprint': Practice 1, Qualifying, Practice 2, Sprint, Race**
This is the original sprint format that was used in some races in 2021 and
2022. The Qualifying on friday set the grid for the Sprint on saturday.
The results from the Sprint set the grid for the Race on sunday.
- **'sprint_shootout': Practice 1, Qualifying, Sprint Shootout, Sprint, Race**
This format was used in 2023. The Qualifying on friday sets the grid for the
main Race on sunday. The Sprint Shootout on saturday is held in similar
fashion to a normal Qualifying session and sets the grid for the Sprint that
takes place on saturday as well.
- **'sprint_qualifying': Practice 1, Sprint Qualifying, Sprint, Qualifying,
Race**
This format is used starting from 2024. In general, it is similar to the
previous 'sprint_shootout' format, but the order of the sessions was changed
and 'Sprint Shootout' is renamed to 'Sprint Qualifying'. This means that
the Sprint Qualifying on friday is held in similar fashion to a normal
Qualifying and sets the grid for the Sprint on saturday. The Qualifying later
on saturday then sets the grid for the race on Sunday.
- **'testing': no fixed session order**
usually three practice sessions on three separate days
.. _SessionIdentifier:
Expand All @@ -109,16 +134,22 @@
identifier to differentiate between the various sessions of one event.
This identifier can currently be one of the following:
- session name abbreviation: ``'FP1', 'FP2', 'FP3', 'Q', 'S', 'SS', 'R'``
- session name abbreviation: ``'FP1', 'FP2', 'FP3', 'Q', 'S', 'SS', 'SQ',
'R'``
- full session name: ``'Practice 1', 'Practice 2',
'Practice 3', 'Sprint', 'Sprint Shootout', 'Qualifying', 'Race'``;
'Practice 3', 'Sprint', 'Sprint Shootout', 'Sprint Qualifying',
'Qualifying', 'Race'``;
provided names will be normalized, so that the name is
case-insensitive
- number of the session: ``1, 2, 3, 4, 5``
Note that 'Sprint Qualifying' is now always called 'Sprint'.
The event name will silently be corrected if you use 'Sprint Qualifying'/'SQ'
instead of 'Sprint'/'S'.
Note that the old ``'sprint'`` event format from 2021 and 2022 originally used
the name 'Sprint Qualifying' before renaming these sessions to just 'Sprint'.
The official schedule for 2021 now lists all these sessions as 'Sprint' and
FastF1 will therefore return all these session as 'Sprint'. When querying for a
specific session, FastF1 will also accept the 'Sprint Qualifying'/'SQ'
identifier instead of only 'Sprint'/'S' for backwards compatibility.
Functions for accessing schedule data
-------------------------------------
Expand Down Expand Up @@ -612,7 +643,7 @@ def _get_schedule_ff1(year):
@soft_exceptions("F1 API schedule",
"Failed to load schedule from F1 API backend!",
_logger)
def _get_schedule_from_f1_timing(year):
def _get_schedule_from_f1_timing(year: int):
# create an event schedule using data from the F1 API
response = fastf1._api.season_schedule(f'/static/{year}/')
data = collections.defaultdict(list)
Expand All @@ -633,20 +664,37 @@ def _get_schedule_from_f1_timing(year):
# number of events, usually 3 for testing, 5 for race weekends
# in special cases there are additional unrelated events

if (n_events >= 4) and ('Sprint' in sessions[3]['Name']):
if 'test' in event['Name'].lower():
data['EventFormat'].append('testing')
data['RoundNumber'].append(0)

elif year <= 2020:
data['EventFormat'].append('conventional')
data['RoundNumber'].append(event['Number'])

elif year in (2021, 2022):
if sessions[3]['Name'] == 'Sprint Qualifying':
# fix for 2021 where Sprint was called Sprint Qualifying
sessions[3]['Name'] = 'Sprint'

if sessions[3]['Name'] == 'Sprint':
data['EventFormat'].append('sprint')
else:
data['EventFormat'].append('conventional')
data['RoundNumber'].append(event['Number'])

elif year == 2023:
if sessions[2]['Name'] == 'Sprint Shootout':
data['EventFormat'].append('sprint_shootout')
else:
data['EventFormat'].append('sprint')
data['EventFormat'].append('conventional')
data['RoundNumber'].append(event['Number'])
elif 'test' in event['Name'].lower():
data['EventFormat'].append('testing')
data['RoundNumber'].append(0)
else:
data['EventFormat'].append('conventional')

elif year >= 2024:
if sessions[1]['Name'] == 'Sprint Qualifying':
data['EventFormat'].append('sprint_qualifying')
else:
data['EventFormat'].append('conventional')
data['RoundNumber'].append(event['Number'])

data['F1ApiSupport'].append(True)
Expand Down Expand Up @@ -707,37 +755,59 @@ def _get_schedule_from_ergast(year) -> "EventSchedule":

if 'Sprint' in rnd:
# Ergast doesn't support sprint shootout format yet
data['EventFormat'].append(
"sprint_shootout" if year == 2023 else "sprint")
data['Session1'].append('Practice 1')
if year in (2021, 2022):
_format = 'sprint'
session_names = ['Practice 1', 'Qualifying', 'Practice 2',
'Sprint', 'Race']
elif year == 2023:
_format = 'sprint_shootout'
session_names = ['Practice 1', 'Qualifying', 'Sprint Shootout',
'Sprint', 'Race']
else:
_format = 'sprint_qualifying'
session_names = ['Practice 1', 'Sprint Qualifying', 'Sprint',
'Qualifying', 'Race']

data['EventFormat'].append(_format)

data['Session1'].append(session_names[0])
data['Session1DateUtc'].append(
date.floor('D') - pd.Timedelta(days=2))
data['Session2'].append('Qualifying')

data['Session2'].append(session_names[1])
data['Session2DateUtc'].append(
date.floor('D') - pd.Timedelta(days=2))
data['Session3'].append(
'Sprint Shootout' if year == 2023 else 'Practice 2')

data['Session3'].append(session_names[2])
data['Session3DateUtc'].append(
date.floor('D') - pd.Timedelta(days=1))
data['Session4'].append('Sprint')

data['Session4'].append(session_names[3])
data['Session4DateUtc'].append(
date.floor('D') - pd.Timedelta(days=1))
data['Session5'].append('Race')

data['Session5'].append(session_names[4])
data['Session5DateUtc'].append(date)

else:
data['EventFormat'].append("conventional")

data['Session1'].append('Practice 1')
data['Session1DateUtc'].append(
date.floor('D') - pd.Timedelta(days=2))

data['Session2'].append('Practice 2')
data['Session2DateUtc'].append(
date.floor('D') - pd.Timedelta(days=2))

data['Session3'].append('Practice 3')
data['Session3DateUtc'].append(
date.floor('D') - pd.Timedelta(days=1))

data['Session4'].append('Qualifying')
data['Session4DateUtc'].append(
date.floor('D') - pd.Timedelta(days=1))

data['Session5'].append('Race')
data['Session5DateUtc'].append(date)

Expand Down Expand Up @@ -987,8 +1057,12 @@ def get_session_name(self, identifier) -> str:
except KeyError:
raise ValueError(f"Invalid session type '{identifier}'")

# 'Sprint' is called 'Sprint Qualifying' only in 2021
if session_name == 'Sprint Qualifying':
# 'Sprint' was originally called 'Sprint Qualifying' only in the
# old 'sprint' event format and renamed later; support the old
# name for backwards compatibility by silently correcting to the
# new name
if ((session_name == 'Sprint Qualifying')
and (self.year in (2021, 2022))):
session_name = 'Sprint'

if session_name not in self.values:
Expand Down Expand Up @@ -1083,6 +1157,10 @@ def get_sprint_shootout(self) -> "Session":
"""Return the sprint shootout session."""
return self.get_session('Sprint Shootout')

def get_sprint_qualifying(self) -> "Session":
"""Return the sprint qualifying session."""
return self.get_session('Sprint Qualifying')

def get_practice(self, number: int) -> "Session":
"""Return the specified practice session.
Args:
Expand Down
26 changes: 23 additions & 3 deletions fastf1/tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,16 @@ def test_event_is_testing(backend, no_testing_support):

@pytest.mark.parametrize('backend', ['fastf1', 'f1timing', 'ergast'])
def test_event_get_session_name(backend):
# conventional event before sprint era
event = fastf1.get_event(2019, 1, backend=backend)
assert event.EventFormat == 'conventional'
assert event.get_session_name(3) == 'Practice 3'
assert event.get_session_name('Q') == 'Qualifying'
assert event.get_session_name('praCtice 1') == 'Practice 1'

# conventional event from sprint era
event = fastf1.get_event(2021, 1, backend=backend)
assert event.EventFormat == 'conventional'
assert event.get_session_name(3) == 'Practice 3'
assert event.get_session_name('Q') == 'Qualifying'
assert event.get_session_name('praCtice 1') == 'Practice 1'
Expand All @@ -171,15 +180,26 @@ def test_event_get_session_name(backend):

# Sprint Shootout format introduced for 2023
event = fastf1.get_event(2023, 4, backend=backend)
print(event)
assert event.year == 2023
assert event.EventFormat == 'sprint_shootout'
assert event.get_session_name('SS') == 'Sprint Shootout'
assert event.get_session_name('SQ') == 'Sprint'
assert event.get_session_name('S') == 'Sprint'
assert event.get_session_name('Sprint Shootout') == 'Sprint Shootout'
assert event.get_session_name('Sprint') == 'Sprint'
assert event.get_session_name('Sprint Qualifying') == 'Sprint'

# Sprint Qualifying format introduced for 2024
if ((backend == 'f1timing')
and (datetime.datetime.now() < datetime.datetime(2024, 4, 21))):
# disables this test until the data should be available
# TODO: remove early exit at any time after 2024/04/21
return
event = fastf1.get_event(2024, 5, backend=backend)
assert event.year == 2024
assert event.EventFormat == 'sprint_qualifying'
assert event.get_session_name('SQ') == 'Sprint Qualifying'
assert event.get_session_name('S') == 'Sprint'
assert event.get_session_name('Sprint Qualifying') == 'Sprint Qualifying'
assert event.get_session_name('Sprint') == 'Sprint'


@pytest.mark.parametrize(
Expand Down

0 comments on commit 84f2a0d

Please sign in to comment.