diff --git a/fastf1/_api.py b/fastf1/_api.py index 535003a37..7676c53cc 100644 --- a/fastf1/_api.py +++ b/fastf1/_api.py @@ -892,10 +892,10 @@ def timing_app_data(path, response=None, livedata=None): row = entry[1] for driver_number in row['Lines']: if update := recursive_dict_get(row, 'Lines', driver_number, 'Stints'): - for stint_number, stint in enumerate(update): - if isinstance(update, dict): - stint_number = int(stint) - stint = update[stint] + if isinstance(update, list): + update = {str(i): s for i, s in enumerate(update)} + for stint_number, stint in update.items(): + stint_number = int(stint_number) for key in data: if key in stint: val = stint[key] diff --git a/fastf1/tests/test_api.py b/fastf1/tests/test_api.py index 8c3c78728..ee170db11 100644 --- a/fastf1/tests/test_api.py +++ b/fastf1/tests/test_api.py @@ -266,6 +266,68 @@ def test_driver_list_contains_support_race(caplog): _, _, warn_message = caplog.record_tuples[0] assert warn_message.startswith("Skipping delayed declaration of driver") +def test_timing_app_data_legacy_list_format(): + """Stints in list format (legacy API, ~2018-2019) must be parsed correctly. + + Historic races return stints as a plain list instead of a dict with + string indices. Previously, this caused stints to be silently dropped. + See issue #863. + """ + response = [ + [ + "00:05:00:000", + { + "Lines": { + "1": { + "Stints": [ + {"Compound": "SOFT", "New": "true", + "TotalLaps": 0, "StartLaps": 0}, + {"Compound": "HARD", "New": "false", + "TotalLaps": 10, "StartLaps": 10}, + ] + } + } + } + ] + ] + + data = fastf1._api.timing_app_data('api/path', response=response) + + assert isinstance(data, pd.DataFrame) + assert len(data) == 2 # both stints must be present + assert list(data['Stint']) == [0, 1] + assert list(data['Compound']) == ['SOFT', 'HARD'] + assert list(data['Driver']) == ['1', '1'] + + +def test_timing_app_data_modern_dict_format(): + """Stints in dict format (modern API) must continue to work correctly.""" + response = [ + [ + "00:05:00:000", + { + "Lines": { + "1": { + "Stints": { + "0": {"Compound": "MEDIUM", "New": "true", + "TotalLaps": 0, "StartLaps": 0}, + "1": {"Compound": "HARD", "New": "false", + "TotalLaps": 20, "StartLaps": 20}, + } + } + } + } + ] + ] + + data = fastf1._api.timing_app_data('api/path', response=response) + + assert isinstance(data, pd.DataFrame) + assert len(data) == 2 # both stints must be present + assert list(data['Stint']) == [0, 1] + assert list(data['Compound']) == ['MEDIUM', 'HARD'] + assert list(data['Driver']) == ['1', '1'] + @pytest.mark.f1telapi def test_deleted_laps_not_marked_personal_best(): # see issue #165