Загрузка данных


# Принципы расчётов АМКС

Документ описывает текущую реализацию и договорённости на 15.10.2026.
Версия исходного рейтинга — `top35-v2`. Здесь отдельно изложены правила,
предпосылки модели и результаты проверки на исторических данных.

## 1. Какие величины мы рассчитываем

| Расчёт | Входные данные | Результат |
| --- | --- | --- |
| Исходный рейтинг картов | Тренировки и квалификации одного этапа | Баллы, категория, тренировочное отклонение и место карты |
| Перемещения машин | Начальная расстановка, число резервов и события питов всех предшествующих финалов | Расчётная машина у участника и порядок машин в питах |
| Темп до/после пита | Пригодные круги конкретного пилота в конкретном финале | Медианы двух отрезков и разница между ними |
| Проверка гипотезы | Исходный рейтинг и изменение темпа после замены | Согласованность направления и ранговая корреляция |

Этап определяется картодромом и датой. Для сохранённого исходного рейтинга
учитывается также версия правил. Результаты разных трасс и дней не смешиваются.
Времена внутри расчётов хранятся в миллисекундах; в интерфейсе показываются секунды.

Рейтинг — эвристическая оценка по результатам пилотов. Он не отделяет полностью
влияние машины от пилота, трафика, состояния трассы и других условий.

## 2. Номер участника и физическая машина

Во время тренировок и квалификаций машина сохраняет свой номер. Этот номер
становится её исходным обозначением в рейтинге этапа, например «исходная №15».

В финалах участник после обязательного пита получает другую машину. Поэтому
номер участника в протоколе и исходный номер физической машины могут различаться.
После замены участник №10 может ехать на исходной машине №15.

Пилоты и их идентификаторы могут меняться между финалами. Между заездами модель
переносит соответствие **по номеру участника/слота**, а не по имени пилота.
Идентификатор участника KartChrono используется для его событий внутри заезда.
Сопоставление имён для списка сильных пилотов не используется для переноса машин.

Начальные резервные машины обозначаются `X1`, `X2` и далее. Их исходные номера
и рейтинги неизвестны. Они сохраняют эти обозначения и после нескольких обменов;
мы не присваиваем им номер или баллы по косвенным признакам темпа.

## 3. Отбор заездов этапа

Исходные протоколы берутся из архива выбранного KartChrono-домена:
Маяк или ADM. Формулы для обоих картодромов одинаковые.
Заезды упорядочиваются по времени начала.

- Квалификации распознаются по названиям `Квалификация N` и `Квала N`.
- Буквенные финалы распознаются по названию `Финал А`, `Финал B` и аналогичным.
- Для прошедшего дня тренировками считаются заезды перед первой квалификацией.
- Для сегодняшнего дня граница тренировок — первая квалификация или финал.
  Если их ещё нет, используются уже опубликованные заезды до этой границы.

Для полного расчёта прошедшего этапа нужны хотя бы одна тренировка,
ненулевое чётное число квалификаций и последний буквенный финал А.
Для сегодняшнего дня эти требования к полноте снимаются: рейтинг предварительно
считается даже без квалификаций или при их нечётном количестве.

На ADM 27.09.2026 заезд `650315`, опубликованный как «Заезд №21», подтверждён
пользователем как финал А. Этот алиас действует только для указанного этапа.
Другие безымянные финалы не определяются автоматически по порядку или темпу.
В live-панели сохранённый заезд можно явно включить в расчёт финалов;
выбор хранится в данном браузере для выбранного картодрома и даты.

## 4. Квалификационные баллы: топ-35%

Для каждой квалификации граница рассчитывается заново:

```text
N = число разных непустых номеров картов в её протоколе
K = ceil(N × 35 / 100)
```

В `N` входят и карты без времени круга. Повтор одного номера не увеличивает `N`.
Округление всегда вверх: 14 картов дают `K = 5`, 15 картов — `K = 6`,
20 картов — `K = 7`.

Зачётная строка должна иметь непустой номер карты, положительное место
и положительное время лучшего круга. Если опубликованное место не больше `K`,
карта получает одно попадание, кроме исключения из следующего раздела.

