Формулы метрик
Версия: 0.1 (v0) Дата: 2026-05-04 Аудитория: внутренний документ команды (продукт + разработка). Не предназначен для пользователя.
Этот документ — единая точка входа в логику расчётов Lumo. Сами формулы живут в screens/ рядом со своими экранами; здесь — рамка, которая их связывает: какие данные у нас уже есть, что требует персонального baseline, а что работает с первого дня, и откуда взяты веса.
1. Главный принцип — ярусы персонализации
Заголовок раздела «1. Главный принцип — ярусы персонализации»В wearable-алгоритмах нет «правильного абсолютного значения HRV». RMSSD у разных людей отличается в 10 раз (20–200 мс — обе нормы), и интерпретация без личного baseline бессмысленна. Поэтому каждая метрика принадлежит к одному из трёх ярусов:
| Ярус | Когда работает | Что туда попадает | Что показываем до |
|---|---|---|---|
| 0. Абсолютные нормы | День 1 | HR-зоны (220 − возраст), SpO2 < 90% тревожно, температура > 37.5°C лихорадка, сон 7–9ч adult, цель 8000 шагов | Работает сразу |
| 1. Персональный baseline | 14 дней ношения | HRV (RMSSD), RHR, кожная температура (отклонение), фоновый стресс, время отхода ко сну (хронотип) | Плашка «Накапливаем данные» + абсолютная норма как fallback (см. §3) |
| 2. Долгосрочный тренд | 30–90 дней | Здоровье, Lumo Age, кардио-тренды | Скрыто полностью |
Правило: каждая метрика в screens/ должна явно указывать свой ярус. Если ярус 1 или 2, обязателен cold-start UX — что показываем, пока baseline не сформирован.
2. Baseline — как формируется
Заголовок раздела «2. Baseline — как формируется»Окна:
- Recent window — 7 дней (свежее состояние)
- Baseline window — 14 дней (личная норма)
- Long-term window — 60 дней (для медленных метрик: Здоровье, Lumo Age)
Большинство «балансовых» метрик (HRV Balance, RHR vs baseline) сравнивают recent с baseline.
Минимум данных для запуска:
- Ярус 1 (Energy, Stress, Battery): 14 ночей ношения с покрытием ≥ 70% времени сна
- Ярус 2 (Здоровье): 10 полных дней для индикатора, 30 дней для Lumo Age
Что делаем при перерывах ношения:
- < 3 дня без данных — продолжаем считать на старом baseline
- ≥ 3 дня без данных — фризим обновление baseline до накопления свежих 7 дней
- Полный сброс кольца / новый пользователь — пересчёт с нуля, плашка «Накапливаем данные, точность улучшится через N дней»
Fallback на ярус 0 для ярусов 1/2 в cold-start: используем популяционные нормы по возрасту/полу. Например, RHR ожидаемый = 60–80 для 25–45 лет; HRV-нормы по age-band. Это прокси-baseline на первые 14 дней — точность ниже, но лучше пустого экрана.
3. Карта SDK → метрика → статус интеграции
Заголовок раздела «3. Карта SDK → метрика → статус интеграции»Расширение таблицы из product-spec §5 колонкой «вытащено в RingDebug». Источник реальности — RingDebugView.swift.
| Метрика | SDK объект | Режим | В RingDebug |
|---|---|---|---|
| Фазы сна | QCSleepModel | Ночной batch | ✅ 3 дня |
| Пульс scheduled | QCSchedualHeartRateModel | Каждые 5 мин | ✅ 3 дня + расписание |
| Пульс real-time | realTimeHeartRate | Стриминг | ✅ HR raw + processed |
| RHR (производная) | из QCSchedualHeartRateModel ночью | Ночной batch | ⚠️ данные есть, расчёт RHR — нет |
| HRV scheduled | QCHRVModel | Дневной batch | ✅ 7 дней |
| HRV + RR on-demand | QCRealOneKeyMeasureHeartRateModel | По запросу | ❌ не подключено |
| SpO2 scheduled | QCBloodOxygenModel | Ночью + manual | ✅ raw + scheduled + 3 дня |
| Температура | QCThreeValueTemperatureModel | Интервально | ✅ 3 датчика + расписание + 3 дня |
| Стресс (raw) | QCStressModel | Интервально | ✅ 7 дней |
| Шаги / активность | QCSportModel | Batch | ❌ не подключено |
| Шаги real-time | currentStepInfo | Стриминг | ❌ не подключено |
| Тренировки | QCExerciseModel | По событию | ❌ не подключено |
| Сидячесть | QCSedentaryModel | По событию | ❌ не подключено |
| Давление | QCBloodPressureModel | Scheduled + manual | ❌ не подключено |
| Глюкоза | QCBloodGlucoseModel | Scheduled + manual | ❌ не подключено |
Следствие для v1: половина композитных формул упирается не в код, а в источник данных. Activity-компоненты в Energy / Battery (вес 0.05 / drain) и любые «вы много сидите» — сейчас не считаются. Это не блокер запуска (5% в Energy не критично), но в доке метрики должно стоять ⚠️ компонент недоступен — пересчитываем веса оставшихся к 1.0.
4. Композитные метрики — индекс
Заголовок раздела «4. Композитные метрики — индекс»Все формулы держим у соответствующих экранов, чтобы не было двух источников правды. Здесь — обзор: ярус, источник весов, ссылка.
| Метрика | Где формула | Ярус | Базовая модель |
|---|---|---|---|
| Sleep Score | 06-sleep-detail.md | 0 + 1 (timing — 1) | Oura Sleep Score (см. §8) |
| Energy | 07-energy-detail.md | 1 (требует HRV/RHR baseline) | Oura Readiness contributors |
| Stress (фоновый) | 08-stress-detail.md | 1 | Garmin Body Battery / Polar Stress |
| Stress (on-demand) | 08-stress-detail.md | 0 | Baevsky SI (классика) |
| Battery (Lumo) | 04-battery-detail.md | 1 (через Energy + Stress) | Garmin Body Battery |
| Здоровье | 05-health-detail.md | 2 | Oura Resilience |
| Lumo Age | 05-health-detail.md | 2 | Композит, см. §8 |
5. Raw-метрики и пороги
Заголовок раздела «5. Raw-метрики и пороги»Что отдаём на экране 11-raw-metrics.md и какие пороги используем для алертов / окрашивания.
| Метрика | Ярус | Абсолютный порог (ярус 0) | Персональный baseline (ярус 1) |
|---|---|---|---|
| HR (день) | 0 | < 40 brady, > 100 покой — алерт | — |
| HR (тренировка) | 0 | HR-зоны от max HR = 220 − возраст | Уточняем измеренным max за 60 дней |
| RHR (ночной) | 0 + 1 | < 40 / > 90 — алерт | Δ vs personal baseline (±5 bpm заметно) |
| HRV (RMSSD) | 1 | — (нет универсального порога) | Δ vs personal 14-day baseline в % |
| SpO2 | 0 | < 90% тревожно, < 95% во сне — насторожиться | Δ vs personal ночной baseline |
| Температура (кожа) | 1 | > 37.5°C — fever-flag | Δ vs personal night baseline (±0.5°C — паттерн) |
Правило подачи raw: для ярус-0 показываем абсолютное значение + цвет по порогу. Для ярус-1 показываем абсолютное значение, но интерпретация («норма / выше / ниже») — только после baseline.
6. Ручные измерения — куда заходят
Заголовок раздела «6. Ручные измерения — куда заходят»Пользователь может вручную измерить через детальные экраны. Вопрос: использовать ли эти точки в композитных формулах или они только для самоощущения?
| Измерение | Источник | Использование в формулах | Использование в UI |
|---|---|---|---|
| Manual HR | realTimeHeartRate | ❌ нет (формулы используют scheduled и ночные) | Точечный snapshot в карточке |
| Manual SpO2 | QCBloodOxygenModel (manual) | ❌ нет (Sleep Score использует ночной) | Снапшот + история ручных замеров |
| Manual Stress (Baevsky) | QCRealOneKeyMeasureHeartRateModel | ❌ не входит в фоновый Stress score (другая шкала) | Отдельный snapshot «стресс прямо сейчас» |
| Manual HRV (RR) | QCRealOneKeyMeasureHeartRateModel | ⚠️ кандидат: использовать как замену scheduled HRV в этот день | Snapshot + потенциальное обновление дневной HRV |
Решение для v1: ручные измерения — отдельный канал, не вмешиваются в композитные скоры. Иначе один невозможный замер посреди дня сломает Energy/Battery.
Открыто: Manual HRV (RR) — единственный кандидат на интеграцию в формулы, потому что time-domain HRV из RR точнее scheduled. Решаем после первых пользователей.
7. Cold-start таймлайн
Заголовок раздела «7. Cold-start таймлайн»Что видит пользователь на разных стадиях накопления данных:
| День | Что доступно | Что скрыто / fallback |
|---|---|---|
| 1 | Sleep Score (если поспал в кольце), HR, SpO2, Температура с абсолютными порогами | Energy / Stress / Battery / Здоровье — плашка «Накапливаем данные» |
| 3 | + первые тренды HR / SpO2 / Sleep | Те же |
| 7 | + ранний Sleep Score уверенный, ранний proxy Energy на population baseline | Stress / Battery — всё ещё плашка |
| 14 | ✅ Полный набор: Energy, Stress, Battery, baseline-зависимые raw | Здоровье — ещё нет |
| 30 | ✅ Здоровье | Lumo Age — ещё нет |
| 90 | ✅ Lumo Age | — |
Это в т.ч. ответ на вопрос продаж: «А когда уже будет польза от кольца?» — конкретные ответы по дням.
8. Веса формул — обоснование на референсах
Заголовок раздела «8. Веса формул — обоснование на референсах»Текущие веса в формулах (Energy, Sleep Score, Здоровье, Lumo Age) — округлённые, требуют валидации на ≥100 пользователях после запуска. Базируются на следующих публичных решениях:
Sleep Score (наша формула — 7 компонентов). Опирается на Oura Sleep Score: те же 7 компонентов (Total / Efficiency / REM / Deep / Restfulness / Latency / Timing). Точные веса Oura не публикует, но в их whitepaper и help-центре указано, что Total Sleep и Efficiency — доминирующие. Наши 0.25 / 0.15 для них консистентны. WASO выделили отдельным компонентом (0.10) — клинический маркер фрагментации, отсутствует у Oura, но есть у медицинских стандартов (AASM).
Energy (наша формула — 5 компонентов: Sleep / HRV / RHR / Temp / Activity). Аналог Oura Readiness. Oura использует 8 contributors, мы свернули в 5 (объединили sleep score / sleep balance, выкинули recovery index, требующий ночных RR). Наш вес Sleep 0.40 близок к доминирующему вкладу сна у Oura (по их breakdown — крупнейший контрибьютор). HRV 0.30 + RHR 0.15 = 0.45 на сердечные сигналы — близко к Whoop Recovery, где HRV — основной фактор (~50%+).
Stress (фоновый). Путь Garmin Body Battery / Polar Stress: эвристика на HRV vs personal baseline + HR + исключение активного движения. Точные формулы Garmin/Polar закрыты (Firstbeat — проприетарный алгоритм), но принцип «HRV ниже baseline → стресс выше» документирован в их whitepapers. Наша реализация = тот же принцип.
Stress on-demand (Baevsky SI). Академическая формула из российской/советской космической медицины (Баевский Р.М., 1979–2000е). Используется в Polar OwnRelax, упоминается в современных исследованиях по HRV. Норма 80–150, рост ×1.5–10 при стрессе. Главное отличие от фонового: даёт точечный snapshot за 2–3 минуты, а не балансное значение.
Battery (Lumo). Аналог Garmin Body Battery: стартовое значение = утренний Energy, в течение дня drain от стресса/нагрузки, charge от отдыха. Наша формула проще (нет Firstbeat-математики), но архитектура совпадает.
Здоровье (14 дней). Аналог Oura Resilience, Whoop Recovery Trend, Polar Sleep Plus 7-day trend. Все строятся на 14–28-дневной агрегации тех же базовых сигналов. Наши веса (0.40 / 0.30 / 0.20 / 0.10) — приоритет Восстановления (HRV+RHR) > Стресса > Сна > Активности — соответствует логике Oura: устойчивость = в первую очередь автономная нервная система.
Lumo Age. Композит без прямого аналога. Reverse-fit к паспортному возрасту по 5 сигналам (см. 05-health-detail.md). Веса доминируют RHR (0.40) — научно обоснованно: RHR — самый сильный одиночный предиктор сердечно-сосудистого возраста (см. Cooper Clinic Longitudinal Study). HRV-trend (0.20) — второй валидированный сигнал. Остальные веса — рабочие, требуют калибровки на референсной когорте.
9. Stress: raw vs score — однозначный data flow
Заголовок раздела «9. Stress: raw vs score — однозначный data flow»В формуле Батарейки drain(t) стоит «Стресс(t) высокий». Чтобы избежать рекурсии, фиксируем направление потока:
QCStressModel (raw, интервальный ряд из SDK) ↓HRV (RMSSD) baseline + HR + sedentary penalty ↓Stress score (0–100, карточка) ← это вход в Battery.drain ↓Battery (0–100)Stress score (карточка, 0–100) — производная: пересчитывается из raw QCStressModel + HRV baseline + контекста. Battery использует уже готовый Stress score, не raw. Stress score в свою очередь не использует Battery — петли нет.
«Стресс(t) высокий» в формуле Батарейки = Stress score > 50 (порог = граница «Повышенный» из 08-stress-detail.md).
10. Открытые вопросы
Заголовок раздела «10. Открытые вопросы»Сводка TBD из всех экранов, чтобы не искать по 7 файлам:
- Веса формул — все текущие веса (0.40 / 0.30 / …) требуют калибровки на ≥100 пользователях после запуска. До этого — round-numbers по аналогии с Oura/Whoop.
- Cold-start fallback на популяционные нормы — нужна таблица RHR/HRV ожидаемых по возрасту/полу. Источник: NHANES / Garmin user data tables / академические работы. Не сделано.
- RHR-расчёт — сырые ночные HR в SDK есть, но extract минимума устойчивого окна (5+ мин низкого HR в глубоком сне) не реализован.
- Manual HRV (RR) → замена дневной scheduled HRV — оставлено на v1.1 после первых пользователей.
- PPG sample rate — для Cardio Age на морфологии waveform нужно ≥100 Hz. Запрос вендору не отправлен (см. 05-health-detail.md).
- Dynamic порог «высокого стресса» для Battery — сейчас фиксируем 50 (граница «Повышенный»). Возможно стоит делать персональным (75-й перцентиль личного распределения за 14 дней).
- Stress: активный vs сидячий — карточка различает их, но фоновый расчёт ещё не имеет источника движения (
QCSportModel/QCSedentaryModelне подключены).
Версия: 0.1 Последнее обновление: 2026-05-04