README: explain rename behavior with races_dir (recursion, auto-sort, move misplaced files, skip in-place files) ARCHITECTURE: update rename.py description and gotchas section to document races_dir parameter, os.walk, and file movement logic
9.7 KiB
Архитектура
CSV-телеметрия превращается в Markdown-отчёт через короткий линейный конвейер. Всё на стандартной библиотеке Python; ни фреймворка, ни глобального состояния.
Файлы
telemetry/
__main__.py Точка входа CLI: разбор аргументов, диспатч режима, stdout. Без аргументов -> GUI.
parser.py CSV -> Lap (frozen dataclass: session, track, setup, ch, n).
segments.py Lap -> повороты и прямые. Детекция зон + нумерация.
corner_metrics.py Подсчёт по зоне: точка торможения, lock/spin, накат, температуры.
resample.py Адаптивная сетка по дистанции + интерполяция каналов для трассы.
report_common.py Шапка, строки легенды, md_table, token_estimate, load_prompt, атомарная запись.
report_technique.py Отчёт о пилотировании (повороты, прямые, расход, трасса).
report_setup.py Отчёт о машине (сетап, баланс, шины, тормоза, подвеска, фазы).
report_compare.py Два+ круга рядом, дельта по поворотам и накопленная.
report_race.py Папка кругов: лаптаймы (с позицией), стинты, износ, тепло, ERS.
rename.py Вставка типа сессии + номера круга (P1_L7) в имя; с races_dir — сортировка в races/<track>/<session>/ и рекурсия по подпапкам.
gui.py Окно tkinter; память последней папки по режиму; генерация/переименование в потоке.
prompts/ Промпты LLM, подпапки ru/ и en/, по одному файлу на режим. Добавляются в конец каждого отчёта.
tests/ Набор pytest + фикстуры-круги.
Как CSV становится отчётом
__main__.mainразбирает<режим> <файлы…>, проверяет количество (technique/setup = 1 файл, compare = 2+, race = 1 папка) и диспатчит.parser.load_lapчитает файл как UTF-8 и делит на четыре мета-блока (session, track, setup) плюс таблицу телеметрии на 108 каналов. Имена каналов нормализуются срезанием суффикса[unit]. Каждая колонка, целиком состоящая из нулей, отбрасывается — игра пишет много мёртвых каналов (см. «Подводные камни»).segments.detect_cornersпроходит по сэмплам, помечает сэмпл как «в повороте», если|Steer| > STEER_ONилиBrake > BRAKE_ON, сливает соседние зоны с зазором меньшеMERGE_GAP_M, отбрасывает зоны корочеMIN_LEN_Mи нумерует оставшиеся T1..Tn по дистанции. Для каждой зоны вызываетсяcorner_metrics.corner_from_zone, которая извлекает точку торможения, апекс, передачу, точку полного газа, пик тормоза/gLat, относительные lock%/spin%, дистанцию наката и пик температуры задней шины.straightsзаполняет промежутки между поворотами.resample.adaptive_pointsстроит сетку по дистанции — плотную внутри поворотов (и на подходе) и редкую на прямых; шаги по умолчанию берутся из медианного интервала сэмплов круга черезauto_steps, так что более плотная сырая телеметрия даёт более плотную трассу.sample_atинтерполирует каждый канал на эту сетку (ближайший сэмпл для дискретных вроде Gear/DRS).- Функция
report_<режим>.generate(…, lang)собирает Markdown из помощниковreport_common(header_block,md_table,legend) и данных выше. Затем вызываетload_prompt(mode, lang), которая читает промпт изprompts/<lang>/<mode>.mdи добавляет его после разделителя---. Финальный текст пишется черезwrite_reportатомарно (временный файл +os.replace), возвращается(abs_path, tokens). __main__печатает путь и~N tokens (budget 60k). GUI показывает тот же кортеж в логе вместо печати.
Как добавлять
Новый режим отчёта. Создай telemetry/report_x.py с generate(lap, out_path) -> tuple[str, int] (или generate(laps, out_path) для multi-lap). Собери текст из помощников report_common и заверши return write_report(out_path, text). В __main__.py добавь 'x' в список режимов и ветку диспатча, которая считает путь вывода (report_path(input, 'x')) и вызывает твой generate. Добавь режим в радиокнопки GUI и _worker_generate, если нужен в интерфейсе.
Новый канал трассы. Сэмплируй его в построителе трассы через sample_at(lap, 'ChannelName', dists), добавь в заголовок трассы и resample.format_trace_row, и заведи строку LEGEND_* в report_common.py, чтобы колонка была задокументирована.
Новая метрика поворота. Добавь поле в dataclass Corner в segments.py и посчитай его в corner_metrics.corner_from_zone. Поскольку Corner — frozen, задавай через конструктор.
Подводные камни
Файлы в UTF-8, а кодировка ОС здесь — cp1251. parser.load_lap всегда передаёт encoding='utf-8'; открытие файла телеметрии дефолтным кодеком падает с UnicodeDecodeError на первом не-ASCII байте. rename.read_lap_number читает только первые пять строк, но обязан использовать ту же кодировку.
Многие каналы в этой версии игры целиком нулевые — Torque, IcePower, высоты подвески, нагрузки, посекционные температуры шин, Clutch, Handbrake и другие. Парсер отбрасывает любую полностью нулевую колонку, поэтому код ниже должен трактовать отсутствие канала как «не записан», а не «ошибка».
WheelSlip* — это разница скоростей в км/ч, не отношение. Переднее значение −4.5 при торможении — норма. Поэтому lock/spin считаются как относительное скольжение (slip / max(speed, 30) * 100) с порогами −10 % / +8 %; сырые значения слипа несравнимы между скоростями, и базовые уровни передней/задней оси различаются.
Steer — это процент (−100..100), не доля. Малые значения на прямой — всё равно проценты.
Строка сетапа заканчивается лишней запятой (пустой хвост 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 работает как раньше — переименовывает в исходной папке.
Отчёты выходят рядом с входом, а не рядом с программой. report_path выводит папку reports/ из папки входного CSV. Режим race кладёт отчёт внутрь папки сессии (<race_dir>/reports/). Перемещение инструмента не меняет, куда попадают отчёты.