Привет, Хабр! Я Михаил Ефанов, инженер в команде, которая отвечает за всё, что происходит между нажатием кнопки «забронировать» и началом вашей поездки в каршеринге Ситидрайв. Мы работаем со всеми типами авто, но электрокары — отдельная история. Однажды ко мне обратился менеджер продукта с таким запросом:
«Было бы круто научиться рассчитывать запас хода для электрокаров в реальном времени. Желательно — точно. У нас есть телеметрия с автомобилей — RPS около 600, данные по пробегу, заряду, состоянию батареи».
Звучит несложно? Но вот ещё пару вводных:
заряд батареи может увеличиваться сам по себе — даже без зарядки;
заправка никак не фиксируется как событие и определяется только косвенно.

Так начался мой путь: от первых попыток найти закономерность — до построения системы, которая умеет отличать настоящую зарядку от просто капризов датчика и выдаёт максимально приближённый к реальности запас хода.
В этой статье я расскажу, как решал задачу, с какими ограничениями столкнулся, какие выводы сделал и зачем вообще это всё нужно. Пользователю — точные данные, компании — экономия, разработчику — выполненная таска.
Навигаторы в потоке данных: как я определял ключевые события
Чтобы хоть как-то упорядочить поток телеметрии и начать извлекать из него смысл, нужно было выстроить систему координат. Другими словами — научиться понимать, где мы сейчас и что было до этого. Для этого я ввёл понятие указателей — ключевых точек во временной шкале, к которым можно привязаться при анализе данных.

Вот какие указатели оказались необходимыми:
Текущее событие — то, что мы обрабатываем в данный момент. Основной источник сигнала: что происходит прямо сейчас с уровнем заряда.
Первое событие — самое старое из полученных. Оно помогает зафиксировать начальное состояние и понять динамику изменений.
Предыдущее событие — то, что было перед текущим. Нужно, чтобы видеть тренд: заряда становилось больше, меньше или он стоял на месте.
Событие перед заправкой — последняя стабильная точка перед резким скачком заряда вверх. Она важна, так как первые 24 часа после заправки мы будем считать расход, используя самое первое событие и событие перед заправкой
Сама заправка — то самое событие, ради которого всё затевалось. Определяется по косвенным признакам, и, как выяснилось, вовсе не такая тривиальная вещь, как может показаться.
Всё это — не просто абстрактные переменные, а опорные точки, которые позволяют алгоритму принимать взвешенные решения в условиях нестабильных данных.
Когда указатели живут своей жизнью
Самым непростым этапом во всей задаче оказалось вовсе не парсить телеметрию и не фильтровать шум, а понять, какие именно точки на временной оси реально важны. Казалось бы, что может быть сложного — зафиксировал момент зарядки, сравнил с предыдущим состоянием, записал результат. Но на практике данные ведут себя куда менее предсказуемо, чем хочется.
Я выбрал несколько опорных событий и стал проверять гипотезы. Но в зависимости от кейса роль указателей менялась.

Простой пример:
Сначала приходят события, где заряд падает с 100% до 50%. Потом, спустя пару десятков событий, происходит плавное увеличение заряда — допустим, до 75%. Такое увеличение мы принимаем за зарядку, если бы прирост был меньше 20 %, то это было бы списано на кратковременную зарядку, погрешность датчика или, в худшем случае, баг.
В этом кейсе в работу вступают сразу два указателя:
первое событие — чтобы понимать, откуда начался спад
событие перед заправкой — чтобы зафиксировать последнюю стабильную точку перед резким скачком.
Именно разница этих указателей будет расчетным запасом хода.
Сценариев — десятки, и почти каждый требует своего подхода. Где-то достаточно анализировать пару событий подряд, где-то важно смотреть на динамику за всю сессию. Сейчас, конечно, логика стала чище, устойчивее и компактнее, но тогда — это был настоящий квест: собрать из разрозненных событий осмысленную картину, где указатели срабатывают ровно там, где надо.
Суть итогового алгоритма простыми словами: если заправка была менее, чем 24 часа назад, то мы берем указатель “Текущее событие” и “Событие перед заправкой”, считаем расход между двумя этими событиями, так как данных между текущим событием и заправкой будет недостаточно, для точной оценки расхода. И второй вариант, если заправка была более чем 24 часа назад, то мы берем указатели “Заправка” и “Текущее событие” – данных уже точно достаточно и они будут более актуальными.
Что я понял, пока гонял десятки тысяч событий
После множества итераций, экспериментов и множественных дебагов, я выделил для себя несколько принципов, без которых такая задача быстро превращается в бесконечную череду костылей:
Не стоит хвататься за всё подряд. Если событие ничего не меняет (заряд остался прежним), изменилось на 1–2% или пришло уже после зарядки — оно нам не нужно. Удаляешь шум — получаешь внятную картину.
Учитывай реальные кейсы. Реальные кейсы — это боль, хаос и тысячи мелких нюансов. Поэтому я загрузил в систему десятки тысяч реальных событий, чтобы посмотреть, как моя логика работает в бою. И вот там всё становится по-настоящему интересно.
Рисовать — это не стыдно. BPMN-схемы — настоящая находка. Один раз набросал на диаграмме логику переходов — и сразу стало понятно, где баг, а где просто невнятный переход состояния.
И, пожалуй, главный инсайт — философский. Иногда действительно кажется, что вся эта возня с зарядом, графиками и указателями — это какие-то излишества ради пары цифр в интерфейсе. Вся моя работа — это максимум 3 знака в приложении пользователя.

Дада, вот это:

Но когда анализируешь метрику отказов от аренды по причине “не совпало количество топлива”, и видишь, что количество отказов в пиковые моменты сократилось почти в 8 раз просто потому, что пользователь стал видеть актуальный запас хода — всё встаёт на свои места.
Инженерное тщеславие удовлетворено, бизнес доволен, пользователи спокойны.