Места берутся из протокола. После исключения строк без времени места оставшихся
участников не пересчитываются, граница `K` не сдвигается.
Баллы суммируются по зачётным строкам протоколов; уникальность номеров используется
при расчёте `N`. Обычный протокол предполагает одну строку на карту в квалификации.

```text
баллы карты = сумма засчитанных попаданий во всех квалификациях этапа
```

Нет дополнительных баллов за победу, отрыв, величину лучшего круга или финал.
Число баллов не делится на количество стартов или квалификаций.

## 5. Исключение для сильных пилотов

Исключение действует только на **одном последнем месте** топ-35%, то есть на `K`.
Если на этом месте сильный пилот из настроенного списка, попадание не засчитывается.
Его результаты на местах раньше `K` учитываются обычным образом.
Место следующего пилота не повышается и не получает освободившийся балл.

Например, при `N = 14` граница равна пяти. Сильный пилот на пятом месте
не приносит балл своей карте; на четвёртом — приносит. Шестое место в обоих случаях
остаётся за пределами зачёта.

Имена сравниваются следующим образом:

1. Приводятся к lowercase.
2. `ё` заменяется на `е`.
3. Допускается расстояние Левенштейна не больше одного по всему имени:
   одна вставка, удаление или замена символа.

`ЛОКОТКОВ АРТЁМ`, `локотков артем` и `ЛОКОТКОФ АРТЕМ` соответствуют одному
элементу списка. Перестановка соседних разных букв считается двумя правками.
Это правило не означает исправление всех имён в источнике.
Список сильных пилотов хранится в [analysis-rules.ts](../lib/analysis-rules.ts).

## 6. Оценка по тренировкам

Для каждого тренировочного заезда берётся **лучший круг** каждой зачётной строки,
а не среднее всех её кругов. Нужны известный номер карты, положительное место
и положительное время.

```text
среднее заезда = среднее арифметическое лучших кругов зачётных строк
отклонение карты в заезде = её лучший круг − среднее заезда
тренировочное отклонение карты = среднее её отклонений по учтённым записям
```

Последняя величина округляется до целой миллисекунды через `Math.round`.
`trainingStarts` — число учтённых тренировочных записей этой карты.
Заезд без зачётных времён не даёт тренировочных отклонений.

Пример: лучшие круги трёх картов — 40,000, 41,000 и 42,000 секунды.
Среднее заезда — 41,000; отклонения — −1,000, 0,000 и +1,000 секунды.
Если у первой карты в следующем заезде отклонение −0,600 секунды,
её среднее по двум записям составит −0,800 секунды.
Отрицательная величина означает быстрее среднего состава соответствующих заездов.

Фильтр +3% из оценки финального темпа к этим лучшим кругам не применяется.
Нормализация относительно среднего заезда частично учитывает разные условия,
но не выравнивает силу пилотов и состав участников полностью.

## 7. Категории и полный порядок картов

| Квалификационные попадания | Категория |
| --- | --- |
| 4 и больше | ракета |
| 2–3 | рабочая лошадка |
| 1 | под наблюдением |
| 0 | нет сигнала |

Пороги абсолютные: они не меняются от количества проведённых квалификаций.
Поэтому одинаковая категория на этапах разной длины не означает одинаковую
статистическую уверенность.

Полный список сортируется последовательно:

1. Больше квалификационных попаданий — выше.
2. При равенстве — меньшее среднее тренировочное отклонение выше.
3. При отсутствии тренировочного времени карта уступает карте с известным
   отклонением при том же числе попаданий.
4. При полном равенстве — меньший числовой номер карты выше.

Место в рейтинге — позиция в этом отсортированном списке, начиная с единицы.
Это порядковое место: разница в пять мест не переводится в фиксированное число секунд.

В список входят все известные номера из квалификаций, включая карты без времени,
а также карты с зачётным тренировочным временем. Карта без попаданий может иметь
хорошее тренировочное отклонение, но её категория всё равно будет «нет сигнала».

## 8. Фиксация исходного рейтинга для финалов

Финальные круги не добавляют квалификационные баллы и не меняют тренировочные
отклонения. В live-панели они служат для расчёта перемещений и отдельной оценки темпа.

