diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 1ca752f..32a5525 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -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/// and recurse subdirectories. + rename.py Insert session type + lap number (P1_L7) into filenames; with races_dir, sort into races//// 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//acc/.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//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//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 (`/reports/`). Moving the tool does not move where reports land. diff --git a/ARCHITECTURE.ru.md b/ARCHITECTURE.ru.md index 007c59d..dc8a92c 100644 --- a/ARCHITECTURE.ru.md +++ b/ARCHITECTURE.ru.md @@ -19,7 +19,7 @@ telemetry/ report_compare.py Два+ круга рядом, дельта по поворотам и накопленная. report_race.py Папка кругов: лаптаймы (с позицией), стинты, износ, тепло, ERS. Дедуп дублей номеров кругов. report_profile.py Папка кругов: дешёвые тенденции по поворотам (медиана на поворот, по доминирующему составу) + авто-выбор кругов для разбора. Без трассы. - rename.py Вставка типа сессии + номера круга (P1_L7) в имя; с races_dir — сортировка в races/// и рекурсия по подпапкам. + rename.py Вставка типа сессии + номера круга (P1_L7) в имя; с races_dir — сортировка в races//// и рекурсия по подпапкам. gui.py Окно tkinter с двумя вкладками (F1 22 / ACC); память последней папки по режиму; генерация/переименование в потоке. Предупреждает (без блокировки), если определённая игра не совпадает с активной вкладкой. prompts/ Промпты LLM, подпапки ru/ и en/, по одному файлу на режим. Добавляются в конец каждого отчёта. Переопределения для ACC лежат в prompts//acc/.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//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//Practice|Qualifying|Race|Sprint/` и перемещает туда файл, (5) даже уже переименованные файлы переносятся если лежат не там где надо, (6) пропускаются только файлы, которые уже находятся в нужной папке с нужным именем. Без `races_dir` работает как раньше — переименовывает в исходной папке. **Отчёты выходят рядом с входом, а не рядом с программой.** `report_path` выводит папку `reports/` из папки *входного CSV*. Режим `race` кладёт отчёт внутрь папки сессии (`/reports/`). Перемещение инструмента не меняет, куда попадают отчёты. diff --git a/README.md b/README.md index 1e94f49..9636cd9 100644 --- a/README.md +++ b/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/ diff --git a/README.ru.md b/README.ru.md index 66d9973..d3b4036 100644 --- a/README.ru.md +++ b/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/ diff --git a/telemetry/rename.py b/telemetry/rename.py index 2c74753..0961c7f 100644 --- a/telemetry/rename.py +++ b/telemetry/rename.py @@ -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')) diff --git a/tests/test_rename.py b/tests/test_rename.py index ec41e86..7a8f7d5 100644 --- a/tests/test_rename.py +++ b/tests/test_rename.py @@ -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//Practice/, not Other.""" + """ACC 'Practice' event files into races/ACC//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)