Appearance
Use Case #UC-027.2: Расчет физического износа и Горячая замена ⚖️ (Движок физики износа)
1. Жизненная ситуация (Боль пользователя)
Представь: Велосипедист совершил отличную тренировку в виртуальном мире Zwift (VirtualRide) на станке. Его велосипед закреплен в трейлере: колеса не крутятся по асфальту, тормоза не используются, передняя вилка не работает. Однако цепь и кассета изнашиваются так же интенсивно, как и на улице. Или другой сценарий: райдер едет на мощном электровелосипеде (E-Bike). Электромотор передает колоссальный крутящий момент на цепь, из-за чего трансмиссия изнашивается в 1.5 раза быстрее обычной.
Как он мучается сейчас: Существующие трекеры (Strava, Garmin) просто прибавляют километраж ко всему велосипеду без разбора. После домашней тренировки на станке у пользователя "изнашиваются" тормоза и передняя покрышка, которой даже не было на колесе! А пробег цепи на электробайке считается обычным, из-за чего она неожиданно рвется посреди леса.
Мечта юзера: Приложение должно автоматически разделять износ. Виртуальная поездка? Изнашивается только трансмиссия. Поездка на электробайке? Пробег цепи умножается на коэффициент нагрузки. А сразу после завершения поездки приложение предлагает удобное окно подтверждения (Checkout), где можно в один клик "провести замену" растянувшейся цепи на новую.
2. Как это ДОЛЖНО работать у тебя (UX-Магия)
Интерфейс импорта тренировок становится интерактивным чек-листом.
Сценарий (Что видит юзер):
- Пользователь заходит в раздел Входящие (Inbox) и видит новую поездку из Strava.
- Он нажимает кнопку [ Подтвердить ]. Вместо мгновенного сохранения открывается красивое модальное окно Post-Ride Checkout.
- В окне перечислены все установленные на выбранный байк детали. Напротив каждой стоит галочка
[✓]. - Умное авто-заполнение:
- Если поездка помечена в Strava как VirtualRide (Zwift / Rouvy), приложение автоматически снимает галочки со всех деталей, кроме трансмиссии (
DRIVETRAIN).
- Если поездка помечена в Strava как VirtualRide (Zwift / Rouvy), приложение автоматически снимает галочки со всех деталей, кроме трансмиссии (
- Горячая замена (Hot Swapping):
- Напротив любой детали есть кнопка
🔄 [ Заменить ]. - Если пользователь заменил цепь сразу после поездки, он нажимает эту кнопку, выбирает из выпадающего списка совместимую новую цепь со склада, и система автоматически фиксирует замену именно в момент этой тренировки!
- Напротив любой детали есть кнопка
- Пользователь нажимает [ Завершить поездку ]. Тренировка подтверждается, а ресурс деталей обновляется с ювелирной точностью.
3. СЕКРЕТ ДЛЯ ПРОГРАММИСТА (Физический движок и транзакции)
Расчет износа производится на сервере в транзакции подтверждения активности POST /api/strava/activities/:id/confirm:
Формат Payload:
typescript{ localBikeId: string, customDistanceKm?: number, wearComponentIds: string[], // компоненты, на которые пойдет износ swaps?: { unmountId: string, mountId: string }[] // горячие замены }Алгоритм Физического Движка:
- Шаг 1: Горячие замены. Сначала обрабатывается массив
swaps. У старого компонентаunmountIdстатус сбрасывается в'INVENTORY', аbikeIdзануляется. Новый компонентmountIdполучает статус'MOUNTED'и привязывается кlocalBikeId. Это происходит ДО начисления пробега, чтобы износ пошел уже на свежую деталь! - Шаг 2: Расчет дистанции и времени.
baseKm = customDistanceKm ?? (activity.distance / 1000)durationHours = activity.moving_time / 3600
- Шаг 3: Начисление износа по правилам физики.
- Правило Zwift (VirtualRide): Если тип активности
'VirtualRide'и категория компонента НЕ трансмиссия ('DRIVETRAIN'), деталь получает ровно0износа по километрам и часам. - Правило E-Bike (EBikeRide): Если тип активности
'EBikeRide'и категория компонента равна'DRIVETRAIN', износ по километрам умножается на коэффициент нагрузки:deltaKm = baseKm * 1.5. В остальных случаяхdeltaKm = baseKm.
- Правило Zwift (VirtualRide): Если тип активности
- Шаг 4: Обновление счетчиков. Добавляем полученные дельты к
currentLifespanKm,currentServiceKm,currentLifespanHours,currentServiceHoursвыбранных компонентов. - Шаг 5: Обновление шасси. Увеличиваем общий пробег рамы (
bike.totalKm) наbaseKm. Помечаем активность как'CONFIRMED'. Все операции выполняются строго внутри одной базы данных SQL-транзакции.
- Шаг 1: Горячие замены. Сначала обрабатывается массив
4. Защита от "дурака" (Бизнес-политики безопасности)
- Защита транзакции: Если любой шаг горячей замены или начисления износа падает с ошибкой, вся транзакция откатывается (
rollback), гарантируя отсутствие "фантомного" пробега и рассинхронизации. - Фильтрация деталей: В модальном окне выбора замены отображаются только компоненты со статусом
'INVENTORY', которые явным образом совместимы с данным велосипедом (их ID содержится в массивеcompatibleBikeIdsкомпонента).
Эпики и Задачи (Epics & Tasks)
Epic 1: Серверный Физический Движок (apps/api)
- Task 1.1: Расширить схему валидации эндпоинта подтверждения активности Strava.
- Task 1.2: Реализовать обработку горячих замен компонентов внутри транзакции.
- Task 1.3: Реализовать физические правила Zwift (0x для рамы/тормозов) и E-Bike (1.5x для цепи).
- Task 1.4: Добавить обновление пробега шасси велосипеда и статуса активности.
Epic 2: Пользовательский интерфейс Checkout (apps/web)
- Task 2.1: Разработать модальное окно
PostRideCheckoutModalс отображением списка установленных деталей. - Task 2.2: Добавить авто-исключение не-трансмиссионных деталей при виртуальных тренировках.
- Task 2.3: Сверстать селектор горячей замены деталей со склада.
Epic 3: Автоматическое тестирование
- Task 3.1: Написать интеграционные тесты для API, эмулируя VirtualRide и EBikeRide, и сравнивая начисленный пробег.
- Task 3.2: Протестировать корректность сборки payload в модальном окне фронтенда при отправке на подтверждение.