Рейтинг загружается при обращении к панели и обновляется до обнаружения первого
финала. При его обнаружении выполняется загрузка рейтинга перед фиксацией.
Затем рейтинг сохраняется в памяти процесса, чтобы все финалы сравнивались
с одной исходной оценкой. После перезапуска он снова получается из
тренировок и квалификаций: отдельного неизменяемого файла рейтинга live-панели нет.

Для прошедших дней основной API использует ранее сохранённый снимок D1, если он
есть, по ключу «картодром + дата + версия правил». При его отсутствии сохраняет
успешный расчёт. Последующие чтения снимка не пересчитывают рейтинг по источнику;
поздние исправления исходных протоколов автоматически в него не переносятся.
Сегодняшний рейтинг в D1 не фиксируется.
Отсутствующий рейтинг не заменяется нулевым подтверждённым рейтингом:
при временном отказе источника машины показываются без известной оценки.

## 9. Восстановление замен: очередь FIFO

Начальное состояние берётся перед первым финалом: каждому номеру участника
соответствует машина с тем же исходным номером. Резервная очередь —
`[X1, X2, …, XR]`. Число `R` задаётся вручную, допустимо от 1 до 20;
по умолчанию используется два. Оно не определяется по длительности питов.

Главная договорённость: пилоты выезжают в том же порядке, в котором заезжают.
Для каждого пригодного PIT-IN алгоритм:

1. Запоминает текущую физическую машину участника.
2. Берёт первую машину резервной очереди как ожидаемую замену.
3. Помещает возвращаемую машину в конец очереди.
4. Помечает участника как находящегося в питах.

PIT-OUT связывается с ожидающим входом того же участника, подтверждает выезд
и завершает визит. Показанное количество питов — количество завершённых визитов;
текущий незавершённый вход отображается отдельно.

Пример для двух резервов:

| Событие | Ожидаемая машина участника после обмена | Очередь после PIT-IN |
| --- | --- | --- |
| Начало первого финала | Каждый на своей исходной машине | X1, X2 |
| Первым въезжает №10 | X1 | X2, 10 |
| Вторым въезжает №18 | X2 | 10, 18 |
| Третьим въезжает №7 | Исходная №10 | 18, 7 |

Очередь отражает порядок распределения машин. Она не подтверждает, что первая
машина уже физически готова к немедленному выезду.

События обрабатываются по времени отсечки; при равном времени — по идентификатору
записи круга. После финала очередь и соответствие машин переносятся в следующий
без сброса. При просмотре отдельного финала сначала воспроизводятся все предыдущие.
Для повторного визита одного пилота в том же заезде применяется тот же алгоритм.

## 10. Какие события и круги учитываются

Для гоночных событий требуется флаг зелёного или жёлтого режима. Не учитываются
записи прогрева, красного режима, финиша, удалённые и отклонённые записи.
Для питов дополнительно нужен соответствующий флаг PIT-IN или PIT-OUT.
Сводное число кругов из таблицы не заменяет фактические записи отсечек.

Повторная доставка записи не создаёт новый пит: внутри заезда запись определяется
парой «идентификатор участника + идентификатор записи круга» и обновляется.
Удаления и исправления применяются с последующим повторным расчётом истории.
Исправленный полный снимок заезда может заменить предыдущий целиком.

Архив и сохранённый live-поток объединяются как один заезд по устойчивому ключу
или общим гоночным записям. Пока текущий поток подключён и получен его начальный
снимок, его данные для текущего заезда имеют приоритет над отстающим архивом.
Для остальных снимков используется время получения.

В ленте показано время самой отсечки. Время последнего сообщения и последнего
полученного изменения — отдельные показатели доставки данных сборщику.

## 11. Оценка темпа до и после пита

Темп относится к конкретному пилоту в конкретном финале. В панели используется
его последний визит в пит в этом заезде.

Сначала отбираются пригодные обычные круги: гоночный зелёный/жёлтый режим,
положительное время, номер круга не меньше двух. PIT-IN/PIT-OUT, прогрев,
финишные, красные, удалённые и отклонённые записи исключаются.

Границы отрезков:

- До пита — круги, закончившиеся раньше PIT-IN. При повторном визите начало
  круга должно быть не раньше предыдущего PIT-OUT.
- После пита — круги, начало которых не раньше PIT-OUT.
  Начало вычисляется как «время окончания круга − длительность круга».
  Так исключается смешанный круг, который включает пребывание в питах.

Далее каждый отрезок обрабатывается **отдельно**:

```text
эталон M = медиана всех пригодных кругов своего отрезка
чистые круги = круги со временем t ≤ 1,03 × M
до пита: последние до пяти чистых кругов перед PIT-IN
после пита: первые до пяти чистых кругов после PIT-OUT
темп отрезка = медиана выбранных кругов
```

Ровно +3% остаётся допустимым; исключается строго больше. Быстрые круги этим
фильтром не отбрасываются. Добор идёт из того же отрезка и не пересекает пит.
Для чётного числа значений медиана — среднее двух центральных после сортировки.

Например, при `M = 40,000 с` граница — `41,200 с`. Круг `41,201 с` исключается.
Если среди пяти ближайших кругов был медленный, вместо него берётся следующий
ближайший чистый круг, когда он существует. Всегда пять кругов не гарантируются.

```text
Δ темпа = медиана после пита − медиана до пита
```

Отрицательная дельта — ускорение, положительная — замедление.
Для дельты нужны минимум три чистых круга с каждой стороны. Отдельная медиана
может отображаться и на меньшей выборке; число использованных кругов показано рядом.
Нет подтверждённого выхода — нет оценки отрезка после этого визита.

В live используются только уже полученные круги. Новые круги и исправления
могут изменить медиану-эталон, набор чистых кругов и результат. Ретроспективный
расчёт по полному отрезку может отличаться от ранней оценки во время заезда.

Порог +3% — эвристика для просадки темпа. Он не доказывает вылет, столкновение
или ошибку пилота и не исправляет оставшееся влияние трафика и состояния трассы.

## 12. Очереди в питах и различия трасс

На Маяке используются отсечки: по договорённости пользователя очередь не влияет
на измеренную длительность пита. На ADM используется светофор и один коридор;
ожидание может увеличивать длительность визита. Длинные питы в финале Е ADM
27.09.2026 сами по себе не считаются нарушением модели.

Для других картодромов зафиксирована договорённость о светофоре и возможном
ожидании. Самостоятельный расчёт обязательного времени, очереди ожидания и момента
физической готовности машины пока не реализован.
Порядок обменов определяется событиями, а не длительностью пита.

## 13. Когда результат считается неподтверждённым

Расчёт сохраняет предупреждение и скрывает рейтинги текущих физических машин,
если обнаружены:

- PIT-OUT без соответствующего PIT-IN или повторный вход без выхода;
- незавершённые визиты перед следующим финалом;
- нарушение порядка входов и выходов;
- изменение набора номеров между финалами, неизвестный новый номер или дубли номеров;
- пит без участника либо операторские удаления, замены и сбросы, требующие проверки.

Незавершённый вход в текущем финале сам по себе допустим: участник ещё в питах.

Нужны правильное число резервов и полная цепочка предшествующих финалов.
Полностью отсутствующий заезд или неотмеченную замену между финалами невозможно
надёжно восстановить из одной текущей таблицы. Отсутствие предупреждений означает
согласованность полученных данных с моделью; независимого подтверждения шасси нет.
Пропуски не заполняются догадкой по скорости, длительности пита или одному выходу.

Ошибка загрузки, повреждённый протокол и подтверждённое отсутствие данных — разные
состояния. При временном отказе сохраняется последний доступный результат
с предупреждением. После перезапуска доступны записанные питы даже при недоступном
архиве; исходный рейтинг до восстановления может оставаться неизвестным.
Повреждённая история не принимается за пустую и не перезаписывается как пустая.

## 14. Когда данные поступают в расчёт

Фоновый сбор идёт независимо от страниц, но только в конкретные даты
[календаря гонок](../config/race-calendar.json), на нужном картодроме, на весь день
по Москве. Сам факт субботы или воскресенья сбор не включает. Календарь
перечитывается каждые 30 секунд. Этапы неподключённых трасс имеют `track: null`.
Вне расписания live-панель за сегодня не загружает новый архив или рейтинг.
Ручной анализ на главной странице и запрос архива прошлой даты доступны отдельно.

На главной странице сегодняшний предварительный рейтинг обновляется каждые
30 секунд. Live-панель опрашивает сервер каждые две секунды. Загрузка архива
в ней кешируется на 30 секунд за сегодня и на час для прошлых дат;
завершённые протоколы — на пять минут. После ошибки повтор разрешён не раньше
чем через пять секунд. Одинаковые одновременные запросы разделяют одну загрузку.

Частота опроса страницы не равна задержке измерения пита: на неё также влияют
публикация KartChrono, доставка сообщения, сохранение и обработка.
Длительная тишина источника не считается доказанным разрывом связи: подтверждённого
heartbeat нет. Задержка нового пита в реальном соревновании пока не измерена.

## 15. Как мы проверяли гипотезу на ADM

На этапе ADM 27.09.2026 перебирались два, три и четыре начальных резерва.
Два резерва дали лучшее согласование в первоначальном сравнении, но это
не подтверждение фактического количества машин в питах.
Для этой гипотезы последовательно воспроизведены шесть финалов и 78 обменов.

Для сравнения с рейтингом нужны известные исходные машины до и после замены
и минимум три пригодных круга с каждой стороны. В итоговом наборе таких
наблюдений 47; остальные не входят в знаменатель этой метрики.

```text
ожидаемый выигрыш по рейтингу = место старой машины − место новой
фактический выигрыш = темп до пита − темп после
совпадение направления: ожидаемый выигрыш × фактический выигрыш > 0
```

Для принятого варианта оценки совпало 34 из 47 направлений, то есть 72,3%.
Нулевое произведение не считается совпадением в этой проверке направления.
Это не «точность определения машины»: схема замен восстановлена по предпосылкам
FIFO, а независимой разметки физических шасси нет.

Для численного сравнения способов оценки темпа использовали корреляцию Спирмена
на том же наборе 47 наблюдений. Она считается как корреляция Пирсона рангов;
при равных значениях берётся средний ранг. Равные квалификационные баллы
из этого сравнения не удалялись.

Ожидаемые выигрыши определяются отдельно:

- Квалификации: баллы новой машины минус баллы старой.
- Тренировки: тренировочное отклонение старой минус отклонение новой.
- Общий рейтинг: место старой минус место новой.

| Оценка темпа | Связь с квалификационными баллами | Связь с тренировочным отклонением | Связь с общим рейтингом |
| --- | --- | --- | --- |
| Медиана без фильтра | 0,681 | 0,560 | 0,725 |
| Медиана +3%, с добором до пяти | 0,682 | 0,567 | 0,728 |
| Среднее +3%, с добором до пяти | 0,661 | 0,546 | 0,713 |

Оставлен вариант **«медиана +3% с добором ближайших чистых кругов до пяти»**.
Его преимущество над обычной медианой небольшое. Ретроспективная проверка
на одном этапе не устанавливает статистическую значимость или переносимость
на другие соревнования. Размер окна и порог не считаются доказанно оптимальными.

## 16. Где закреплены правила

| Правила | Реализация / проверка |
| --- | --- |
| Процент, версия и список сильных пилотов | [analysis-rules.ts](../lib/analysis-rules.ts) |
| Сравнение имён | [pilot-names.ts](../lib/pilot-names.ts) |
| Структура этапа, баллы, тренировки и сортировка | [kart-analysis.ts](../lib/kart-analysis.ts) |
| Флаги отсечек и идентичность заезда | [pit-protocol.ts](../lib/pit-protocol.ts) |
| FIFO и оценка темпа | [pit-monitor.ts](../lib/pit-monitor.ts) |
| Слияние источников и фиксация исходного рейтинга | [pit-source.ts](../lib/pit-source.ts), [final-monitor-server.ts](../lib/final-monitor-server.ts) |
| Календарь сбора | [race-calendar.ts](../lib/race-calendar.ts) |
| Реальные протоколы шести финалов и ожидаемые состояния | [adm-finals.json](../tests/fixtures/adm-finals.json), [pit-monitor.test.ts](../tests/pit-monitor.test.ts) |

При изменении формул исходного рейтинга нужно обновлять версию правил,
документацию и проверки. При изменении гипотезы о резервах или порядке обменов
следует заново проверять всю цепочку финалов, а не только последний заезд.