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_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.
|
||||
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.
|
||||
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.
|
||||
|
|
@ -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.
|
||||
|
||||
**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.
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ telemetry/
|
|||
report_compare.py Два+ круга рядом, дельта по поворотам и накопленная.
|
||||
report_race.py Папка кругов: лаптаймы (с позицией), стинты, износ, тепло, ERS. Дедуп дублей номеров кругов.
|
||||
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); память последней папки по режиму; генерация/переименование в потоке. Предупреждает (без блокировки), если определённая игра не совпадает с активной вкладкой.
|
||||
prompts/ Промпты LLM, подпапки ru/ и en/, по одному файлу на режим. Добавляются в конец каждого отчёта.
|
||||
Переопределения для ACC лежат в prompts/<lang>/acc/<mode>.md; при отсутствии — откат к F1-промпту.
|
||||
|
|
@ -72,7 +72,7 @@ tests/ Набор pytest + фикстуры-круги.
|
|||
|
||||
**Строка сетапа заканчивается лишней запятой** (пустой хвост `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/`). Перемещение инструмента не меняет, куда попадают отчёты.
|
||||
|
||||
|
|
|
|||
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/
|
||||
races/
|
||||
Zandvoort/
|
||||
Practice/ <- CSVs go here after renaming
|
||||
zandvoort_P1_L7_74.074_ferrari.csv
|
||||
reports/ <- technique/setup/compare reports land here
|
||||
Qualifying/
|
||||
zandvoort_Q1_L3_74.897_ferrari.csv
|
||||
reports/
|
||||
Race/
|
||||
zandvoort_R_L7_74.152_ferrari.csv
|
||||
reports/
|
||||
Race_race.md <- race mode report
|
||||
F1 22/
|
||||
Zandvoort/
|
||||
Practice/ <- CSVs go here after renaming
|
||||
zandvoort_P1_L7_74.074_ferrari.csv
|
||||
reports/ <- technique/setup/compare reports land here
|
||||
Qualifying/
|
||||
zandvoort_Q1_L3_74.897_ferrari.csv
|
||||
reports/
|
||||
Race/
|
||||
zandvoort_R_L7_74.152_ferrari.csv
|
||||
reports/
|
||||
Race_race.md <- race mode report
|
||||
ACC/
|
||||
Hungaroring/
|
||||
Practice/
|
||||
hungaroring_P_L1_123.702_ktm_gt4.csv
|
||||
reports/
|
||||
tool/ <- this repository
|
||||
telemetry/
|
||||
prompts/
|
||||
|
|
|
|||
28
README.ru.md
28
README.ru.md
|
|
@ -51,17 +51,23 @@ python -m telemetry technique "races/Zandvoort/Практика/zandvoort_P1_L7_
|
|||
```
|
||||
Telemetry/
|
||||
races/
|
||||
Zandvoort/
|
||||
Практика/ <- CSV-файлы после переименования
|
||||
zandvoort_P1_L7_74.074_ferrari.csv
|
||||
reports/ <- отчёты technique/setup/compare
|
||||
Квалификация/
|
||||
zandvoort_Q1_L3_74.897_ferrari.csv
|
||||
reports/
|
||||
Гонка/
|
||||
zandvoort_R_L7_74.152_ferrari.csv
|
||||
reports/
|
||||
Гонка_race.md <- отчёт режима race
|
||||
F1 22/
|
||||
Zandvoort/
|
||||
Practice/ <- CSV-файлы после переименования
|
||||
zandvoort_P1_L7_74.074_ferrari.csv
|
||||
reports/ <- отчёты technique/setup/compare
|
||||
Qualifying/
|
||||
zandvoort_Q1_L3_74.897_ferrari.csv
|
||||
reports/
|
||||
Race/
|
||||
zandvoort_R_L7_74.152_ferrari.csv
|
||||
reports/
|
||||
Race_race.md <- отчёт режима race
|
||||
ACC/
|
||||
Hungaroring/
|
||||
Practice/
|
||||
hungaroring_P_L1_123.702_ktm_gt4.csv
|
||||
reports/
|
||||
tool/ <- этот репозиторий
|
||||
telemetry/
|
||||
prompts/
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ def _normalise_event(raw: str) -> str:
|
|||
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:
|
||||
# event is already the canonical token at call sites inside this module.
|
||||
e = event.upper()
|
||||
|
|
@ -123,8 +127,8 @@ def read_session_type(path: str) -> str:
|
|||
return session.get('event', '').strip()
|
||||
|
||||
|
||||
def read_metadata(path: str) -> tuple[int, str, str]:
|
||||
"""Returns (lap_number, event_type, track_name)."""
|
||||
def read_metadata(path: str) -> tuple[int, str, str, str]:
|
||||
"""Returns (lap_number, event_type, track_name, game_raw)."""
|
||||
with open(path, encoding='utf-8') as f:
|
||||
lines = [f.readline() for _ in range(5)]
|
||||
sess_keys = _parse_header_row(lines[1])
|
||||
|
|
@ -137,6 +141,7 @@ def read_metadata(path: str) -> tuple[int, str, str]:
|
|||
int(float(track['Lap'])),
|
||||
session.get('event', '').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'))
|
||||
continue
|
||||
try:
|
||||
lap, session_type, _ = read_metadata(p)
|
||||
lap, session_type, _, _game = read_metadata(p)
|
||||
except Exception as e:
|
||||
results.append(RenameResult(name, None, 'error', str(e)))
|
||||
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)
|
||||
else:
|
||||
try:
|
||||
lap, session_type, track_name = read_metadata(p)
|
||||
lap, session_type, track_name, game_raw = read_metadata(p)
|
||||
except Exception as e:
|
||||
results.append(RenameResult(name, None, 'error', str(e)))
|
||||
continue
|
||||
|
|
@ -202,7 +207,8 @@ def rename_unprocessed(targets: list[str], races_dir: str | None = None) -> list
|
|||
continue
|
||||
session_type = _normalise_event(session_type)
|
||||
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)
|
||||
if os.path.abspath(p) == os.path.abspath(dest):
|
||||
results.append(RenameResult(name, None, 'skipped', 'already in place'))
|
||||
|
|
|
|||
|
|
@ -85,10 +85,11 @@ def test_read_session_type_sample(sample_path):
|
|||
# --- read_metadata ---
|
||||
|
||||
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 event == 'P1'
|
||||
assert track != ''
|
||||
assert 'F1' in game or game != ''
|
||||
|
||||
|
||||
# --- 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 os.path.exists(r.new)
|
||||
assert r.new.startswith(races_dir)
|
||||
assert 'F1 22' in r.new
|
||||
assert 'Practice' in r.new
|
||||
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
|
||||
r = results[0]
|
||||
assert r.status == 'renamed'
|
||||
assert 'F1 22' in r.new
|
||||
assert 'Practice' in r.new
|
||||
assert os.path.exists(r.new)
|
||||
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):
|
||||
"""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")
|
||||
shutil.copy(acc_path, src)
|
||||
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]
|
||||
assert r.status == 'renamed'
|
||||
assert r.new is not None
|
||||
assert 'ACC' in r.new
|
||||
assert 'Practice' in r.new
|
||||
assert 'Other' not in r.new
|
||||
assert os.path.exists(r.new)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue