mirror of
https://github.com/Horizontal42/f1-telemetry.git
synced 2026-11-10 15:22:03 +03:00
rename: sort by game — races/F1 22|ACC/<track>/<session>/
read_metadata now returns game_raw; _game_folder maps it to 'F1 22' or 'ACC'; dest_dir gains a game subfolder between races_dir and track
This commit is contained in:
parent
c1ee2f82e4
commit
15bb552166
6 changed files with 55 additions and 33 deletions
|
|
@ -19,7 +19,7 @@ telemetry/
|
||||||
report_compare.py Two+ laps side by side, per-corner and cumulative delta.
|
report_compare.py Two+ laps side by side, per-corner and cumulative delta.
|
||||||
report_race.py A folder of laps: lap times (with position), stints, wear, thermals, ERS. Dedups duplicate lap numbers.
|
report_race.py A folder of laps: lap times (with position), stints, wear, thermals, ERS. Dedups duplicate lap numbers.
|
||||||
report_profile.py A folder of laps: cheap cross-lap corner tendencies (median per corner, per dominant compound) + auto-picked deep-dive laps. No trace.
|
report_profile.py A folder of laps: cheap cross-lap corner tendencies (median per corner, per dominant compound) + auto-picked deep-dive laps. No trace.
|
||||||
rename.py Insert session type + lap number (P1_L7) into filenames; with races_dir, sort into races/<track>/<session>/ and recurse subdirectories.
|
rename.py Insert session type + lap number (P1_L7) into filenames; with races_dir, sort into races/<game>/<track>/<session>/ and recurse subdirectories.
|
||||||
gui.py tkinter window with two tabs (F1 22 / ACC); per-mode browse memory; runs generation/rename on a worker thread. Warns (non-blocking) when the detected game doesn't match the active tab.
|
gui.py tkinter window with two tabs (F1 22 / ACC); per-mode browse memory; runs generation/rename on a worker thread. Warns (non-blocking) when the detected game doesn't match the active tab.
|
||||||
prompts/ LLM analysis prompts, ru/ and en/ subdirectories, one file per mode. Auto-appended to every report.
|
prompts/ LLM analysis prompts, ru/ and en/ subdirectories, one file per mode. Auto-appended to every report.
|
||||||
ACC-specific overrides live in prompts/<lang>/acc/<mode>.md; missing files fall back to the F1 prompt.
|
ACC-specific overrides live in prompts/<lang>/acc/<mode>.md; missing files fall back to the F1 prompt.
|
||||||
|
|
@ -72,7 +72,7 @@ If a future track needs a different corner grouping, `merge_gap_for` already ada
|
||||||
|
|
||||||
**The setup row has a trailing comma** (an empty `FuelLoad` tail field in some exports). The parser drops trailing empties so the setup dict has no blank-string key.
|
**The setup row has a trailing comma** (an empty `FuelLoad` tail field in some exports). The parser drops trailing empties so the setup dict has no blank-string key.
|
||||||
|
|
||||||
**Rename with `races_dir` sorts into folders and walks subdirectories.** `rename_unprocessed(targets, races_dir)` when `races_dir` is set: (1) recursively scans all subdirectories via `os.walk`, (2) reads metadata from each CSV (`read_metadata` → lap, event, track), (3) inserts tokens if absent, (4) creates `races/<track_safe>/Practice|Qualifying|Race|Sprint/` and moves the file there, (5) even already-renamed files are moved if they're in the wrong folder, (6) only skips files that are already in the right place with the right name. Without `races_dir` it works as before — renames in-place in the original folder.
|
**Rename with `races_dir` sorts into folders and walks subdirectories.** `rename_unprocessed(targets, races_dir)` when `races_dir` is set: (1) recursively scans all subdirectories via `os.walk`, (2) reads metadata from each CSV (`read_metadata` → lap, event, track, game), (3) inserts tokens if absent, (4) creates `races/F1 22|ACC/<track_safe>/Practice|Qualifying|Race|Sprint/` and moves the file there, (5) even already-renamed files are moved if they're in the wrong folder, (6) only skips files that are already in the right place with the right name. Without `races_dir` it works as before — renames in-place in the original folder.
|
||||||
|
|
||||||
**Reports go next to the input, not next to the program.** `report_path` derives the `reports/` directory from the *input CSV's* folder. `race` mode puts the report inside the race session folder (`<race_dir>/reports/`). Moving the tool does not move where reports land.
|
**Reports go next to the input, not next to the program.** `report_path` derives the `reports/` directory from the *input CSV's* folder. `race` mode puts the report inside the race session folder (`<race_dir>/reports/`). Moving the tool does not move where reports land.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ telemetry/
|
||||||
report_compare.py Два+ круга рядом, дельта по поворотам и накопленная.
|
report_compare.py Два+ круга рядом, дельта по поворотам и накопленная.
|
||||||
report_race.py Папка кругов: лаптаймы (с позицией), стинты, износ, тепло, ERS. Дедуп дублей номеров кругов.
|
report_race.py Папка кругов: лаптаймы (с позицией), стинты, износ, тепло, ERS. Дедуп дублей номеров кругов.
|
||||||
report_profile.py Папка кругов: дешёвые тенденции по поворотам (медиана на поворот, по доминирующему составу) + авто-выбор кругов для разбора. Без трассы.
|
report_profile.py Папка кругов: дешёвые тенденции по поворотам (медиана на поворот, по доминирующему составу) + авто-выбор кругов для разбора. Без трассы.
|
||||||
rename.py Вставка типа сессии + номера круга (P1_L7) в имя; с races_dir — сортировка в races/<track>/<session>/ и рекурсия по подпапкам.
|
rename.py Вставка типа сессии + номера круга (P1_L7) в имя; с races_dir — сортировка в races/<game>/<track>/<session>/ и рекурсия по подпапкам.
|
||||||
gui.py Окно tkinter с двумя вкладками (F1 22 / ACC); память последней папки по режиму; генерация/переименование в потоке. Предупреждает (без блокировки), если определённая игра не совпадает с активной вкладкой.
|
gui.py Окно tkinter с двумя вкладками (F1 22 / ACC); память последней папки по режиму; генерация/переименование в потоке. Предупреждает (без блокировки), если определённая игра не совпадает с активной вкладкой.
|
||||||
prompts/ Промпты LLM, подпапки ru/ и en/, по одному файлу на режим. Добавляются в конец каждого отчёта.
|
prompts/ Промпты LLM, подпапки ru/ и en/, по одному файлу на режим. Добавляются в конец каждого отчёта.
|
||||||
Переопределения для ACC лежат в prompts/<lang>/acc/<mode>.md; при отсутствии — откат к F1-промпту.
|
Переопределения для ACC лежат в prompts/<lang>/acc/<mode>.md; при отсутствии — откат к F1-промпту.
|
||||||
|
|
@ -72,7 +72,7 @@ tests/ Набор pytest + фикстуры-круги.
|
||||||
|
|
||||||
**Строка сетапа заканчивается лишней запятой** (пустой хвост `FuelLoad` в части экспортов). Парсер отбрасывает пустые хвостовые поля, чтобы в словаре сетапа не было ключа с пустой строкой.
|
**Строка сетапа заканчивается лишней запятой** (пустой хвост `FuelLoad` в части экспортов). Парсер отбрасывает пустые хвостовые поля, чтобы в словаре сетапа не было ключа с пустой строкой.
|
||||||
|
|
||||||
**Rename с `races_dir` распределяет по папкам и рекурсит.** `rename_unprocessed(targets, races_dir)` когда `races_dir` установлен: (1) сканирует всё дерево подпапок (`os.walk`), (2) читает метаданные из CSV (`read_metadata` → lap, event, track), (3) вставляет токены если их нет, (4) создаёт `races/<track_safe>/Practice|Qualifying|Race|Sprint/` и перемещает туда файл, (5) даже уже переименованные файлы переносятся если лежат не там где надо, (6) пропускаются только файлы, которые уже находятся в нужной папке с нужным именем. Без `races_dir` работает как раньше — переименовывает в исходной папке.
|
**Rename с `races_dir` распределяет по папкам и рекурсит.** `rename_unprocessed(targets, races_dir)` когда `races_dir` установлен: (1) сканирует всё дерево подпапок (`os.walk`), (2) читает метаданные из CSV (`read_metadata` → lap, event, track, game), (3) вставляет токены если их нет, (4) создаёт `races/F1 22|ACC/<track_safe>/Practice|Qualifying|Race|Sprint/` и перемещает туда файл, (5) даже уже переименованные файлы переносятся если лежат не там где надо, (6) пропускаются только файлы, которые уже находятся в нужной папке с нужным именем. Без `races_dir` работает как раньше — переименовывает в исходной папке.
|
||||||
|
|
||||||
**Отчёты выходят рядом с входом, а не рядом с программой.** `report_path` выводит папку `reports/` из папки *входного CSV*. Режим `race` кладёт отчёт внутрь папки сессии (`<race_dir>/reports/`). Перемещение инструмента не меняет, куда попадают отчёты.
|
**Отчёты выходят рядом с входом, а не рядом с программой.** `report_path` выводит папку `reports/` из папки *входного CSV*. Режим `race` кладёт отчёт внутрь папки сессии (`<race_dir>/reports/`). Перемещение инструмента не меняет, куда попадают отчёты.
|
||||||
|
|
||||||
|
|
|
||||||
28
README.md
28
README.md
|
|
@ -51,17 +51,23 @@ GT3/GT4 cars have no hybrid system, and ACC does not export the car setup:
|
||||||
```
|
```
|
||||||
Telemetry/
|
Telemetry/
|
||||||
races/
|
races/
|
||||||
Zandvoort/
|
F1 22/
|
||||||
Practice/ <- CSVs go here after renaming
|
Zandvoort/
|
||||||
zandvoort_P1_L7_74.074_ferrari.csv
|
Practice/ <- CSVs go here after renaming
|
||||||
reports/ <- technique/setup/compare reports land here
|
zandvoort_P1_L7_74.074_ferrari.csv
|
||||||
Qualifying/
|
reports/ <- technique/setup/compare reports land here
|
||||||
zandvoort_Q1_L3_74.897_ferrari.csv
|
Qualifying/
|
||||||
reports/
|
zandvoort_Q1_L3_74.897_ferrari.csv
|
||||||
Race/
|
reports/
|
||||||
zandvoort_R_L7_74.152_ferrari.csv
|
Race/
|
||||||
reports/
|
zandvoort_R_L7_74.152_ferrari.csv
|
||||||
Race_race.md <- race mode report
|
reports/
|
||||||
|
Race_race.md <- race mode report
|
||||||
|
ACC/
|
||||||
|
Hungaroring/
|
||||||
|
Practice/
|
||||||
|
hungaroring_P_L1_123.702_ktm_gt4.csv
|
||||||
|
reports/
|
||||||
tool/ <- this repository
|
tool/ <- this repository
|
||||||
telemetry/
|
telemetry/
|
||||||
prompts/
|
prompts/
|
||||||
|
|
|
||||||
28
README.ru.md
28
README.ru.md
|
|
@ -51,17 +51,23 @@ python -m telemetry technique "races/Zandvoort/Практика/zandvoort_P1_L7_
|
||||||
```
|
```
|
||||||
Telemetry/
|
Telemetry/
|
||||||
races/
|
races/
|
||||||
Zandvoort/
|
F1 22/
|
||||||
Практика/ <- CSV-файлы после переименования
|
Zandvoort/
|
||||||
zandvoort_P1_L7_74.074_ferrari.csv
|
Practice/ <- CSV-файлы после переименования
|
||||||
reports/ <- отчёты technique/setup/compare
|
zandvoort_P1_L7_74.074_ferrari.csv
|
||||||
Квалификация/
|
reports/ <- отчёты technique/setup/compare
|
||||||
zandvoort_Q1_L3_74.897_ferrari.csv
|
Qualifying/
|
||||||
reports/
|
zandvoort_Q1_L3_74.897_ferrari.csv
|
||||||
Гонка/
|
reports/
|
||||||
zandvoort_R_L7_74.152_ferrari.csv
|
Race/
|
||||||
reports/
|
zandvoort_R_L7_74.152_ferrari.csv
|
||||||
Гонка_race.md <- отчёт режима race
|
reports/
|
||||||
|
Race_race.md <- отчёт режима race
|
||||||
|
ACC/
|
||||||
|
Hungaroring/
|
||||||
|
Practice/
|
||||||
|
hungaroring_P_L1_123.702_ktm_gt4.csv
|
||||||
|
reports/
|
||||||
tool/ <- этот репозиторий
|
tool/ <- этот репозиторий
|
||||||
telemetry/
|
telemetry/
|
||||||
prompts/
|
prompts/
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,10 @@ def _normalise_event(raw: str) -> str:
|
||||||
return raw.strip()
|
return raw.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _game_folder(game_raw: str) -> str:
|
||||||
|
return 'ACC' if game_raw.upper().startswith('ACC') else 'F1 22'
|
||||||
|
|
||||||
|
|
||||||
def _session_folder(event: str) -> str:
|
def _session_folder(event: str) -> str:
|
||||||
# event is already the canonical token at call sites inside this module.
|
# event is already the canonical token at call sites inside this module.
|
||||||
e = event.upper()
|
e = event.upper()
|
||||||
|
|
@ -123,8 +127,8 @@ def read_session_type(path: str) -> str:
|
||||||
return session.get('event', '').strip()
|
return session.get('event', '').strip()
|
||||||
|
|
||||||
|
|
||||||
def read_metadata(path: str) -> tuple[int, str, str]:
|
def read_metadata(path: str) -> tuple[int, str, str, str]:
|
||||||
"""Returns (lap_number, event_type, track_name)."""
|
"""Returns (lap_number, event_type, track_name, game_raw)."""
|
||||||
with open(path, encoding='utf-8') as f:
|
with open(path, encoding='utf-8') as f:
|
||||||
lines = [f.readline() for _ in range(5)]
|
lines = [f.readline() for _ in range(5)]
|
||||||
sess_keys = _parse_header_row(lines[1])
|
sess_keys = _parse_header_row(lines[1])
|
||||||
|
|
@ -137,6 +141,7 @@ def read_metadata(path: str) -> tuple[int, str, str]:
|
||||||
int(float(track['Lap'])),
|
int(float(track['Lap'])),
|
||||||
session.get('event', '').strip(),
|
session.get('event', '').strip(),
|
||||||
session.get('track', '').strip(),
|
session.get('track', '').strip(),
|
||||||
|
session.get('Game', '').strip(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -181,7 +186,7 @@ def rename_unprocessed(targets: list[str], races_dir: str | None = None) -> list
|
||||||
results.append(RenameResult(name, None, 'skipped', 'already processed'))
|
results.append(RenameResult(name, None, 'skipped', 'already processed'))
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
lap, session_type, _ = read_metadata(p)
|
lap, session_type, _, _game = read_metadata(p)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
results.append(RenameResult(name, None, 'error', str(e)))
|
results.append(RenameResult(name, None, 'error', str(e)))
|
||||||
continue
|
continue
|
||||||
|
|
@ -193,7 +198,7 @@ def rename_unprocessed(targets: list[str], races_dir: str | None = None) -> list
|
||||||
dest = os.path.join(os.path.dirname(p), new_name)
|
dest = os.path.join(os.path.dirname(p), new_name)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
lap, session_type, track_name = read_metadata(p)
|
lap, session_type, track_name, game_raw = read_metadata(p)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
results.append(RenameResult(name, None, 'error', str(e)))
|
results.append(RenameResult(name, None, 'error', str(e)))
|
||||||
continue
|
continue
|
||||||
|
|
@ -202,7 +207,8 @@ def rename_unprocessed(targets: list[str], races_dir: str | None = None) -> list
|
||||||
continue
|
continue
|
||||||
session_type = _normalise_event(session_type)
|
session_type = _normalise_event(session_type)
|
||||||
new_name = name if _is_processed(stem) else insert_tokens(name, session_type, lap)
|
new_name = name if _is_processed(stem) else insert_tokens(name, session_type, lap)
|
||||||
dest_dir = os.path.join(races_dir, _safe_dirname(track_name), _session_folder(session_type))
|
dest_dir = os.path.join(races_dir, _game_folder(game_raw),
|
||||||
|
_safe_dirname(track_name), _session_folder(session_type))
|
||||||
dest = os.path.join(dest_dir, new_name)
|
dest = os.path.join(dest_dir, new_name)
|
||||||
if os.path.abspath(p) == os.path.abspath(dest):
|
if os.path.abspath(p) == os.path.abspath(dest):
|
||||||
results.append(RenameResult(name, None, 'skipped', 'already in place'))
|
results.append(RenameResult(name, None, 'skipped', 'already in place'))
|
||||||
|
|
|
||||||
|
|
@ -85,10 +85,11 @@ def test_read_session_type_sample(sample_path):
|
||||||
# --- read_metadata ---
|
# --- read_metadata ---
|
||||||
|
|
||||||
def test_read_metadata_sample(sample_path):
|
def test_read_metadata_sample(sample_path):
|
||||||
lap, event, track = read_metadata(sample_path)
|
lap, event, track, game = read_metadata(sample_path)
|
||||||
assert lap == 7
|
assert lap == 7
|
||||||
assert event == 'P1'
|
assert event == 'P1'
|
||||||
assert track != ''
|
assert track != ''
|
||||||
|
assert 'F1' in game or game != ''
|
||||||
|
|
||||||
|
|
||||||
# --- rename_unprocessed ---
|
# --- rename_unprocessed ---
|
||||||
|
|
@ -141,6 +142,7 @@ def test_rename_unprocessed_moves_to_races_dir(tmp_path, sample_path):
|
||||||
assert r.new is not None and 'P1' in r.new and 'L7' in r.new
|
assert r.new is not None and 'P1' in r.new and 'L7' in r.new
|
||||||
assert os.path.exists(r.new)
|
assert os.path.exists(r.new)
|
||||||
assert r.new.startswith(races_dir)
|
assert r.new.startswith(races_dir)
|
||||||
|
assert 'F1 22' in r.new
|
||||||
assert 'Practice' in r.new
|
assert 'Practice' in r.new
|
||||||
assert not os.path.exists(src)
|
assert not os.path.exists(src)
|
||||||
|
|
||||||
|
|
@ -154,6 +156,7 @@ def test_rename_unprocessed_moves_already_renamed(tmp_path, sample_path):
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
r = results[0]
|
r = results[0]
|
||||||
assert r.status == 'renamed'
|
assert r.status == 'renamed'
|
||||||
|
assert 'F1 22' in r.new
|
||||||
assert 'Practice' in r.new
|
assert 'Practice' in r.new
|
||||||
assert os.path.exists(r.new)
|
assert os.path.exists(r.new)
|
||||||
assert not os.path.exists(src)
|
assert not os.path.exists(src)
|
||||||
|
|
@ -200,7 +203,7 @@ def test_acc_rename_inserts_compact_token(tmp_path, acc_path):
|
||||||
|
|
||||||
|
|
||||||
def test_acc_rename_lands_in_practice_folder(tmp_path, acc_path):
|
def test_acc_rename_lands_in_practice_folder(tmp_path, acc_path):
|
||||||
"""ACC 'Practice' event files into races/<Track>/Practice/, not Other."""
|
"""ACC 'Practice' event files into races/ACC/<Track>/Practice/, not Other."""
|
||||||
src = str(tmp_path / "hungaroring_123.702_ktm_gt4.csv")
|
src = str(tmp_path / "hungaroring_123.702_ktm_gt4.csv")
|
||||||
shutil.copy(acc_path, src)
|
shutil.copy(acc_path, src)
|
||||||
races_dir = str(tmp_path / 'races')
|
races_dir = str(tmp_path / 'races')
|
||||||
|
|
@ -209,6 +212,7 @@ def test_acc_rename_lands_in_practice_folder(tmp_path, acc_path):
|
||||||
r = results[0]
|
r = results[0]
|
||||||
assert r.status == 'renamed'
|
assert r.status == 'renamed'
|
||||||
assert r.new is not None
|
assert r.new is not None
|
||||||
|
assert 'ACC' in r.new
|
||||||
assert 'Practice' in r.new
|
assert 'Practice' in r.new
|
||||||
assert 'Other' not in r.new
|
assert 'Other' not in r.new
|
||||||
assert os.path.exists(r.new)
|
assert os.path.exists(r.new)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue