Compare commits

...

4 commits

Author SHA1 Message Date
Andrew Pimenov
2604cfc302 Revert "switch license to PolyForm Noncommercial 1.0.0"
This reverts commit cbc88abe5a.
2026-08-07 18:52:55 +03:00
Andrew Pimenov
cbc88abe5a switch license to PolyForm Noncommercial 1.0.0 2026-08-07 17:51:54 +03:00
Andrew Pimenov
15bb552166 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
2026-06-19 16:40:02 +03:00
Andrew Pimenov
c1ee2f82e4 add ACC (GT3/GT4) support
- game_of() + load_prompt() with acc→f1 fallback in report_common
- technique/setup/race: skip ERS/setup sections for ACC; compare: routes prompt
- --game {auto,f1,acc} CLI flag
- 8 ACC prompt files (ru/en × 4 modes)
- GUI: ttk.Notebook F1 22/ACC tabs, isolated state, game-mismatch warning
- rename: normalise ACC full-word events to compact tokens (Practice→P, Qualifying→Q, Race→R, Hotlap→HL, Hotstint→HS)
- tests: ACC fixture + 9 new tests (87 total); all F1 regressions green
- docs: README + ARCHITECTURE bilingual updates
2026-06-19 16:29:52 +03:00
24 changed files with 1210 additions and 196 deletions

View file

@ -2,7 +2,7 @@
# Architecture # Architecture
A telemetry CSV becomes a Markdown report through a small linear pipeline. Everything is stdlib Python; there is no framework and no global state. A telemetry CSV (F1 22 or ACC) becomes a Markdown report through a small linear pipeline. Everything is stdlib Python; there is no framework and no global state.
## Files ## Files
@ -13,15 +13,16 @@ telemetry/
segments.py Lap -> corners & straights. Zone detection + numbering. segments.py Lap -> corners & straights. Zone detection + numbering.
corner_metrics.py Per-zone number crunching: braking point, lock/spin, coast, temps. corner_metrics.py Per-zone number crunching: braking point, lock/spin, coast, temps.
resample.py Adaptive distance grid + channel interpolation for the trace. resample.py Adaptive distance grid + channel interpolation for the trace.
report_common.py Header block, legend strings, md_table, token_estimate, load_prompt, atomic write. report_common.py Header block, legend strings, md_table, token_estimate, game_of, load_prompt, atomic write.
report_technique.py Driving report (corners, straights, cost, trace). report_technique.py Driving report (corners, straights, cost, trace).
report_setup.py Car report (setup, balance, tyres, brakes, suspension, phases). report_setup.py Car report (setup, balance, tyres, brakes, suspension, phases).
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; per-mode browse memory; runs generation/rename on a worker thread. 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.
tests/ pytest suite + fixture laps. tests/ pytest suite + fixture laps.
``` ```
@ -31,7 +32,7 @@ tests/ pytest suite + fixture laps.
2. **`parser.load_lap`** reads the file as UTF-8 and splits it into four meta blocks (session, track, setup) plus the 108-channel telemetry table. Channel names are normalised by stripping the ` [unit]` suffix. Every column that is entirely zero is dropped — the game writes many dead channels (see Gotchas). 2. **`parser.load_lap`** reads the file as UTF-8 and splits it into four meta blocks (session, track, setup) plus the 108-channel telemetry table. Channel names are normalised by stripping the ` [unit]` suffix. Every column that is entirely zero is dropped — the game writes many dead channels (see Gotchas).
3. **`segments.detect_corners`** walks the samples, marks each as "cornering" when `|Steer| > STEER_ON` or `Brake > BRAKE_ON`, merges adjacent zones closer than `MERGE_GAP_M`, drops zones shorter than `MIN_LEN_M`, and numbers what's left T1..Tn by distance. For each zone it calls **`corner_metrics.corner_from_zone`**, which extracts the braking point, apex, gear, full-throttle point, peak brake/gLat, relative lock%/spin%, coast distance, and rear tyre temperature peak. `straights` fills the gaps between corners. 3. **`segments.detect_corners`** walks the samples, marks each as "cornering" when `|Steer| > STEER_ON` or `Brake > BRAKE_ON`, merges adjacent zones closer than `MERGE_GAP_M`, drops zones shorter than `MIN_LEN_M`, and numbers what's left T1..Tn by distance. For each zone it calls **`corner_metrics.corner_from_zone`**, which extracts the braking point, apex, gear, full-throttle point, peak brake/gLat, relative lock%/spin%, coast distance, and rear tyre temperature peak. `straights` fills the gaps between corners.
4. **`resample.adaptive_points`** builds a distance grid that is dense inside (and on approach to) corners and sparse on straights — the step sizes default to the lap's own median sample spacing via `auto_steps`, so denser raw telemetry yields a denser trace. **`sample_at`** interpolates each channel onto that grid (nearest-sample for discrete channels like Gear/DRS). 4. **`resample.adaptive_points`** builds a distance grid that is dense inside (and on approach to) corners and sparse on straights — the step sizes default to the lap's own median sample spacing via `auto_steps`, so denser raw telemetry yields a denser trace. **`sample_at`** interpolates each channel onto that grid (nearest-sample for discrete channels like Gear/DRS).
5. The **`report_<mode>.generate(…, lang, include_prompt=True)`** function assembles Markdown from `report_common` helpers (`header_block`, `md_table`, `legend`) and the data above. When `include_prompt` is set it calls **`load_prompt(mode, lang)`** to read the matching prompt from `prompts/<lang>/<mode>.md` and appends it after a `---` separator (CLI `--no-prompt` / GUI checkbox turn this off). Finally **`write_report`** writes atomically (temp file + `os.replace`) and returns `(abs_path, tokens)`. Before generating, `__main__` skips the run via **`is_fresh(report, sources)`** when an up-to-date report exists (override with `--force`). 5. The **`report_<mode>.generate(…, lang, include_prompt=True)`** function assembles Markdown from `report_common` helpers (`header_block`, `md_table`, `legend`) and the data above. **`game_of(lap)`** inspects the `Game` session field and returns `'acc'` (if the field starts with `"ACC"`) or `'f1'`; the report generators use this to suppress DRS/ERS/hybrid sections for ACC and to replace the setup parameter table with a note when all setup values are zero (as ACC always exports). When `include_prompt` is set it calls **`load_prompt(mode, lang, game)`** which first tries `prompts/<lang>/acc/<mode>.md` for ACC and falls back to `prompts/<lang>/<mode>.md` if absent, then appends the result after a `---` separator (CLI `--no-prompt` / GUI checkbox turn this off). Finally **`write_report`** writes atomically (temp file + `os.replace`) and returns `(abs_path, tokens)`. Before generating, `__main__` skips the run via **`is_fresh(report, sources)`** when an up-to-date report exists (override with `--force`).
6. **`__main__`** prints that path and `~N tokens (budget 60k)`. The **GUI** shows the same tuple in its log instead of printing. 6. **`__main__`** prints that path and `~N tokens (budget 60k)`. The **GUI** shows the same tuple in its log instead of printing.
## Adding things ## Adding things
@ -71,6 +72,12 @@ 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.
**Game is derived from the `Game` metadata field and threaded through report generation.** `game_of(lap)` reads `lap.session['Game']`; the field starts with `"ACC"` for Assetto Corsa Competizione and is something else for F1 22. Every report generator receives the lap and calls `game_of` to decide which sections to emit. The CLI `--game` flag can override detection by patching the session field before dispatch.
**ACC setup is all zeros and is intentionally not rendered as a parameter table.** ACC's CSV export always writes zeros for every setup field. `report_setup` checks whether all setup values are zero and, when true, replaces the parameter table with a single explanatory note rather than printing a table of meaningless zeros.
**ACC session events use full words; F1 22 uses short codes.** F1 22 writes session codes like `P1`, `Q1`, `R` directly. ACC writes `Practice`, `Qualifying`, `Race`. The rename module normalises ACC full-word events to compact canonical codes (Practice→P, Qualifying→Q, Race→R) before inserting them into the filename and choosing the destination folder. The inserted token is the same compact code that the session-pattern recogniser expects, so re-running rename on already-processed ACC files is idempotent (they are skipped, not double-tagged).

View file

@ -2,7 +2,7 @@
# Архитектура # Архитектура
CSV-телеметрия превращается в Markdown-отчёт через короткий линейный конвейер. Всё на стандартной библиотеке Python; ни фреймворка, ни глобального состояния. CSV-телеметрия (F1 22 или ACC) превращается в Markdown-отчёт через короткий линейный конвейер. Всё на стандартной библиотеке Python; ни фреймворка, ни глобального состояния.
## Файлы ## Файлы
@ -13,15 +13,16 @@ telemetry/
segments.py Lap -> повороты и прямые. Детекция зон + нумерация. segments.py Lap -> повороты и прямые. Детекция зон + нумерация.
corner_metrics.py Подсчёт по зоне: точка торможения, lock/spin, накат, температуры. corner_metrics.py Подсчёт по зоне: точка торможения, lock/spin, накат, температуры.
resample.py Адаптивная сетка по дистанции + интерполяция каналов для трассы. resample.py Адаптивная сетка по дистанции + интерполяция каналов для трассы.
report_common.py Шапка, строки легенды, md_table, token_estimate, load_prompt, атомарная запись. report_common.py Шапка, строки легенды, md_table, token_estimate, game_of, load_prompt, атомарная запись.
report_technique.py Отчёт о пилотировании (повороты, прямые, расход, трасса). report_technique.py Отчёт о пилотировании (повороты, прямые, расход, трасса).
report_setup.py Отчёт о машине (сетап, баланс, шины, тормоза, подвеска, фазы). report_setup.py Отчёт о машине (сетап, баланс, шины, тормоза, подвеска, фазы).
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; память последней папки по режиму; генерация/переименование в потоке. gui.py Окно tkinter с двумя вкладками (F1 22 / ACC); память последней папки по режиму; генерация/переименование в потоке. Предупреждает (без блокировки), если определённая игра не совпадает с активной вкладкой.
prompts/ Промпты LLM, подпапки ru/ и en/, по одному файлу на режим. Добавляются в конец каждого отчёта. prompts/ Промпты LLM, подпапки ru/ и en/, по одному файлу на режим. Добавляются в конец каждого отчёта.
Переопределения для ACC лежат в prompts/<lang>/acc/<mode>.md; при отсутствии — откат к F1-промпту.
tests/ Набор pytest + фикстуры-круги. tests/ Набор pytest + фикстуры-круги.
``` ```
@ -31,7 +32,7 @@ tests/ Набор pytest + фикстуры-круги.
2. **`parser.load_lap`** читает файл как UTF-8 и делит на четыре мета-блока (session, track, setup) плюс таблицу телеметрии на 108 каналов. Имена каналов нормализуются срезанием суффикса ` [unit]`. Каждая колонка, целиком состоящая из нулей, отбрасывается — игра пишет много мёртвых каналов (см. «Подводные камни»). 2. **`parser.load_lap`** читает файл как UTF-8 и делит на четыре мета-блока (session, track, setup) плюс таблицу телеметрии на 108 каналов. Имена каналов нормализуются срезанием суффикса ` [unit]`. Каждая колонка, целиком состоящая из нулей, отбрасывается — игра пишет много мёртвых каналов (см. «Подводные камни»).
3. **`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` заполняет промежутки между поворотами. 3. **`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` заполняет промежутки между поворотами.
4. **`resample.adaptive_points`** строит сетку по дистанции — плотную внутри поворотов (и на подходе) и редкую на прямых; шаги по умолчанию берутся из медианного интервала сэмплов круга через `auto_steps`, так что более плотная сырая телеметрия даёт более плотную трассу. **`sample_at`** интерполирует каждый канал на эту сетку (ближайший сэмпл для дискретных вроде Gear/DRS). 4. **`resample.adaptive_points`** строит сетку по дистанции — плотную внутри поворотов (и на подходе) и редкую на прямых; шаги по умолчанию берутся из медианного интервала сэмплов круга через `auto_steps`, так что более плотная сырая телеметрия даёт более плотную трассу. **`sample_at`** интерполирует каждый канал на эту сетку (ближайший сэмпл для дискретных вроде Gear/DRS).
5. Функция **`report_<режим>.generate(…, lang, include_prompt=True)`** собирает Markdown из помощников `report_common` (`header_block`, `md_table`, `legend`) и данных выше. При `include_prompt` вызывает **`load_prompt(mode, lang)`**, которая читает промпт из `prompts/<lang>/<mode>.md` и добавляет его после разделителя `---` (CLI `--no-prompt` / чекбокс GUI это отключают). Финальный текст пишется через **`write_report`** атомарно (временный файл + `os.replace`), возвращается `(abs_path, tokens)`. Перед генерацией `__main__` пропускает прогон через **`is_fresh(report, sources)`**, если свежий отчёт уже есть (override — `--force`). 5. Функция **`report_<режим>.generate(…, lang, include_prompt=True)`** собирает Markdown из помощников `report_common` (`header_block`, `md_table`, `legend`) и данных выше. **`game_of(lap)`** читает поле `Game` из сессии и возвращает `'acc'` (если значение начинается с `"ACC"`) или `'f1'`; генераторы отчётов используют это, чтобы убрать разделы DRS/ERS/гибрид для ACC и заменить таблицу сетапа пометкой, когда все значения нулевые (ACC всегда так экспортирует). При `include_prompt` вызывает **`load_prompt(mode, lang, game)`**, которая для ACC сначала ищет `prompts/<lang>/acc/<mode>.md` и откатывается к `prompts/<lang>/<mode>.md` при его отсутствии, затем добавляет результат после разделителя `---` (CLI `--no-prompt` / чекбокс GUI это отключают). Финальный текст пишется через **`write_report`** атомарно (временный файл + `os.replace`), возвращается `(abs_path, tokens)`. Перед генерацией `__main__` пропускает прогон через **`is_fresh(report, sources)`**, если свежий отчёт уже есть (override — `--force`).
6. **`__main__`** печатает путь и `~N tokens (budget 60k)`. **GUI** показывает тот же кортеж в логе вместо печати. 6. **`__main__`** печатает путь и `~N tokens (budget 60k)`. **GUI** показывает тот же кортеж в логе вместо печати.
## Как добавлять ## Как добавлять
@ -71,6 +72,12 @@ 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/`). Перемещение инструмента не меняет, куда попадают отчёты.
**Игра определяется из поля `Game` в метаданных и передаётся сквозь всю генерацию отчёта.** `game_of(lap)` читает `lap.session['Game']`; для Assetto Corsa Competizione поле начинается с `"ACC"`, для F1 22 — иначе. Каждый генератор отчёта вызывает `game_of`, чтобы решить, какие секции выводить. Флаг CLI `--game` позволяет переопределить определение, патча поле сессии до диспатча.
**Сетап ACC — всегда нули, и таблица параметров намеренно не рендерится.** CSV-экспорт ACC записывает нули во все поля сетапа. `report_setup` проверяет, все ли значения нулевые, и, если да, заменяет таблицу параметров поясняющей пометкой вместо вывода бессмысленных нулей.
**События сессии в ACC — полные слова; в F1 22 — короткие коды.** F1 22 пишет коды вроде `P1`, `Q1`, `R`. ACC пишет `Practice`, `Qualifying`, `Race`. Модуль переименования нормализует полные слова ACC до компактных канонических кодов (Practice→P, Qualifying→Q, Race→R) перед вставкой в имя файла и выбором папки назначения. Вставляемый токен совпадает с тем, что ожидает распознаватель паттерна сессии, поэтому повторный запуск rename на уже обработанных файлах ACC идемпотентен (они пропускаются, а не получают двойной тег).

View file

@ -1,8 +1,8 @@
[Русский](README.ru.md) [Русский](README.ru.md)
# F1 Telemetry Analyzer # F1 22 / ACC Telemetry Analyzer
Turns raw F1 22 lap telemetry into compact Markdown reports you can paste into an LLM for analysis. A full raw lap is ~1M tokens; these reports are 1–35k. Turns raw F1 22 and Assetto Corsa Competizione lap telemetry into compact Markdown reports you can paste into an LLM for analysis. A full raw lap is ~1M tokens; these reports are 1–35k.
```bash ```bash
# from the tool/ folder # from the tool/ folder
@ -21,28 +21,37 @@ Download or clone the repository. Keep your telemetry CSVs in the parent folder
## What it does ## What it does
Six modes. Five produce reports, one renames files. Six modes. Five produce reports, one renames files. Both F1 22 and ACC (GT3/GT4) CSVs are supported — the game is detected automatically from each file's metadata.
| Mode | Input | Use it to analyse | | Mode | Input | Use it to analyse |
|------|-------|-------------------| |------|-------|-------------------|
| `technique` | one lap | **how** you drive — braking points, apex speeds, throttle application, lockups, resource cost | | `technique` | one lap | **how** you drive — braking points, apex speeds, throttle application, lockups, resource cost |
| `setup` | one lap | **what** is set — balance (US/OS), tyre temps, brakes, suspension rake & roll | | `setup` | one lap | **what** is set — balance (US/OS), tyre temps, brakes, suspension rake & roll |
| `compare` | two+ laps | **where** one lap is faster — per-corner and cumulative delta, plus a deterministic `Verdict` (fuel-corrected) | | `compare` | two+ laps | **where** one lap is faster — per-corner and cumulative delta, plus a deterministic `Verdict` (fuel-corrected) |
| `race` | a folder of laps | the whole race — pace, stint degradation, tyre wear, ERS per lap | | `race` | a folder of laps | the whole race — pace, stint degradation, tyre wear, ERS per lap (F1) |
| `profile` | a folder of laps | **cheap** (~400 tokens) cross-lap driver tendencies per tyre compound (median coast/lock/spin per corner, flagged relative to the session) + auto-picked laps to deep-dive. Track-agnostic: corners are clustered by apex distance, not index | | `profile` | a folder of laps | **cheap** (~400 tokens) cross-lap driver tendencies per tyre compound (median coast/lock/spin per corner, flagged relative to the session) + auto-picked laps to deep-dive. Track-agnostic: corners are clustered by apex distance, not index |
| `rename` | files/folder | insert session type and lap number into each filename (`..._74.074_...` → `..._P1_L7_74.074_...`) | | `rename` | files/folder | insert session type and lap number into each filename (`..._74.074_...` → `..._P1_L7_74.074_...`) |
Reports include a legend explaining every column and have the analysis prompt auto-appended at the end — just drop the file into an LLM. Prompts come in RU and EN; pick with `--lang en` on the CLI or the language radio in the GUI. Reports include a legend explaining every column and have the analysis prompt auto-appended at the end — just drop the file into an LLM. Prompts come in RU and EN; pick with `--lang en` on the CLI or the language radio in the GUI.
**Flags:** `--no-prompt` omits the embedded analysis prompt (smaller files for agent pipelines; in the GUI it's the **Include prompt** checkbox). `--force` regenerates even when an up-to-date report already exists (runs are otherwise skipped as `cached`). **Flags:** `--no-prompt` omits the embedded analysis prompt (smaller files for agent pipelines; in the GUI it's the **Include prompt** checkbox). `--force` regenerates even when an up-to-date report already exists (runs are otherwise skipped as `cached`). `--game {auto,f1,acc}` overrides the auto-detected game (default `auto`).
> Typical optimized flow: run `profile` first to see chronic tendencies and which laps matter, then generate `technique`/`setup`/`compare` only for those laps with `--no-prompt`. > Typical optimized flow: run `profile` first to see chronic tendencies and which laps matter, then generate `technique`/`setup`/`compare` only for those laps with `--no-prompt`.
### ACC vs F1 22 differences
GT3/GT4 cars have no hybrid system, and ACC does not export the car setup:
- `technique` and `race` reports omit the DRS / ERS / MGU-K sections.
- `setup` mode: ACC's in-game setup export is all zeros, so the parameter table is replaced by a note ("ACC does not export setup — analysis derived from telemetry"); the telemetry-derived sections (corner balance, tyres, brakes, suspension) are retained.
- Fuel is in **litres** (GT) rather than kg (F1). Tyres are Pirelli dry/wet compounds, not F1 C1–C5.
### Folder layout ### Folder layout
``` ```
Telemetry/ Telemetry/
races/ races/
F1 22/
Zandvoort/ Zandvoort/
Practice/ <- CSVs go here after renaming Practice/ <- CSVs go here after renaming
zandvoort_P1_L7_74.074_ferrari.csv zandvoort_P1_L7_74.074_ferrari.csv
@ -54,6 +63,11 @@ Telemetry/
zandvoort_R_L7_74.152_ferrari.csv zandvoort_R_L7_74.152_ferrari.csv
reports/ reports/
Race_race.md <- race mode report 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/
@ -62,7 +76,7 @@ Telemetry/
## Handy things ## Handy things
**GUI** — double-click `run-gui.bat` (or `run-gui.pyw` for no console window, or run `python -m telemetry gui`). Pick a mode and language (RU/EN), browse to the file/folder, click Generate. The file browser remembers the last used directory per mode. The **Rename unprocessed** button adds the session type and lap number to files that don't have them yet and leaves the rest alone. **GUI** — double-click `run-gui.bat` (or `run-gui.pyw` for no console window, or run `python -m telemetry gui`). The window has two tabs — **F1 22** and **ACC** — each with its own independent controls and log. Pick a mode and language (RU/EN), browse to the file/folder, click Generate. The file browser remembers the last used directory per mode. The **Rename unprocessed** button adds the session type and lap number to files that don't have them yet and leaves the rest alone. If a file's detected game doesn't match the active tab, the GUI logs a warning but proceeds.
**CLI** — from the `tool/` folder: **CLI** — from the `tool/` folder:
@ -77,6 +91,9 @@ python -m telemetry rename races/Zandvoort/Practice # or individual files
# choose prompt language (default: ru) # choose prompt language (default: ru)
python -m telemetry technique races/Zandvoort/Practice/lap.csv --lang en python -m telemetry technique races/Zandvoort/Practice/lap.csv --lang en
# override game detection (default: auto)
python -m telemetry technique ACC/races/Hungaroring/Practice/lap.csv --game acc
# agent pipeline: drop the prompt, reuse fresh reports # agent pipeline: drop the prompt, reuse fresh reports
python -m telemetry technique races/Zandvoort/Practice/lap.csv --no-prompt python -m telemetry technique races/Zandvoort/Practice/lap.csv --no-prompt
python -m telemetry technique races/Zandvoort/Practice/lap.csv --force # ignore cache python -m telemetry technique races/Zandvoort/Practice/lap.csv --force # ignore cache
@ -101,7 +118,7 @@ Re-running on the same folder is safe — in-place files are left alone.
## Input format ## Input format
F1 22 UDP telemetry exported to CSV (tested with [F1Laps](https://www.f1laps.com)). UTF-8, ~1800 rows per lap (≈29 Hz), 108 channels. One file = one lap. For `race` mode, drop all of a race's lap files into one folder. F1 22 UDP telemetry or ACC telemetry, both exported to CSV (F1 22 tested with [F1Laps](https://www.f1laps.com)). UTF-8, 108 channels, one file = one lap (F1 22 is ≈1800 rows/lap at ≈29 Hz; ACC laps are denser, ~14k rows — reports stay compact via adaptive resampling). For `race` mode, drop all of a race's lap files into one folder. The CSV format is identical between F1 22 and ACC; the game is detected from the `Game` metadata field in each file.
## For developers ## For developers
@ -117,7 +134,7 @@ Adding a mode: drop a `telemetry/report_<mode>.py` exposing `generate(...) -> (a
## Credits ## Credits
Built for analysing personal F1 22 hotlaps. Telemetry export format from [F1Laps](https://www.f1laps.com). Built for analysing personal F1 22 and ACC hotlaps. Telemetry export format from [F1Laps](https://www.f1laps.com). In-game telemetry captured with Telemetry Tool by Iasione.
## License ## License

View file

@ -1,8 +1,8 @@
[English](README.md) [English](README.md)
# F1 Telemetry Analyzer # F1 22 / ACC Telemetry Analyzer
Превращает сырую телеметрию круга F1 22 в компактные Markdown-отчёты для анализа через LLM. Сырой круг — около 1М токенов; отчёты — 1–35k. Превращает сырую телеметрию круга F1 22 и Assetto Corsa Competizione в компактные Markdown-отчёты для анализа через LLM. Сырой круг — около 1М токенов; отчёты — 1–35k.
```bash ```bash
# из папки tool/ # из папки tool/
@ -21,39 +21,53 @@ python -m telemetry technique "races/Zandvoort/Практика/zandvoort_P1_L7_
## Что умеет ## Что умеет
Шесть режимов. Пять строят отчёты, один переименовывает файлы. Шесть режимов. Пять строят отчёты, один переименовывает файлы. Поддерживаются CSV из F1 22 и ACC (GT3/GT4) — игра определяется автоматически из метаданных каждого файла.
| Режим | Вход | Для анализа | | Режим | Вход | Для анализа |
|-------|------|-------------| |-------|------|-------------|
| `technique` | один круг | **как** едешь — точки торможения, скорости в апексах, работа газом, блокировки, расход ресурсов | | `technique` | один круг | **как** едешь — точки торможения, скорости в апексах, работа газом, блокировки, расход ресурсов |
| `setup` | один круг | **что** настроено — баланс (US/OS), температуры шин, тормоза, rake и крен подвески | | `setup` | один круг | **что** настроено — баланс (US/OS), температуры шин, тормоза, rake и крен подвески |
| `compare` | два+ круга | **где** один круг быстрее — дельта по поворотам и накопленная, плюс детерминированный `Verdict` (с топливной поправкой) | | `compare` | два+ круга | **где** один круг быстрее — дельта по поворотам и накопленная, плюс детерминированный `Verdict` (с топливной поправкой) |
| `race` | папка кругов | вся гонка — темп, деградация в стинтах, износ резины, ERS по кругам | | `race` | папка кругов | вся гонка — темп, деградация в стинтах, износ резины, ERS по кругам (F1) |
| `profile` | папка кругов | **дёшево** (~400 токенов) тенденции пилота по каждому составу (медиана coast/lock/spin на поворот, флаги относительно сессии) + авто-выбор кругов. Универсально: повороты кластеризуются по дистанции апекса, не по индексу | | `profile` | папка кругов | **дёшево** (~400 токенов) тенденции пилота по каждому составу (медиана coast/lock/spin на поворот, флаги относительно сессии) + авто-выбор кругов. Универсально: повороты кластеризуются по дистанции апекса, не по индексу |
| `rename` | файлы/папка | вставить тип сессии и номер круга в имя файла (`..._74.074_...` → `..._P1_L7_74.074_...`) | | `rename` | файлы/папка | вставить тип сессии и номер круга в имя файла (`..._74.074_...` → `..._P1_L7_74.074_...`) |
Отчёты содержат легенду с пояснением каждой колонки, а промпт анализа добавляется в конец файла автоматически — достаточно скинуть файл в LLM. Промпты доступны на RU и EN; выбирается флагом `--lang en` в CLI или переключателем языка в GUI. Отчёты содержат легенду с пояснением каждой колонки, а промпт анализа добавляется в конец файла автоматически — достаточно скинуть файл в LLM. Промпты доступны на RU и EN; выбирается флагом `--lang en` в CLI или переключателем языка в GUI.
**Флаги:** `--no-prompt` убирает встроенный промпт (меньше файлы для агентных конвейеров; в GUI — чекбокс **Включить промпт**). `--force` перегенерирует, даже если свежий отчёт уже есть (иначе прогон пропускается как `cached`). **Флаги:** `--no-prompt` убирает встроенный промпт (меньше файлы для агентных конвейеров; в GUI — чекбокс **Включить промпт**). `--force` перегенерирует, даже если свежий отчёт уже есть (иначе прогон пропускается как `cached`). `--game {auto,f1,acc}` переопределяет автоопределение игры (по умолчанию `auto`).
> Оптимальный порядок: сначала `profile` — увидеть хронические тенденции и какие круги важны, затем `technique`/`setup`/`compare` только по этим кругам с `--no-prompt`. > Оптимальный порядок: сначала `profile` — увидеть хронические тенденции и какие круги важны, затем `technique`/`setup`/`compare` только по этим кругам с `--no-prompt`.
### Отличия ACC от F1 22
Машины GT3/GT4 не имеют гибридной системы, а ACC не экспортирует настройки машины:
- Отчёты `technique` и `race` не содержат разделов DRS / ERS / MGU-K.
- Режим `setup`: экспорт настроек из ACC — все нули, поэтому таблица параметров заменяется пометкой («ACC не экспортирует сетап — анализ построен по телеметрии»); разделы, основанные на телеметрии (баланс в поворотах, шины, тормоза, подвеска), остаются.
- Топливо в **литрах** (GT), а не в кг (F1). Шины — составы Pirelli dry/wet, а не F1 C1–C5.
### Структура папок ### Структура папок
``` ```
Telemetry/ Telemetry/
races/ races/
F1 22/
Zandvoort/ Zandvoort/
Практика/ <- CSV-файлы после переименования Practice/ <- CSV-файлы после переименования
zandvoort_P1_L7_74.074_ferrari.csv zandvoort_P1_L7_74.074_ferrari.csv
reports/ <- отчёты technique/setup/compare reports/ <- отчёты technique/setup/compare
Квалификация/ Qualifying/
zandvoort_Q1_L3_74.897_ferrari.csv zandvoort_Q1_L3_74.897_ferrari.csv
reports/ reports/
Гонка/ Race/
zandvoort_R_L7_74.152_ferrari.csv zandvoort_R_L7_74.152_ferrari.csv
reports/ reports/
Гонка_race.md <- отчёт режима race Race_race.md <- отчёт режима race
ACC/
Hungaroring/
Practice/
hungaroring_P_L1_123.702_ktm_gt4.csv
reports/
tool/ <- этот репозиторий tool/ <- этот репозиторий
telemetry/ telemetry/
prompts/ prompts/
@ -62,7 +76,7 @@ Telemetry/
## Полезное ## Полезное
**GUI** — двойной клик по `run-gui.bat` (или `run-gui.pyw` без окна консоли, либо `python -m telemetry gui`). Выбери режим и язык промпта (RU/EN), укажи файл/папку, нажми Generate. Диалог открывается в последней использованной папке для каждого режима отдельно. Кнопка **Rename unprocessed** добавляет тип сессии и номер круга к файлам, у которых их ещё нет, и не трогает остальные. **GUI** — двойной клик по `run-gui.bat` (или `run-gui.pyw` без окна консоли, либо `python -m telemetry gui`). Окно разделено на две вкладки — **F1 22** и **ACC** — у каждой свои независимые элементы управления и лог. Выбери режим и язык промпта (RU/EN), укажи файл/папку, нажми Generate. Диалог открывается в последней использованной папке для каждого режима отдельно. Кнопка **Rename unprocessed** добавляет тип сессии и номер круга к файлам, у которых их ещё нет, и не трогает остальные. Если игра в файле не совпадает с активной вкладкой, GUI выводит предупреждение, но продолжает работу.
**CLI** — из папки `tool/`: **CLI** — из папки `tool/`:
@ -77,6 +91,9 @@ python -m telemetry rename races/Zandvoort/Практика # или отд
# выбор языка промпта (по умолчанию: ru) # выбор языка промпта (по умолчанию: ru)
python -m telemetry technique races/Zandvoort/Практика/lap.csv --lang en python -m telemetry technique races/Zandvoort/Практика/lap.csv --lang en
# переопределить определение игры (по умолчанию: auto)
python -m telemetry technique ACC/races/Hungaroring/Practice/lap.csv --game acc
# агентный конвейер: убрать промпт, переиспользовать свежие отчёты # агентный конвейер: убрать промпт, переиспользовать свежие отчёты
python -m telemetry technique races/Zandvoort/Практика/lap.csv --no-prompt python -m telemetry technique races/Zandvoort/Практика/lap.csv --no-prompt
python -m telemetry technique races/Zandvoort/Практика/lap.csv --force # игнорировать кэш python -m telemetry technique races/Zandvoort/Практика/lap.csv --force # игнорировать кэш
@ -101,7 +118,7 @@ C:\...\Telemetry\reports\lap_technique.md
## Входной формат ## Входной формат
Телеметрия F1 22 (UDP), экспортированная в CSV (тестировалось на [F1Laps](https://www.f1laps.com)). UTF-8, ~1800 строк на круг (≈29 Гц), 108 каналов. Один файл = один круг. Для режима `race` сложи все круги гонки в одну папку. Телеметрия F1 22 (UDP) или ACC, экспортированная в CSV (F1 22 тестировалось на [F1Laps](https://www.f1laps.com)). UTF-8, 108 каналов, один файл = один круг (F1 22 — ≈1800 строк/круг при ≈29 Гц; круги ACC плотнее, ~14k строк — отчёты остаются компактными за счёт адаптивного ресэмплинга). Для режима `race` сложи все круги гонки в одну папку. Формат CSV одинаков у F1 22 и ACC; игра определяется из поля `Game` в метаданных каждого файла.
## Для разработчиков ## Для разработчиков
@ -117,7 +134,7 @@ python -m telemetry gui # запустить GUI
## Благодарности ## Благодарности
Сделано для разбора личных хотлапов в F1 22. Формат экспорта телеметрии — [F1Laps](https://www.f1laps.com). Сделано для разбора личных хотлапов в F1 22 и ACC. Формат экспорта телеметрии — [F1Laps](https://www.f1laps.com). Телеметрия в игре записывается через Telemetry Tool от Iasione.
## Лицензия ## Лицензия

62
prompts/en/acc/compare.md Normal file
View file

@ -0,0 +1,62 @@
You are a race engineer comparing two laps from the same driver in Assetto Corsa Competizione (GT3/GT4). Lap 1 is the reference. Find where and why one lap is faster than the other and what it reveals about the driving.
## Data format
**Header** — comparison table: laptime, sectors S1/S2/S3, tyre, fuel at lap start [L], tyre wear, weather.
**Corners** — per corner:
- `Vmin L1 / Vmin L2` — minimum apex speed [km/h]
- `time L1 / time L2` — time in corner zone [s]
- `Δt seg` — time delta in this corner (L2 − L1): negative = L2 faster, positive = L1 faster
- `Δt cum` — cumulative delta at end of this corner: shows running score to this point in the lap
**Delta trace** — per sample by distance:
- `dist` — distance [m]
- `spd_L1 / spd_L2` — speed per lap [km/h]
- `Δt` — cumulative delta L2 − L1 [s]: negative = L2 leads, positive = L1 leads
---
## Task
Analyse each point. Back every conclusion with specific Δt numbers, distances, speeds.
### 1. Overview
- Overall time difference and which sector it was built in
- Was the gap steady across the lap or created sharply in one area
- Fuel and tyre wear effect: if laps are from different stints, account for load and degradation
### 2. Corner-by-corner
Per corner with non-zero `Δt seg`:
- Who is faster and by how much
- `Vmin L1 vs L2`: higher apex speed = either later braking or better line
- High Vmin but higher `time` = faster apex but worse exit (traction or trajectory loss)
- Lower Vmin but lower `time` = better exit compensating for the slower apex
- `Δt cum` after corner: does the gap grow, or does the next section recover it
### 3. Delta trace analysis
Working with `Δt` by distance:
- **Where delta builds sharply** (steep kink): critical points — braking or traction where styles diverge
- **Where delta is flat**: both laps equivalent, lower priority zones
- **Braking zones**: Δt shrinking under braking = L2 brakes later (or more effectively)
- **Traction zones**: after apex `spd_L2 > spd_L1` = L2 traction better (earlier throttle or less wheelspin); in GT cars this matters especially at slow-corner exits where mechanical grip is the limiting factor
- **Straights**: speed difference on a straight = result of the preceding corner exit, not the straight itself
### 4. Patterns
- One lap systematically better in slow corners, the other in fast?
- One style risks entries, the other risks exits?
- Any corner where the "slower" lap is actually faster at that specific point — and why?
### 5. Summary and priorities
- **What to take from the faster lap**: specific corners and technique
- **Priority zones to work on**: biggest `|Δt seg|` opportunities
- **What not to copy**: corners where the "faster" lap is still slower than the reference — note them
---
Below is the attached comparison report. Begin analysis.

92
prompts/en/acc/race.md Normal file
View file

@ -0,0 +1,92 @@
You are a race engineer analysing full-race telemetry in Assetto Corsa Competizione (GT3/GT4). The report covers all laps: lap times, stints, tyre wear, and thermals. Perform a detailed race analysis.
## Data format
**Lap times** — per lap:
- `Lap` — lap number
- `Time` — lap time `M:SS.mmm`
- `S1 / S2 / S3` — sector times [s]
- `Tyre` — compound (dry/wet type)
- `Age` — laps on this set (1 = first lap on fresh set)
- `Fuel L` — fuel at lap start [L]
- `Note` — flags: `FAST` = fastest valid lap, `PIT` = in-lap, `INV` = invalidated
**Stint summary** — per stint:
- `Stint` — stint number
- `Laps` — laps in stint
- `Tyre` — compound
- `Laps on tyre` — total laps on this set
- `Avg pace` — average pace (excluding pit laps and invalidated)
- `Best / Worst` — best and worst lap in stint
- `Deg s/lap` — degradation slope per tyre age lap (positive = slowing, negative = improving)
**Tyre wear per lap** — `FL / FR / RL / RR` — wear % at end of each lap
**Thermal summary per lap**:
- `Tf avg°C` — average peak front tyre temperature for the lap
- `Tr avg°C` — rear
- `Brk FL max°C` — peak front-left brake temperature
- `Brk RR max°C` — peak rear-right brake temperature
---
## Task
Analyse each point. Back every conclusion with specific numbers from the tables.
### 1. Race overview
- How did pace evolve through the race: progression or degradation?
- Were there outliers (abnormally slow or fast laps) — name the lap numbers and reason (PIT, INV, safety car?)
- Compare best vs average pace: how large is the gap?
- One-sentence overall race assessment
### 2. Stint analysis
Per stint from `Stint summary`:
- Average pace and relative quality (best/worst stint?)
- `Best–Worst` spread: large spread = inconsistent pace, small = consistent
- Compare stints: where was the driver faster and why (tyre, fuel level, track condition?)
- Was the pit stop timed well: not too early/late given degradation trend
### 3. Tyre degradation
From `Deg s/lap` and `Tyre wear`:
- Which stint has the most/least degradation?
- Compare `Deg s/lap` vs actual wear increment per lap — consistent?
- When did the tyre "die": identify the lap where pace dropped sharply
- Wear evenness: FL/FR or RL/RR asymmetry?
### 4. Fuel effect
GT cars carry fuel in litres; as fuel burns off the car gets lighter.
- For the first (or long) stint: correlate `Fuel L` and `Time` per lap
- Is there a visible correlation between fuel burn and improving pace as the car lightens?
- If early-stint pace is poor — tyre warm-up or a heavy car?
- Check whether pace dynamics are consistent with the expected weight-reduction benefit
### 5. Thermal management
From `Thermal summary`:
- How do tyre temps evolve through each stint: warm-up, stable, overheating?
- Pirelli GT tyre operating window is approximately ~70–90°C; a sharp rise above this with simultaneous pace drop signals overheating
- Brake temps: rising through the race distance or stable? FL vs RR asymmetry?
- Which laps had critical thermal conditions?
### 6. Best and worst laps
- `FAST` lap: which stint, what tyre age, what fuel level?
- Why did the best lap happen when it did — track rubber, clean air, cool conditions?
- Worst valid lap (not INV, not PIT): reason — degradation, traffic, driver error?
- Patterns: "out lap" always slow, "in lap" pace dip?
### 7. Summary and recommendations
- **What worked**: strategy and driving that was executed well
- **Main losses**: specific laps and zones where the most time was lost
- **Strategic recommendations**: pit timing, compound choice, stint length
- **Driving technique**: if degradation is abnormal — what to change in driving style
---
Below is the attached race report. Begin analysis.

80
prompts/en/acc/setup.md Normal file
View file

@ -0,0 +1,80 @@
You are an experienced race engineer. The following is a telemetry report for one lap in Assetto Corsa Competizione (GT3/GT4). Perform a detailed analysis of car balance and handling based on the available telemetry channels.
## Important note
ACC does not export car setup parameters to telemetry — the in-game setup table is all zeros and is omitted from the report. The balance and handling analysis is derived entirely from **telemetry channels**: corner balance, tyre temperatures and pressures, brake temperatures, and suspension travel.
## Data format
**Corner balance** — per corner:
- `gLat_max` — peak lateral G-force [G]
- `steer/g` — steering angle to lateral G ratio: high = steering a lot for the G = understeer
- `front|slip|` / `rear|slip|` — average absolute slip of front/rear axle in the corner zone
- `balance` — outcome: US (front axle slips more), OS (rear), — (neutral)
**Tyres** — per wheel:
- `wearΔ%` — wear over the lap
- `surf min/avg/max °C` — surface temperature
- `carc min/avg/max °C` — carcass temperature
- `pressure` — hot pressure [PSI]
**Brakes** — min/avg/max temperature per wheel [°C]
**Suspension**:
- Rake — average front vs rear ride height [mm]; positive rake = nose higher than tail
- Roll per corner — body roll in each corner [mm]; shows outer-wheel loading
**Lap phases** — average tyre surface temperature across three equal lap segments
---
## Task
Analyse each point. Back every conclusion with numbers from the report.
### 1. Mechanical balance
- Corner balance: systematic US or OS across most corners?
- Analyse fast (high gLat) vs slow (low gLat) corners separately — different patterns?
- High `steer/g` = car demands more steering for the load level = understeer tendency; low `steer/g` with high rear slip = oversteer
- GT cars are sensitive to front/rear spring and anti-roll bar balance: abnormal Roll per corner may indicate a soft spring or weak anti-roll bar on one axle
### 2. Differential and traction
- From `spin%` in the corner table: high rear slip on exit indicates an aggressive on-throttle differential lock or premature throttle application
- If `spin%` is consistently high in slow corners but modest in fast corners — on-throttle differential setting is the likely cause
- Check whether rear slip is symmetric across left and right sides — asymmetry suggests a geometry or tyre pressure issue
### 3. Tyre thermal window
- Pirelli GT tyre operating window is approximately ~70–90°C surface; outside this range grip is compromised
- Are tyres in window; who overheats, who underheats
- Surface vs carcass delta: large gap = tyre overheating on the outside but cold carcass (delamination risk), or vice versa
- Wear: even across 4 wheels — abnormally high wear on one wheel signals load imbalance
- Lap phases: normal warm-up pattern — should rise in phase 1 and stabilise
### 4. Tyre pressures
- Hot pressure (from Tyres): too high = overheating and loss of contact patch; too low = under-temperature, tyre wanders
- F/R pressure balance: asymmetry affects mechanical balance
- L/R pressure difference on the same axle — sign of asymmetric heating or incorrect cold starting pressure
### 5. Brakes
- Temperature range: <200°C = cold (poor braking), 350–800°C = normal GT working range, >900°C = dangerous overheating
- L/R asymmetry: different temperatures on same axle = uneven pad wear or brake balance issue
- If rears are consistently hotter than fronts — bias is too far rearward; if fronts dominate — bias is forward; both reduce braking stability
### 6. Suspension
- Rake: moderate positive rake is standard for GT; very high rake increases rear aerodynamic load but can cause a lazy front on turn-in
- Roll per corner: typical roll 3–8 mm; large roll = soft suspension or weak anti-roll bars; small = stiff, sensitive to bumps
- L/R roll asymmetry in the same corner indicates uneven suspension travel or load distribution
### 7. Recommendations
Per identified issue — what specifically to change (direction of adjustment) and expected effect. Prioritise by lap time impact.
---
Below is the attached report. Begin analysis.

View file

@ -0,0 +1,92 @@
You are an experienced racing engineer. The following is single-lap telemetry from Assetto Corsa Competizione (GT3/GT4). Perform a detailed driving technique analysis.
## Data format
The report contains:
**Corners** — corner table. Per corner:
- `brake@m` — distance at which braking starts from lap start [m]
- `Ventry / Vmin / Vexit` — entry / apex / exit speed [km/h]
- `apex@m` — distance of the minimum-speed point
- `gear` — gear at apex
- `fullThr@m` — distance of first sustained full throttle after apex
- `maxBrake%` — peak brake pressure
- `gLat` — peak lateral G-force [G]
- `time s` — time in the corner zone
- `lock%` — peak relative front wheel slip under braking (negative; −10% or below = LOCK flag)
- `spin%` — peak relative rear wheel slip on throttle (positive; 8%+ = SPIN flag)
- `coast m` — distance with neither brake nor full throttle between braking and throttle application; 0 = abrupt transition
- `Tr peak °C` — peak rear tyre temperature in the zone
**Straights** — Vmax, time.
**Driving cost** — whole lap: fuel used [L], per-tyre wear, peak brake temperatures.
**Trace** — per-sample telemetry by distance:
`dist` `time` `spd` `thr` `brk` `steer` `gear` `gLat` `gLon` `fuel` `Tf` `Tr` `rpm` `slipF` `slipR` `yaw`
Key columns:
- `slipF` — front relative wheel slip (% of speed): negative under braking = locking tendency
- `slipR` — rear relative slip: positive on throttle = wheelspin
- `yaw` — yaw rate [rad/s]: large `steer` + small `yaw` = car not rotating (understeer)
- `Tf / Tr` — average front/rear tyre surface temperature [°C]
---
## Task
Analyse each point below. Back every conclusion with specific numbers from the tables and trace — no general statements without data references.
### 1. Braking
Per corner with non-zero `brake@m`:
- Evaluate the braking point and how fully the brakes are used (`maxBrake%`)
- Analyse `lock%`: how severe is the lockup, isolated or repeated across corners
- Evaluate `coast m`: long coast = cautious transition or trail braking; zero = snap release
- Compare `Ventry` and `Vmin`: large delta = hard braking deep into the corner; small = braking complete before turn-in
### 2. Apexes
Per corner:
- `Vmin` in context of the corner's speed class — over-braked or on the limit?
- `gear` at apex — too low (excess torque, wheelspin risk on exit)?
- `gLat` vs `Vmin`: high gLat at low Vmin = good apex loading; low gLat = missed apex
### 3. Exits
Per corner:
- `fullThr@m` vs `apex@m`: smaller gap = more aggressive exit
- `spin%`: high value = wheelspin on exit — throttle applied too early or too aggressively
- `Vexit`: final result — does it match the corner's speed potential
- From Trace: how `thr` builds after apex — stepped or smooth; any drops after reaching full throttle
### 4. Steering and handling
From Trace (focus on cornering zones):
- `steer`: any sharp sawtooth inputs or excessive corrections
- `steer` + `yaw`: zones where yaw lags far behind steer — specific understeer points
- Corners where the car refuses to rotate (high steer, weak yaw response)
### 5. Throttle control
GT cars have no hybrid system — traction is managed purely by the right foot and the differential. From Trace:
- How throttle is applied on exits from slow corners: abrupt or progressive
- Where `slipR` exceeds normal working slip — excessive wheelspin destroys tyres and slows exits
- Corners where the driver clearly over-applies throttle before the car is ready to take it
### 6. Thermal management
- `Tf / Tr` from Trace: how temperatures evolve through the lap, where peaks occur, any overheating signs
- Pirelli GT tyre operating window is approximately ~70–90°C; below = cold tyres, incomplete grip; above = degradation risk
- `Tr peak` per corner: which corners load the rear tyres hardest
- Brake temps from Driving cost: overheating (>900°C is a concern for GT), L/R or F/R asymmetry
### 7. Summary
- **Top-3 time-loss zones** with specific corners and root causes
- **Top-3 strengths** — what is done well and why
- **Recommendations** per problem area: what specifically to change in driving technique
---
Below is the attached report. Begin analysis.

64
prompts/ru/acc/compare.md Normal file
View file

@ -0,0 +1,64 @@
Ты — гоночный инженер, сравнивающий два круга одного пилота в Assetto Corsa Competizione (GT3/GT4). Первый круг (Lap 1) — эталонный. Найди, где и почему один круг быстрее другого, и что это говорит о пилотировании.
## Формат данных
**Шапка** — сравнительная таблица: laptime, секторы S1/S2/S3, шина, топливо на старте круга [л], износ шин, погода.
**Corners** — по каждому повороту:
- `Vmin L1 / Vmin L2` — минимальная скорость в апексе [км/ч]
- `time L1 / time L2` — время в зоне поворота [с]
- `Δt seg` — разница времён в данном повороте (L2 − L1): отрицательное = L2 быстрее, положительное = L1 быстрее
- `Δt cum` — накопленная дельта к концу этого поворота: показывает общий счёт на данном участке круга
**Delta trace** — построчно по дистанции:
- `dist` — дистанция [м]
- `spd_L1 / spd_L2` — скорость каждого круга [км/ч]
- `Δt` — накопленная дельта L2 − L1 [с]: отрицательное = L2 ведёт, положительное = L1 ведёт
---
## Задача
Разбери каждый пункт. Каждый вывод подкрепляй конкретными числами Δt, дистанциями, скоростями.
### 1. Общая картина
- Итоговая разница времён и в каком секторе она сформировалась
- Был ли разрыв стабильным по кругу или резко возник в одном месте
- Влияние топлива и износа шин: если круги из разных стинтов — учти поправку на загрузку и деградацию
### 2. Разбор по поворотам
По каждому повороту с ненулевым `Δt seg`:
- Кто быстрее и на сколько
- `Vmin L1 vs L2`: кто выше скорость в апексе — тот либо позже тормозил, либо лучше нашёл траекторию
- Если Vmin выше, но `time` больше — апекс быстрее, но выход хуже (потеря на тяге или траектории)
- Если Vmin ниже, но `time` меньше — лучший выход компенсировал потерю в апексе
- `Δt cum` после поворота: нарастает ли отрыв или следующий участок его нивелирует
### 3. Анализ дельта-трассы
Работай с `Δt` по дистанции:
- **Где дельта строится резко** (крутой излом): это критические точки — торможение или тяга, где стили расходятся
- **Где дельта стабильна**: оба круга едут одинаково, эти зоны не приоритет
- **Тормозные зоны**: если Δt уменьшается при торможении — L2 тормозит позже (или эффективнее)
- **Тяговые зоны**: если после апекса `spd_L2 > spd_L1` — L2 лучше тянет (раньше газ или меньше буксовки); в GT это особенно важно на выходах из шпилек, где тяговый контроль — ключ
- **Прямые**: разница скоростей на прямой = результат предыдущего выхода из поворота, а не самой прямой
### 4. Паттерны
- Один круг систематически лучше в медленных поворотах, другой — в быстрых?
- Один стиль рискует на входах, другой — на выходах?
- Есть ли повороты, где «медленный» круг на самом деле быстрее в этом конкретном месте — и почему?
### 5. Итог и приоритеты
- **Что взять из быстрого круга**: конкретные повороты и техника
- **Приоритетные зоны для работы**: где наибольший потенциал (самые большие `|Δt seg|`)
- **Что не копировать**: если в «быстром» круге есть места, где он всё же медленнее — зафиксируй
---
Ниже прикреплён отчёт сравнения. Начинай анализ.

95
prompts/ru/acc/race.md Normal file
View file

@ -0,0 +1,95 @@
Ты — гоночный инженер, анализирующий телеметрию целой гонки в Assetto Corsa Competizione (GT3/GT4). Перед тобой отчёт по всем кругам: времена, стинты, износ шин и тепловой режим. Проведи детальный разбор гонки.
## Формат данных
Отчёт содержит:
**Lap times** — таблица кругов:
- `Lap` — номер круга
- `Time` — время круга в формате `M:SS.mmm`
- `S1 / S2 / S3` — времена секторов [с]
- `Tyre` — тип шины (dry/wet, состав)
- `Age` — количество кругов на данном комплекте (1 = первый круг на новом комплекте)
- `Fuel L` — топливо на старте круга [л]
- `Note` — флаги: `FAST` = быстрейший действительный круг, `PIT` = пит-лэп, `INV` = недействительный круг
**Stint summary** — сводка по стинтам:
- `Stint` — номер стинта
- `Laps` — количество кругов в стинте
- `Tyre` — тип шины
- `Laps on tyre` — всего кругов на комплекте
- `Avg pace` — средний темп (без пит-лэпов и недействительных)
- `Best / Worst` — лучший и худший круг стинта
- `Deg s/lap` — деградация: наклон тренда времён по тирейджу (положительное = замедление, отрицательное = улучшение)
**Tyre wear per lap** — износ по кругам:
- `FL / FR / RL / RR` — процент износа каждой шины (последний замер круга)
**Thermal summary per lap** — тепловой режим:
- `Tf avg°C` — средняя максимальная температура передних шин за круг
- `Tr avg°C` — задних шин
- `Brk FL max°C` — максимальная температура переднего левого тормоза
- `Brk RR max°C` — заднего правого тормоза
---
## Задача
Разбери каждый из пунктов. Каждый вывод подкрепляй конкретными числами из таблиц — не делай общих утверждений без ссылки на данные.
### 1. Общая картина гонки
- Как менялся темп по ходу гонки: прогрессия или деградация?
- Были ли выбросы (аномально медленные или быстрые круги) — назови номера кругов и причину (PIT, INV, safety car?)
- Сравни лучший и средний темп: насколько велик разрыв?
- Итоговая оценка гонки в одном предложении
### 2. Анализ стинтов
По каждому стинту из `Stint summary`:
- Средний темп и его относительное качество (лучший / худший стинт?)
- Разброс `Best–Worst`: большой разброс = непостоянный темп, малый = стабильный
- Сравни стинты между собой: где пилот ехал быстрее и почему (шина, топливо, трек, условия?)
- Был ли питстоп своевременным: не слишком ли рано/поздно по тренду деградации?
### 3. Деградация резины
По `Deg s/lap` и таблице `Tyre wear`:
- В каком стинте деградация наибольшая и наименьшая?
- Сопоставь `Deg s/lap` с фактическим приростом износа по кругам — соответствуют ли друг другу?
- Когда резина «умерла»: определи круг, после которого темп резко упал
- Равномерность износа: есть ли асимметрия FL/FR или RL/RR?
### 4. Топливный эффект
GT-машины заправляются в литрах; по мере выжигания топлива машина становится легче.
- По первому стинту (или длинному стинту): сопоставь `Fuel L` и `Time` по кругам
- Есть ли видимая корреляция между расходом топлива и улучшением темпа по мере облегчения машины?
- Если темп в начале стинта плохой — прогрев шин или тяжёлая машина?
- Оцени, согласуется ли динамика темпа с ожидаемым эффектом снижения веса
### 5. Тепловой менеджмент
По `Thermal summary`:
- Как меняются температуры шин по кругам в каждом стинте: прогрев, стабилизация, перегрев?
- Рабочий диапазон шин Pirelli (GT) ориентировочно ~70–90°C; резкий рост выше с одновременным ухудшением темпа — признак перегрева
- Температуры тормозов: рост по дистанции или стабильные? Асимметрия FL vs RR?
- В каких кругах тепловой режим был критическим?
### 6. Лучшие и худшие круги
- `FAST` круг: в каком стинте, на каком тирейдже, при каком запасе топлива?
- Почему лучший круг получился лучшим — трек rubber? чистый воздух? прохлада?
- Худший действительный круг (не INV, не PIT): причина — деградация, трафик, ошибка?
- Есть ли интересные паттерны: «out lap» всегда медленный, «in lap» провальный по темпу?
### 7. Итог и рекомендации
- **Что сработало:** что в стратегии и пилотировании было сделано хорошо
- **Главные потери:** конкретные круги и зоны, где потеряно больше всего
- **Стратегические рекомендации:** момент питстопа, выбор состава, длина стинтов
- **Техника пилотирования:** если деградация аномальна — что нужно изменить в стиле езды
---
Ниже прикреплён отчёт гонки. Начинай анализ.

80
prompts/ru/acc/setup.md Normal file
View file

@ -0,0 +1,80 @@
Ты — опытный гоночный инженер. Перед тобой телеметрический отчёт за один круг в Assetto Corsa Competizione (GT3/GT4). Проведи детальный разбор баланса и поведения машины на основе доступных телеметрических данных.
## Важное замечание
ACC не экспортирует параметры сетапа в телеметрию — таблица сетапа в игре содержит нули и в отчёт не включается. Анализ баланса и управляемости строится **исключительно по телеметрическим каналам**: угловой баланс, температуры и давления шин, тормоза, ходы подвески.
## Формат данных
**Corner balance** — по каждому повороту:
- `gLat_max` — пиковая боковая перегрузка [G]
- `steer/g` — отношение угла руля к боковой G: высокое = рулишь много на данном G = understeer
- `front|slip|` / `rear|slip|` — средний модуль скольжения переднего / заднего осей в зоне поворота
- `balance` — итог: US (передняя ось проскальзывает сильнее), OS (задняя), — (нейтрально)
**Tyres** — по каждому колесу:
- `wearΔ%` — износ за круг
- `surf min/avg/max °C` — температура поверхности
- `carc min/avg/max °C` — температура каркаса
- `pressure` — горячее давление [PSI]
**Brakes** — температура мин/сред/макс по каждому колесу [°C]
**Suspension**:
- Rake — средняя высота носа vs кормы [мм]; положительный rake = нос выше кормы
- Roll по поворотам — крен кузова в каждом повороте [мм]; показывает загрузку внешних колёс
**Lap phases** — средняя температура поверхности шин по трём равным отрезкам круга
---
## Задача
Разбери каждый пункт. Каждый вывод подкрепляй числами из отчёта.
### 1. Механический баланс
- Corner balance: есть ли систематический US или OS по большинству поворотов
- Отдельно рассмотри быстрые (высокий gLat) vs медленные (низкий gLat) повороты — разные ли паттерны
- Высокий `steer/g` = машина требует больше руля на данной загрузке = недостаточная поворачиваемость; низкий `steer/g` при высоком скольжении задней оси = избыточная
- GT-машины чувствительны к балансу жёсткостей подвески и антикренов: аномальный Roll в поворотах может указывать на мягкую пружину или слабый стабилизатор на одной оси
### 2. Дифференциал и тяга
- По `spin%` из corner table: высокое скольжение задних на выходе указывает на агрессивный блок дифференциала на газу или слишком раннюю подачу газа
- Если `spin%` системно высок в медленных поворотах, но невелик в быстрых — причина, вероятно, в настройке OnThrottle дифференциала
- Оцени, насколько равномерно скольжение по левой и правой сторонам — асимметрия = проблема с геометрией или давлением шин
### 3. Тепловой режим шин
- Рабочий диапазон шин Pirelli (GT) ориентировочно ~70–90°C поверхности; выход за границы снижает сцепление
- Попадают ли шины в рабочий диапазон; кто перегревается, кто недогревается
- Разница поверхности и каркаса: большая разница = шина перегревается снаружи, но каркас холодный (риск расслоения), или наоборот
- Износ: равномерный ли по 4 колёсам — аномально высокий износ одного колеса указывает на дисбаланс нагрузки
- Lap phases: нормальный ли прогрев — должен расти в первой фазе и стабилизироваться
### 4. Давление шин
- Горячее давление (из Tyres): слишком высокое = перегрев и потеря контакта с дорогой; слишком низкое = недогрев, шина «плывёт»
- Баланс F/R давления: асимметрия влияет на механический баланс
- Разница L/R давления на одной оси — признак асимметричного нагрева или неправильного старта давления в холодном состоянии
### 5. Тормоза
- Диапазон температур: <200°C = холодные (плохое торможение), 350–800°C = рабочий диапазон GT, >900°C = опасный перегрев
- Асимметрия L/R: разные температуры на одной оси = неравномерный износ колодок или проблема с балансом тормозного усилия
- Если задние тормоза стабильно горячее передних — bias смещён назад; если передние — вперёд; оба отклонения ухудшают управляемость при торможении
### 6. Подвеска
- Rake: умеренный положительный rake стандартен для GT; очень большой rake увеличивает аэродинамическую прижимную силу кормы, но снижает нейтральность на входе
- Roll по поворотам: нормальный крен 3–8 мм; большой крен = мягкая подвеска или слабые антикрены; малый = жёсткая, чувствительная к буграм
- Асимметрия Roll L/R в одном повороте указывает на разные ходы подвески или неравномерную нагрузку
### 7. Рекомендации
По каждой выявленной проблеме — что конкретно менять (направление изменения настройки) и каков ожидаемый эффект. Приоритизируй по влиянию на время круга.
---
Ниже прикреплён отчёт. Начинай анализ.

View file

@ -0,0 +1,92 @@
Ты — опытный инженер-аналитик гоночной команды. Перед тобой телеметрия одного круга в Assetto Corsa Competizione (GT3/GT4). Проведи максимально детальный разбор стиля пилотирования.
## Формат данных
Отчёт содержит:
**Corners** — таблица поворотов. По каждому:
- `brake@m` — дистанция начала торможения от старта круга
- `Ventry / Vmin / Vexit` — скорость входа / апекса / выхода [км/ч]
- `apex@m` — дистанция точки минимальной скорости
- `gear` — передача в апексе
- `fullThr@m` — дистанция первого устойчивого полного газа после апекса
- `maxBrake%` — пиковое давление на тормоз
- `gLat` — пиковая боковая перегрузка [G]
- `time s` — время в зоне поворота
- `lock%` — пик относительного скольжения передних колёс при торможении (отрицательное; −10% и ниже = флаг LOCK)
- `spin%` — пик относительного скольжения задних колёс на газу (положительное; 8%+ = флаг SPIN)
- `coast m` — дистанция без тормоза и без полного газа между торможением и тягой; 0 = резкий переход
- `Tr peak °C` — пиковая температура задней шины в зоне
**Straights** — прямые: Vmax, время.
**Driving cost** — за весь круг: расход топлива [л], износ каждой шины, пиковые температуры тормозов.
**Trace** — построчная телеметрия по дистанции:
`dist` `time` `spd` `thr` `brk` `steer` `gear` `gLat` `gLon` `fuel` `Tf` `Tr` `rpm` `slipF` `slipR` `yaw`
Пояснения к ключевым колонкам:
- `slipF` — относительное скольжение передних колёс (% от скорости): уход в минус при торможении = блокировка
- `slipR` — задних колёс: положительный на газу = пробуксовка
- `yaw` — угловая скорость рыскания [рад/с]: большой `steer` + маленький `yaw` = машина не поворачивает (understeer)
- `Tf / Tr` — средняя температура передних / задних шин [°C]
---
## Задача
Разбери каждый из пунктов ниже. Каждый вывод подкрепляй конкретными числами из таблиц и трассы — не делай общих утверждений без ссылки на данные.
### 1. Торможения
По каждому повороту с ненулевым `brake@m`:
- Оцени точку торможения и полноту использования тормозов (`maxBrake%`)
- Проанализируй `lock%`: насколько сильная блокировка, стабильна ли она или разовая
- Оцени `coast m`: длинный накат — осторожный переход или trail braking; нулевой — резкий перенос ноги
- Сравни `Ventry` и `Vmin`: большая разница = активное торможение в повороте; малая = торможение завершено до поворота
### 2. Апексы
По каждому повороту:
- `Vmin` в контексте скоростного класса поворота — перетормозил или точно?
- `gear` в апексе — не слишком ли низкая передача (лишний момент, риск буксовки на выходе)
- `gLat` vs `Vmin`: высокий gLat при низком Vmin = хорошая загрузка в апексе; низкий gLat = промах мимо вершины
### 3. Выходы
По каждому повороту:
- `fullThr@m` относительно `apex@m`: чем меньше разрыв, тем агрессивнее выход
- `spin%`: высокое значение = буксовка на выходе — слишком рано или слишком резко газ
- `Vexit`: итоговый результат выхода — соответствует ли скоростному потенциалу поворота
- По Trace: как нарастает `thr` после апекса — ступенчато или плавно; есть ли просадки после полного газа
### 4. Работа руля и поворачиваемость
По Trace (фокус на зоны поворотов):
- `steer`: нет ли резких пилообразных движений или избыточных коррекций
- `steer` + `yaw`: в каких зонах yaw сильно отстаёт от steer — конкретные точки understeer
- В каких поворотах машина отказывается поворачивать (большой steer, слабый yaw)
### 5. Тяговый контроль
GT-машины не имеют гибридных систем — тяга управляется исключительно правой ногой и дифференциалом. По Trace:
- Как подаётся газ на выходе из медленных поворотов: резко или прогрессивно
- Где `slipR` выходит за пределы нормального скольжения — чрезмерная буксовка разрушает шины и замедляет выход
- Есть ли повороты, где пилот явно «переливает» газ раньше, чем машина готова к тяге
### 6. Тепловой режим
- `Tf / Tr` по Trace: как меняется температура по кругу, где пики, есть ли перегрев
- Рабочий диапазон шин Pirelli (GT) ориентировочно ~70–90°C; ниже = резина холодная, сцепление неполное; выше = риск деградации
- `Tr peak` по поворотам: какие повороты наиболее нагружают задние шины
- Brake temps из Driving cost: перегрев (>900°C — тревожный порог для GT), асимметрия L/R или F/R
### 7. Итог
- **Топ-3 зоны потери времени** с конкретными поворотами и причинами
- **Топ-3 сильные стороны** — что делается хорошо и почему
- **Рекомендации** по каждой проблемной зоне: что конкретно изменить в технике пилотирования
---
Ниже прикреплён отчёт. Начинай анализ.

View file

@ -61,6 +61,8 @@ def main() -> None:
parser.add_argument('mode', choices=['technique', 'setup', 'compare', 'race', 'profile', 'rename', 'gui']) parser.add_argument('mode', choices=['technique', 'setup', 'compare', 'race', 'profile', 'rename', 'gui'])
parser.add_argument('files', nargs='*') parser.add_argument('files', nargs='*')
parser.add_argument('--lang', choices=['ru', 'en'], default='ru') parser.add_argument('--lang', choices=['ru', 'en'], default='ru')
parser.add_argument('--game', choices=['auto', 'f1', 'acc'], default='auto',
help='override game detection (default: auto, from the Game field)')
parser.add_argument('--no-prompt', action='store_true', parser.add_argument('--no-prompt', action='store_true',
help='omit the embedded analysis prompt (for agent pipelines)') help='omit the embedded analysis prompt (for agent pipelines)')
parser.add_argument('--force', action='store_true', parser.add_argument('--force', action='store_true',
@ -70,6 +72,7 @@ def main() -> None:
mode: str = args.mode mode: str = args.mode
files: list[str] = args.files files: list[str] = args.files
lang: str = args.lang lang: str = args.lang
game: str | None = None if args.game == 'auto' else args.game
include_prompt: bool = not args.no_prompt include_prompt: bool = not args.no_prompt
force: bool = args.force force: bool = args.force
@ -105,18 +108,18 @@ def main() -> None:
if mode == 'technique': if mode == 'technique':
out = report_path(files[0], 'technique') out = report_path(files[0], 'technique')
_emit(out, [files[0]], _emit(out, [files[0]],
lambda: report_technique.generate(load_lap(files[0]), out, lang, include_prompt)) lambda: report_technique.generate(load_lap(files[0]), out, lang, include_prompt, game))
elif mode == 'setup': elif mode == 'setup':
out = report_path(files[0], 'setup') out = report_path(files[0], 'setup')
_emit(out, [files[0]], _emit(out, [files[0]],
lambda: report_setup.generate(load_lap(files[0]), out, lang, include_prompt)) lambda: report_setup.generate(load_lap(files[0]), out, lang, include_prompt, game))
elif mode == 'compare': elif mode == 'compare':
stem_b = os.path.splitext(os.path.basename(files[1]))[0] stem_b = os.path.splitext(os.path.basename(files[1]))[0]
out = report_path(files[0], 'compare', stem_b) out = report_path(files[0], 'compare', stem_b)
_emit(out, files, _emit(out, files,
lambda: report_compare.generate([load_lap(f) for f in files], out, lang, include_prompt)) lambda: report_compare.generate([load_lap(f) for f in files], out, lang, include_prompt, game))
elif mode in ('race', 'profile'): elif mode in ('race', 'profile'):
race_dir = os.path.abspath(files[0]) race_dir = os.path.abspath(files[0])
@ -129,9 +132,12 @@ def main() -> None:
dirname = os.path.basename(race_dir.rstrip('/\\')) dirname = os.path.basename(race_dir.rstrip('/\\'))
suffix = '_race.md' if mode == 'race' else '_profile.md' suffix = '_race.md' if mode == 'race' else '_profile.md'
out = os.path.join(race_dir, 'reports', dirname + suffix) out = os.path.join(race_dir, 'reports', dirname + suffix)
gen_mod = report_race if mode == 'race' else report_profile if mode == 'race':
_emit(out, csv_paths, _emit(out, csv_paths,
lambda: gen_mod.generate([load_lap(p) for p in csv_paths], out, lang, include_prompt)) lambda: report_race.generate([load_lap(p) for p in csv_paths], out, lang, include_prompt, game))
else:
_emit(out, csv_paths,
lambda: report_profile.generate([load_lap(p) for p in csv_paths], out, lang, include_prompt))
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -5,112 +5,141 @@ import tkinter as tk
from tkinter import filedialog, scrolledtext, ttk from tkinter import filedialog, scrolledtext, ttk
from .parser import load_lap from .parser import load_lap
from .report_common import report_path from .report_common import game_of, report_path
from . import report_technique, report_setup, report_compare, report_race, report_profile from . import report_technique, report_setup, report_compare, report_race, report_profile
from .rename import rename_unprocessed from .rename import rename_unprocessed
# project root = parent of the tool\ dir
_PROJECT_ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..')) _PROJECT_ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..'))
_RACES_DIR = os.path.join(_PROJECT_ROOT, 'races') _RACES_DIR = os.path.join(_PROJECT_ROOT, 'races')
# Maps tab id → display name used in mismatch warnings.
_TAB_GAME = {'f1': 'F1 22', 'acc': 'ACC'}
def _race_output_path(race_dir: str) -> str:
dirname = os.path.basename(race_dir.rstrip('/\\')) class _TabState:
return os.path.join(race_dir, 'reports', dirname + '_race.md') """All mutable state that belongs to one tab."""
def __init__(self) -> None:
self.mode = tk.StringVar(value='technique')
self.lang = tk.StringVar(value='ru')
self.include_prompt = tk.BooleanVar(value=True)
self.path_var = tk.StringVar()
self.selection: list[str] = []
self.last_dir: dict[str, str] = {}
self.log_queue: queue.Queue[str | None] = queue.Queue()
self.busy = False
# widgets set by _build_panel
self.log_widget: scrolledtext.ScrolledText | None = None
self.btn_generate: ttk.Button | None = None
self.btn_rename: ttk.Button | None = None
class _App: class _App:
def __init__(self, root: tk.Tk) -> None: def __init__(self, root: tk.Tk) -> None:
self._root = root self._root = root
root.title('F1 Telemetry Analyzer') root.title('Telemetry Analyzer (F1 22 / ACC)')
root.resizable(True, True) root.resizable(True, True)
self._mode = tk.StringVar(value='technique') self._tabs: dict[str, _TabState] = {
self._lang = tk.StringVar(value='ru') 'f1': _TabState(),
self._include_prompt = tk.BooleanVar(value=True) 'acc': _TabState(),
self._selection: list[str] = [] }
self._last_dir: dict[str, str] = {}
self._log_queue: queue.Queue[str | None] = queue.Queue()
self._busy = False
self._build_ui() self._build_ui()
self._poll_log() self._poll_all_logs()
# ── UI construction ──────────────────────────────────────────────────────
def _build_ui(self) -> None: def _build_ui(self) -> None:
nb = ttk.Notebook(self._root)
nb.pack(fill='both', expand=True, padx=6, pady=6)
for tab_id, label in [('f1', 'F1 22'), ('acc', 'ACC')]:
frame = ttk.Frame(nb)
nb.add(frame, text=label)
self._build_panel(frame, tab_id)
def _build_panel(self, parent: ttk.Frame, tab_id: str) -> None:
st = self._tabs[tab_id]
pad = {'padx': 8, 'pady': 4} pad = {'padx': 8, 'pady': 4}
mode_frame = ttk.LabelFrame(self._root, text='Mode') mode_frame = ttk.LabelFrame(parent, text='Mode')
mode_frame.pack(fill='x', **pad) mode_frame.pack(fill='x', **pad)
for label, value in [('Technique', 'technique'), ('Setup', 'setup'), for label, value in [('Technique', 'technique'), ('Setup', 'setup'),
('Compare', 'compare'), ('Race', 'race'), ('Compare', 'compare'), ('Race', 'race'),
('Profile', 'profile')]: ('Profile', 'profile')]:
ttk.Radiobutton(mode_frame, text=label, variable=self._mode, ttk.Radiobutton(mode_frame, text=label, variable=st.mode,
value=value).pack(side='left', padx=6, pady=4) value=value).pack(side='left', padx=6, pady=4)
lang_frame = ttk.LabelFrame(self._root, text='Prompt language / Язык промпта') lang_frame = ttk.LabelFrame(parent, text='Prompt language / Язык промпта')
lang_frame.pack(fill='x', **pad) lang_frame.pack(fill='x', **pad)
ttk.Radiobutton(lang_frame, text='RU', variable=self._lang, ttk.Radiobutton(lang_frame, text='RU', variable=st.lang,
value='ru').pack(side='left', padx=6, pady=4) value='ru').pack(side='left', padx=6, pady=4)
ttk.Radiobutton(lang_frame, text='EN', variable=self._lang, ttk.Radiobutton(lang_frame, text='EN', variable=st.lang,
value='en').pack(side='left', padx=6, pady=4) value='en').pack(side='left', padx=6, pady=4)
ttk.Checkbutton(lang_frame, text='Включить промпт в отчёт / Include prompt', ttk.Checkbutton(lang_frame, text='Включить промпт в отчёт / Include prompt',
variable=self._include_prompt).pack(side='left', padx=16, pady=4) variable=st.include_prompt).pack(side='left', padx=16, pady=4)
input_frame = ttk.LabelFrame(self._root, text='Input') input_frame = ttk.LabelFrame(parent, text='Input')
input_frame.pack(fill='x', **pad) input_frame.pack(fill='x', **pad)
self._path_var = tk.StringVar() ttk.Entry(input_frame, textvariable=st.path_var,
ttk.Entry(input_frame, textvariable=self._path_var,
state='readonly', width=60).pack(side='left', fill='x', state='readonly', width=60).pack(side='left', fill='x',
expand=True, padx=(6, 2), pady=4) expand=True, padx=(6, 2), pady=4)
ttk.Button(input_frame, text='Browse…', ttk.Button(input_frame, text='Browse…',
command=self._browse).pack(side='left', padx=(2, 6), pady=4) command=lambda: self._browse(tab_id)).pack(side='left', padx=(2, 6), pady=4)
btn_frame = ttk.Frame(self._root) btn_frame = ttk.Frame(parent)
btn_frame.pack(fill='x', **pad) btn_frame.pack(fill='x', **pad)
self._btn_generate = ttk.Button(btn_frame, text='Generate report', st.btn_generate = ttk.Button(btn_frame, text='Generate report',
command=self._on_generate) command=lambda: self._on_generate(tab_id))
self._btn_generate.pack(side='left', padx=(0, 4)) st.btn_generate.pack(side='left', padx=(0, 4))
self._btn_rename = ttk.Button(btn_frame, text='Rename unprocessed (add lap #)', st.btn_rename = ttk.Button(btn_frame, text='Rename unprocessed (add lap #)',
command=self._on_rename) command=lambda: self._on_rename(tab_id))
self._btn_rename.pack(side='left', padx=4) st.btn_rename.pack(side='left', padx=4)
ttk.Button(btn_frame, text='Open races folder', ttk.Button(btn_frame, text='Open races folder',
command=self._open_reports).pack(side='left', padx=4) command=self._open_reports).pack(side='left', padx=4)
log_frame = ttk.LabelFrame(self._root, text='Log') log_frame = ttk.LabelFrame(parent, text='Log')
log_frame.pack(fill='both', expand=True, **pad) log_frame.pack(fill='both', expand=True, **pad)
self._log = scrolledtext.ScrolledText(log_frame, state='disabled', st.log_widget = scrolledtext.ScrolledText(log_frame, state='disabled',
wrap='word', height=16) wrap='word', height=16)
self._log.pack(fill='both', expand=True, padx=4, pady=4) st.log_widget.pack(fill='both', expand=True, padx=4, pady=4)
# ── Browse ───────────────────────────────────────────────────────────────
def _browse(self, tab_id: str) -> None:
st = self._tabs[tab_id]
mode = st.mode.get()
init = st.last_dir.get(mode, _RACES_DIR)
def _browse(self) -> None:
mode = self._mode.get()
init = self._last_dir.get(mode, _RACES_DIR)
if mode in ('race', 'profile'): if mode in ('race', 'profile'):
d = filedialog.askdirectory(title='Select race folder', initialdir=init) d = filedialog.askdirectory(title='Select race folder', initialdir=init)
if d: if d:
self._selection = [d] st.selection = [d]
self._path_var.set(d) st.path_var.set(d)
self._last_dir[mode] = d st.last_dir[mode] = d
elif mode == 'compare': elif mode == 'compare':
files = filedialog.askopenfilenames( files = filedialog.askopenfilenames(
title='Select 2+ CSV files', initialdir=init, title='Select 2+ CSV files', initialdir=init,
filetypes=[('CSV', '*.csv')]) filetypes=[('CSV', '*.csv')])
if files: if files:
self._selection = list(files) st.selection = list(files)
self._path_var.set(' | '.join(os.path.basename(f) for f in files)) st.path_var.set(' | '.join(os.path.basename(f) for f in files))
self._last_dir[mode] = os.path.dirname(files[0]) st.last_dir[mode] = os.path.dirname(files[0])
else: else:
f = filedialog.askopenfilename( f = filedialog.askopenfilename(
title='Select CSV file', initialdir=init, title='Select CSV file', initialdir=init,
filetypes=[('CSV', '*.csv')]) filetypes=[('CSV', '*.csv')])
if f: if f:
self._selection = [f] st.selection = [f]
self._path_var.set(f) st.path_var.set(f)
self._last_dir[mode] = os.path.dirname(f) st.last_dir[mode] = os.path.dirname(f)
def _validate_selection(self) -> str | None: # ── Validation / busy state / logging ────────────────────────────────────
mode = self._mode.get()
sel = self._selection def _validate_selection(self, tab_id: str) -> str | None:
st = self._tabs[tab_id]
mode = st.mode.get()
sel = st.selection
if not sel: if not sel:
return 'No file selected' return 'No file selected'
if mode in ('technique', 'setup') and len(sel) != 1: if mode in ('technique', 'setup') and len(sel) != 1:
@ -121,66 +150,76 @@ class _App:
return f'{mode} requires a directory' return f'{mode} requires a directory'
return None return None
def _set_busy(self, busy: bool) -> None: def _set_busy(self, tab_id: str, busy: bool) -> None:
self._busy = busy st = self._tabs[tab_id]
st.busy = busy
state = 'disabled' if busy else 'normal' state = 'disabled' if busy else 'normal'
self._btn_generate.configure(state=state) st.btn_generate.configure(state=state)
self._btn_rename.configure(state=state) st.btn_rename.configure(state=state)
def _log_append(self, text: str) -> None: def _log_append(self, tab_id: str, text: str) -> None:
self._log.configure(state='normal') w = self._tabs[tab_id].log_widget
self._log.insert('end', text + '\n') w.configure(state='normal')
self._log.see('end') w.insert('end', text + '\n')
self._log.configure(state='disabled') w.see('end')
w.configure(state='disabled')
def _poll_log(self) -> None: def _push(self, tab_id: str, msg: str) -> None:
self._tabs[tab_id].log_queue.put(msg)
def _poll_all_logs(self) -> None:
for tab_id, st in self._tabs.items():
while True: while True:
try: try:
msg = self._log_queue.get_nowait() msg = st.log_queue.get_nowait()
except queue.Empty: except queue.Empty:
break break
if msg is None: if msg is None:
self._set_busy(False) self._set_busy(tab_id, False)
else: else:
self._log_append(msg) self._log_append(tab_id, msg)
self._root.after(100, self._poll_log) self._root.after(100, self._poll_all_logs)
def _push(self, msg: str) -> None: # ── Generate ─────────────────────────────────────────────────────────────
self._log_queue.put(msg)
def _on_generate(self) -> None: def _on_generate(self, tab_id: str) -> None:
err = self._validate_selection() err = self._validate_selection(tab_id)
if err: if err:
self._log_append(f'✗ {err}') self._log_append(tab_id, f'✗ {err}')
return return
mode = self._mode.get() st = self._tabs[tab_id]
sel = list(self._selection) mode = st.mode.get()
lang = self._lang.get() sel = list(st.selection)
inc = self._include_prompt.get() lang = st.lang.get()
self._set_busy(True) inc = st.include_prompt.get()
threading.Thread(target=self._worker_generate, args=(mode, sel, lang, inc), self._set_busy(tab_id, True)
threading.Thread(target=self._worker_generate,
args=(tab_id, mode, sel, lang, inc),
daemon=True).start() daemon=True).start()
def _worker_generate(self, mode: str, sel: list[str], lang: str, inc: bool) -> None: def _worker_generate(self, tab_id: str, mode: str, sel: list[str],
lang: str, inc: bool) -> None:
try: try:
self._check_game_mismatch(tab_id, mode, sel)
if mode == 'technique': if mode == 'technique':
lap = load_lap(sel[0]) lap = load_lap(sel[0])
out = report_path(sel[0], 'technique') out = report_path(sel[0], 'technique')
abs_path, tokens = report_technique.generate(lap, out, lang, inc) abs_path, tokens = report_technique.generate(lap, out, lang, inc)
self._push(f'✓ {abs_path} (~{tokens} tokens)') self._push(tab_id, f'✓ {abs_path} (~{tokens} tokens)')
elif mode == 'setup': elif mode == 'setup':
lap = load_lap(sel[0]) lap = load_lap(sel[0])
out = report_path(sel[0], 'setup') out = report_path(sel[0], 'setup')
abs_path, tokens = report_setup.generate(lap, out, lang, inc) abs_path, tokens = report_setup.generate(lap, out, lang, inc)
self._push(f'✓ {abs_path} (~{tokens} tokens)') self._push(tab_id, f'✓ {abs_path} (~{tokens} tokens)')
elif mode == 'compare': elif mode == 'compare':
laps = [load_lap(f) for f in sel] laps = [load_lap(f) for f in sel]
stem_b = os.path.splitext(os.path.basename(sel[1]))[0] stem_b = os.path.splitext(os.path.basename(sel[1]))[0]
out = report_path(sel[0], 'compare', stem_b) out = report_path(sel[0], 'compare', stem_b)
abs_path, tokens = report_compare.generate(laps, out, lang, inc) abs_path, tokens = report_compare.generate(laps, out, lang, inc)
self._push(f'✓ {abs_path} (~{tokens} tokens)') self._push(tab_id, f'✓ {abs_path} (~{tokens} tokens)')
elif mode in ('race', 'profile'): elif mode in ('race', 'profile'):
race_dir = os.path.abspath(sel[0]) race_dir = os.path.abspath(sel[0])
@ -188,7 +227,7 @@ class _App:
f for f in os.listdir(race_dir) if f.lower().endswith('.csv') f for f in os.listdir(race_dir) if f.lower().endswith('.csv')
) )
if not csv_files: if not csv_files:
self._push(f'✗ No CSV files found in {race_dir}') self._push(tab_id, f'✗ No CSV files found in {race_dir}')
return return
laps = [load_lap(os.path.join(race_dir, f)) for f in csv_files] laps = [load_lap(os.path.join(race_dir, f)) for f in csv_files]
dirname = os.path.basename(race_dir.rstrip('/\\')) dirname = os.path.basename(race_dir.rstrip('/\\'))
@ -196,39 +235,71 @@ class _App:
out = os.path.join(race_dir, 'reports', dirname + suffix) out = os.path.join(race_dir, 'reports', dirname + suffix)
gen_mod = report_race if mode == 'race' else report_profile gen_mod = report_race if mode == 'race' else report_profile
abs_path, tokens = gen_mod.generate(laps, out, lang, inc) abs_path, tokens = gen_mod.generate(laps, out, lang, inc)
self._push(f'✓ {abs_path} (~{tokens} tokens)') self._push(tab_id, f'✓ {abs_path} (~{tokens} tokens)')
except Exception as e: except Exception as e:
self._push(f'✗ {e}') self._push(tab_id, f'✗ {e}')
finally: finally:
self._log_queue.put(None) self._tabs[tab_id].log_queue.put(None)
def _on_rename(self) -> None: def _check_game_mismatch(self, tab_id: str, mode: str, sel: list[str]) -> None:
init = self._last_dir.get('rename', os.path.expanduser('~')) """Warn (but don't block) when the file's game doesn't match the active tab."""
try:
if mode in ('race', 'profile'):
race_dir = os.path.abspath(sel[0])
csv_files = sorted(
f for f in os.listdir(race_dir) if f.lower().endswith('.csv')
)
if not csv_files:
return
probe = os.path.join(race_dir, csv_files[0])
else:
probe = sel[0]
detected = game_of(load_lap(probe))
except Exception:
return # if probe fails, let the main worker surface the real error
tab_game = tab_id # 'f1' or 'acc'
if detected != tab_game:
tab_label = _TAB_GAME[tab_id]
file_label = _TAB_GAME.get(detected, detected.upper())
self._push(tab_id,
f'⚠ Selected file is {file_label}, but you are on the '
f'{tab_label} tab — proceeding anyway.')
# ── Rename ───────────────────────────────────────────────────────────────
def _on_rename(self, tab_id: str) -> None:
st = self._tabs[tab_id]
init = st.last_dir.get('rename', os.path.expanduser('~'))
d = filedialog.askdirectory(title='Select folder with CSV files', initialdir=init) d = filedialog.askdirectory(title='Select folder with CSV files', initialdir=init)
if not d: if not d:
return return
self._last_dir['rename'] = d st.last_dir['rename'] = d
self._set_busy(True) self._set_busy(tab_id, True)
threading.Thread(target=self._worker_rename, args=(d,), daemon=True).start() threading.Thread(target=self._worker_rename, args=(tab_id, d),
daemon=True).start()
def _worker_rename(self, d: str) -> None: def _worker_rename(self, tab_id: str, d: str) -> None:
try: try:
results = rename_unprocessed([d], races_dir=_RACES_DIR) results = rename_unprocessed([d], races_dir=_RACES_DIR)
if not results: if not results:
self._push('No CSV files found') self._push(tab_id, 'No CSV files found')
for r in results: for r in results:
if r.status == 'renamed': if r.status == 'renamed':
new_display = os.path.relpath(r.new, _RACES_DIR) if r.new and r.new.startswith(_RACES_DIR) else r.new new_display = (os.path.relpath(r.new, _RACES_DIR)
self._push(f'renamed: {r.old} -> {new_display}') if r.new and r.new.startswith(_RACES_DIR) else r.new)
self._push(tab_id, f'renamed: {r.old} -> {new_display}')
elif r.status == 'skipped': elif r.status == 'skipped':
self._push(f'skipped: {r.old} ({r.detail})') self._push(tab_id, f'skipped: {r.old} ({r.detail})')
else: else:
self._push(f'error: {r.old} ({r.detail})') self._push(tab_id, f'error: {r.old} ({r.detail})')
except Exception as e: except Exception as e:
self._push(f'✗ {e}') self._push(tab_id, f'✗ {e}')
finally: finally:
self._log_queue.put(None) self._tabs[tab_id].log_queue.put(None)
# ── Misc ─────────────────────────────────────────────────────────────────
def _open_reports(self) -> None: def _open_reports(self) -> None:
target = _RACES_DIR if os.path.isdir(_RACES_DIR) else _PROJECT_ROOT target = _RACES_DIR if os.path.isdir(_RACES_DIR) else _PROJECT_ROOT

View file

@ -4,19 +4,61 @@ from dataclasses import dataclass
from .parser import _parse_header_row, _parse_value_row from .parser import _parse_header_row, _parse_value_row
_SESSION_PAT = re.compile(r'^(FP[1-3]|P[1-3]|Q[1-3]|R|Race|Sprint)$', re.IGNORECASE) # Canonical token pattern — every token we ever insert must be listed here so
# _is_processed returns True on a second run (idempotency guarantee).
_SESSION_PAT = re.compile(
r'^(FP[1-3]|P[1-3]|P|Q[1-3]|SQ[1-3]|Q|R|Race|Sprint|HL|HS)$',
re.IGNORECASE,
)
# Maps ACC full-word event values to compact tokens used in filenames.
# Prefix-match on lowercased value so "Practice 1" etc. also normalise.
# F1 short codes pass through unchanged (they won't match any prefix here).
_ACC_NORM: list[tuple[str, str]] = [
('practice', 'P'),
('qualifying', 'Q'),
# Superpole is ACC's single-lap qualifying format → Q folder makes sense.
('superpole', 'Q'),
('hotlap', 'HL'),
('hotstint', 'HS'),
('race', 'R'),
('sprint', 'Sprint'),
]
def _normalise_event(raw: str) -> str:
"""Return the canonical session token for a raw event string.
F1 short codes (P1, Q2, R, Sprint, FP1 …) are returned as-is.
ACC full-word values are mapped to compact tokens via prefix match.
"""
lo = raw.strip().lower()
for prefix, token in _ACC_NORM:
if lo.startswith(prefix):
return token
# Unknown / F1 code — pass through unchanged so F1 files still work.
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.
e = event.upper() e = event.upper()
if re.match(r'^(FP[1-3]|P[1-3])$', e): if re.match(r'^(FP[1-3]|P[1-3]|P)$', e):
return 'Practice' return 'Practice'
if re.match(r'^(SQ[1-3]|Q[1-3])$', e): if re.match(r'^(SQ[1-3]|Q[1-3]|Q)$', e):
return 'Qualifying' return 'Qualifying'
if re.match(r'^SPRINT$', e): if re.match(r'^SPRINT$', e):
return 'Sprint' return 'Sprint'
if re.match(r'^(R|RACE)$', e): if re.match(r'^(R|RACE)$', e):
return 'Race' return 'Race'
if re.match(r'^HL$', e):
return 'Hotlap'
if re.match(r'^HS$', e):
return 'Hotstint'
return 'Other' return 'Other'
@ -85,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])
@ -99,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(),
) )
@ -143,26 +186,29 @@ 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
if not session_type: if not session_type:
results.append(RenameResult(name, None, 'error', 'session type not found in CSV')) results.append(RenameResult(name, None, 'error', 'session type not found in CSV'))
continue continue
session_type = _normalise_event(session_type)
new_name = insert_tokens(name, session_type, lap) new_name = insert_tokens(name, session_type, lap)
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
if not session_type: if not session_type:
results.append(RenameResult(name, None, 'error', 'session type not found in CSV')) results.append(RenameResult(name, None, 'error', 'session type not found in CSV'))
continue continue
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'))

View file

@ -4,6 +4,10 @@ import tempfile
from .parser import Lap, laptime_s, sector_times from .parser import Lap, laptime_s, sector_times
def game_of(lap: Lap) -> str:
return 'acc' if lap.session.get('Game', '').upper().startswith('ACC') else 'f1'
def header_block(lap: Lap) -> str: def header_block(lap: Lap) -> str:
s = lap.session s = lap.session
t = lap.track t = lap.track
@ -103,13 +107,20 @@ def write_report(path: str, text: str) -> tuple[str, int]:
return os.path.abspath(path), tokens return os.path.abspath(path), tokens
def load_prompt(mode: str, lang: str) -> str: def load_prompt(mode: str, lang: str, game: str = 'f1') -> str:
here = os.path.dirname(__file__) here = os.path.dirname(__file__)
p = os.path.normpath(os.path.join(here, '..', 'prompts', lang, f'{mode}.md')) base = os.path.join(here, '..', 'prompts', lang)
# ACC gets its own prompt set, falling back to the F1 prompt when absent.
candidates = []
if game == 'acc':
candidates.append(os.path.normpath(os.path.join(base, 'acc', f'{mode}.md')))
candidates.append(os.path.normpath(os.path.join(base, f'{mode}.md')))
for p in candidates:
try: try:
with open(p, encoding='utf-8') as f: with open(p, encoding='utf-8') as f:
return f.read().strip() return f.read().strip()
except FileNotFoundError: except FileNotFoundError:
continue
return '' return ''

View file

@ -4,7 +4,7 @@ from .parser import Lap, laptime_s, sector_times
from .segments import detect_corners, Corner from .segments import detect_corners, Corner
from .resample import adaptive_points, sample_at from .resample import adaptive_points, sample_at
from .report_common import ( from .report_common import (
legend, md_table, write_report, token_estimate, load_prompt, legend, md_table, write_report, token_estimate, load_prompt, game_of,
LEGEND_DIST, LEGEND_SPD, LEGEND_DT_SEG, LEGEND_DT_CUM, LEGEND_DIST, LEGEND_SPD, LEGEND_DT_SEG, LEGEND_DT_CUM,
) )
@ -230,8 +230,10 @@ def _delta_trace(laps: list[Lap], corners: list[Corner]) -> tuple[str, list[floa
return block, final_deltas return block, final_deltas
def generate(laps: list[Lap], out_path: str, lang: str = 'ru', include_prompt: bool = True) -> tuple[str, int]: def generate(laps: list[Lap], out_path: str, lang: str = 'ru', include_prompt: bool = True,
game: str | None = None) -> tuple[str, int]:
lap_a = laps[0] lap_a = laps[0]
g = game or game_of(lap_a)
corners = detect_corners(lap_a) corners = detect_corners(lap_a)
head_table = _header_table(laps) head_table = _header_table(laps)
@ -257,7 +259,7 @@ def generate(laps: list[Lap], out_path: str, lang: str = 'ru', include_prompt: b
] ]
text = '\n'.join(parts) text = '\n'.join(parts)
if include_prompt: if include_prompt:
prompt = load_prompt('compare', lang) prompt = load_prompt('compare', lang, g)
if prompt: if prompt:
text += '\n\n---\n\n' + prompt text += '\n\n---\n\n' + prompt
return write_report(out_path, text) return write_report(out_path, text)

View file

@ -1,5 +1,5 @@
from .parser import Lap, laptime_s, sector_times from .parser import Lap, laptime_s, sector_times
from .report_common import md_table, write_report, load_prompt from .report_common import md_table, write_report, load_prompt, game_of
def _fmt_laptime(s: float) -> str: def _fmt_laptime(s: float) -> str:
@ -293,10 +293,12 @@ def _ers_table(laps: list[Lap]) -> str:
return md_table(['Lap', 'ERS MJ', 'MGU-K MJ', 'MGU-H MJ'], rows) return md_table(['Lap', 'ERS MJ', 'MGU-K MJ', 'MGU-H MJ'], rows)
def generate(laps: list[Lap], out_path: str, lang: str = 'ru', include_prompt: bool = True) -> tuple[str, int]: def generate(laps: list[Lap], out_path: str, lang: str = 'ru', include_prompt: bool = True,
game: str | None = None) -> tuple[str, int]:
laps = _dedup_laps(laps) laps = _dedup_laps(laps)
laps = _sort_laps(laps) laps = _sort_laps(laps)
stints, ages = _assign_stints(laps) stints, ages = _assign_stints(laps)
g = game or (game_of(laps[0]) if laps else 'f1')
parts = [ parts = [
_header(laps), _header(laps),
@ -308,12 +310,12 @@ def generate(laps: list[Lap], out_path: str, lang: str = 'ru', include_prompt: b
_tyre_wear_table(laps), _tyre_wear_table(laps),
'\n## Thermal summary per lap', '\n## Thermal summary per lap',
_thermal_table(laps), _thermal_table(laps),
'\n## ERS per lap',
_ers_table(laps),
] ]
if g != 'acc':
parts += ['\n## ERS per lap', _ers_table(laps)]
text = '\n'.join(parts) text = '\n'.join(parts)
if include_prompt: if include_prompt:
prompt = load_prompt('race', lang) prompt = load_prompt('race', lang, g)
if prompt: if prompt:
text += '\n\n---\n\n' + prompt text += '\n\n---\n\n' + prompt
return write_report(out_path, text) return write_report(out_path, text)

View file

@ -2,7 +2,7 @@ from .parser import Lap
from .segments import detect_corners, Corner from .segments import detect_corners, Corner
from .segments import _indices_in_range from .segments import _indices_in_range
from .report_common import ( from .report_common import (
header_block, legend, md_table, write_report, load_prompt, header_block, legend, md_table, write_report, load_prompt, game_of,
LEGEND_DIST, LEGEND_TIME, LEGEND_SPD, LEGEND_THR_BRK, LEGEND_STEER, LEGEND_DIST, LEGEND_TIME, LEGEND_SPD, LEGEND_THR_BRK, LEGEND_STEER,
LEGEND_GEAR, LEGEND_DRS, LEGEND_GLAT, LEGEND_GLON, LEGEND_FUEL, LEGEND_GEAR, LEGEND_DRS, LEGEND_GLAT, LEGEND_GLON, LEGEND_FUEL,
LEGEND_LOCK, LEGEND_SPIN, LEGEND_STEER_G_CAVEAT, LEGEND_SUSP, LEGEND_LOCK, LEGEND_SPIN, LEGEND_STEER_G_CAVEAT, LEGEND_SUSP,
@ -220,14 +220,23 @@ def _suspension_section(lap: Lap, corners: list[Corner]) -> str | None:
return rake_line + '\n\n' + roll_table return rake_line + '\n\n' + roll_table
def generate(lap: Lap, out_path: str, lang: str = 'ru', include_prompt: bool = True) -> tuple[str, int]: def generate(lap: Lap, out_path: str, lang: str = 'ru', include_prompt: bool = True,
game: str | None = None) -> tuple[str, int]:
g = game or game_of(lap)
corners = detect_corners(lap) corners = detect_corners(lap)
susp = _suspension_section(lap, corners) susp = _suspension_section(lap, corners)
if g == 'acc':
setup_section = ('_ACC does not export car setup — analysis derived from telemetry below._'
if lang == 'en' else
'_ACC не экспортирует сетап — разбор баланса из телеметрии ниже._')
else:
setup_section = _full_setup_table(lap)
parts = [ parts = [
header_block(lap), header_block(lap),
'\n## Setup', '\n## Setup',
_full_setup_table(lap), setup_section,
'\n## Corner balance', '\n## Corner balance',
_corner_balance(lap, corners), _corner_balance(lap, corners),
'\n## Tyres', '\n## Tyres',
@ -250,7 +259,7 @@ def generate(lap: Lap, out_path: str, lang: str = 'ru', include_prompt: bool = T
] ]
text = '\n'.join(parts) text = '\n'.join(parts)
if include_prompt: if include_prompt:
prompt = load_prompt('setup', lang) prompt = load_prompt('setup', lang, g)
if prompt: if prompt:
text += '\n\n---\n\n' + prompt text += '\n\n---\n\n' + prompt
return write_report(out_path, text) return write_report(out_path, text)

View file

@ -3,7 +3,7 @@ from .segments import detect_corners, straights, Corner, Straight
from .resample import adaptive_points, sample_at, format_trace_row from .resample import adaptive_points, sample_at, format_trace_row
from .report_common import ( from .report_common import (
header_block, setup_summary, legend, md_table, write_report, report_path, header_block, setup_summary, legend, md_table, write_report, report_path,
load_prompt, load_prompt, game_of,
LEGEND_DIST, LEGEND_TIME, LEGEND_SPD, LEGEND_THR_BRK, LEGEND_STEER, LEGEND_DIST, LEGEND_TIME, LEGEND_SPD, LEGEND_THR_BRK, LEGEND_STEER,
LEGEND_GEAR, LEGEND_DRS, LEGEND_GLAT, LEGEND_GLON, LEGEND_FUEL, LEGEND_ERS, LEGEND_GEAR, LEGEND_DRS, LEGEND_GLAT, LEGEND_GLON, LEGEND_FUEL, LEGEND_ERS,
LEGEND_LOCK, LEGEND_SPIN, LEGEND_TF, LEGEND_RPM, LEGEND_SLIP, LEGEND_YAW, LEGEND_LOCK, LEGEND_SPIN, LEGEND_TF, LEGEND_RPM, LEGEND_SLIP, LEGEND_YAW,
@ -56,7 +56,7 @@ def _straight_rows(ss: list[Straight]) -> list[list[str]]:
return rows return rows
def _driving_cost(lap: Lap, corners: list[Corner]) -> str: def _driving_cost(lap: Lap, corners: list[Corner], game: str = 'f1') -> str:
ch = lap.ch ch = lap.ch
fuel = ch.get('FuelRemaining', []) fuel = ch.get('FuelRemaining', [])
fuel_used = (fuel[0] - fuel[-1]) if len(fuel) >= 2 else 0.0 fuel_used = (fuel[0] - fuel[-1]) if len(fuel) >= 2 else 0.0
@ -94,7 +94,12 @@ def _driving_cost(lap: Lap, corners: list[Corner]) -> str:
f'- Fuel used: {fuel_used:.2f} kg', f'- Fuel used: {fuel_used:.2f} kg',
'- Tyre wear Δ:', '- Tyre wear Δ:',
*wear_lines, *wear_lines,
f'- ERS spent: {ers_mj:.3f} MJ | MGU-K harvested: {mguk_mj:.3f} MJ | MGU-H harvested: {mguh_mj:.3f} MJ', ]
if game != 'acc':
lines.append(
f'- ERS spent: {ers_mj:.3f} MJ | MGU-K harvested: {mguk_mj:.3f} MJ | MGU-H harvested: {mguh_mj:.3f} MJ'
)
lines += [
f'- Corners flagged LOCK: {lock_count} | SPIN: {spin_count}', f'- Corners flagged LOCK: {lock_count} | SPIN: {spin_count}',
f'- Worst lock%: {worst_lock_str} | Worst spin%: {worst_spin_str}', f'- Worst lock%: {worst_lock_str} | Worst spin%: {worst_spin_str}',
'- Max brake temps:', '- Max brake temps:',
@ -150,7 +155,9 @@ def _trace_block(lap: Lap, corners: list[Corner]) -> str:
return '```\n' + header + '\n' + '\n'.join(rows) + '\n```' return '```\n' + header + '\n' + '\n'.join(rows) + '\n```'
def generate(lap: Lap, out_path: str, lang: str = 'ru', include_prompt: bool = True) -> tuple[str, int]: def generate(lap: Lap, out_path: str, lang: str = 'ru', include_prompt: bool = True,
game: str | None = None) -> tuple[str, int]:
g = game or game_of(lap)
corners = detect_corners(lap) corners = detect_corners(lap)
ss = straights(corners, lap) ss = straights(corners, lap)
@ -163,15 +170,16 @@ def generate(lap: Lap, out_path: str, lang: str = 'ru', include_prompt: bool = T
_straight_rows(ss), _straight_rows(ss),
) )
parts = [ parts = [header_block(lap)]
header_block(lap), if g != 'acc':
f'\n**Setup:** {setup_summary(lap)}', parts.append(f'\n**Setup:** {setup_summary(lap)}')
parts += [
'\n## Corners', '\n## Corners',
corner_table, corner_table,
'\n## Straights', '\n## Straights',
straight_table, straight_table,
'\n## Driving cost (per lap)', '\n## Driving cost (per lap)',
_driving_cost(lap, corners), _driving_cost(lap, corners, g),
'\n## Trace', '\n## Trace',
_trace_block(lap, corners), _trace_block(lap, corners),
'\n' + legend([ '\n' + legend([
@ -182,7 +190,7 @@ def generate(lap: Lap, out_path: str, lang: str = 'ru', include_prompt: bool = T
] ]
text = '\n'.join(parts) text = '\n'.join(parts)
if include_prompt: if include_prompt:
prompt = load_prompt('technique', lang) prompt = load_prompt('technique', lang, g)
if prompt: if prompt:
text += '\n\n---\n\n' + prompt text += '\n\n---\n\n' + prompt
return write_report(out_path, text) return write_report(out_path, text)

View file

@ -18,3 +18,13 @@ def truncated_path():
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def sample_lap(sample_path): def sample_lap(sample_path):
return load_lap(sample_path) return load_lap(sample_path)
@pytest.fixture(scope="session")
def acc_path():
return os.path.join(_FIXTURES, "acc_lap.csv")
@pytest.fixture(scope="session")
def acc_lap(acc_path):
return load_lap(acc_path)

40
tests/fixtures/acc_lap.csv vendored Normal file
View file

@ -0,0 +1,40 @@
player,v8,???,0,2026061914565357
Game,version,date,track,car,event,laptime [s],S1 [s],S2 [s],S3 [s],S4+ [s]
ACC,1.1,2026-06-19 12:01:23,Hungaroring,CaR,Practice,123.702,43.470,45.950,34.282
TrackID [int],Tracklen [m],EventID [int],EventID2 [int],Weather [txt],TeamID [int],Tyre [txt],Valid [b],Pitlap [b],Online [b],Lap [int],LapsInRace [int],Fuel at Start [kg],MaxGears [int],MaxRevs [rpm],IdleRevs [rpm},TrackTemp [C],AmbientTemp [C],Windspeed [m/s],WindDirection [ ]
7,4381.009765625,0,50,Clear,57,DHA,false,false,false,1,20,60.019676208496094,6,6500.0,1950.0001,27.946645736694336,27.15789031982422,2.18314528465271,75.69888305664062
FWing,RWing,OnThrottle,OffThrottle,FrontCamber,RearCamber,FrontToe,RearToe,FrontSusp,RearSusp,FrontAntiRoll,RearAntiRoll,FrontSuspH,RearSuspH,BrakePressure,BrakeBias,FLTyrePressure,FRTyrePressure,RLTyrePressure,RRTyrePressure,Ballast,FuelLoad
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
LapDistance [m],TotalDistance [m],LapTime [s],Sector1Time [s],Sector2Time [s],Speed [km/h],EngineRevs [rpm],ThrottlePercentage [%],BrakePercentage [%],Steer [%],Clutch [%],Gear [int],DRS [0/1],CanUseDRS [0/1],KERSLevel [J],EngineTemperature [C],RacePosition [int],Sector [int],InPits [int],FuelRemaining [l],CurrentFlag [int],CurrentLapInvalid [int],X [m],Y [m],Z [m],GForceLatitudinal [G],GForceLongitudinal [G],GForceVertical [G],WheelSpeedRearLeft [km/h],WheelSpeedRearRight [km/h],WheelSpeedFrontLeft [km/h],WheelSpeedFrontRight [km/h],BrakeTemperatureRearLeft [C],BrakeTemperatureRearRight [C],BrakeTemperatureFrontLeft [C],BrakeTemperatureFrontRight [C],TyreTemperatureRearLeft [C],TyreTemperatureRearRight [C],TyreTemperatureFrontLeft [C],TyreTemperatureFrontRight [C],TyreWearRearLeft [%],TyreWearRearRight [%],TyreWearFrontLeft [%],TyreWearFrontRight [%],SuspensionPositionRearLeft [m],SuspensionPositionRearRight [m],SuspensionPositionFrontLeft [m],SuspensionPositionFrontRight [m],SuspensionVelocityRearLeft [m/s],SuspensionVelocityRearRight [m/s],SuspensionVelocityFrontLeft [m/s],SuspensionVelocityFrontRight [m/s],SuspensionAccelerationRearLeft [m/s^2],SuspensionAccelerationRearRight [m/s^2],SuspensionAccelerationFrontLeft [m/s^2],SuspensionAccelerationFrontRight [m/s^2],MGUKHarvested [J],MGUHHarvested [J],ERSSpent [J],ERSMode [int],FuelMixMode [int],Yaw [rad],Roll [rad],Pitch [rad],TyreCarcassTemperatureRearLeft [C],TyreCarcassTemperatureRearRight [C],TyreCarcassTemperatureFrontLeft [C],TyreCarcassTemperatureFrontRight [C],TyrePressureRearLeft [PSI],TyrePressureRearRight [PSI],TyrePressureFrontLeft [PSI],TyrePressureFrontRight [PSI],WheelSlipRearLeft [%],WheelSlipRearRight [%],WheelSlipFrontLeft [%],WheelSlipFrontRight [%],Torque [Nm],Handbrake [%],RearLeftInside [C],RearLeftMiddle [C],RearLeftOutside [C],RearRightInside [C],RearRightMiddle [C],RearRightOutside [C],FrontLeftInside [C],FrontLeftMiddle [C],FrontLeftOutside [C],FrontRightInside [C],FrontRightMiddle [C],FrontRightOutside [C],WoldSpeedX [km/h],WoldSpeedY [km/h],WoldSpeedZ [km/h],LocalAngularVelocityX [rad/s],LocalAngularVelocityY [rad/s],LocalAngularVelocityZ [rad/s],IcePower [W],MgukPower [W],FrontRideHeight [m],RearRideHeight [m],LoadRearLeft [N],LoadRearRight [N],LoadFrontLeft [N],LoadFrontRight [N],Extra1 [m],Extra2 [],Extra3 [],Extra4 []
11.52857,4036.4016,0.282,0.0,0.0,169.64093,5723.0,100.0,0.0,-0.010964928,0.0,4.0,0.0,0.0,0.0,84.686035,1.0,0.0,0.0,60.019676,0.0,1.0,-109.23591,24.842262,544.1296,-0.036001448,0.2483705,0.025862258,173.77263,173.87315,169.28203,169.32233,305.99573,304.57455,401.18625,396.96573,73.76696,69.08077,70.795746,65.185524,0.0,0.0,0.0,0.0,0.034967814,0.034488626,0.013857091,0.013722197,2.075326E-5,1.89149E-5,-7.246143E-6,-2.5508643E-6,-1.3962284E-7,-1.2725481E-7,4.8750273E-8,1.716159E-8,0.0,0.0,0.0,-1.0,1.0,-2.1652644,-0.03211098,0.0017947316,73.76696,69.08077,70.795746,65.185524,25.10461,24.530798,25.619375,25.080778,0.024355568,0.024948094,-0.0021156704,-0.0018781277,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.042892,0.0030937572,26.396013,-0.0044563767,-5.508619E-4,0.013827926,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
12.014709,4036.7551,0.29,0.0,0.0,169.72768,5726.0,100.0,0.0,0.0018527993,0.0,4.0,0.0,0.0,0.0,84.688995,1.0,0.0,0.0,60.0194,0.0,1.0,-109.5287,24.84228,543.93164,-0.023693023,0.24786447,0.012986496,174.02707,173.90872,169.38522,169.4204,305.94714,304.52625,401.0366,396.81787,73.76947,69.08246,70.79732,65.18582,0.0,0.0,0.0,0.0,0.034994584,0.03462096,0.0141735785,0.013794035,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-1.0,1.0,-2.1652737,-0.03226933,0.0018673544,73.76947,69.08246,70.79732,65.18582,25.10485,24.53096,25.619526,25.080807,0.025331153,0.024633894,-0.0020176482,-0.0018104152,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.061405,0.0047848364,26.411741,-0.009338445,-8.7213836E-4,0.017504098,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
12.338801,4037.1086,0.3,0.0,0.0,169.79353,5729.0,100.0,0.0,-0.005238485,0.0,4.0,0.0,0.0,0.0,84.691216,1.0,0.0,0.0,60.019196,0.0,1.0,-109.82159,24.84231,543.73364,-0.019859513,0.24973202,0.0029058661,174.1567,173.91837,169.62521,169.51479,305.89853,304.47787,400.88684,396.66995,73.771355,69.083725,70.79849,65.18604,0.0,0.0,0.0,0.0,0.034860697,0.034790497,0.014345023,0.013859233,-0.0059509445,0.016770637,0.027107334,0.0076130964,-0.33060804,0.931702,1.505963,0.4229498,0.0,0.0,0.0,-1.0,1.0,-2.1652846,-0.03241021,0.0019555911,73.771355,69.083725,70.79849,65.18604,25.105034,24.531082,25.61964,25.08083,0.025696889,0.024293179,-9.913363E-4,-0.0016416886,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.07583,0.0050230986,26.423271,-0.012692683,-0.0012003235,0.017881883,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
12.82494,4037.5803,0.307,0.0,0.0,169.86078,5732.0,100.0,0.0,-0.0020330295,0.0,4.0,0.0,0.0,0.0,84.693436,1.0,0.0,0.0,60.01899,0.0,1.0,-110.21227,24.842361,543.4695,-0.015500802,0.25483412,-0.035387367,174.24815,174.01012,169.7397,169.63368,305.89853,304.47787,400.88684,396.66995,73.77323,69.08499,70.799644,65.18625,0.0,0.0,0.0,0.0,0.034660157,0.034980144,0.014283132,0.01383878,-0.019672163,0.021128532,0.006444314,0.002632082,-1.157186,1.2428548,0.3790773,0.15482835,0.0,0.0,0.0,-1.0,1.0,-2.1652973,-0.03250376,0.0020536045,73.77323,69.08499,70.799644,65.18625,25.105213,24.531204,25.619751,25.080849,0.025829243,0.024427839,-7.1281136E-4,-0.0013369576,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.090744,0.0024308157,26.434696,-0.012943774,-0.0012463271,0.009102043,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
13.149033,4037.9343,0.315,0.0,0.0,169.9762,5737.0,100.0,0.0,0.009160281,0.0,4.0,0.0,0.0,0.0,84.697136,1.0,0.0,0.0,60.018642,0.0,1.0,-110.50542,24.842388,543.27124,-0.032200195,0.26522526,-0.046108786,173.96013,174.05067,169.72847,169.74197,305.8012,304.38113,400.5873,396.37408,73.77638,69.087105,70.80156,65.1866,0.0,0.0,0.0,0.0,0.03464579,0.035214636,0.013901675,0.013589289,-0.014327218,0.028275946,-0.029556515,-0.017996257,-0.95514786,1.885063,-1.9704343,-1.1997504,0.0,0.0,0.0,-1.0,1.0,-2.1653147,-0.032495715,0.0021680715,73.77638,69.087105,70.80156,65.1866,25.105516,24.531408,25.619934,25.080883,0.02343825,0.023970794,-0.0014574057,-0.0013779213,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.115627,-0.0038357505,26.455435,-0.005286799,-0.0010240518,-0.0044617346,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
13.4731245,4038.2883,0.325,0.0,0.0,170.04463,5738.0,100.0,0.0,0.016547244,0.0,4.0,0.0,0.0,0.0,84.699356,1.0,0.0,0.0,60.018436,0.0,1.0,-110.798676,24.842382,543.0729,-0.026762703,0.25481597,-0.0077182837,174.0757,174.09074,169.71399,169.76973,305.8012,304.38113,400.5873,396.37408,73.778275,69.08838,70.80272,65.186806,0.0,0.0,0.0,0.0,0.03487389,0.03536873,0.013778092,0.013468641,0.011874156,0.021588057,-0.028057748,-0.020563291,0.65967536,1.1993365,-1.5587639,-1.142405,0.0,0.0,0.0,-1.0,1.0,-2.165323,-0.03248412,0.0021698782,73.778275,69.08838,70.80272,65.186806,25.105698,24.53153,25.620045,25.080902,0.023705985,0.023794403,-0.0019443971,-0.0016166419,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.130116,-0.0042215604,26.467607,0.0018667248,-0.001168449,9.254145E-4,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
13.959263,4038.7605,0.332,0.0,0.0,170.1122,5739.0,100.0,0.0,0.0083989715,0.0,4.0,0.0,0.0,0.0,84.70158,1.0,0.0,0.0,60.018227,0.0,1.0,-111.18986,24.842344,542.80835,-0.027512636,0.25494838,0.019121999,174.11198,174.16544,169.7345,169.77376,305.75256,304.33273,400.4375,396.2261,73.78017,69.089645,70.80388,65.18702,0.0,0.0,0.0,0.0,0.03509519,0.035522744,0.013817208,0.013471926,0.026435317,0.018123975,-0.004968661,-0.00690373,1.5550187,1.0661162,-0.29227418,-0.40610176,0.0,0.0,0.0,-1.0,1.0,-2.1653323,-0.032541778,0.0021323483,73.78017,69.089645,70.80388,65.18702,25.105879,24.531652,25.620157,25.080923,0.023512656,0.023826884,-0.0022203415,-0.001989509,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.144463,-0.0028066994,26.4798,0.006246484,-0.0014940045,0.0111888945,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
14.283356,4039.1147,0.342,0.0,0.0,170.17862,5739.0,100.0,0.0,-0.0034686916,0.0,4.0,0.0,0.0,0.0,84.7038,1.0,0.0,0.0,60.01802,0.0,1.0,-111.48337,24.842316,542.6098,-0.028546177,0.24940713,0.07171454,174.1544,174.22723,169.84029,169.7952,305.70386,304.28433,400.28775,396.07816,73.78207,69.09092,70.805046,65.18724,0.0,0.0,0.0,0.0,0.035337783,0.035671897,0.014006146,0.013649582,0.027287751,0.017833402,0.013414934,0.010643593,1.6051619,1.0490236,0.78911376,0.6260937,0.0,0.0,0.0,-1.0,1.0,-2.165343,-0.032671917,0.0020802144,73.78207,69.09092,70.805046,65.18724,25.106062,24.531775,25.62027,25.080944,0.023362413,0.02379034,-0.001988125,-0.0022530768,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.158398,0.002200487,26.491964,0.0066807917,-0.001680956,0.019356037,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
14.769494,4039.5874,0.35,0.0,0.0,170.26718,5740.0,100.0,0.0,2.7964596E-4,0.0,4.0,0.0,0.0,0.0,84.70676,1.0,0.0,0.0,60.017742,0.0,1.0,-111.87488,24.84231,542.3449,-0.024774725,0.25113165,0.080288865,174.34059,174.34473,170.01518,169.89398,305.65518,304.23593,400.13797,395.93024,73.784584,69.092606,70.80659,65.187546,0.0,0.0,0.0,0.0,0.03564696,0.03586361,0.014187679,0.013973102,0.030653965,0.0189371,0.020581711,0.02784313,1.702998,1.0520611,1.1434284,1.5468405,0.0,0.0,0.0,-1.0,1.0,-2.1653576,-0.03284161,0.0020180475,73.784584,69.092606,70.80659,65.187546,25.106304,24.531937,25.620417,25.080975,0.023923587,0.023947949,-0.0014799545,-0.002191845,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.17719,0.009847768,26.507994,0.005680933,-0.00166586,0.014170732,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
15.253259,4039.9421,0.357,0.0,0.0,170.3318,5741.0,100.0,0.0,-0.0037863501,0.0,4.0,0.0,0.0,0.0,84.70898,1.0,0.0,0.0,60.017536,0.0,1.0,-112.16864,24.842356,542.1462,-0.01671688,0.24182548,0.051545005,174.62819,174.5305,170.05142,169.97345,305.6065,304.1875,399.98825,395.7823,73.78646,69.09387,70.80774,65.187775,0.0,0.0,0.0,0.0,0.035722785,0.035942208,0.014252585,0.014245591,0.025666753,0.01802072,0.016429275,0.039733946,1.7111169,1.2013813,1.095285,2.6489298,0.0,0.0,0.0,-1.0,1.0,-2.16537,-0.032910272,0.001991446,73.78646,69.09387,70.80774,65.187775,25.106483,24.532059,25.62053,25.080997,0.025223643,0.024650058,-0.0016460561,-0.0021038703,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.191162,0.013625955,26.519114,0.002056227,-0.0017836776,0.0065005226,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
15.567857,4040.2969,0.367,0.0,0.0,170.39563,5744.0,100.0,0.0,2.2442082E-4,0.0,4.0,0.0,0.0,0.0,84.7112,1.0,0.0,0.0,60.01733,0.0,1.0,-112.4625,24.84244,541.9473,-0.01776832,0.24116075,0.024250237,174.77617,174.67091,170.07419,170.066,305.6065,304.1875,399.98825,395.7823,73.78833,69.09513,70.8089,65.18801,0.0,0.0,0.0,0.0,0.035606816,0.03589045,0.01432358,0.014522852,-0.0023613959,0.0015786466,0.007994199,0.032338258,-0.13890564,0.09286156,0.470247,1.9022504,0.0,0.0,0.0,-1.0,1.0,-2.1653836,-0.03292371,0.0020090973,73.78833,69.09513,70.8089,65.18801,25.106663,24.532179,25.62064,25.081018,0.025708094,0.025090385,-0.0018864868,-0.0019345023,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.20509,0.015505659,26.53018,-0.004986443,-0.0016786046,-3.4433513E-4,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
16.039753,4040.7703,0.375,0.0,0.0,170.45953,5748.0,100.0,0.0,0.0040749414,0.0,4.0,0.0,0.0,0.0,84.71342,1.0,0.0,0.0,60.01712,0.0,1.0,-112.854485,24.842587,541.68207,-0.018202942,0.2413529,0.017172432,174.78044,174.73691,170.10362,170.1539,305.5578,304.1391,399.8385,395.6344,73.79021,69.09639,70.81005,65.18824,0.0,0.0,0.0,0.0,0.03545997,0.035773963,0.0144413635,0.014785069,-0.014600862,-0.00934696,0.010487676,0.029970996,-0.811159,-0.51927555,0.58264863,1.6650554,0.0,0.0,0.0,-1.0,1.0,-2.1653988,-0.032894265,0.002088995,73.79021,69.09639,70.81005,65.18824,25.106844,24.532299,25.62075,25.081041,0.025348512,0.025093224,-0.002087924,-0.0017930274,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.21901,0.016820235,26.541273,-0.013632805,-0.0016907271,-0.0055084135,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
16.354351,4041.1255,0.382,0.0,0.0,170.54517,5755.0,100.0,0.0,0.0012987609,0.0,4.0,0.0,0.0,0.0,84.71641,1.0,0.0,0.0,60.016846,0.0,1.0,-113.14859,24.84271,541.48303,-0.022501389,0.2451741,0.0393255,174.67075,174.71301,170.24464,170.33131,305.5091,304.09067,399.68878,395.48648,73.79271,69.09807,70.81159,65.18854,0.0,0.0,0.0,0.0,0.035401274,0.035641193,0.0146766985,0.015073612,-0.013702859,-0.01661703,0.023541227,0.03671733,-0.913524,-1.107802,1.5694151,2.4478219,0.0,0.0,0.0,-1.0,1.0,-2.1654234,-0.032835644,0.002294316,73.79271,69.09807,70.81159,65.18854,25.107084,24.532461,25.6209,25.08107,0.024190536,0.024438366,-0.0017621501,-0.0012539756,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.237606,0.02003613,26.556547,-0.02424377,-0.0018405571,-0.00504553,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
16.66895,4041.4807,0.392,0.0,0.0,170.63432,5761.0,100.0,0.0,-0.012933018,0.0,4.0,0.0,0.0,0.0,84.7194,1.0,0.0,0.0,60.016567,0.0,1.0,-113.4428,24.842848,541.28394,-0.02279238,0.25629854,0.0285197,174.57756,174.62965,170.44597,170.5077,305.46036,304.0422,399.539,395.33853,73.79522,69.099754,70.81311,65.18882,0.0,0.0,0.0,0.0,0.035602823,0.035672452,0.014721351,0.0151250595,0.00840316,-0.005971202,0.016469836,0.01999944,0.49430352,-0.3512472,0.9688139,1.1764377,0.0,0.0,0.0,-1.0,1.0,-2.1654518,-0.032781146,0.0025523542,73.79522,69.099754,70.81311,65.18882,25.107327,24.532623,25.621044,25.081099,0.023109358,0.023414625,-0.0011038778,-7.420546E-4,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.257008,0.022770366,26.57248,-0.025350533,-0.0020437962,-0.00586406,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
17.140846,4041.9546,0.4,0.0,0.0,170.70256,5763.0,100.0,0.0,-0.021631196,0.0,4.0,0.0,0.0,0.0,84.72164,1.0,0.0,0.0,60.016357,0.0,1.0,-113.83525,24.843058,541.0183,-0.01922873,0.25786906,0.008813665,174.71027,174.66154,170.55545,170.61969,305.4116,303.9937,399.3892,395.19055,73.797104,69.10103,70.81424,65.18902,0.0,0.0,0.0,0.0,0.035794284,0.035845757,0.014541981,0.014907652,0.021833926,0.011364619,-0.007484315,-0.009220041,1.2129959,0.63136774,-0.41579527,-0.5122245,0.0,0.0,0.0,-1.0,1.0,-2.165474,-0.032745425,0.0027118518,73.797104,69.10103,70.81424,65.18902,25.107508,24.532745,25.621153,25.081116,0.023477638,0.02319233,-8.6181273E-4,-4.8549226E-4,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.271946,0.0233458,26.584316,-0.01879117,-0.0023562151,-0.003961567,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
17.612743,4042.3103,0.41,0.0,0.0,170.79535,5764.0,100.0,0.0,-0.05187564,0.0,4.0,0.0,0.0,0.0,84.72463,1.0,0.0,0.0,60.01608,0.0,1.0,-114.129715,24.843231,540.819,-0.016898781,0.26488048,-0.03438637,174.85413,174.76385,170.60626,170.6618,305.36285,303.94522,399.23935,395.0425,73.79961,69.10272,70.81574,65.18927,0.0,0.0,0.0,0.0,0.03597383,0.036112472,0.014156679,0.014442531,0.020611411,0.024445562,-0.03137067,-0.03791823,1.1450784,1.3580867,-1.742815,-2.1065683,0.0,0.0,0.0,-1.0,1.0,-2.1655045,-0.032718446,0.0028147027,73.79961,69.10272,70.81574,65.18927,25.107746,24.53291,25.621298,25.08114,0.023764038,0.023235457,-0.0011070737,-7.818763E-4,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.29251,0.020403259,26.600136,-0.005004784,-0.002925523,-0.0024073077,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
18.08464,4042.7847,0.417,0.0,0.0,170.84209,5764.0,100.0,0.0,-0.05187564,0.0,4.0,0.0,0.0,0.0,84.72613,1.0,0.0,0.0,60.01594,0.0,1.0,-114.522514,24.843454,540.5531,-0.017355524,0.2645813,-0.028130485,174.8914,174.82014,170.62549,170.66968,305.36285,303.94522,399.23935,395.0425,73.80086,69.10358,70.81649,65.1894,0.0,0.0,0.0,0.0,0.03605872,0.036253184,0.013992093,0.014216232,0.01555506,0.023966327,-0.032346368,-0.040671732,0.9150035,1.409784,-1.9027275,-2.3924549,0.0,0.0,0.0,-1.0,1.0,-2.1655192,-0.032705612,0.0028123893,73.80086,69.10358,70.81649,65.1894,25.107868,24.532991,25.621368,25.081152,0.023702193,0.023285033,-0.001267824,-0.0010091378,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.3028,0.019186966,26.608093,0.0021187798,-0.002998144,-0.0026582738,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
18.399239,4043.1406,0.427,0.0,0.0,170.9358,5763.0,100.0,0.0,-0.058447283,0.0,4.0,0.0,0.0,0.0,84.72912,1.0,0.0,0.0,60.015663,0.0,1.0,-114.817245,24.843603,540.3536,-0.017351182,0.26599306,-0.019857155,174.96233,174.9395,170.75694,170.68292,305.31406,303.8967,399.08948,394.89447,73.80337,69.10527,70.81798,65.18965,0.0,0.0,0.0,0.0,0.036252815,0.036529295,0.013673156,0.01382406,0.016410999,0.024518983,-0.028442537,-0.036380693,0.9653529,1.4422932,-1.6730905,-2.1400409,0.0,0.0,0.0,-1.0,1.0,-2.1655464,-0.032651495,0.00270092,73.80337,69.10527,70.81798,65.18965,25.108109,24.533154,25.621513,25.081179,0.023555756,0.023422193,-0.0010463779,-0.001479471,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.323437,0.017381918,26.624083,0.01657727,-0.0030503247,-0.008598872,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
18.871134,4043.6155,0.435,0.0,0.0,171.02904,5762.0,100.0,0.0,-0.05161566,0.0,4.0,0.0,0.0,0.0,84.73211,1.0,0.0,0.0,60.015385,0.0,1.0,-115.2104,24.843784,540.0874,-0.019829765,0.2624005,-0.0019270547,175.03053,175.09914,170.74245,170.69574,305.26523,303.84818,398.93958,394.7464,73.80588,69.106964,70.819466,65.18992,0.0,0.0,0.0,0.0,0.036502145,0.036762293,0.013357432,0.013613336,0.024634724,0.028283853,-0.03525894,-0.03349424,1.3685958,1.5713252,-1.9588301,-1.8607912,0.0,0.0,0.0,-1.0,1.0,-2.165567,-0.03249422,0.0024466952,73.80588,69.106964,70.819466,65.18992,25.10835,24.533318,25.621655,25.081203,0.023396553,0.02379777,-0.0016757208,-0.0019487994,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.34369,0.01690556,26.640028,0.030272061,-0.0027883833,-0.018927325,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
19.185734,4043.9717,0.442,0.0,0.0,171.09583,5763.0,100.0,0.0,-0.044326268,0.0,4.0,0.0,0.0,0.0,84.73435,1.0,0.0,0.0,60.01518,0.0,1.0,-115.5054,24.84391,539.88763,-0.01922547,0.24736242,0.008626948,175.27032,175.32149,170.73315,170.74261,305.21643,303.79965,398.7897,394.59833,73.807755,69.10822,70.820595,65.190125,0.0,0.0,0.0,0.0,0.036639597,0.036803745,0.013291526,0.013668455,0.025785467,0.018296639,-0.025441995,-0.01037363,1.7190311,1.2197759,-1.696133,-0.6915753,0.0,0.0,0.0,-1.0,1.0,-2.1655786,-0.03235155,0.002190889,73.807755,69.10822,70.820595,65.190125,25.10853,24.533438,25.621765,25.081223,0.024398552,0.024697684,-0.002119682,-0.0020644295,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.35799,0.017505331,26.651459,0.035164107,-0.0026199582,-0.018351326,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
19.657629,4044.3281,0.452,0.0,0.0,171.18163,5767.0,100.0,0.0,-0.042977434,0.0,4.0,0.0,0.0,0.0,84.73734,1.0,0.0,0.0,60.0149,0.0,1.0,-115.80052,24.844038,539.6878,-0.021774048,0.23947896,-0.0031411645,175.5444,175.60716,170.78072,170.87808,305.16763,303.7511,398.6399,394.45035,73.810234,69.10989,70.822105,65.1904,0.0,0.0,0.0,0.0,0.036588937,0.0365657,0.013468195,0.013896169,0.005105401,-0.011564396,0.006515478,0.016637256,0.3003177,-0.68025863,0.3832634,0.97866213,0.0,0.0,0.0,-1.0,1.0,-2.165594,-0.032213278,0.0018545655,73.810234,69.10989,70.822105,65.1904,25.108768,24.5336,25.62191,25.081251,0.02548629,0.02585287,-0.0023420742,-0.0017732137,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.376392,0.0173921,26.666403,0.031326007,-0.002644165,-0.008772555,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
20.129528,4044.8035,0.46,0.0,0.0,171.24388,5771.0,100.0,0.0,-0.047827054,0.0,4.0,0.0,0.0,0.0,84.739586,1.0,0.0,0.0,60.01469,0.0,1.0,-116.19417,24.844215,539.42126,-0.022482496,0.2338695,-0.03322093,175.69981,175.77182,170.86798,170.89658,305.1188,303.70258,398.4901,394.3024,73.812096,69.11114,70.82324,65.190605,0.0,0.0,0.0,0.0,0.036385402,0.03626933,0.013675446,0.014058191,-0.014121954,-0.029689737,0.02132889,0.021652007,-0.784553,-1.6494298,1.1849384,1.2028893,0.0,0.0,0.0,-1.0,1.0,-2.1656082,-0.032209214,0.0016591968,73.812096,69.11114,70.82324,65.190605,25.108948,24.533718,25.622019,25.08127,0.026021011,0.026441412,-0.002195113,-0.0020281922,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.38975,0.01505751,26.677374,0.022958325,-0.0027453755,0.0028090784,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
20.447374,4045.1602,0.47,0.0,0.0,171.3059,5778.0,100.0,0.0,-0.062244527,0.0,4.0,0.0,0.0,0.0,84.74183,1.0,0.0,0.0,60.01448,0.0,1.0,-116.489525,24.844341,539.2212,-0.02323555,0.23502758,-0.034066625,175.73271,175.83405,170.9717,170.9541,305.06998,303.65402,398.34036,394.15442,73.81395,69.11238,70.82438,65.19081,0.0,0.0,0.0,0.0,0.036146622,0.035968594,0.013882383,0.0142436335,-0.02457305,-0.03317247,0.023010446,0.019303575,-1.3651695,-1.8429149,1.2783581,1.0724208,0.0,0.0,0.0,-1.0,1.0,-2.1656253,-0.03225248,0.001537294,73.81395,69.11238,70.82438,65.19081,25.109125,24.533836,25.622128,25.08129,0.025841586,0.026433097,-0.0019509464,-0.0020535914,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.40316,0.0125868805,26.688421,0.012787492,-0.0027570797,0.0064510647,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
20.925768,4045.636,0.477,0.0,0.0,171.36821,5784.0,100.0,0.0,-0.06566952,0.0,4.0,0.0,0.0,0.0,84.744095,1.0,0.0,0.0,60.01427,0.0,1.0,-116.88349,24.844479,538.9544,-0.020693334,0.23424439,-0.032882582,175.83826,175.87889,171.07758,171.0785,305.06998,303.65402,398.34036,394.15442,73.8158,69.113625,70.8255,65.191025,0.0,0.0,0.0,0.0,0.03591611,0.03568515,0.014066943,0.014416676,-0.027605496,-0.034363393,0.023029251,0.021087334,-1.6238527,-2.0213761,1.3546618,1.2404314,0.0,0.0,0.0,-1.0,1.0,-2.1656444,-0.032297246,0.0014890591,73.8158,69.113625,70.8255,65.191025,25.109304,24.533957,25.622236,25.081312,0.026084458,0.026321536,-0.001695974,-0.001690497,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.416653,0.009974838,26.699299,0.0031771893,-0.0027551234,0.0058646416,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
21.244698,4045.993,0.485,0.0,0.0,171.4524,5792.0,100.0,0.0,-0.073485024,0.0,4.0,0.0,0.0,0.0,84.747116,1.0,0.0,0.0,60.013992,0.0,1.0,-117.17908,24.84456,538.75415,-0.014153696,0.24148393,-0.06599551,175.97643,175.8869,171.22888,171.17216,305.02115,303.60547,398.19058,394.00647,73.81827,69.11529,70.826996,65.19129,0.0,0.0,0.0,0.0,0.035547975,0.03535814,0.014167956,0.014536584,-0.03990978,-0.040697057,0.01903822,0.01953002,-2.660652,-2.7131371,1.2692147,1.3020014,0.0,0.0,0.0,-1.0,1.0,-2.1656735,-0.032350145,0.0015241038,73.81827,69.11529,70.826996,65.19129,25.10954,24.534117,25.62238,25.081337,0.026386462,0.025864357,-0.0013036191,-0.0016344176,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.43546,0.003714017,26.713604,-0.0072334763,-0.0028177002,0.0038037421,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
21.723093,4046.35,0.495,0.0,0.0,171.518,5796.0,100.0,0.0,-0.07679934,0.0,4.0,0.0,0.0,0.0,84.74938,1.0,0.0,0.0,60.013783,0.0,1.0,-117.47478,24.844614,538.5539,-0.013632373,0.25245753,-0.06559541,175.91951,175.82158,171.30203,171.30792,304.9723,303.5569,398.04077,393.8585,73.82012,69.11654,70.8281,65.19148,0.0,0.0,0.0,0.0,0.03531124,0.03522051,0.0140859345,0.01452019,-0.033603773,-0.025813363,0.001055085,0.005750762,-1.8668762,-1.4340757,0.058615834,0.31948677,0.0,0.0,0.0,-1.0,1.0,-2.165697,-0.032351553,0.001600171,73.82012,69.11654,70.8281,65.19148,25.109718,24.534237,25.622486,25.081356,0.025662057,0.02509104,-0.0012592374,-0.0012248752,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.45033,-0.0011681489,26.724682,-0.011095868,-0.0028733583,-0.0014085505,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
22.201488,4046.8264,0.502,0.0,0.0,171.6084,5798.0,100.0,0.0,-0.083627366,0.0,4.0,0.0,0.0,0.0,84.7524,1.0,0.0,0.0,60.013504,0.0,1.0,-117.8692,24.844625,538.2867,-0.013671633,0.25631142,-0.057238538,175.86954,175.81245,171.36868,171.37608,304.92343,303.5083,397.8909,393.71042,73.8226,69.11822,70.829575,65.191734,0.0,0.0,0.0,0.0,0.035153784,0.035233572,0.013865768,0.014293048,-0.023187742,-0.0073274267,-0.01777577,-0.014325659,-1.3639848,-0.4310251,-1.0456336,-0.8426858,0.0,0.0,0.0,-1.0,1.0,-2.16573,-0.032319028,0.0017003558,73.8226,69.11822,70.829575,65.191734,25.109957,24.534399,25.622627,25.081379,0.024830593,0.024497962,-0.0013969055,-0.0013537349,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.47048,-0.00694992,26.739777,-0.008831456,-0.003015354,-0.004377713,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
22.520418,4047.1838,0.51,0.0,0.0,171.67668,5797.0,100.0,0.0,-0.08616711,0.0,4.0,0.0,0.0,0.0,84.75467,1.0,0.0,0.0,60.013294,0.0,1.0,-118.16515,24.844593,538.0862,-0.019227957,0.2582171,-0.044518888,175.88156,175.88544,171.37125,171.43143,304.8745,303.45966,397.741,393.56235,73.82446,69.11948,70.83068,65.191925,0.0,0.0,0.0,0.0,0.035166744,0.035313994,0.013727826,0.014096176,-0.009633104,0.0062321625,-0.023873895,-0.028267564,-0.64220697,0.41547748,-1.591593,-1.8845043,0.0,0.0,0.0,-1.0,1.0,-2.1657543,-0.032279488,0.0017462259,73.82446,69.11948,70.83068,65.191925,25.110136,24.53452,25.622734,25.081398,0.024492983,0.024515623,-0.0017791511,-0.0014285297,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.48551,-0.010167306,26.75147,-0.004878632,-0.0030249038,-0.005207439,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
22.839348,4047.5415,0.517,0.0,0.0,171.74548,5795.0,100.0,0.0,-0.08860586,0.0,4.0,0.0,0.0,0.0,84.756935,1.0,0.0,0.0,60.013084,0.0,1.0,-118.46122,24.844532,537.8856,-0.016487962,0.26103613,-0.022542765,175.92683,175.91576,171.46367,171.48314,304.82562,303.41104,397.5911,393.41428,73.826324,69.120735,70.83179,65.19211,0.0,0.0,0.0,0.0,0.03527517,0.03543609,0.013676049,0.01391935,0.008092324,0.013501197,-0.012647919,-0.02491319,0.53948826,0.9000798,-0.8431946,-1.6608794,0.0,0.0,0.0,-1.0,1.0,-2.165778,-0.032253243,0.0017618624,73.826324,69.120735,70.83179,65.19211,25.110315,24.534641,25.62284,25.081415,0.024346262,0.024281703,-0.0016409023,-0.0015274936,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.500732,-0.011831859,26.763191,-6.815667E-4,-0.0031339019,-0.00269921,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
23.317741,4047.8992,0.527,0.0,0.0,171.83765,5792.0,100.0,0.0,-0.09496216,0.0,4.0,0.0,0.0,0.0,84.75996,1.0,0.0,0.0,60.012806,0.0,1.0,-118.7574,24.844452,537.68494,-0.016842874,0.26147953,-0.011131329,176.03027,176.0295,171.57098,171.57745,304.77667,303.36237,397.44116,393.26617,73.828804,69.12241,70.83325,65.19235,0.0,0.0,0.0,0.0,0.03545368,0.03564789,0.013607756,0.013733252,0.016878633,0.019641045,-0.007062931,-0.021348488,0.99286073,1.1553556,-0.41546655,-1.2557933,0.0,0.0,0.0,-1.0,1.0,-2.1658087,-0.032234173,0.0017262091,73.828804,69.12241,70.83325,65.19235,25.110554,24.534801,25.622982,25.08144,0.024398785,0.0243943,-0.0015518327,-0.001514221,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.520996,-0.013003436,26.778904,0.005979049,-0.0031720053,-0.0017585277,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
23.796137,4048.3765,0.535,0.0,0.0,171.88348,5792.0,100.0,0.0,-0.09539544,0.0,4.0,0.0,0.0,0.0,84.76147,1.0,0.0,0.0,60.012665,0.0,1.0,-119.15249,24.844328,537.41724,-0.018743541,0.25865552,-0.018018097,176.11974,176.11406,171.6248,171.62872,304.77667,303.36237,397.44116,393.26617,73.83004,69.12325,70.833984,65.192474,0.0,0.0,0.0,0.0,0.035528727,0.035723917,0.013566235,0.013642805,0.014086564,0.015990395,-0.0061007836,-0.015363615,0.7825869,0.88835526,-0.33893242,-0.85353416,0.0,0.0,0.0,-1.0,1.0,-2.165823,-0.032226935,0.0016839136,73.83004,69.12325,70.833984,65.192474,25.110672,24.534883,25.62305,25.081451,0.024646044,0.024612974,-0.0015050384,-0.0014821178,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.530987,-0.014075285,26.786732,0.009123739,-0.0031545938,-0.001318757,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
24.115067,4048.7346,0.542,0.0,0.0,171.97446,5793.0,100.0,0.0,-0.101628795,0.0,4.0,0.0,0.0,0.0,84.76449,1.0,0.0,0.0,60.012386,0.0,1.0,-119.44893,24.844227,537.2164,-0.016709078,0.25675243,-0.04038965,176.28296,176.25171,171.69395,171.72241,304.72772,303.3137,397.29123,393.11807,73.83251,69.12493,70.83545,65.19272,0.0,0.0,0.0,0.0,0.03555339,0.035814002,0.01346909,0.013442952,0.00664716,0.011074047,-0.009244432,-0.019353317,0.443144,0.73826975,-0.61629546,-1.2902211,0.0,0.0,0.0,-1.0,1.0,-2.1658506,-0.032227688,0.0015559354,73.83251,69.12493,70.83545,65.19272,25.11091,24.535046,25.623194,25.081476,0.025053147,0.0248715,-0.001631055,-0.0014655663,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.550945,-0.017810632,26.80214,0.014740507,-0.0031819611,0.0013267533,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
1 player,v8,???,0,2026061914565357
2 Game,version,date,track,car,event,laptime [s],S1 [s],S2 [s],S3 [s],S4+ [s]
3 ACC,1.1,2026-06-19 12:01:23,Hungaroring,CaR,Practice,123.702,43.470,45.950,34.282
4 TrackID [int],Tracklen [m],EventID [int],EventID2 [int],Weather [txt],TeamID [int],Tyre [txt],Valid [b],Pitlap [b],Online [b],Lap [int],LapsInRace [int],Fuel at Start [kg],MaxGears [int],MaxRevs [rpm],IdleRevs [rpm},TrackTemp [C],AmbientTemp [C],Windspeed [m/s],WindDirection [ ]
5 7,4381.009765625,0,50,Clear,57,DHA,false,false,false,1,20,60.019676208496094,6,6500.0,1950.0001,27.946645736694336,27.15789031982422,2.18314528465271,75.69888305664062
6 FWing,RWing,OnThrottle,OffThrottle,FrontCamber,RearCamber,FrontToe,RearToe,FrontSusp,RearSusp,FrontAntiRoll,RearAntiRoll,FrontSuspH,RearSuspH,BrakePressure,BrakeBias,FLTyrePressure,FRTyrePressure,RLTyrePressure,RRTyrePressure,Ballast,FuelLoad
7 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
8 LapDistance [m],TotalDistance [m],LapTime [s],Sector1Time [s],Sector2Time [s],Speed [km/h],EngineRevs [rpm],ThrottlePercentage [%],BrakePercentage [%],Steer [%],Clutch [%],Gear [int],DRS [0/1],CanUseDRS [0/1],KERSLevel [J],EngineTemperature [C],RacePosition [int],Sector [int],InPits [int],FuelRemaining [l],CurrentFlag [int],CurrentLapInvalid [int],X [m],Y [m],Z [m],GForceLatitudinal [G],GForceLongitudinal [G],GForceVertical [G],WheelSpeedRearLeft [km/h],WheelSpeedRearRight [km/h],WheelSpeedFrontLeft [km/h],WheelSpeedFrontRight [km/h],BrakeTemperatureRearLeft [C],BrakeTemperatureRearRight [C],BrakeTemperatureFrontLeft [C],BrakeTemperatureFrontRight [C],TyreTemperatureRearLeft [C],TyreTemperatureRearRight [C],TyreTemperatureFrontLeft [C],TyreTemperatureFrontRight [C],TyreWearRearLeft [%],TyreWearRearRight [%],TyreWearFrontLeft [%],TyreWearFrontRight [%],SuspensionPositionRearLeft [m],SuspensionPositionRearRight [m],SuspensionPositionFrontLeft [m],SuspensionPositionFrontRight [m],SuspensionVelocityRearLeft [m/s],SuspensionVelocityRearRight [m/s],SuspensionVelocityFrontLeft [m/s],SuspensionVelocityFrontRight [m/s],SuspensionAccelerationRearLeft [m/s^2],SuspensionAccelerationRearRight [m/s^2],SuspensionAccelerationFrontLeft [m/s^2],SuspensionAccelerationFrontRight [m/s^2],MGUKHarvested [J],MGUHHarvested [J],ERSSpent [J],ERSMode [int],FuelMixMode [int],Yaw [rad],Roll [rad],Pitch [rad],TyreCarcassTemperatureRearLeft [C],TyreCarcassTemperatureRearRight [C],TyreCarcassTemperatureFrontLeft [C],TyreCarcassTemperatureFrontRight [C],TyrePressureRearLeft [PSI],TyrePressureRearRight [PSI],TyrePressureFrontLeft [PSI],TyrePressureFrontRight [PSI],WheelSlipRearLeft [%],WheelSlipRearRight [%],WheelSlipFrontLeft [%],WheelSlipFrontRight [%],Torque [Nm],Handbrake [%],RearLeftInside [C],RearLeftMiddle [C],RearLeftOutside [C],RearRightInside [C],RearRightMiddle [C],RearRightOutside [C],FrontLeftInside [C],FrontLeftMiddle [C],FrontLeftOutside [C],FrontRightInside [C],FrontRightMiddle [C],FrontRightOutside [C],WoldSpeedX [km/h],WoldSpeedY [km/h],WoldSpeedZ [km/h],LocalAngularVelocityX [rad/s],LocalAngularVelocityY [rad/s],LocalAngularVelocityZ [rad/s],IcePower [W],MgukPower [W],FrontRideHeight [m],RearRideHeight [m],LoadRearLeft [N],LoadRearRight [N],LoadFrontLeft [N],LoadFrontRight [N],Extra1 [m],Extra2 [],Extra3 [],Extra4 []
9 11.52857,4036.4016,0.282,0.0,0.0,169.64093,5723.0,100.0,0.0,-0.010964928,0.0,4.0,0.0,0.0,0.0,84.686035,1.0,0.0,0.0,60.019676,0.0,1.0,-109.23591,24.842262,544.1296,-0.036001448,0.2483705,0.025862258,173.77263,173.87315,169.28203,169.32233,305.99573,304.57455,401.18625,396.96573,73.76696,69.08077,70.795746,65.185524,0.0,0.0,0.0,0.0,0.034967814,0.034488626,0.013857091,0.013722197,2.075326E-5,1.89149E-5,-7.246143E-6,-2.5508643E-6,-1.3962284E-7,-1.2725481E-7,4.8750273E-8,1.716159E-8,0.0,0.0,0.0,-1.0,1.0,-2.1652644,-0.03211098,0.0017947316,73.76696,69.08077,70.795746,65.185524,25.10461,24.530798,25.619375,25.080778,0.024355568,0.024948094,-0.0021156704,-0.0018781277,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.042892,0.0030937572,26.396013,-0.0044563767,-5.508619E-4,0.013827926,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
10 12.014709,4036.7551,0.29,0.0,0.0,169.72768,5726.0,100.0,0.0,0.0018527993,0.0,4.0,0.0,0.0,0.0,84.688995,1.0,0.0,0.0,60.0194,0.0,1.0,-109.5287,24.84228,543.93164,-0.023693023,0.24786447,0.012986496,174.02707,173.90872,169.38522,169.4204,305.94714,304.52625,401.0366,396.81787,73.76947,69.08246,70.79732,65.18582,0.0,0.0,0.0,0.0,0.034994584,0.03462096,0.0141735785,0.013794035,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-1.0,1.0,-2.1652737,-0.03226933,0.0018673544,73.76947,69.08246,70.79732,65.18582,25.10485,24.53096,25.619526,25.080807,0.025331153,0.024633894,-0.0020176482,-0.0018104152,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.061405,0.0047848364,26.411741,-0.009338445,-8.7213836E-4,0.017504098,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
11 12.338801,4037.1086,0.3,0.0,0.0,169.79353,5729.0,100.0,0.0,-0.005238485,0.0,4.0,0.0,0.0,0.0,84.691216,1.0,0.0,0.0,60.019196,0.0,1.0,-109.82159,24.84231,543.73364,-0.019859513,0.24973202,0.0029058661,174.1567,173.91837,169.62521,169.51479,305.89853,304.47787,400.88684,396.66995,73.771355,69.083725,70.79849,65.18604,0.0,0.0,0.0,0.0,0.034860697,0.034790497,0.014345023,0.013859233,-0.0059509445,0.016770637,0.027107334,0.0076130964,-0.33060804,0.931702,1.505963,0.4229498,0.0,0.0,0.0,-1.0,1.0,-2.1652846,-0.03241021,0.0019555911,73.771355,69.083725,70.79849,65.18604,25.105034,24.531082,25.61964,25.08083,0.025696889,0.024293179,-9.913363E-4,-0.0016416886,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.07583,0.0050230986,26.423271,-0.012692683,-0.0012003235,0.017881883,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
12 12.82494,4037.5803,0.307,0.0,0.0,169.86078,5732.0,100.0,0.0,-0.0020330295,0.0,4.0,0.0,0.0,0.0,84.693436,1.0,0.0,0.0,60.01899,0.0,1.0,-110.21227,24.842361,543.4695,-0.015500802,0.25483412,-0.035387367,174.24815,174.01012,169.7397,169.63368,305.89853,304.47787,400.88684,396.66995,73.77323,69.08499,70.799644,65.18625,0.0,0.0,0.0,0.0,0.034660157,0.034980144,0.014283132,0.01383878,-0.019672163,0.021128532,0.006444314,0.002632082,-1.157186,1.2428548,0.3790773,0.15482835,0.0,0.0,0.0,-1.0,1.0,-2.1652973,-0.03250376,0.0020536045,73.77323,69.08499,70.799644,65.18625,25.105213,24.531204,25.619751,25.080849,0.025829243,0.024427839,-7.1281136E-4,-0.0013369576,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.090744,0.0024308157,26.434696,-0.012943774,-0.0012463271,0.009102043,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
13 13.149033,4037.9343,0.315,0.0,0.0,169.9762,5737.0,100.0,0.0,0.009160281,0.0,4.0,0.0,0.0,0.0,84.697136,1.0,0.0,0.0,60.018642,0.0,1.0,-110.50542,24.842388,543.27124,-0.032200195,0.26522526,-0.046108786,173.96013,174.05067,169.72847,169.74197,305.8012,304.38113,400.5873,396.37408,73.77638,69.087105,70.80156,65.1866,0.0,0.0,0.0,0.0,0.03464579,0.035214636,0.013901675,0.013589289,-0.014327218,0.028275946,-0.029556515,-0.017996257,-0.95514786,1.885063,-1.9704343,-1.1997504,0.0,0.0,0.0,-1.0,1.0,-2.1653147,-0.032495715,0.0021680715,73.77638,69.087105,70.80156,65.1866,25.105516,24.531408,25.619934,25.080883,0.02343825,0.023970794,-0.0014574057,-0.0013779213,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.115627,-0.0038357505,26.455435,-0.005286799,-0.0010240518,-0.0044617346,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
14 13.4731245,4038.2883,0.325,0.0,0.0,170.04463,5738.0,100.0,0.0,0.016547244,0.0,4.0,0.0,0.0,0.0,84.699356,1.0,0.0,0.0,60.018436,0.0,1.0,-110.798676,24.842382,543.0729,-0.026762703,0.25481597,-0.0077182837,174.0757,174.09074,169.71399,169.76973,305.8012,304.38113,400.5873,396.37408,73.778275,69.08838,70.80272,65.186806,0.0,0.0,0.0,0.0,0.03487389,0.03536873,0.013778092,0.013468641,0.011874156,0.021588057,-0.028057748,-0.020563291,0.65967536,1.1993365,-1.5587639,-1.142405,0.0,0.0,0.0,-1.0,1.0,-2.165323,-0.03248412,0.0021698782,73.778275,69.08838,70.80272,65.186806,25.105698,24.53153,25.620045,25.080902,0.023705985,0.023794403,-0.0019443971,-0.0016166419,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.130116,-0.0042215604,26.467607,0.0018667248,-0.001168449,9.254145E-4,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
15 13.959263,4038.7605,0.332,0.0,0.0,170.1122,5739.0,100.0,0.0,0.0083989715,0.0,4.0,0.0,0.0,0.0,84.70158,1.0,0.0,0.0,60.018227,0.0,1.0,-111.18986,24.842344,542.80835,-0.027512636,0.25494838,0.019121999,174.11198,174.16544,169.7345,169.77376,305.75256,304.33273,400.4375,396.2261,73.78017,69.089645,70.80388,65.18702,0.0,0.0,0.0,0.0,0.03509519,0.035522744,0.013817208,0.013471926,0.026435317,0.018123975,-0.004968661,-0.00690373,1.5550187,1.0661162,-0.29227418,-0.40610176,0.0,0.0,0.0,-1.0,1.0,-2.1653323,-0.032541778,0.0021323483,73.78017,69.089645,70.80388,65.18702,25.105879,24.531652,25.620157,25.080923,0.023512656,0.023826884,-0.0022203415,-0.001989509,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.144463,-0.0028066994,26.4798,0.006246484,-0.0014940045,0.0111888945,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
16 14.283356,4039.1147,0.342,0.0,0.0,170.17862,5739.0,100.0,0.0,-0.0034686916,0.0,4.0,0.0,0.0,0.0,84.7038,1.0,0.0,0.0,60.01802,0.0,1.0,-111.48337,24.842316,542.6098,-0.028546177,0.24940713,0.07171454,174.1544,174.22723,169.84029,169.7952,305.70386,304.28433,400.28775,396.07816,73.78207,69.09092,70.805046,65.18724,0.0,0.0,0.0,0.0,0.035337783,0.035671897,0.014006146,0.013649582,0.027287751,0.017833402,0.013414934,0.010643593,1.6051619,1.0490236,0.78911376,0.6260937,0.0,0.0,0.0,-1.0,1.0,-2.165343,-0.032671917,0.0020802144,73.78207,69.09092,70.805046,65.18724,25.106062,24.531775,25.62027,25.080944,0.023362413,0.02379034,-0.001988125,-0.0022530768,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.158398,0.002200487,26.491964,0.0066807917,-0.001680956,0.019356037,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
17 14.769494,4039.5874,0.35,0.0,0.0,170.26718,5740.0,100.0,0.0,2.7964596E-4,0.0,4.0,0.0,0.0,0.0,84.70676,1.0,0.0,0.0,60.017742,0.0,1.0,-111.87488,24.84231,542.3449,-0.024774725,0.25113165,0.080288865,174.34059,174.34473,170.01518,169.89398,305.65518,304.23593,400.13797,395.93024,73.784584,69.092606,70.80659,65.187546,0.0,0.0,0.0,0.0,0.03564696,0.03586361,0.014187679,0.013973102,0.030653965,0.0189371,0.020581711,0.02784313,1.702998,1.0520611,1.1434284,1.5468405,0.0,0.0,0.0,-1.0,1.0,-2.1653576,-0.03284161,0.0020180475,73.784584,69.092606,70.80659,65.187546,25.106304,24.531937,25.620417,25.080975,0.023923587,0.023947949,-0.0014799545,-0.002191845,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.17719,0.009847768,26.507994,0.005680933,-0.00166586,0.014170732,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
18 15.253259,4039.9421,0.357,0.0,0.0,170.3318,5741.0,100.0,0.0,-0.0037863501,0.0,4.0,0.0,0.0,0.0,84.70898,1.0,0.0,0.0,60.017536,0.0,1.0,-112.16864,24.842356,542.1462,-0.01671688,0.24182548,0.051545005,174.62819,174.5305,170.05142,169.97345,305.6065,304.1875,399.98825,395.7823,73.78646,69.09387,70.80774,65.187775,0.0,0.0,0.0,0.0,0.035722785,0.035942208,0.014252585,0.014245591,0.025666753,0.01802072,0.016429275,0.039733946,1.7111169,1.2013813,1.095285,2.6489298,0.0,0.0,0.0,-1.0,1.0,-2.16537,-0.032910272,0.001991446,73.78646,69.09387,70.80774,65.187775,25.106483,24.532059,25.62053,25.080997,0.025223643,0.024650058,-0.0016460561,-0.0021038703,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.191162,0.013625955,26.519114,0.002056227,-0.0017836776,0.0065005226,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
19 15.567857,4040.2969,0.367,0.0,0.0,170.39563,5744.0,100.0,0.0,2.2442082E-4,0.0,4.0,0.0,0.0,0.0,84.7112,1.0,0.0,0.0,60.01733,0.0,1.0,-112.4625,24.84244,541.9473,-0.01776832,0.24116075,0.024250237,174.77617,174.67091,170.07419,170.066,305.6065,304.1875,399.98825,395.7823,73.78833,69.09513,70.8089,65.18801,0.0,0.0,0.0,0.0,0.035606816,0.03589045,0.01432358,0.014522852,-0.0023613959,0.0015786466,0.007994199,0.032338258,-0.13890564,0.09286156,0.470247,1.9022504,0.0,0.0,0.0,-1.0,1.0,-2.1653836,-0.03292371,0.0020090973,73.78833,69.09513,70.8089,65.18801,25.106663,24.532179,25.62064,25.081018,0.025708094,0.025090385,-0.0018864868,-0.0019345023,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.20509,0.015505659,26.53018,-0.004986443,-0.0016786046,-3.4433513E-4,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
20 16.039753,4040.7703,0.375,0.0,0.0,170.45953,5748.0,100.0,0.0,0.0040749414,0.0,4.0,0.0,0.0,0.0,84.71342,1.0,0.0,0.0,60.01712,0.0,1.0,-112.854485,24.842587,541.68207,-0.018202942,0.2413529,0.017172432,174.78044,174.73691,170.10362,170.1539,305.5578,304.1391,399.8385,395.6344,73.79021,69.09639,70.81005,65.18824,0.0,0.0,0.0,0.0,0.03545997,0.035773963,0.0144413635,0.014785069,-0.014600862,-0.00934696,0.010487676,0.029970996,-0.811159,-0.51927555,0.58264863,1.6650554,0.0,0.0,0.0,-1.0,1.0,-2.1653988,-0.032894265,0.002088995,73.79021,69.09639,70.81005,65.18824,25.106844,24.532299,25.62075,25.081041,0.025348512,0.025093224,-0.002087924,-0.0017930274,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.21901,0.016820235,26.541273,-0.013632805,-0.0016907271,-0.0055084135,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
21 16.354351,4041.1255,0.382,0.0,0.0,170.54517,5755.0,100.0,0.0,0.0012987609,0.0,4.0,0.0,0.0,0.0,84.71641,1.0,0.0,0.0,60.016846,0.0,1.0,-113.14859,24.84271,541.48303,-0.022501389,0.2451741,0.0393255,174.67075,174.71301,170.24464,170.33131,305.5091,304.09067,399.68878,395.48648,73.79271,69.09807,70.81159,65.18854,0.0,0.0,0.0,0.0,0.035401274,0.035641193,0.0146766985,0.015073612,-0.013702859,-0.01661703,0.023541227,0.03671733,-0.913524,-1.107802,1.5694151,2.4478219,0.0,0.0,0.0,-1.0,1.0,-2.1654234,-0.032835644,0.002294316,73.79271,69.09807,70.81159,65.18854,25.107084,24.532461,25.6209,25.08107,0.024190536,0.024438366,-0.0017621501,-0.0012539756,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.237606,0.02003613,26.556547,-0.02424377,-0.0018405571,-0.00504553,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
22 16.66895,4041.4807,0.392,0.0,0.0,170.63432,5761.0,100.0,0.0,-0.012933018,0.0,4.0,0.0,0.0,0.0,84.7194,1.0,0.0,0.0,60.016567,0.0,1.0,-113.4428,24.842848,541.28394,-0.02279238,0.25629854,0.0285197,174.57756,174.62965,170.44597,170.5077,305.46036,304.0422,399.539,395.33853,73.79522,69.099754,70.81311,65.18882,0.0,0.0,0.0,0.0,0.035602823,0.035672452,0.014721351,0.0151250595,0.00840316,-0.005971202,0.016469836,0.01999944,0.49430352,-0.3512472,0.9688139,1.1764377,0.0,0.0,0.0,-1.0,1.0,-2.1654518,-0.032781146,0.0025523542,73.79522,69.099754,70.81311,65.18882,25.107327,24.532623,25.621044,25.081099,0.023109358,0.023414625,-0.0011038778,-7.420546E-4,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.257008,0.022770366,26.57248,-0.025350533,-0.0020437962,-0.00586406,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
23 17.140846,4041.9546,0.4,0.0,0.0,170.70256,5763.0,100.0,0.0,-0.021631196,0.0,4.0,0.0,0.0,0.0,84.72164,1.0,0.0,0.0,60.016357,0.0,1.0,-113.83525,24.843058,541.0183,-0.01922873,0.25786906,0.008813665,174.71027,174.66154,170.55545,170.61969,305.4116,303.9937,399.3892,395.19055,73.797104,69.10103,70.81424,65.18902,0.0,0.0,0.0,0.0,0.035794284,0.035845757,0.014541981,0.014907652,0.021833926,0.011364619,-0.007484315,-0.009220041,1.2129959,0.63136774,-0.41579527,-0.5122245,0.0,0.0,0.0,-1.0,1.0,-2.165474,-0.032745425,0.0027118518,73.797104,69.10103,70.81424,65.18902,25.107508,24.532745,25.621153,25.081116,0.023477638,0.02319233,-8.6181273E-4,-4.8549226E-4,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.271946,0.0233458,26.584316,-0.01879117,-0.0023562151,-0.003961567,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
24 17.612743,4042.3103,0.41,0.0,0.0,170.79535,5764.0,100.0,0.0,-0.05187564,0.0,4.0,0.0,0.0,0.0,84.72463,1.0,0.0,0.0,60.01608,0.0,1.0,-114.129715,24.843231,540.819,-0.016898781,0.26488048,-0.03438637,174.85413,174.76385,170.60626,170.6618,305.36285,303.94522,399.23935,395.0425,73.79961,69.10272,70.81574,65.18927,0.0,0.0,0.0,0.0,0.03597383,0.036112472,0.014156679,0.014442531,0.020611411,0.024445562,-0.03137067,-0.03791823,1.1450784,1.3580867,-1.742815,-2.1065683,0.0,0.0,0.0,-1.0,1.0,-2.1655045,-0.032718446,0.0028147027,73.79961,69.10272,70.81574,65.18927,25.107746,24.53291,25.621298,25.08114,0.023764038,0.023235457,-0.0011070737,-7.818763E-4,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.29251,0.020403259,26.600136,-0.005004784,-0.002925523,-0.0024073077,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
25 18.08464,4042.7847,0.417,0.0,0.0,170.84209,5764.0,100.0,0.0,-0.05187564,0.0,4.0,0.0,0.0,0.0,84.72613,1.0,0.0,0.0,60.01594,0.0,1.0,-114.522514,24.843454,540.5531,-0.017355524,0.2645813,-0.028130485,174.8914,174.82014,170.62549,170.66968,305.36285,303.94522,399.23935,395.0425,73.80086,69.10358,70.81649,65.1894,0.0,0.0,0.0,0.0,0.03605872,0.036253184,0.013992093,0.014216232,0.01555506,0.023966327,-0.032346368,-0.040671732,0.9150035,1.409784,-1.9027275,-2.3924549,0.0,0.0,0.0,-1.0,1.0,-2.1655192,-0.032705612,0.0028123893,73.80086,69.10358,70.81649,65.1894,25.107868,24.532991,25.621368,25.081152,0.023702193,0.023285033,-0.001267824,-0.0010091378,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.3028,0.019186966,26.608093,0.0021187798,-0.002998144,-0.0026582738,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
26 18.399239,4043.1406,0.427,0.0,0.0,170.9358,5763.0,100.0,0.0,-0.058447283,0.0,4.0,0.0,0.0,0.0,84.72912,1.0,0.0,0.0,60.015663,0.0,1.0,-114.817245,24.843603,540.3536,-0.017351182,0.26599306,-0.019857155,174.96233,174.9395,170.75694,170.68292,305.31406,303.8967,399.08948,394.89447,73.80337,69.10527,70.81798,65.18965,0.0,0.0,0.0,0.0,0.036252815,0.036529295,0.013673156,0.01382406,0.016410999,0.024518983,-0.028442537,-0.036380693,0.9653529,1.4422932,-1.6730905,-2.1400409,0.0,0.0,0.0,-1.0,1.0,-2.1655464,-0.032651495,0.00270092,73.80337,69.10527,70.81798,65.18965,25.108109,24.533154,25.621513,25.081179,0.023555756,0.023422193,-0.0010463779,-0.001479471,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.323437,0.017381918,26.624083,0.01657727,-0.0030503247,-0.008598872,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
27 18.871134,4043.6155,0.435,0.0,0.0,171.02904,5762.0,100.0,0.0,-0.05161566,0.0,4.0,0.0,0.0,0.0,84.73211,1.0,0.0,0.0,60.015385,0.0,1.0,-115.2104,24.843784,540.0874,-0.019829765,0.2624005,-0.0019270547,175.03053,175.09914,170.74245,170.69574,305.26523,303.84818,398.93958,394.7464,73.80588,69.106964,70.819466,65.18992,0.0,0.0,0.0,0.0,0.036502145,0.036762293,0.013357432,0.013613336,0.024634724,0.028283853,-0.03525894,-0.03349424,1.3685958,1.5713252,-1.9588301,-1.8607912,0.0,0.0,0.0,-1.0,1.0,-2.165567,-0.03249422,0.0024466952,73.80588,69.106964,70.819466,65.18992,25.10835,24.533318,25.621655,25.081203,0.023396553,0.02379777,-0.0016757208,-0.0019487994,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.34369,0.01690556,26.640028,0.030272061,-0.0027883833,-0.018927325,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
28 19.185734,4043.9717,0.442,0.0,0.0,171.09583,5763.0,100.0,0.0,-0.044326268,0.0,4.0,0.0,0.0,0.0,84.73435,1.0,0.0,0.0,60.01518,0.0,1.0,-115.5054,24.84391,539.88763,-0.01922547,0.24736242,0.008626948,175.27032,175.32149,170.73315,170.74261,305.21643,303.79965,398.7897,394.59833,73.807755,69.10822,70.820595,65.190125,0.0,0.0,0.0,0.0,0.036639597,0.036803745,0.013291526,0.013668455,0.025785467,0.018296639,-0.025441995,-0.01037363,1.7190311,1.2197759,-1.696133,-0.6915753,0.0,0.0,0.0,-1.0,1.0,-2.1655786,-0.03235155,0.002190889,73.807755,69.10822,70.820595,65.190125,25.10853,24.533438,25.621765,25.081223,0.024398552,0.024697684,-0.002119682,-0.0020644295,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.35799,0.017505331,26.651459,0.035164107,-0.0026199582,-0.018351326,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
29 19.657629,4044.3281,0.452,0.0,0.0,171.18163,5767.0,100.0,0.0,-0.042977434,0.0,4.0,0.0,0.0,0.0,84.73734,1.0,0.0,0.0,60.0149,0.0,1.0,-115.80052,24.844038,539.6878,-0.021774048,0.23947896,-0.0031411645,175.5444,175.60716,170.78072,170.87808,305.16763,303.7511,398.6399,394.45035,73.810234,69.10989,70.822105,65.1904,0.0,0.0,0.0,0.0,0.036588937,0.0365657,0.013468195,0.013896169,0.005105401,-0.011564396,0.006515478,0.016637256,0.3003177,-0.68025863,0.3832634,0.97866213,0.0,0.0,0.0,-1.0,1.0,-2.165594,-0.032213278,0.0018545655,73.810234,69.10989,70.822105,65.1904,25.108768,24.5336,25.62191,25.081251,0.02548629,0.02585287,-0.0023420742,-0.0017732137,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.376392,0.0173921,26.666403,0.031326007,-0.002644165,-0.008772555,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
30 20.129528,4044.8035,0.46,0.0,0.0,171.24388,5771.0,100.0,0.0,-0.047827054,0.0,4.0,0.0,0.0,0.0,84.739586,1.0,0.0,0.0,60.01469,0.0,1.0,-116.19417,24.844215,539.42126,-0.022482496,0.2338695,-0.03322093,175.69981,175.77182,170.86798,170.89658,305.1188,303.70258,398.4901,394.3024,73.812096,69.11114,70.82324,65.190605,0.0,0.0,0.0,0.0,0.036385402,0.03626933,0.013675446,0.014058191,-0.014121954,-0.029689737,0.02132889,0.021652007,-0.784553,-1.6494298,1.1849384,1.2028893,0.0,0.0,0.0,-1.0,1.0,-2.1656082,-0.032209214,0.0016591968,73.812096,69.11114,70.82324,65.190605,25.108948,24.533718,25.622019,25.08127,0.026021011,0.026441412,-0.002195113,-0.0020281922,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.38975,0.01505751,26.677374,0.022958325,-0.0027453755,0.0028090784,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
31 20.447374,4045.1602,0.47,0.0,0.0,171.3059,5778.0,100.0,0.0,-0.062244527,0.0,4.0,0.0,0.0,0.0,84.74183,1.0,0.0,0.0,60.01448,0.0,1.0,-116.489525,24.844341,539.2212,-0.02323555,0.23502758,-0.034066625,175.73271,175.83405,170.9717,170.9541,305.06998,303.65402,398.34036,394.15442,73.81395,69.11238,70.82438,65.19081,0.0,0.0,0.0,0.0,0.036146622,0.035968594,0.013882383,0.0142436335,-0.02457305,-0.03317247,0.023010446,0.019303575,-1.3651695,-1.8429149,1.2783581,1.0724208,0.0,0.0,0.0,-1.0,1.0,-2.1656253,-0.03225248,0.001537294,73.81395,69.11238,70.82438,65.19081,25.109125,24.533836,25.622128,25.08129,0.025841586,0.026433097,-0.0019509464,-0.0020535914,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.40316,0.0125868805,26.688421,0.012787492,-0.0027570797,0.0064510647,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
32 20.925768,4045.636,0.477,0.0,0.0,171.36821,5784.0,100.0,0.0,-0.06566952,0.0,4.0,0.0,0.0,0.0,84.744095,1.0,0.0,0.0,60.01427,0.0,1.0,-116.88349,24.844479,538.9544,-0.020693334,0.23424439,-0.032882582,175.83826,175.87889,171.07758,171.0785,305.06998,303.65402,398.34036,394.15442,73.8158,69.113625,70.8255,65.191025,0.0,0.0,0.0,0.0,0.03591611,0.03568515,0.014066943,0.014416676,-0.027605496,-0.034363393,0.023029251,0.021087334,-1.6238527,-2.0213761,1.3546618,1.2404314,0.0,0.0,0.0,-1.0,1.0,-2.1656444,-0.032297246,0.0014890591,73.8158,69.113625,70.8255,65.191025,25.109304,24.533957,25.622236,25.081312,0.026084458,0.026321536,-0.001695974,-0.001690497,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.416653,0.009974838,26.699299,0.0031771893,-0.0027551234,0.0058646416,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
33 21.244698,4045.993,0.485,0.0,0.0,171.4524,5792.0,100.0,0.0,-0.073485024,0.0,4.0,0.0,0.0,0.0,84.747116,1.0,0.0,0.0,60.013992,0.0,1.0,-117.17908,24.84456,538.75415,-0.014153696,0.24148393,-0.06599551,175.97643,175.8869,171.22888,171.17216,305.02115,303.60547,398.19058,394.00647,73.81827,69.11529,70.826996,65.19129,0.0,0.0,0.0,0.0,0.035547975,0.03535814,0.014167956,0.014536584,-0.03990978,-0.040697057,0.01903822,0.01953002,-2.660652,-2.7131371,1.2692147,1.3020014,0.0,0.0,0.0,-1.0,1.0,-2.1656735,-0.032350145,0.0015241038,73.81827,69.11529,70.826996,65.19129,25.10954,24.534117,25.62238,25.081337,0.026386462,0.025864357,-0.0013036191,-0.0016344176,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.43546,0.003714017,26.713604,-0.0072334763,-0.0028177002,0.0038037421,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
34 21.723093,4046.35,0.495,0.0,0.0,171.518,5796.0,100.0,0.0,-0.07679934,0.0,4.0,0.0,0.0,0.0,84.74938,1.0,0.0,0.0,60.013783,0.0,1.0,-117.47478,24.844614,538.5539,-0.013632373,0.25245753,-0.06559541,175.91951,175.82158,171.30203,171.30792,304.9723,303.5569,398.04077,393.8585,73.82012,69.11654,70.8281,65.19148,0.0,0.0,0.0,0.0,0.03531124,0.03522051,0.0140859345,0.01452019,-0.033603773,-0.025813363,0.001055085,0.005750762,-1.8668762,-1.4340757,0.058615834,0.31948677,0.0,0.0,0.0,-1.0,1.0,-2.165697,-0.032351553,0.001600171,73.82012,69.11654,70.8281,65.19148,25.109718,24.534237,25.622486,25.081356,0.025662057,0.02509104,-0.0012592374,-0.0012248752,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.45033,-0.0011681489,26.724682,-0.011095868,-0.0028733583,-0.0014085505,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
35 22.201488,4046.8264,0.502,0.0,0.0,171.6084,5798.0,100.0,0.0,-0.083627366,0.0,4.0,0.0,0.0,0.0,84.7524,1.0,0.0,0.0,60.013504,0.0,1.0,-117.8692,24.844625,538.2867,-0.013671633,0.25631142,-0.057238538,175.86954,175.81245,171.36868,171.37608,304.92343,303.5083,397.8909,393.71042,73.8226,69.11822,70.829575,65.191734,0.0,0.0,0.0,0.0,0.035153784,0.035233572,0.013865768,0.014293048,-0.023187742,-0.0073274267,-0.01777577,-0.014325659,-1.3639848,-0.4310251,-1.0456336,-0.8426858,0.0,0.0,0.0,-1.0,1.0,-2.16573,-0.032319028,0.0017003558,73.8226,69.11822,70.829575,65.191734,25.109957,24.534399,25.622627,25.081379,0.024830593,0.024497962,-0.0013969055,-0.0013537349,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.47048,-0.00694992,26.739777,-0.008831456,-0.003015354,-0.004377713,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
36 22.520418,4047.1838,0.51,0.0,0.0,171.67668,5797.0,100.0,0.0,-0.08616711,0.0,4.0,0.0,0.0,0.0,84.75467,1.0,0.0,0.0,60.013294,0.0,1.0,-118.16515,24.844593,538.0862,-0.019227957,0.2582171,-0.044518888,175.88156,175.88544,171.37125,171.43143,304.8745,303.45966,397.741,393.56235,73.82446,69.11948,70.83068,65.191925,0.0,0.0,0.0,0.0,0.035166744,0.035313994,0.013727826,0.014096176,-0.009633104,0.0062321625,-0.023873895,-0.028267564,-0.64220697,0.41547748,-1.591593,-1.8845043,0.0,0.0,0.0,-1.0,1.0,-2.1657543,-0.032279488,0.0017462259,73.82446,69.11948,70.83068,65.191925,25.110136,24.53452,25.622734,25.081398,0.024492983,0.024515623,-0.0017791511,-0.0014285297,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.48551,-0.010167306,26.75147,-0.004878632,-0.0030249038,-0.005207439,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
37 22.839348,4047.5415,0.517,0.0,0.0,171.74548,5795.0,100.0,0.0,-0.08860586,0.0,4.0,0.0,0.0,0.0,84.756935,1.0,0.0,0.0,60.013084,0.0,1.0,-118.46122,24.844532,537.8856,-0.016487962,0.26103613,-0.022542765,175.92683,175.91576,171.46367,171.48314,304.82562,303.41104,397.5911,393.41428,73.826324,69.120735,70.83179,65.19211,0.0,0.0,0.0,0.0,0.03527517,0.03543609,0.013676049,0.01391935,0.008092324,0.013501197,-0.012647919,-0.02491319,0.53948826,0.9000798,-0.8431946,-1.6608794,0.0,0.0,0.0,-1.0,1.0,-2.165778,-0.032253243,0.0017618624,73.826324,69.120735,70.83179,65.19211,25.110315,24.534641,25.62284,25.081415,0.024346262,0.024281703,-0.0016409023,-0.0015274936,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.500732,-0.011831859,26.763191,-6.815667E-4,-0.0031339019,-0.00269921,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
38 23.317741,4047.8992,0.527,0.0,0.0,171.83765,5792.0,100.0,0.0,-0.09496216,0.0,4.0,0.0,0.0,0.0,84.75996,1.0,0.0,0.0,60.012806,0.0,1.0,-118.7574,24.844452,537.68494,-0.016842874,0.26147953,-0.011131329,176.03027,176.0295,171.57098,171.57745,304.77667,303.36237,397.44116,393.26617,73.828804,69.12241,70.83325,65.19235,0.0,0.0,0.0,0.0,0.03545368,0.03564789,0.013607756,0.013733252,0.016878633,0.019641045,-0.007062931,-0.021348488,0.99286073,1.1553556,-0.41546655,-1.2557933,0.0,0.0,0.0,-1.0,1.0,-2.1658087,-0.032234173,0.0017262091,73.828804,69.12241,70.83325,65.19235,25.110554,24.534801,25.622982,25.08144,0.024398785,0.0243943,-0.0015518327,-0.001514221,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.520996,-0.013003436,26.778904,0.005979049,-0.0031720053,-0.0017585277,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
39 23.796137,4048.3765,0.535,0.0,0.0,171.88348,5792.0,100.0,0.0,-0.09539544,0.0,4.0,0.0,0.0,0.0,84.76147,1.0,0.0,0.0,60.012665,0.0,1.0,-119.15249,24.844328,537.41724,-0.018743541,0.25865552,-0.018018097,176.11974,176.11406,171.6248,171.62872,304.77667,303.36237,397.44116,393.26617,73.83004,69.12325,70.833984,65.192474,0.0,0.0,0.0,0.0,0.035528727,0.035723917,0.013566235,0.013642805,0.014086564,0.015990395,-0.0061007836,-0.015363615,0.7825869,0.88835526,-0.33893242,-0.85353416,0.0,0.0,0.0,-1.0,1.0,-2.165823,-0.032226935,0.0016839136,73.83004,69.12325,70.833984,65.192474,25.110672,24.534883,25.62305,25.081451,0.024646044,0.024612974,-0.0015050384,-0.0014821178,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.530987,-0.014075285,26.786732,0.009123739,-0.0031545938,-0.001318757,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0
40 24.115067,4048.7346,0.542,0.0,0.0,171.97446,5793.0,100.0,0.0,-0.101628795,0.0,4.0,0.0,0.0,0.0,84.76449,1.0,0.0,0.0,60.012386,0.0,1.0,-119.44893,24.844227,537.2164,-0.016709078,0.25675243,-0.04038965,176.28296,176.25171,171.69395,171.72241,304.72772,303.3137,397.29123,393.11807,73.83251,69.12493,70.83545,65.19272,0.0,0.0,0.0,0.0,0.03555339,0.035814002,0.01346909,0.013442952,0.00664716,0.011074047,-0.009244432,-0.019353317,0.443144,0.73826975,-0.61629546,-1.2902211,0.0,0.0,0.0,-1.0,1.0,-2.1658506,-0.032227688,0.0015559354,73.83251,69.12493,70.83545,65.19272,25.11091,24.535046,25.623194,25.081476,0.025053147,0.0248715,-0.001631055,-0.0014655663,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,39.550945,-0.017810632,26.80214,0.014740507,-0.0031819611,0.0013267533,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,58.0

View file

@ -42,7 +42,11 @@ def test_lap_token_present(stem, expected):
("zandvoort_Q2_L3_ferrari", True), ("zandvoort_Q2_L3_ferrari", True),
("zandvoort_R_L1_ferrari", True), ("zandvoort_R_L1_ferrari", True),
("zandvoort_FP1_L5_ferrari", True), ("zandvoort_FP1_L5_ferrari", True),
("zandvoort_P_74.074_ferrari", False), # bare P without digit not matched # ACC compact tokens
("hungaroring_P_L1_123.702_ktm_gt4", True), # bare P (ACC Practice)
("hungaroring_Q_L1_123.702_ktm_gt4", True), # bare Q (ACC Qualifying)
("hungaroring_HL_L1_123.702_ktm_gt4", True), # Hotlap
("hungaroring_HS_L1_123.702_ktm_gt4", True), # Hotstint
("zandvoort_L7_74.074_ferrari", False), ("zandvoort_L7_74.074_ferrari", False),
("notoken", False), ("notoken", False),
]) ])
@ -81,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 ---
@ -137,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)
@ -150,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)
@ -176,3 +183,45 @@ def test_rename_unprocessed_walks_subdirs(tmp_path, sample_path):
assert len(results) == 1 assert len(results) == 1
assert results[0].status == 'renamed' assert results[0].status == 'renamed'
assert 'Practice' in results[0].new assert 'Practice' in results[0].new
# --- ACC support ---
def test_acc_rename_inserts_compact_token(tmp_path, acc_path):
"""ACC 'Practice' event normalises to 'P' and lap reads as L1."""
src = str(tmp_path / "hungaroring_123.702_ktm_gt4.csv")
shutil.copy(acc_path, src)
results = rename_unprocessed([src])
assert len(results) == 1
r = results[0]
assert r.status == 'renamed'
assert r.new is not None
new_stem = os.path.splitext(os.path.basename(r.new))[0]
tokens = new_stem.split('_')
assert 'P' in tokens
assert 'L1' in tokens
def test_acc_rename_lands_in_practice_folder(tmp_path, acc_path):
"""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')
results = rename_unprocessed([src], races_dir=races_dir)
assert len(results) == 1
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)
def test_acc_rename_idempotent(tmp_path, acc_path):
"""Second rename_unprocessed run on an already-renamed ACC file returns skipped."""
src = str(tmp_path / "hungaroring_123.702_ktm_gt4.csv")
shutil.copy(acc_path, src)
rename_unprocessed([src])
results = rename_unprocessed([str(tmp_path)])
assert all(r.status == 'skipped' for r in results)

View file

@ -8,6 +8,8 @@ import telemetry.report_race as rr
import telemetry.report_profile as rp import telemetry.report_profile as rp
from telemetry.__main__ import is_fresh from telemetry.__main__ import is_fresh
from telemetry.parser import Lap from telemetry.parser import Lap
from telemetry import report_common
from telemetry.report_common import game_of, load_prompt
def _check(result, path): def _check(result, path):
@ -179,6 +181,59 @@ def test_is_fresh_cache(tmp_path):
assert is_fresh(str(rep), [str(src)]) is False assert is_fresh(str(rep), [str(src)]) is False
class TestACC:
"""ACC support: game detection, prompt fallback, game-aware report sections."""
def test_game_of_acc_vs_f1(self, acc_lap, sample_lap):
assert game_of(acc_lap) == 'acc'
assert game_of(sample_lap) == 'f1'
def test_load_prompt_acc_falls_back_to_f1(self):
# ACC requests resolve to the ACC prompt if present, else the F1 prompt —
# never empty while an F1 prompt exists.
base = os.path.normpath(os.path.join(
os.path.dirname(report_common.__file__), '..', 'prompts', 'ru'))
for mode in ('technique', 'setup', 'compare', 'race'):
acc_file = os.path.join(base, 'acc', f'{mode}.md')
f1_file = os.path.join(base, f'{mode}.md')
expected = open(acc_file if os.path.exists(acc_file) else f1_file,
encoding='utf-8').read().strip()
assert load_prompt(mode, 'ru', 'acc') == expected
assert load_prompt(mode, 'ru', 'f1') == open(f1_file, encoding='utf-8').read().strip()
def test_acc_setup_no_param_table_keeps_balance(self, acc_lap, tmp_path):
out = str(tmp_path / "acc_setup.md")
rs.generate(acc_lap, out, 'ru', include_prompt=False)
text = open(out, encoding="utf-8").read()
assert "| Parameter | Value |" not in text # zero-filled setup table dropped
assert "не экспортирует сетап" in text # explanatory note instead
assert "## Corner balance" in text # telemetry-derived sections kept
assert "## Tyres" in text
def test_acc_technique_no_setup_line_no_ers(self, acc_lap, tmp_path):
out = str(tmp_path / "acc_tech.md")
rt.generate(acc_lap, out, 'ru', include_prompt=False)
text = open(out, encoding="utf-8").read()
assert "## Corners" in text
assert "**Setup:**" not in text # zero-filled setup summary skipped
assert "ERS spent:" not in text # hybrid block dropped
def test_f1_technique_keeps_setup_and_ers(self, sample_lap, tmp_path):
# regression: F1 path unchanged
out = str(tmp_path / "f1_tech.md")
rt.generate(sample_lap, out, 'ru', include_prompt=False)
text = open(out, encoding="utf-8").read()
assert "**Setup:**" in text
assert "ERS spent:" in text
def test_acc_race_skips_ers_table(self, acc_lap, tmp_path):
out = str(tmp_path / "acc_race.md")
rr.generate([acc_lap], out, 'ru', include_prompt=False)
text = open(out, encoding="utf-8").read()
assert "## ERS per lap" not in text
assert "## Lap times" in text
class TestRegressionReports: class TestRegressionReports:
"""Regression: report generators must not crash on valid single-lap input.""" """Regression: report generators must not crash on valid single-lap input."""