From ecff37a66c618ae6567554a9512d14d1de783f20 Mon Sep 17 00:00:00 2001 From: Sergey Shchukin Date: Thu, 9 Apr 2026 17:56:06 +0300 Subject: [PATCH] Update docs --- README.md | 88 +++-- backend/README.md | 283 ++++++++++----- backend/ROADMAP.md | 30 +- docs/ai/GLOSSARY.md | 173 +++++---- docs/ai/SYSTEM_MAP.md | 141 ++++--- docs/architecture/ARCHITECTURE.md | 275 +++++++++----- docs/architecture/OPEN_DECISIONS.md | 216 +++++------ docs/data/DATA_MODEL.md | 357 +++++++++++------- docs/models/METRICS.md | 174 +++++---- docs/models/READINESS_MODEL.md | 249 ++++++------- docs/models/RIDE_BRIEFING.md | 157 ++++---- docs/models/current-metrics-methodology.md | 404 +++++++-------------- docs/models/model_v2_architecture.md | 360 +++++++++--------- 13 files changed, 1574 insertions(+), 1333 deletions(-) diff --git a/README.md b/README.md index 03e320d..baa3c44 100644 --- a/README.md +++ b/README.md @@ -2,28 +2,29 @@ > A system for analyzing training load, estimating athlete state, and supporting training decisions. > -> `signal -> load/recovery state -> readiness -> decision` +> `signal -> load state + recovery state -> readiness -> decision` ## Core Idea **The right training on the right day.** -Human Engine is not just a training log and not an AI coach. +Human Engine is not a training log and not an AI coach. It is an engineering system designed to support decisions through explicit, reproducible, and deterministic logic. ## What Human Engine Does -- Collects training data -- Estimates load and recovery state +- Collects training and recovery data +- Stores raw source payloads for reproducibility +- Builds daily load and recovery state - Calculates readiness and good-day probability - Supports training load decisions ## What the System Is - A training data processing system -- A load adaptation model +- A physiology-driven load and recovery model - A readiness evaluation engine -- A foundation for training decisions +- A deterministic foundation for training decisions ## What the System Is Not @@ -36,15 +37,18 @@ See: [docs/ai/PRODUCT_CONTEXT.md](docs/ai/PRODUCT_CONTEXT.md) ## Current State -The system is currently in a stabilization phase. The current setup includes: +The current backend already includes: -- Backend built with FastAPI +- FastAPI backend - PostgreSQL - Strava ingestion pipeline -- Health recovery ingestion and normalization -- Raw data storage -- Daily load and recovery feature layer -- Model V2 architecture for readiness +- HealthKit raw ingestion and full-sync orchestration +- Raw data storage for Strava and HealthKit payloads +- HealthKit normalized tables +- Recovery layer via `health_recovery_daily` +- Load model v2 via `load_state_daily_v2` +- Readiness layer via `readiness_daily` +- Probability layer via `good_day_probability` - Docker deployment - Public API exposed through a VPS @@ -53,6 +57,7 @@ The system is currently in a stabilization phase. The current setup includes: - Deterministic core - Transparent logic - Reproducible results +- Stabilization of model v2 and downstream decision outputs See: [docs/ai/CURRENT_PRIORITIES.md](docs/ai/CURRENT_PRIORITIES.md) @@ -60,6 +65,17 @@ See: [docs/ai/CURRENT_PRIORITIES.md](docs/ai/CURRENT_PRIORITIES.md) ### Data Flow +```text +Strava ---------------------------> daily_training_load ----------+ + | +HealthKit -> raw ingest -> normalized health tables -> recovery -+-> readiness + | + v + load_state_daily_v2 +``` + +### Current End-to-End Pipeline + ```text Strava + HealthKit | @@ -70,13 +86,13 @@ Strava + HealthKit PostgreSQL | v -Normalized / Daily Features +Raw / Normalized / Daily State | v -Model V2 +LoadState + RecoveryState | v -Readiness / Insights +Readiness + GoodDayProbability ``` ### Infrastructure @@ -101,8 +117,9 @@ Backend + DB - Simplicity over complexity - Deterministic logic over AI -- Calculations should remain transparent -- Data and outputs should remain reproducible +- Calculations remain transparent +- Data and outputs remain reproducible +- Load and recovery remain separate physiological contours - AI is an auxiliary layer, not the product core ## Repository Structure @@ -112,7 +129,6 @@ backend/ main service backend/infra/ local infrastructure db-init/ database initialization compose.yaml deployment -sql_*.sql analytics and ingestion scripts docs/ system documentation ``` @@ -132,19 +148,35 @@ docs/ system documentation - [docs/ai/GLOSSARY.md](docs/ai/GLOSSARY.md) - [AGENTS.md](AGENTS.md) +## Current Model V2 Baseline + +- Load contour: `daily_training_load -> load_state_daily_v2` +- Recovery contour: HealthKit normalized tables -> `health_recovery_daily` +- Readiness contour: `load_state_daily_v2 + health_recovery_daily -> readiness_daily` +- `freshness = fitness - fatigue_total` +- `fatigue_total` is a weighted mixture of `fatigue_fast` and `fatigue_slow` +- Readiness is not equal to freshness +- `good_day_probability` is stored as a separate probability-like output + ## Short Roadmap -- Streams ingestion -- Recovery data normalization -- Load model v2: nonlinear load, fitness, fast/slow fatigue -- Readiness model v2 -- Good day probability -- Prediction engine -- iOS client +Already implemented: -## Documentation +- HealthKit ingestion and normalization +- Recovery daily aggregation +- Load model v2 +- Readiness model v2 baseline +- Good day probability baseline + +Next: + +- activity streams ingestion +- feature extraction expansion +- readiness / probability calibration +- prediction engine +- iOS client integration polish -Repository knowledge is organized as follows: +## Documentation Map - `docs/ai/` — AI context and system language - `docs/architecture/` — architecture and decisions @@ -155,4 +187,4 @@ Repository knowledge is organized as follows: ## Status -Experimental engineering project with a strong focus on a deterministic product core. +Experimental engineering project with a deterministic product core and an implemented model v2 baseline in backend. diff --git a/backend/README.md b/backend/README.md index 1848fb9..c0d4952 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,38 +1,46 @@ -# Human Engine +# Human Engine Backend -Human Engine — экспериментальная платформа анализа тренировок. +Human Engine backend — FastAPI-сервис и orchestration слой для ingestion, нормализации данных и расчета daily state. -Проект предназначен для загрузки, хранения и анализа тренировочных данных -из Strava с использованием собственной инфраструктуры. +## Назначение -## Принципы проекта +Backend отвечает за: -Human Engine создается как инженерный эксперимент по построению -системы анализа тренировочных данных на собственной инфраструктуре. +- прием данных из внешних источников +- сохранение raw payloads +- нормализацию данных +- расчет derived daily state +- предоставление API для пересчета и интеграции -Основные принципы проекта: +## Принципы -• простые и понятные модели вместо сложных ML без необходимости -• прозрачные вычисления тренировочных метрик -• минимальная зависимость от внешних сервисов -• self-hosted инфраструктура -• воспроизводимость расчетов -• простая и наблюдаемая архитектура +- deterministic logic first +- прозрачные вычисления +- воспроизводимость через raw storage +- минимальная скрытая магия +- AI не участвует в core-расчетах -## Архитектура +## Текущая архитектура -Текущая архитектура системы: +Источники: -Strava -↓ -Webhook -↓ -FastAPI backend -↓ -PostgreSQL +- Strava +- HealthKit + +Базовый поток: + +```text +Strava -> raw ingest / daily load ------------------+ + | +HealthKit -> raw ingest -> normalized -> recovery --+-> readiness + | + v + load_state_daily_v2 +``` Деплой: +```text Internet ↓ VPS (Caddy reverse proxy) @@ -42,88 +50,168 @@ Tailscale Home server ↓ FastAPI + PostgreSQL +``` + +## Реализованные backend layers + +### 1. Strava ingestion + +- webhook endpoint +- raw storage +- ingest jobs +- загрузка активностей +- формирование `daily_training_load` + +### 2. HealthKit ingestion + +Реализованы: + +- raw ingestion endpoint +- orchestration endpoint `POST /api/v1/healthkit/full-sync/{user_id}` +- raw table `healthkit_ingest_raw` +- pipeline raw ingest -> normalized -> recovery -> readiness + +### 3. Health normalized layer + +Реализованы таблицы: + +- `health_sleep_night` +- `health_resting_hr_daily` +- `health_hrv_sample` +- `health_weight_measurement` + +Назначение: + +- привести payload HealthKit к детерминированной и пересчитываемой форме +- отделить raw payload от прикладных расчетов + +### 4. Recovery layer + +Реализована таблица: + +- `health_recovery_daily` + +Текущий baseline включает: + +- sleep metrics +- resting HR +- HRV daily median +- latest known weight +- `recovery_score_simple` + +### 5. Load model v2 + +Реализована таблица: -## Pipeline загрузки данных +- `load_state_daily_v2` -Система уже реализует первый pipeline загрузки тренировок из Strava. +Текущий расчет: -Текущий поток данных: +- идет по непрерывной календарной оси +- использует `tss = 0` в дни без тренировок +- использует текущий линейный input по TSS +- хранит `fitness` +- хранит `fatigue_fast` +- хранит `fatigue_slow` +- хранит `fatigue_total` как взвешенную смесь fast/slow fatigue +- хранит `freshness = fitness - fatigue_total` -Strava -↓ -Webhook event -↓ -/webhook/strava -↓ -strava_webhook_event -↓ -strava_activity_ingest_job -↓ -загрузка активности из Strava API -↓ -strava_activity_raw +### 6. Readiness layer -На текущий момент система: -- принимает webhook события Strava -- сохраняет события в базе данных -- создает jobs для загрузки активности -- загружает сырые данные активности +Реализована таблица: -В дальнейшем ingestion слой будет выделен в отдельный worker сервис. +- `readiness_daily` + +Текущий readiness baseline: + +- объединяет load contour и recovery contour +- использует `freshness` из `load_state_daily_v2` +- использует `recovery_score_simple` из `health_recovery_daily` +- считает `readiness_score_raw = 0.6 * freshness_norm + 0.4 * recovery_score_simple` +- сохраняет `readiness_score` +- сохраняет `good_day_probability` +- сохраняет `status_text` +- сохраняет `explanation_json` + +## HealthKit full sync pipeline + +Текущий orchestration pipeline: + +```text +POST /api/v1/healthkit/full-sync/{user_id} + -> save raw payload + -> process latest raw payload into normalized tables + -> collect affected dates + -> recompute health_recovery_daily for affected dates + -> recompute readiness_daily for affected dates +``` + +Важно: + +- recovery пересчитывается поверх normalized health tables +- readiness пересчитывается как отдельный слой +- readiness больше не является просто полем внутри load state ## Технологический стек -Backend -FastAPI -PostgreSQL -Python +Backend: -Infrastructure -Docker -Docker Compose -Caddy -Tailscale +- FastAPI +- Python +- PostgreSQL + +Infrastructure: + +- Docker +- Docker Compose +- Caddy +- Tailscale -External integrations -Strava API -Strava Webhooks +External integrations: +- Strava API +- Strava Webhooks +- Apple HealthKit via iOS sync client ## Структура проекта -backend/ -Основной код backend-сервиса на FastAPI и документация проекта. +`backend/` +Основной код backend-сервиса на FastAPI. -backend/infra/ -Локальная инфраструктура для разработки (docker-compose для PostgreSQL). +`backend/infra/` +Локальная инфраструктура для разработки. -db-init/ +`db-init/` SQL для инициализации базы данных. -compose.yaml -docker compose стек для домашнего сервера. +`compose.yaml` +docker compose стек для сервера. -sql_*.sql -Черновые SQL-запросы для ingestion и аналитических метрик. +## Roadmap +Уже реализовано: -## Roadmap +- HealthKit ingestion и normalization +- recovery daily aggregation +- load model v2 baseline +- readiness baseline +- good day probability baseline -Ближайшие этапы развития проекта: +Следующие шаги: -- загрузка streams данных из Strava -- нормализация recovery-данных -- расчет тренировочных метрик и load state v2 -- readiness model v2 и probability layer -- API для аналитики -- мобильное приложение (iOS) +- activity streams ingestion +- расширение feature extraction +- калибровка readiness / probability +- API и UI для user-facing insights +- iOS integration polish ## AI Context See: -- docs/ai/PRODUCT_CONTEXT.md -- docs/ai/CURRENT_PRIORITIES.md -- AGENTS.md + +- `docs/ai/PRODUCT_CONTEXT.md` +- `docs/ai/CURRENT_PRIORITIES.md` +- `AGENTS.md` ## Run locally @@ -135,47 +223,46 @@ See: ### 1. Clone repository -git clone https://github.com/shchukins/human-engine.git +```bash +git clone https://github.com/shchukins/human-engine.git cd human-engine/backend +``` ### 2. Create environment file -Copy example configuration: - +```bash cp infra/.env.example infra/.env - -Edit values if necessary. +``` ### 3. Start PostgreSQL -cd infra +```bash +cd infra docker compose up -d +``` -PostgreSQL will start on: - -localhost:5433 +PostgreSQL: -Database: - -human_engine +- host: `localhost` +- port: `5433` +- database: `human_engine` ### 4. Install backend dependencies +```bash cd .. - -python -m venv .venv +python -m venv .venv source .venv/bin/activate - pip install -r requirements.txt +``` ### 5. Run backend +```bash uvicorn backend.app:app --reload +``` -API will be available at: - -http://localhost:8000 - -Health check: +API: -http://localhost:8000/healthz +- `http://localhost:8000` +- health check: `http://localhost:8000/healthz` diff --git a/backend/ROADMAP.md b/backend/ROADMAP.md index 481dfa6..b53d7f9 100644 --- a/backend/ROADMAP.md +++ b/backend/ROADMAP.md @@ -1,9 +1,21 @@ -Next steps - -• activity streams ingestion -• feature extraction -• recovery normalization -• training load metrics and load state v2 -• readiness and good day probability -• performance model -• iOS client +# Backend Roadmap + +## Already implemented + +- Strava ingestion baseline +- HealthKit raw ingestion +- HealthKit normalized tables +- `health_recovery_daily` +- `load_state_daily_v2` +- `readiness_daily` +- `good_day_probability` baseline +- HealthKit full-sync orchestration + +## Next steps + +- activity streams ingestion +- feature extraction expansion +- readiness / probability calibration +- decision outputs and ride briefing integration +- performance / prediction model +- iOS client integration polish diff --git a/docs/ai/GLOSSARY.md b/docs/ai/GLOSSARY.md index 4c97ab2..5126f6b 100644 --- a/docs/ai/GLOSSARY.md +++ b/docs/ai/GLOSSARY.md @@ -3,56 +3,95 @@ ## 1. Data layer ### Data Engine -Слой, отвечающий за сбор и нормализацию входных данных. +Слой, отвечающий за сбор, сохранение и нормализацию входных данных. Включает: -- webhook ingestion -- загрузку активностей -- приведение данных к единому формату + +- Strava ingestion +- HealthKit ingestion +- raw payload storage +- нормализацию source data --- ### Raw Data -Необработанные данные, полученные из внешних источников (например, Strava). +Необработанные данные, полученные из внешних источников. + +Примеры: -Должны сохраняться без изменений для воспроизводимости. +- `strava_activity_raw` +- `healthkit_ingest_raw` + +Должны сохраняться без изменения для воспроизводимости. + +--- + +### Normalized Data +Нормализованные таблицы, полученные из raw payloads. + +Примеры: + +- `health_sleep_night` +- `health_resting_hr_daily` +- `health_hrv_sample` +- `health_weight_measurement` + +Используются как детерминированный вход для derived layers. --- ### Feature Extraction -Процесс преобразования сырых данных в признаки (features), используемые в моделях. +Процесс преобразования raw и normalized data в daily features и derived state. --- ## 2. Modeling layer ### Physiology Model -Модель, оценивающая внутреннее состояние спортсмена на основе данных. +Модель, оценивающая состояние спортсмена на основе явных физиологических контуров. Примеры: -- накопленная нагрузка -- восстановление -- адаптация + +- load state +- recovery state +- readiness --- ### Training Load -Количественная оценка нагрузки тренировки. +Количественная оценка тренировочной нагрузки. Примеры: -- TSS -- IF -- объем + +- TSS +- daily training load + +--- + +### Load State +Состояние, описывающее накопление и спад тренировочной нагрузки. + +В текущем backend реализовано в `load_state_daily_v2`. + +--- + +### Recovery State +Состояние, описывающее восстановление организма независимо от load state. + +В текущем backend реализовано в `health_recovery_daily`. + +Recovery не заменяет fatigue, а корректирует readiness поверх load model. --- ### Fitness / Fatigue -Компоненты состояния: +Компоненты load state: -- Fitness (долгосрочная адаптация) -- Fatigue Fast (быстрый отклик усталости) -- Fatigue Slow (накопление усталости сериями тренировок) -- Fatigue Total = Fast + Slow +- Fitness: долгосрочная адаптация +- Fatigue Fast: быстрый отклик усталости +- Fatigue Slow: более инерционное накопление усталости +- Fatigue Total: взвешенная смесь fast и slow fatigue +- Freshness: `fitness - fatigue_total` --- @@ -61,15 +100,17 @@ ### Readiness Оценка текущей готовности к нагрузке. -Основывается на: -- load state -- recovery state -- недавней активности +В текущей model v2 readiness: -Должна быть: -- детерминированной -- объяснимой -- проверяемой +- не равен `freshness` +- строится как функция `load_state + recovery_state` +- хранится отдельно в `readiness_daily` + +Должен быть: + +- детерминированным +- объяснимым +- проверяемым --- @@ -78,27 +119,33 @@ Используется для: -- мягкого rule-based маппинга в рекомендации -- снижения зависимости от жестких порогов +- более мягкого rule-based маппинга +- отделения score от вероятностного слоя - explainable decision support +В текущем backend это отдельное поле `good_day_probability` в `readiness_daily`. + --- ### Recommendation -Результат работы системы. +Результат decision layer. Определяет: -- тип тренировки -- допустимую нагрузку + +- тип нагрузки +- допустимую интенсивность +- ограничения по дню --- ### Ride Briefing -Конкретная форма рекомендации перед тренировкой. +Структурированный user-facing output перед тренировкой. Должен быть: -- детерминированным -- стабильным + +- детерминированным +- стабильным +- опираться на readiness layer, а не на свободный текст --- @@ -107,35 +154,35 @@ ### Deterministic Логика, которая: -- дает одинаковый результат при одинаковых входных данных -- не зависит от генеративных моделей +- дает одинаковый результат при одинаковых входных данных +- не зависит от генеративных моделей --- ### Pipeline Последовательность обработки данных: -ingestion → storage → normalized features → models → decision +`ingestion -> raw storage -> normalized data -> daily state -> readiness -> decision` --- ### Core Основная часть системы: -- backend -- database -- domain logic +- backend +- database +- domain logic Где выполняются все расчеты. --- ### Worker -Фоновый процесс, выполняющий: +Фоновый процесс или orchestration path, выполняющий: -- загрузку данных -- обработку -- обновление состояния +- загрузку данных +- нормализацию +- перерасчет derived state --- @@ -144,11 +191,7 @@ ingestion → storage → normalized features → models → decision ### RAG Retrieval-Augmented Generation. -Система, которая: -- ищет релевантные документы -- использует их для ответа - -Используется как вспомогательный инструмент. +Используется как вспомогательный инструмент для навигации и объяснений. --- @@ -157,14 +200,15 @@ Large Language Model. Используется только для: -- генерации текста -- объяснений -- помощи разработчику +- генерации текста +- объяснений +- помощи разработчику Не используется для: -- расчетов -- принятия решений +- расчетов +- readiness logic +- принятия продуктовых решений --- @@ -174,22 +218,25 @@ Large Language Model. Возможность повторить расчет и получить тот же результат. Требует: -- сохранения raw данных -- явной логики + +- сохранения raw данных +- явной логики +- versioned derived layers --- ### Observability Возможность понять: -- как получен результат -- какие данные использовались +- как получен результат +- какие данные использовались +- какой слой повлиял на итог --- ### Simplicity Предпочтение: -- простых моделей -- явной логики -- минимальной магии +- простых моделей +- явной логики +- минимальной магии diff --git a/docs/ai/SYSTEM_MAP.md b/docs/ai/SYSTEM_MAP.md index 80be548..041bd42 100644 --- a/docs/ai/SYSTEM_MAP.md +++ b/docs/ai/SYSTEM_MAP.md @@ -4,30 +4,31 @@ Human Engine — система, которая: -> преобразует тренировочные данные в решение о нагрузке +> преобразует тренировочные и recovery-данные в решение о нагрузке --- ## 2. End-to-end flow + +```text Data Sources ↓ -Data Engine +Ingestion / Raw Storage ↓ -Data Storage +Normalized Data ↓ -Normalized / Daily Features +Daily State Layers ↓ Load Model + Recovery Model ↓ Readiness Engine ↓ -Recommendation +Recommendation / Ride Briefing ↓ Workout Outcome ↓ Feedback -↓ -Model update +``` --- @@ -39,23 +40,29 @@ Model update Включает: -- Strava webhook -- ingestion pipeline -- raw data storage +- Strava ingestion +- HealthKit ingestion +- raw data storage Свойства: -- данные не теряются -- данные не искажаются +- данные не теряются +- данные не искажаются --- -### 3.2 Processing layer +### 3.2 Normalization and processing layer -Преобразует данные в признаки. +Преобразует raw payloads в прикладные таблицы и daily aggregates. -- feature extraction -- расчет базовых метрик +Текущие артефакты: + +- `daily_training_load` +- `health_sleep_night` +- `health_resting_hr_daily` +- `health_hrv_sample` +- `health_weight_measurement` +- `health_recovery_daily` --- @@ -63,8 +70,11 @@ Model update Оценивает состояние человека. -- load model v2 -- recovery model +Текущие артефакты: + +- `load_state_daily_v2` +- `health_recovery_daily` +- `readiness_daily` - fitness / fast fatigue / slow fatigue - readiness score - good day probability @@ -75,19 +85,29 @@ Model update Формирует вывод системы. -- readiness -- recommendation -- ride briefing +Сейчас частично реализован через readiness outputs: + +- readiness +- probability layer +- status text +- explanation payload + +Следующий слой: + +- recommendation +- ride briefing --- ### 3.5 Feedback loop -Система обучается на результате: +Система может развиваться на основе результата: + +- фактическая тренировка +- отклонение от ожидаемого состояния +- корректировка модели -- фактическая тренировка -- отклонение от прогноза -- корректировка модели +Этот слой пока не является основным реализованным контуром. --- @@ -96,13 +116,16 @@ Model update Система должна быть: ### Deterministic -- одинаковый вход → одинаковый результат + +- одинаковый вход -> одинаковый результат ### Reproducible -- любой расчет можно повторить + +- любой расчет можно повторить ### Observable -- можно объяснить результат + +- можно объяснить результат по слоям --- @@ -110,15 +133,15 @@ Model update Не входит в основной pipeline: -- LLM -- генеративные модели -- AI-решения +- LLM +- генеративные модели +- AI-решения AI может работать только как: -- слой объяснения -- инструмент навигации -- developer assistant +- слой объяснения +- инструмент навигации +- developer assistant --- @@ -128,35 +151,40 @@ Human Engine — это не один алгоритм. Это цепочка: -> данные → состояние → решение - -Если система дает неправильный результат: +> данные -> состояние -> решение -ошибка всегда находится в одном из слоев: +Если система дает неправильный результат, ошибка находится в одном из слоев: -- данные -- признаки -- модель -- логика решения +- данные +- нормализация +- модель нагрузки +- модель восстановления +- readiness logic +- decision mapping --- ## 7. Current vs Future -### Сейчас: +### Сейчас -- ingestion pipeline -- raw data -- базовая архитектура +- Strava ingestion pipeline +- HealthKit ingestion pipeline +- raw data storage +- normalized health layer +- recovery daily layer +- load state v2 +- readiness daily +- good day probability baseline -### Далее: +### Далее -- normalized and daily feature layer -- load state v2 -- recovery-aware readiness -- good day probability -- prediction -- adaptive training +- расширение feature layer +- readiness / probability calibration +- recommendation layer +- ride briefing layer +- prediction +- adaptive training --- @@ -164,9 +192,10 @@ Human Engine — это не один алгоритм. При развитии системы: -> каждый новый элемент должен вписываться в эту схему +> каждый новый элемент должен вписываться в схему +> `source -> state -> readiness -> decision` Если не вписывается: -- либо он лишний -- либо схема нарушена +- либо он лишний +- либо схема нарушена diff --git a/docs/architecture/ARCHITECTURE.md b/docs/architecture/ARCHITECTURE.md index 88fae8f..1db757d 100644 --- a/docs/architecture/ARCHITECTURE.md +++ b/docs/architecture/ARCHITECTURE.md @@ -2,14 +2,14 @@ ## 1. Purpose -Этот документ описывает архитектуру системы Human Engine. +Этот документ описывает текущую архитектуру Human Engine. Цель: -- зафиксировать текущую структуру системы -- определить границы компонентов -- показать поток данных -- обеспечить согласованность разработки +- зафиксировать текущую структуру системы +- определить границы компонентов +- показать поток данных +- синхронизировать документацию с backend implementation --- @@ -17,24 +17,31 @@ Human Engine построен как pipeline: -> данные → состояние → решение +> данные -> состояние -> readiness -> решение Высокоуровневый поток: + +```text Data Sources ↓ Backend (Data Engine) ↓ PostgreSQL (Storage) ↓ -Normalized / Daily Features +Raw / Normalized / Daily State +↓ +LoadState + RecoveryState ↓ -Model V2 +Readiness ↓ Insight / Decision layer +``` --- ## 3. Deployment architecture + +```text Internet ↓ VPS (Caddy reverse proxy) @@ -46,12 +53,13 @@ Home server Backend (FastAPI) ↓ PostgreSQL +``` Свойства: -- backend не доступен напрямую из интернета -- доступ только через VPS + Tailscale -- инфраструктура self-hosted +- backend не доступен напрямую из интернета +- доступ идет через VPS + Tailscale +- инфраструктура self-hosted --- @@ -63,12 +71,12 @@ FastAPI сервис. Ответственность: -- прием webhook событий -- управление ingestion pipeline -- API для доступа к данным -- оркестрация обработки +- прием webhook и sync payloads +- управление ingestion pipeline +- orchestration перерасчетов +- API для доступа к данным и derived state -Backend — это центр системы. +Backend — центр deterministic core. --- @@ -78,33 +86,34 @@ PostgreSQL. Хранит: -- webhook события -- активности -- raw данные -- производные метрики (в будущем) +- raw события и payloads +- normalized tables +- derived daily state +- readiness outputs Требование: -- данные должны быть воспроизводимыми +- расчеты должны быть воспроизводимыми --- -### 4.3 Worker +### 4.3 Worker / orchestration paths -Фоновый процесс. +Фоновый процесс и orchestration endpoints. Выполняет: -- загрузку активностей из Strava -- загрузку streams -- обновление данных +- загрузку активностей из Strava +- обработку HealthKit sync payloads +- пересчет recovery и readiness --- -## 5. Data pipeline +## 5. Current data pipelines -Текущий pipeline: +### 5.1 Strava pipeline +```text Strava ↓ Webhook event @@ -120,158 +129,224 @@ Worker Strava API ↓ strava_activity_raw +↓ +daily_training_load +``` Свойства: -- события сохраняются -- ingestion асинхронный -- raw данные не изменяются +- события сохраняются +- ingestion асинхронный +- raw данные не изменяются --- -## 6. Architectural layers +### 5.2 HealthKit full-sync pipeline + +```text +POST /api/v1/healthkit/full-sync/{user_id} +↓ +healthkit_ingest_raw +↓ +latest raw -> normalized health tables +↓ +health_recovery_daily recompute +↓ +readiness_daily recompute +``` + +Normalized health tables: -Система разделена на слои: +- `health_sleep_night` +- `health_resting_hr_daily` +- `health_hrv_sample` +- `health_weight_measurement` + +--- + +## 6. Architectural layers ### 6.1 Data layer (implemented) -- ingestion -- raw storage +- Strava ingestion +- HealthKit ingestion +- raw storage --- -### 6.2 Processing layer (planned) +### 6.2 Normalization / processing layer (implemented) + +- `daily_training_load` +- HealthKit normalized tables +- recovery aggregation -- normalizing source data -- daily feature extraction -- базовые метрики нагрузки и восстановления +Этот слой больше не является только planned. --- -### 6.3 Modeling layer (implemented / evolving) +### 6.3 Modeling layer (implemented baseline) + +Ключевые таблицы: + +- `health_recovery_daily` +- `load_state_daily_v2` +- `readiness_daily` -- `load_state_daily_v2` -- `readiness_daily` -- load model with nonlinear load input -- fitness + fast / slow fatigue -- readiness from load state and recovery state -- `good_day_probability` as probability layer +Ключевые свойства: + +- load и recovery разделены на независимые контуры +- `load_state_daily_v2` считает `fitness`, `fatigue_fast`, `fatigue_slow`, `fatigue_total`, `freshness` +- расчет идет по непрерывной календарной оси +- в дни без тренировок используется `tss = 0` +- `fatigue_total` является взвешенной смесью fast/slow fatigue +- readiness считается из load state и recovery state, а не только из freshness +- `good_day_probability` хранится как отдельный probability layer --- -### 6.4 Decision layer (planned) +### 6.4 Decision layer (partially implemented) + +Сейчас реализованы базовые decision outputs: + +- `readiness_score` +- `good_day_probability` +- `status_text` +- `explanation_json` -- daily readiness summary -- recommendation -- ride briefing +Следующий слой: + +- daily readiness summary +- recommendation +- ride briefing --- ## 7. Core vs AI boundary -Критическое архитектурное разделение: - -### Core (обязательная часть) +### Core -- backend -- database -- ingestion -- domain logic +- backend +- database +- ingestion +- normalization +- domain logic +- readiness logic Свойства: -- deterministic -- воспроизводимый -- проверяемый +- deterministic +- воспроизводимый +- проверяемый --- -### AI (вспомогательный слой) +### AI -- RAG -- LLM -- генерация текста +- RAG +- LLM +- генерация текста Свойства: -- не влияет на расчеты -- не участвует в принятии решений -- работает отдельно от core +- не влияет на расчеты +- не участвует в принятии решений +- работает отдельно от core --- ## 8. Architecture principles -Система строится по следующим принципам: - ### Deterministic first -- логика должна быть явной -- одинаковый вход → одинаковый результат + +- логика должна быть явной +- одинаковый вход -> одинаковый результат --- ### Simplicity over complexity -- простые решения предпочтительнее -- избегать лишних абстракций + +- простые решения предпочтительнее +- избегать лишних абстракций --- ### Reproducibility -- любой расчет можно повторить -- raw данные сохраняются + +- любой расчет можно повторить +- raw данные сохраняются --- ### Separation of concerns -- data / model / decision разделены -- AI не смешивается с core + +- source ingestion, normalization, model and decision разделены +- load и recovery не смешиваются в один неявный сигнал +- AI не смешивается с core --- -## 9. Evolution path +## 9. Current model v2 baseline + +Текущая product-level схема: + +```text +LoadState + RecoveryState -> Readiness -> GoodDayProbability +``` + +Где: + +- `LoadState` описывает тренировочную нагрузку +- `RecoveryState` описывает восстановление организма +- `Readiness` является отдельным слоем, а не полем внутри load state + +--- + +## 10. Evolution path Текущее состояние: -- ingestion pipeline -- storage -- базовая инфраструктура +- ingestion pipelines +- raw storage +- normalized health layer +- recovery layer +- load model v2 baseline +- readiness baseline Следующие шаги: -- feature and normalization layer -- model v2 stabilization -- readiness and probability calibration -- prediction +- feature layer expansion +- readiness and probability calibration +- recommendation layer +- prediction --- -## 10. Constraints - -При изменении архитектуры: +## 11. Constraints Нельзя: -- внедрять AI в core -- скрывать логику -- усложнять систему без причины +- внедрять AI в core +- скрывать логику +- подменять load/recovery контуры текстовой эвристикой +- менять доменный смысл без явной фиксации Можно: -- упрощать -- делать логику явной -- улучшать наблюдаемость +- упрощать +- делать логику явной +- улучшать наблюдаемость --- -## 11. Consistency with System Map +## 12. Consistency rule Любое изменение должно: -- вписываться в pipeline -- не ломать слои -- не смешивать уровни +- вписываться в pipeline +- не ломать границы между слоями +- явно отделять implemented от planned Если компонент не вписывается: -- либо он лишний -- либо архитектура нарушена +- либо он лишний +- либо архитектура нарушена diff --git a/docs/architecture/OPEN_DECISIONS.md b/docs/architecture/OPEN_DECISIONS.md index 8eccd7f..b3a1f62 100644 --- a/docs/architecture/OPEN_DECISIONS.md +++ b/docs/architecture/OPEN_DECISIONS.md @@ -1,89 +1,90 @@ # Open Decisions Этот документ фиксирует архитектурные и продуктовые вопросы, -которые на данный момент не имеют окончательного решения. +которые еще не имеют окончательного решения. Цель: -- сохранить контекст размышлений -- избежать потери важных вопросов -- поддерживать осознанное развитие системы +- сохранить контекст размышлений +- отделить уже реализованное от еще не решенного +- поддерживать осознанное развитие системы --- -## OD-001: Readiness model definition +## OD-001: Readiness calibration ### Context -Базовая структура readiness в model v2 уже определена: +Базовая структура readiness в model v2 уже реализована: - `LoadState + RecoveryState -> Readiness -> GoodDayProbability` -- load state использует `fitness`, `fatigue_fast`, `fatigue_slow`, `freshness` -- recovery state использует sleep / HRV / resting HR aggregates +- load state использует `fitness`, `fatigue_fast`, `fatigue_slow`, `fatigue_total`, `freshness` +- recovery state использует sleep / HRV / resting HR / weight aggregates +- readiness baseline использует формулу `0.6 * freshness_norm + 0.4 * recovery_score_simple` -Но все еще не определены: +Но все еще не определены окончательно: -- точные веса readiness formula -- калибровка probability thresholds -- схема zone mapping +- калибровка весов +- калибровка status thresholds +- калибровка probability interpretation --- ### Options -1. Минимальная v2: `freshness + recovery_score_simple` -2. Расширенная v2+: `freshness + recovery_score + hrv_dev + rhr_dev + sleep_score` -3. Версионируемый гибрид с explicit calibration +1. Оставить текущую baseline formula и калибровать пороги +2. Расширить readiness input через `sleep_score_simple`, `hrv_dev`, `rhr_dev` +3. Ввести более явное versioning readiness calibration --- ### Open questions -- какой минимальный recovery input обязателен -- как калибровать probability без black-box логики -- как versioning readiness model отражать в storage +- как калибровать веса без black-box логики +- какие thresholds считать стабильными для user-facing layer +- как versioning readiness model отражать в storage и docs --- ### Status -partially resolved +partially resolved --- -## OD-002: Feature layer design +## OD-002: Feature layer expansion ### Context -Feature layer пока не реализован. +Базовый feature / derived layer уже частично реализован: -Нужно решить: +- `daily_training_load` +- HealthKit normalized tables +- `health_recovery_daily` -- где он живет -- как хранится -- как пересчитывается +Открытым остается вопрос расширения feature layer и его границ. --- ### Options -1. SQL-based (materialized views) +1. SQL-based aggregates 2. Python pipeline -3. гибрид +3. Гибрид --- ### Open questions -- как обеспечить воспроизводимость -- как делать перерасчет -- как хранить версии +- где хранить дополнительные derived features +- как делать массовый перерасчет +- как versioning расширенных features отражать в storage --- ### Status -open +partially resolved --- @@ -93,68 +94,70 @@ open Система предполагает прогноз: -- как тренировка повлияет на состояние +- как тренировка повлияет на состояние -Но пока нет модели. +Но предиктивная модель пока не реализована. --- ### Options -1. Простая эвристика -2. Физиологическая модель -3. ML-подход +1. Простая эвристика +2. Физиологическая модель +3. ML-подход --- ### Open questions -- нужен ли ML вообще -- как валидировать прогноз -- какие метрики использовать +- нужен ли ML вообще +- как валидировать прогноз +- какие метрики использовать --- ### Status -open +open --- -## OD-004: Data sources expansion +## OD-004: Multi-source data strategy ### Context -Сейчас используется Strava. +Сейчас уже используются: -В будущем возможны: +- Strava +- HealthKit -- Garmin -- HealthKit -- HRV -- сон +Следующий уровень сложности: + +- расширение источников +- source priority +- conflict resolution --- ### Options -1. Strava-only -2. Strava + HealthKit recovery layer -3. Multi-source aggregation with explicit source priority +1. Strava + HealthKit как основная схема +2. Добавление новых источников с явным source priority +3. Multi-source aggregation с правилами консолидации --- ### Open questions -- как синхронизировать источники -- что считать источником истины -- как решать конфликты данных +- что считать источником истины для пересекающихся метрик +- как синхронизировать даты и timezone-sensitive данные +- как решать конфликты при расширении источников --- ### Status -open +partially resolved --- @@ -162,35 +165,35 @@ open ### Context -Ride briefing — ключевой output системы. +Ride briefing — важный output системы. -Но не определено: +Базовая readiness layer уже реализована, но не зафиксировано: -- формат -- уровень детализации -- структура +- окончательный формат user-facing briefing +- уровень детализации +- mapping from readiness to recommendation --- ### Options -1. Короткий текст -2. Структурированный блок -3. Полноценный план тренировки +1. Короткий structured block +2. Rule-based briefing with explanation templates +3. Более подробный plan layer поверх deterministic core --- ### Open questions -- насколько детализирован должен быть вывод -- нужен ли адаптивный формат -- как сохранять детерминированность +- насколько детализирован должен быть вывод +- какие ограничения выводить первыми +- как не потерять детерминированность --- ### Status -open +open --- @@ -198,29 +201,29 @@ open ### Context -Визуализация пока отсутствует. +Visualization layer пока не является основной частью реализованного core. --- ### Options -1. Web dashboard -2. Mobile-first (iOS) -3. Минималистичный интерфейс +1. Web dashboard +2. Mobile-first +3. Минималистичный readiness-first интерфейс --- ### Open questions -- что показывать в первую очередь -- какие метрики критичны -- как не превратить систему в "dashboard без смысла" +- что показывать в первую очередь +- какие метрики критичны +- как не превратить систему в dashboard без решения --- ### Status -open +open --- @@ -228,33 +231,40 @@ open ### Context -Появятся производные данные: +Уже существуют derived данные: + +- normalized health tables +- `health_recovery_daily` +- `load_state_daily_v2` +- `readiness_daily` -- features -- метрики -- readiness +Но еще не зафиксирована общая стратегия: + +- что хранить постоянно +- что пересчитывать +- как versioning делать единообразно --- ### Options -1. хранить все -2. пересчитывать на лету -3. гибрид +1. Хранить все derived layers +2. Пересчитывать часть state on demand +3. Гибрид --- ### Open questions -- баланс storage vs compute -- как обеспечивать консистентность -- как делать versioning +- баланс storage vs compute +- как обеспечивать консистентность между слоями +- как оформлять versioning derived tables --- ### Status -open +open --- @@ -262,47 +272,43 @@ open ### Context -AI временно удален из core. +AI остается вне deterministic core. + +Возможный будущий use-case: -В будущем возможен возврат. +- explainability +- documentation +- structured summaries --- ### Options -1. только RAG -2. AI как explainability слой -3. ограниченные AI endpoints +1. Только RAG +2. AI как explainability слой +3. Ограниченные AI endpoints вне core --- ### Open questions -- где проходит граница допустимого -- как не нарушить deterministic core -- какие use-cases действительно полезны +- где проходит граница допустимого +- как не нарушить deterministic core +- какие use-cases действительно полезны --- ### Status -deferred - ---- - -## How to use this document - -- добавлять новые вопросы по мере появления -- не удалять старые, а переводить в ARCHITECTURE_DECISIONS -- регулярно пересматривать +deferred --- ## Lifecycle -open → decision → ADR +`open -> decision -> ADR` После принятия решения: -- перенос в ARCHITECTURE_DECISIONS.md -- обновление архитектуры при необходимости +- перенос в `ARCHITECTURE_DECISIONS.md` +- обновление архитектуры и data/model docs diff --git a/docs/data/DATA_MODEL.md b/docs/data/DATA_MODEL.md index 8aa12f6..37528d1 100644 --- a/docs/data/DATA_MODEL.md +++ b/docs/data/DATA_MODEL.md @@ -2,13 +2,13 @@ ## 1. Purpose -Этот документ описывает модель данных системы Human Engine. +Этот документ описывает текущую модель данных Human Engine. Цель: -- зафиксировать структуру хранения данных -- обеспечить воспроизводимость расчетов -- разделить raw и derived данные +- зафиксировать структуру хранения +- обеспечить воспроизводимость расчетов +- разделить raw, normalized и derived данные --- @@ -16,26 +16,29 @@ Модель данных должна: -- сохранять raw данные без изменений -- позволять повторный расчет метрик -- быть прозрачной -- быть расширяемой +- сохранять raw payloads без изменения +- позволять повторный расчет derived state +- быть прозрачной +- явно отделять implemented и planned layers --- ## 3. Data layers -Система разделяет данные на уровни: - ### 3.1 Raw data Необработанные данные из внешних источников. +Примеры: + +- `strava_activity_raw` +- `healthkit_ingest_raw` + Свойства: -- не изменяются -- сохраняются полностью -- являются источником истины +- не изменяются +- сохраняются полностью +- являются источником воспроизводимости --- @@ -45,192 +48,281 @@ Содержат: -- события webhook -- jobs -- статусы обработки +- webhook события +- jobs +- статусы обработки --- -### 3.3 Derived data (future) +### 3.3 Normalized data -Производные данные: +Нормализованные таблицы, полученные из raw payloads. -- features -- метрики -- readiness +Примеры: -Могут пересчитываться. +- `health_sleep_night` +- `health_resting_hr_daily` +- `health_hrv_sample` +- `health_weight_measurement` --- -## 4. Core entities +### 3.4 Derived daily state ---- +Производные таблицы, которые могут пересчитываться. -### 4.1 strava_webhook_event +Примеры: -События от Strava. +- `daily_training_load` +- `health_recovery_daily` +- `load_state_daily_v2` +- `readiness_daily` + +--- -Содержит: +## 4. Core ingestion entities -- тип события -- object_id -- время -- payload +### 4.1 `strava_webhook_event` + +События от Strava. Назначение: -- триггер ingestion +- триггер ingestion --- -### 4.2 strava_activity_ingest_job +### 4.2 `strava_activity_ingest_job` Задача на загрузку активности. -Содержит: - -- ссылка на webhook_event -- статус -- попытки -- ошибки - Назначение: -- управление асинхронной загрузкой +- управление асинхронной загрузкой --- -### 4.3 strava_activity_raw +### 4.3 `strava_activity_raw` + +Сырые данные активности из Strava API. + +Назначение: + +- источник для downstream расчетов -Сырые данные активности. +--- -Содержит: +### 4.4 `healthkit_ingest_raw` -- полный ответ Strava API -- метаданные активности +Сырой payload HealthKit sync. Назначение: -- источник для всех расчетов +- воспроизводимость HealthKit ingest +- исходный источник для нормализации health data --- -### 4.4 strava_activity_stream_raw (future) +## 5. Current normalized and derived entities -Streams данных: +### 5.1 `daily_training_load` -- power -- heart rate -- cadence -- speed +Дневная агрегированная нагрузка из тренировок. Назначение: -- детальный анализ +- вход для `load_state_daily_v2` + +Ключевое поле: + +- `tss` --- -## 5. Derived entities (planned) +### 5.2 `health_sleep_night` + +Нормализованная запись о сне. + +Назначение: + +- хранить sleep night по `wake_date` +- быть входом для recovery aggregation + +Ключевые поля: + +- `wake_date` +- `sleep_start_at` +- `sleep_end_at` +- `total_sleep_minutes` +- `awake_minutes` +- `core_minutes` +- `rem_minutes` +- `deep_minutes` +- `in_bed_minutes` --- -### 5.1 activity_metrics +### 5.3 `health_resting_hr_daily` + +Нормализованный resting HR по дате. -Метрики активности: +Ключевые поля: -- NP -- IF -- TSS +- `date` +- `bpm` --- -### 5.2 daily_training_load +### 5.4 `health_hrv_sample` -Агрегированные данные: +Нормализованные HRV samples. -- TSS за день -- суммарная нагрузка +Назначение: + +- хранить sample-level HRV +- поддерживать day-level aggregation через median + +Ключевые поля: + +- `sample_start_at` +- `value_ms` --- -### 5.3 daily_fitness_state +### 5.5 `health_weight_measurement` + +Нормализованные измерения веса. -Legacy-состояние (V1 baseline): +Ключевые поля: -- CTL -- ATL -- TSB +- `measured_at` +- `kilograms` --- -### 5.4 health_recovery_daily +### 5.6 `health_recovery_daily` + +Дневная recovery-агрегация из health tables. + +Источник: + +- `health_sleep_night` +- `health_resting_hr_daily` +- `health_hrv_sample` +- `health_weight_measurement` -Дневная recovery-агрегация: +Ключевые поля: -- sleep_minutes -- resting_hr_bpm -- hrv_daily_median_ms -- weight_kg -- recovery_score_simple +- `sleep_minutes` +- `awake_minutes` +- `rem_minutes` +- `deep_minutes` +- `resting_hr_bpm` +- `hrv_daily_median_ms` +- `weight_kg` +- `recovery_score_simple` --- -### 5.5 load_state_daily_v2 +### 5.7 `load_state_daily_v2` -Load model v2: +Load model v2. -- tss -- load_input_nonlinear -- fitness -- fatigue_fast -- fatigue_slow -- fatigue_total -- freshness +Источник: + +- `daily_training_load` + +Свойства расчета: + +- рассчитывается по непрерывной календарной оси +- в дни без тренировок используется `tss = 0` +- текущий `load_input_nonlinear` фактически равен линейному input по TSS + +Ключевые поля: + +- `tss` +- `load_input_nonlinear` +- `fitness` +- `fatigue_fast` +- `fatigue_slow` +- `fatigue_total` +- `freshness` +- `version` --- -### 5.6 readiness_daily +### 5.8 `readiness_daily` + +Отдельный readiness layer. -Оценка готовности: +Источник: -- freshness -- recovery_score -- readiness_score -- good_day_probability -- explanation_json +- `load_state_daily_v2` +- `health_recovery_daily` + +Ключевые поля: + +- `freshness` +- `recovery_score_simple` +- `readiness_score_raw` +- `readiness_score` +- `good_day_probability` +- `status_text` +- `explanation_json` +- `version` --- ## 6. Relationships -Связи: +Текущие связи: -- webhook_event → ingest_job (1:N) -- ingest_job → activity_raw (1:1) -- activity_raw → activity_metrics (1:1) -- activity_metrics → daily_training_load (N:1) -- daily_training_load → load_state_daily_v2 (N:1) -- health_recovery_daily → readiness_daily (N:1) -- load_state_daily_v2 → readiness_daily (N:1) +- `strava_webhook_event -> strava_activity_ingest_job` (1:N) +- `strava_activity_ingest_job -> strava_activity_raw` (1:1 / 1:N depending on retries) +- `strava_activity_raw -> daily_training_load` (N:1 through processing layer) +- `healthkit_ingest_raw -> health_sleep_night` (1:N) +- `healthkit_ingest_raw -> health_resting_hr_daily` (1:N) +- `healthkit_ingest_raw -> health_hrv_sample` (1:N) +- `healthkit_ingest_raw -> health_weight_measurement` (1:N) +- `daily_training_load -> load_state_daily_v2` (N:1) +- `health_recovery_daily -> readiness_daily` (N:1) +- `load_state_daily_v2 -> readiness_daily` (N:1) --- -## 7. Data flow +## 7. Current data flow + +### 7.1 Health contour -Webhook event +```text +HealthKit ↓ -Ingest job +healthkit_ingest_raw ↓ -Raw activity +health_sleep_night / health_resting_hr_daily / health_hrv_sample / health_weight_measurement +↓ +health_recovery_daily +``` + +### 7.2 Load contour + +```text +Strava ↓ -Metrics +strava raw / processing ↓ -Daily aggregates +daily_training_load ↓ -Load state + Recovery state +load_state_daily_v2 +``` + +### 7.3 Readiness contour + +```text +load_state_daily_v2 + health_recovery_daily ↓ -Readiness +readiness_daily +``` --- @@ -238,9 +330,9 @@ Readiness Для обеспечения воспроизводимости: -- raw данные не изменяются -- все derived данные можно пересчитать -- формулы зафиксированы в METRICS.md +- raw данные не изменяются +- normalized и derived таблицы можно пересчитать +- readiness считается из сохраненных load и recovery layers --- @@ -248,31 +340,28 @@ Readiness ### Raw data -- хранить всегда -- не удалять - ---- - -### Derived data +- хранить всегда +- не удалять без отдельного решения -Возможные стратегии: +### Normalized and derived data -1. хранить полностью -2. пересчитывать -3. гибрид +- хранить как materialized daily state +- поддерживать пересчет из upstream layers -(решение пока открыто) +Общая стратегия versioning и retention еще остается открытым вопросом. --- -## 10. Versioning (future) +## 10. Versioning + +Текущие versioned entities: -При изменении логики: +- `load_state_daily_v2` +- `readiness_daily` -- версии метрик -- версии моделей +Требование: -Исторические данные не должны ломаться. +- при изменении формул не ломать исторические расчеты --- @@ -280,16 +369,16 @@ Readiness Нельзя: -- изменять raw данные -- терять данные ingestion -- хранить только агрегаты без исходных данных +- изменять raw данные +- терять ingestion history +- подменять derived state несохраняемыми эвристиками --- ## 12. Open questions -- где хранить features -- как делать пересчет -- как организовать versioning +- где хранить расширенные features +- как делать массовый перерасчет +- как единообразно организовать versioning -(см. OPEN_DECISIONS.md) +См. `docs/architecture/OPEN_DECISIONS.md`. diff --git a/docs/models/METRICS.md b/docs/models/METRICS.md index d8ffaed..c95a4c9 100644 --- a/docs/models/METRICS.md +++ b/docs/models/METRICS.md @@ -2,13 +2,13 @@ ## 1. Purpose -Этот документ описывает базовые метрики Human Engine. +Этот документ описывает текущие базовые метрики Human Engine. Цель: -- зафиксировать формулы -- обеспечить воспроизводимость -- сделать логику прозрачной +- зафиксировать формулы +- обеспечить воспроизводимость +- синхронизировать документацию с текущим backend baseline --- @@ -16,23 +16,19 @@ Все метрики должны быть: -- deterministic -- объяснимыми -- воспроизводимыми +- deterministic +- объяснимыми +- воспроизводимыми Нельзя: -- использовать скрытые формулы -- менять определения без фиксации +- использовать скрытые формулы +- менять определения без фиксации --- ## 3. Activity-level metrics -Метрики, рассчитываемые для одной тренировки. - ---- - ### 3.1 Duration Время тренировки. @@ -47,76 +43,85 @@ ### 3.3 Normalized Power (NP) -Оценка "эффективной" мощности с учетом вариативности нагрузки. +Оценка физиологической стоимости переменной нагрузки. --- ### 3.4 Intensity Factor (IF) -IF = NP / FTP +`IF = NP / FTP` --- ### 3.5 Training Stress Score (TSS) -TSS = (Duration × NP × IF) / (FTP × 3600) × 100 +`TSS = (Duration × NP × IF) / (FTP × 3600) × 100` --- ## 4. Daily metrics -Агрегируются на уровне дня. - ---- - ### 4.1 Daily Training Load Сумма TSS за день. --- -### 4.2 Nonlinear Load Input +### 4.2 Load Input + +Для `load_state_daily_v2` дневная нагрузка подается через поле `load_input_nonlinear`. -Для model v2 дневная нагрузка подается в load model через нелинейную функцию. +Текущее состояние backend: -Формула: +- функция называется `load_input_nonlinear` +- фактическая baseline-реализация использует линейный input: ```text -load_input = A * (1 - exp(-B * TSS)) +load_input_nonlinear = TSS ``` +Нелинейная трансформация остается возможным следующим шагом, но сейчас в коде не применяется. + --- ### 4.3 Fitness Долгосрочная адаптационная компонента. -Экспоненциальное скользящее среднее: +Экспоненциальное обновление: -- `tau_fitness ≈ 40` +```text +fitness[d] = fitness[d-1] + (load_input[d] - fitness[d-1]) / 40 +``` --- ### 4.4 Fatigue Fast -Короткая компонента усталости. +Быстрая компонента усталости. -- `tau_fatigue_fast ≈ 2` +```text +fatigue_fast[d] = fatigue_fast[d-1] + (load_input[d] - fatigue_fast[d-1]) / 4 +``` --- ### 4.5 Fatigue Slow -Средняя по длительности компонента усталости. +Более медленная компонента усталости. -- `tau_fatigue_slow ≈ 7` +```text +fatigue_slow[d] = fatigue_slow[d-1] + (load_input[d] - fatigue_slow[d-1]) / 9 +``` --- ### 4.6 Fatigue Total +В текущей model v2 это не сумма, а взвешенная смесь: + ```text -fatigue_total = fatigue_fast + fatigue_slow +fatigue_total = 0.65 * fatigue_fast + 0.35 * fatigue_slow ``` --- @@ -129,74 +134,116 @@ freshness = fitness - fatigue_total --- -## 5. Derived metrics +### 4.8 Calendar continuity ---- - -### 5.1 Fatigue +`load_state_daily_v2` считается по непрерывной календарной оси. -В model v2 представлена как: +Это означает: -- `fatigue_fast` -- `fatigue_slow` -- `fatigue_total` +- в модели присутствуют и тренировочные, и нетренировочные дни +- в дни без тренировки используется `tss = 0` --- -### 5.2 Fitness +## 5. Recovery metrics + +### 5.1 Recovery Daily Inputs + +Текущий recovery layer использует: -`fitness` остается отдельной сглаженной компонентой load state. +- `sleep_minutes` +- `resting_hr_bpm` +- `hrv_daily_median_ms` +- `weight_kg` --- -### 5.3 Form +### 5.2 Recovery Score Simple -В качестве основной прикладной метрики model v2 использует `freshness`. +`recovery_score_simple` — baseline heuristic score из `health_recovery_daily`. -Legacy-метрики `CTL / ATL / TSB` могут использоваться только как reference baseline или для обратной совместимости. +Свойства: + +- диапазон `0..100` +- считается из сна, resting HR и HRV +- пока не использует индивидуальные baseline deviations --- -## 6. Readiness (model v2) +## 6. Readiness (model v2 baseline) Readiness — ключевая метрика системы. -В model v2 readiness: +В current backend readiness: - не равна `freshness` -- рассчитывается из `load_state + recovery_state` -- может сопровождаться `good_day_probability` +- считается из `load_state + recovery_state` +- хранится в `readiness_daily` + +### 6.1 Freshness normalization + +Перед объединением с recovery-контуром `freshness` переводится в грубую шкалу `0..100`: + +```text +freshness_norm = clamp(50 + freshness, 0, 100) +``` -Базовая формула: +### 6.2 Baseline formula ```text -readiness_score_raw = - w1 * freshness + - w2 * recovery_score_simple +readiness_score_raw = 0.6 * freshness_norm + 0.4 * recovery_score_simple ``` -Probability layer: +Fallback behavior: + +- если нет recovery, readiness опирается на `freshness_norm` +- если нет load, readiness опирается на `recovery_score_simple` + +### 6.3 Readiness score ```text -good_day_probability = sigmoid(readiness_score_raw) +readiness_score = clamp(round(readiness_score_raw, 1), 0, 100) ``` +### 6.4 Good Day Probability + +Текущий probability layer в backend: + +```text +good_day_probability = readiness_score / 100 +``` + +Это baseline-мэппинг, а не откалиброванная вероятностная модель. + +--- + +## 7. Status mapping + +Текущий `status_text` определяется по `readiness_score`: + +- `0..24` -> `Высокая усталость` +- `25..44` -> `Нагрузка` +- `45..64` -> `Нормальная готовность` +- `65..84` -> `Хорошая готовность` +- `85..100` -> `Очень свежий` + --- -## 7. Constraints +## 8. Constraints Метрики должны: -- быть пересчитываемыми -- использовать raw данные -- не зависеть от AI +- быть пересчитываемыми +- использовать raw и normalized upstream data +- не зависеть от AI --- -## 8. Future extensions +## 9. Future extensions Планируется добавить: +- нелинейную трансформацию load input - `sleep_score_simple` - `hrv_dev` - `rhr_dev` @@ -204,13 +251,14 @@ good_day_probability = sigmoid(readiness_score_raw) Но: -- только без потери прозрачности и versioning +- только с явным versioning +- без потери прозрачности --- -## 9. Versioning +## 10. Versioning При изменении формул: -- фиксировать версию -- не менять исторические расчеты +- фиксировать версию +- не менять исторические расчеты молча diff --git a/docs/models/READINESS_MODEL.md b/docs/models/READINESS_MODEL.md index 3b5d95e..0292729 100644 --- a/docs/models/READINESS_MODEL.md +++ b/docs/models/READINESS_MODEL.md @@ -2,14 +2,13 @@ ## 1. Purpose -Этот документ описывает модель оценки готовности (readiness) -в системе Human Engine. +Этот документ описывает текущую readiness model в Human Engine. Цель: -- определить, насколько спортсмен готов к нагрузке -- обеспечить детерминированное принятие решений -- сделать модель прозрачной и объяснимой +- определить, насколько спортсмен готов к нагрузке +- зафиксировать текущую baseline-логику backend +- сделать модель прозрачной и объяснимой --- @@ -17,32 +16,32 @@ Модель должна быть: -- deterministic -- простой -- объяснимой -- воспроизводимой +- deterministic +- простой +- объяснимой +- воспроизводимой Нельзя: -- использовать скрытую логику -- использовать LLM -- усложнять без необходимости +- использовать скрытую логику +- использовать LLM +- подменять readiness только load-only proxy --- ## 3. Inputs -Модель использует следующие метрики: +Текущая model v2 использует: -- `freshness` из `load_state_daily_v2` -- `recovery_score_simple` из `health_recovery_daily` -- recent training load +- `freshness` из `load_state_daily_v2` +- `recovery_score_simple` из `health_recovery_daily` -Дополнительно в расширенной версии могут использоваться: +Важно: -- `sleep_score_simple` -- `hrv_dev` -- `rhr_dev` +- readiness больше не равен freshness +- recovery contour не заменяет fatigue, а дополняет load contour + +Дополнительные сигналы, такие как `sleep_score_simple`, `hrv_dev`, `rhr_dev`, пока не являются отдельными входами readiness formula. --- @@ -50,202 +49,166 @@ Основная идея: -> readiness определяется не только нагрузкой, а сочетанием load state и recovery state +> readiness определяется сочетанием load state и recovery state -Базовая формула v2: +### 4.1 Load contour -```text -readiness_score_raw = - w1 * freshness + - w2 * recovery_score_simple -``` +Load contour формируется в `load_state_daily_v2`: + +- `fitness` +- `fatigue_fast` +- `fatigue_slow` +- `fatigue_total` +- `freshness` Где: +- `fatigue_total = 0.65 * fatigue_fast + 0.35 * fatigue_slow` - `freshness = fitness - fatigue_total` -- `fatigue_total = fatigue_fast + fatigue_slow` - -Расширенная формула v2+: - -```text -readiness_score_raw = - w1 * freshness - + w2 * recovery_score - + w3 * hrv_dev - - w4 * rhr_dev - + w5 * sleep_score -``` - ---- - -## 5. Readiness zones - -Модель делит состояние на зоны на основе `readiness_score` и/или `good_day_probability`. -### 5.1 Low readiness +### 4.2 Recovery contour -Условие: +Recovery contour формируется в `health_recovery_daily` из: -- низкий `readiness_score` -- низкая `good_day_probability` +- сна +- HRV +- resting HR +- веса -Интерпретация: +Текущий прикладной выход этого слоя: -- высокая усталость -- риск перегрузки +- `recovery_score_simple` -Рекомендация: +### 4.3 Baseline formula v2 -- отдых или легкая тренировка - ---- - -### 5.2 Moderate readiness - -Условие: - -- средний `readiness_score` -- умеренная `good_day_probability` - -Интерпретация: - -- нормальное состояние - -Рекомендация: - -- умеренная нагрузка - ---- +Сначала `freshness` нормализуется: -### 5.3 High readiness - -Условие: - -- высокий `readiness_score` -- высокая `good_day_probability` - -Интерпретация: - -- хорошее восстановление - -Рекомендация: - -- интенсивная тренировка - ---- +```text +freshness_norm = clamp(50 + freshness, 0, 100) +``` -## 6. Adjustments +Затем readiness считается так: -Базовая модель уже включает recovery-контур и может быть расширена: +```text +readiness_score_raw = 0.6 * freshness_norm + 0.4 * recovery_score_simple +``` ---- +Fallback behavior: -### 6.1 Recent load spike +- если нет recovery score, используется `freshness_norm` +- если нет load score, используется `recovery_score_simple` -Если: +### 4.4 Final outputs -- резкий рост нагрузки за последние 2–3 дня +```text +readiness_score = clamp(round(readiness_score_raw, 1), 0, 100) +good_day_probability = readiness_score / 100 +``` -→ снижать readiness +`good_day_probability` пока является baseline probability-like mapping, а не откалиброванной статистической вероятностью. --- -### 6.2 Consecutive training days +## 5. Status zones -Если: +Текущие статусные зоны backend: -- несколько дней подряд без отдыха +### 5.1 Высокая усталость -→ снижать readiness +- `readiness_score <= 24` ---- +### 5.2 Нагрузка -### 6.3 Recovery gap +- `25 <= readiness_score <= 44` -Если: +### 5.3 Нормальная готовность -- длительный отдых +- `45 <= readiness_score <= 64` -→ повышать readiness +### 5.4 Хорошая готовность ---- +- `65 <= readiness_score <= 84` -### 6.4 Recovery signals +### 5.5 Очень свежий -Если: - -- sleep ниже baseline -- HRV ниже baseline -- resting HR выше baseline - -→ снижать readiness даже при приемлемом `freshness` +- `readiness_score >= 85` --- -## 7. Output +## 6. Output -Результат модели: +Результат текущей модели: +- `readiness_score_raw` - `readiness_score` - `good_day_probability` -- readiness zone -- вход для тренировочной рекомендации +- `status_text` +- `explanation_json` + +`readiness_daily` является отдельным storage layer для этих outputs. --- -## 8. Determinism requirement +## 7. Explanation payload -При одинаковых входных данных: +Текущий `explanation_json` хранит: -- результат должен быть одинаковым -- не допускается случайность +- `freshness` +- `freshness_norm` +- `recovery_score_simple` +- веса формулы +- строку формулы + +Это нужно для explainability и отладки. --- -## 9. Limitations +## 8. Limitations Текущая модель: -- использует простой recovery score как текущий recovery-контур -- пока не фиксирует окончательную калибровку весов и зон +- использует простой recovery score как baseline recovery contour +- пока не использует индивидуальные baseline deviations +- пока не имеет отдельной probability calibration - требует дальнейшей верификации на реальных данных --- -## 10. Future extensions +## 9. Planned extensions Планируется: -- калибровка весов `freshness` и `recovery_score_simple` +- калибровка весов `freshness_norm` и `recovery_score_simple` - явные `sleep_score_simple`, `hrv_dev`, `rhr_dev` -- уточнение зон и probability thresholds +- уточнение interpretation layer для `good_day_probability` +- уточнение decision mapping Но: -- без потери прозрачности +- без потери прозрачности +- с явным versioning --- -## 11. Debugging model - -Если результат кажется неверным: +## 10. Debugging model -проверять: +Если результат кажется неверным, проверять: -1. входные данные -2. расчет `load_state_daily_v2` -3. расчет `health_recovery_daily` -4. формирование `readiness_score_raw` -5. примененные правила маппинга в zone / probability +1. входные данные HealthKit и training load +2. расчет `health_recovery_daily` +3. расчет `load_state_daily_v2` +4. нормализацию `freshness` +5. формирование `readiness_score_raw` +6. status mapping и probability mapping --- -## 12. Design constraint +## 11. Design constraint Любое усложнение модели должно: -- улучшать объяснимость -- не нарушать deterministic поведение -- быть обоснованным +- улучшать объяснимость +- не нарушать deterministic поведение +- быть отделено от planned layers -Иначе — не добавлять. +Иначе его не нужно добавлять. diff --git a/docs/models/RIDE_BRIEFING.md b/docs/models/RIDE_BRIEFING.md index 7b15fde..3417c2b 100644 --- a/docs/models/RIDE_BRIEFING.md +++ b/docs/models/RIDE_BRIEFING.md @@ -2,15 +2,15 @@ ## 1. Purpose -Этот документ описывает, как Human Engine формирует ride briefing. +Этот документ описывает, как Human Engine должен формировать ride briefing поверх текущей readiness layer. Ride briefing — это пользовательский вывод системы перед тренировкой. Цель: -- перевести состояние readiness в понятную рекомендацию -- сделать вывод стабильным и детерминированным -- обеспечить прозрачную связь между метриками и рекомендацией +- перевести readiness state в понятную рекомендацию +- сохранить детерминированность +- опираться на уже реализованные backend layers --- @@ -27,45 +27,52 @@ Ride briefing должен быть: - генерировать briefing свободным LLM-текстом - использовать скрытую логику -- давать рекомендации, которые нельзя объяснить через входные метрики +- выдавать рекомендации, которые нельзя объяснить через readiness inputs --- -## 3. Inputs +## 3. Current backend basis -Ride briefing строится на основе: +На текущем этапе ride briefing должен опираться на уже реализованные слои: -- readiness zone -- readiness score -- good_day_probability -- freshness -- recovery signals -- recent training load -- consecutive training days +- `health_recovery_daily` +- `load_state_daily_v2` +- `readiness_daily` -Минимально достаточный вход для MVP: +Это важно, потому что: -- `readiness_daily` -- `load_state_daily_v2` -- recovery summary из `health_recovery_daily` +- readiness больше не равен freshness +- briefing должен учитывать двухконтурную модель `load + recovery` +- вероятность хорошего тренировочного дня уже выделена в отдельный слой --- -## 4. Output structure +## 4. Inputs -Ride briefing должен содержать: +Практический вход для ride briefing: + +- `readiness_score` +- `good_day_probability` +- `status_text` +- `freshness` +- `recovery_score_simple` +- `explanation_json` -### 4.1 Readiness status +Дополнительные rule-based inputs могут использоваться позже, но их нужно отделять от уже реализованного backend baseline. -Краткий статус состояния: +--- -- low readiness -- moderate readiness -- high readiness +## 5. Output structure + +Ride briefing должен содержать: + +### 5.1 Readiness status + +Краткий статус состояния, основанный на `status_text`. --- -### 4.2 Load recommendation +### 5.2 Load recommendation Рекомендация по уровню нагрузки: @@ -76,67 +83,48 @@ Ride briefing должен содержать: --- -### 4.3 Short explanation +### 5.3 Short explanation + +Краткое объяснение, связывающее вывод с двумя контурами: -Краткое объяснение причины рекомендации. +- load contour +- recovery contour -Примеры: +Примеры формулировок: -- высокая накопленная усталость при слабом recovery signal -- сбалансированное состояние load и recovery -- хорошее восстановление после снижения нагрузки +- высокая усталость по load contour при слабом recovery signal +- нормальная готовность: load и recovery не конфликтуют +- хорошая готовность: recovery поддерживает благоприятный load state --- -### 4.4 Optional constraints +### 5.4 Optional constraints Если нужно, briefing может содержать ограничения: - избегать высокой интенсивности - ограничить длительность -- не выполнять вторую тяжелую тренировку подряд - ---- - -## 5. Mapping rules - -### 5.1 Low readiness - -Если readiness = low: +- не делать второй тяжелый день подряд -- recommendation = rest или easy -- explanation = сочетание freshness и recovery указывает на низкую готовность +Эти ограничения должны появляться только как явные rule-based additions. --- -### 5.2 Moderate readiness +## 6. Mapping rules -Если readiness = moderate: +Текущее требование к mapping: -- recommendation = moderate -- explanation = load state и recovery state допускают обычную нагрузку +- опираться на `readiness_score` и `good_day_probability` +- не сводить решение только к `freshness` +- сохранять объяснимую связь с recovery layer ---- - -### 5.3 High readiness +Пример минимального baseline mapping: -Если readiness = high: +- низкий readiness -> `rest` или `easy` +- средний readiness -> `moderate` +- высокий readiness -> `hard` -- recommendation = hard или key workout -- explanation = load state стабилен, recovery поддерживает высокую готовность - ---- - -## 6. Rule-based adjustments - -Итоговая рекомендация может быть понижена, если: - -- был резкий всплеск нагрузки -- несколько дней подряд были тренировки -- recovery signals ухудшились -- наблюдается накопление `fatigue_total` - -Итоговая рекомендация может быть повышена только в пределах заранее определенных правил. +Точный mapping еще требует продуктовой фиксации. --- @@ -146,12 +134,11 @@ Ride briefing должен содержать: Пример структуры: -- Status: Moderate readiness -- Recommendation: Moderate load -- Reason: Balanced load state with adequate recovery signal +- Status: `Нормальная готовность` +- Recommendation: `Moderate load` +- Reason: `Load contour stable, recovery score supports normal training` -Для пользовательского интерфейса могут существовать разные представления, -но логическая структура должна оставаться одинаковой. +Для UI могут существовать разные представления, но логическая структура должна оставаться одинаковой. --- @@ -161,11 +148,11 @@ Ride briefing должен содержать: Это означает: -- одинаковая категория readiness -- одинаковая рекомендация +- одинаковый статус +- одинаковая категория нагрузки - одинаковое объяснение по шаблону -Допускается только шаблонная вариативность, если она не меняет смысл и управляется явными правилами. +Допускается только шаблонная вариативность без изменения логики. --- @@ -174,11 +161,9 @@ Ride briefing должен содержать: На текущем этапе ride briefing не включает: - свободный coaching text -- психологическую мотивацию - разговорный AI-стиль -- персонализированные длинные советы - -Это может быть отдельным дополнительным слоем позже, но не частью core логики. +- скрытые эвристики вне readiness layer +- длинные персонализированные советы --- @@ -191,7 +176,7 @@ Ride briefing должен содержать: - ограничения по зонам мощности или пульса - дополнительный explainability layer -Но только после стабилизации базовой модели. +Но только после фиксации decision mapping поверх текущего readiness baseline. --- @@ -199,19 +184,19 @@ Ride briefing должен содержать: Если ride briefing кажется неверным, проверять: -1. readiness inputs -2. readiness zone -3. applied adjustments -4. final mapping rule +1. `health_recovery_daily` +2. `load_state_daily_v2` +3. `readiness_daily` +4. mapping rule from readiness to recommendation --- ## 12. Design constraint -Ride briefing является частью deterministic core. +Ride briefing должен оставаться частью deterministic decision layer. Любое изменение должно: - сохранять объяснимость - сохранять воспроизводимость -- не превращать вывод в black box +- опираться на уже реализованные backend layers diff --git a/docs/models/current-metrics-methodology.md b/docs/models/current-metrics-methodology.md index 769fb92..7a68aa0 100644 --- a/docs/models/current-metrics-methodology.md +++ b/docs/models/current-metrics-methodology.md @@ -2,33 +2,34 @@ **Version:** v2 baseline **Status:** active baseline -**Last updated:** 2026-04-08 +**Last updated:** 2026-04-09 # Human Engine — методика расчета текущих метрик ## Назначение -Этот документ кратко описывает, как в текущей версии Human Engine рассчитываются основные метрики тренировки и метрики состояния. +Этот документ описывает, как в текущей backend-реализации Human Engine рассчитываются основные метрики тренировки и состояния. -Документ фиксирует **текущий базовый подход для model v2**. Если реализация изменится, методику нужно обновить вместе с кодом. +Документ фиксирует **реально реализованный baseline для model v2**. Если код меняется, методика должна обновляться вместе с ним. ## Область действия Сейчас в Human Engine используются две группы метрик: -1. **Метрики тренировки** +1. Метрики тренировки - длительность - средняя мощность - - нормализованная мощность / эквивалентная интенсивностная мощность + - нормализованная мощность - IF - TSS -2. **Метрики состояния** +2. Метрики состояния - Fitness - Fatigue Fast - Fatigue Slow - Fatigue Total - Freshness + - Recovery Score Simple - Readiness - Good Day Probability - текстовый статус @@ -37,122 +38,73 @@ ## 1. Входные данные -Для расчета метрик используются данные обработанной активности. +Для тренировочных метрик используются данные активности и агрегированной нагрузки. -Минимально нужны: +Для recovery- и readiness-метрик используются: -- `activity_id` -- дата и время тренировки -- длительность тренировки -- мощность: либо временной ряд, либо агрегированные метрики источника -- пульс: если доступен -- актуальное значение `FTP`, используемое Human Engine как базовая пороговая мощность +- `daily_training_load` +- `health_sleep_night` +- `health_resting_hr_daily` +- `health_hrv_sample` +- `health_weight_measurement` ### Принцип выбора FTP -Для внутренних расчетов Human Engine используется **текущее значение FTP, принятое внутри системы**. +Для внутренних расчетов Human Engine используется текущее значение FTP, принятое внутри системы. -Важно: - -- если в Garmin и в Human Engine заданы разные значения FTP, то `IF` и `TSS` будут системно различаться; -- для верификации с Garmin нужно сравнивать расчеты при одинаковом FTP. +Если в источнике и в Human Engine заданы разные значения FTP, то `IF` и `TSS` будут системно различаться. --- ## 2. Метрики тренировки -### 2.1. Длительность - -Длительность тренировки хранится в секундах. +### 2.1 Длительность -Обозначения: +Длительность тренировки хранится в секундах: ```text duration_sec = длительность в секундах duration_hr = duration_sec / 3600 ``` -Для отображения длительность может дополнительно переводиться в формат `hh:mm:ss`. - --- -### 2.2. Средняя мощность - -Средняя мощность считается как обычное среднее по мощности за тренировку. - -Формула: +### 2.2 Средняя мощность ```text Avg Power = mean(power_t) ``` -где `power_t` — мощность в момент времени `t`. - -Если источник уже отдает готовую агрегированную метрику средней мощности, система может использовать это значение напрямую. +Если источник уже отдает агрегированную метрику, система может использовать ее напрямую. --- -### 2.3. Нормализованная мощность +### 2.3 Нормализованная мощность -Нормализованная мощность предназначена для оценки физиологической стоимости переменной нагрузки. - -Базовый индустриальный подход: - -1. строится скользящее среднее мощности по окну 30 секунд; -2. каждое значение возводится в четвертую степень; -3. считается среднее этих значений; -4. берется корень четвертой степени. - -Формула: +Целевой принцип: ```text NP = ( mean( rolling30_power^4 ) )^(1/4) ``` -где: - -- `rolling30_power` — скользящее среднее мощности по окну 30 секунд; -- `NP` — нормализованная мощность в ваттах. - -### Текущее ограничение - -На текущем этапе Human Engine должен стремиться считать `NP` именно так, но итог зависит от доступных данных: +Но итог зависит от доступных данных: -- если есть сырой ряд мощности, расчет должен идти по нему; -- если в систему приходит только агрегированная метрика источника, временно может использоваться агрегированное значение, полученное из источника. - -Поэтому при сравнении с Garmin или другим источником расхождения могут идти не только от формулы, но и от доступного входного ряда. +- если есть сырой ряд мощности, расчет должен идти по нему +- если есть только агрегированное значение источника, временно может использоваться оно --- -### 2.4. Intensity Factor - -`IF` показывает относительную интенсивность тренировки по отношению к `FTP`. - -Формула: +### 2.4 Intensity Factor ```text IF = NP / FTP ``` -где: - -- `NP` — нормализованная мощность; -- `FTP` — пороговая мощность, используемая в Human Engine. - -Интерпретация: - -- `IF < 1.0` — нагрузка ниже FTP; -- `IF = 1.0` — нагрузка на уровне FTP; -- `IF > 1.0` — нагрузка выше FTP. - --- -### 2.5. TSS - -`TSS` отражает суммарную нагрузку тренировки с учетом длительности и интенсивности. +### 2.5 TSS -Эквивалентные формы формулы: +Эквивалентные формы: ```text TSS = duration_hr * IF^2 * 100 @@ -164,286 +116,182 @@ TSS = duration_hr * IF^2 * 100 TSS = (duration_sec * NP * IF) / (FTP * 3600) * 100 ``` -При согласованных входных данных обе записи должны давать одинаковый результат с точностью до округления. - -### Интерпретация - -- рост `IF` увеличивает `TSS` нелинейно; -- при одинаковой длительности небольшое изменение `FTP` может заметно сдвинуть `IF` и `TSS`; -- поэтому при сравнении с внешними системами сначала нужно проверить, одинаков ли `FTP`. - --- ## 3. Метрики состояния -Текущие метрики состояния в Human Engine основаны на load model v2 и recovery-aware readiness: +Текущие метрики состояния опираются на двухконтурную модель: -- нелинейный дневной вход нагрузки -> `load_input` -- долгосрочная нагрузка -> `Fitness` -- быстрая усталость -> `Fatigue Fast` -- средняя по длительности усталость -> `Fatigue Slow` -- итоговая готовность -> `Readiness` +- load contour +- recovery contour -### 3.1. Fitness - -`Fitness` отражает накопленную долговременную тренировочную нагрузку. - -На практике это сглаженный показатель, который растет при регулярных тренировках и снижается медленно. - -Типовая форма обновления: - -```text -Fitness_new = Fitness_prev + (load_input - Fitness_prev) / tau_fitness -``` - -Где `tau_fitness ≈ 40` дней. - -Смысл: - -- отдельная тренировка меняет `Fitness` умеренно; -- регулярная серия тренировок постепенно поднимает базовый уровень. +Их объединение формирует readiness layer. --- -### 3.2. Nonlinear load input - -Перед обновлением состояния дневной `TSS` преобразуется нелинейно: - -```text -load_input = A * (1 - exp(-B * TSS)) -``` - -Смысл: +### 3.1 Load model v2 -- рост нагрузки остается монотонным; -- очень большие значения `TSS` не раздувают модель линейно; -- 200 TSS не интерпретируется как строго 2 x 100 TSS. +`load_state_daily_v2` рассчитывается по непрерывной календарной оси. ---- +Это означает: -### 3.3. Fatigue Fast +- используются все даты в диапазоне +- в дни без тренировок подставляется `tss = 0` -`Fatigue Fast` отражает быстрый отклик усталости на недавнюю нагрузку. +#### 3.1.1 Load input -Типовая форма обновления: +Поле называется `load_input_nonlinear`, но в текущем backend baseline: ```text -FatigueFast_new = FatigueFast_prev + (load_input - FatigueFast_prev) / tau_fatigue_fast +load_input_nonlinear = TSS ``` -Где `tau_fatigue_fast ≈ 2` дня. - ---- +То есть вход сейчас линейный. -### 3.4. Fatigue Slow - -`Fatigue Slow` отражает накопление усталости сериями тренировок. - -Типовая форма обновления: +#### 3.1.2 Fitness ```text -FatigueSlow_new = FatigueSlow_prev + (load_input - FatigueSlow_prev) / tau_fatigue_slow +Fitness_new = Fitness_prev + (load_input - Fitness_prev) / 40 ``` -Где `tau_fatigue_slow ≈ 7` дней. - ---- - -### 3.5. Fatigue Total - -Итоговая усталость в model v2: +#### 3.1.3 Fatigue Fast ```text -FatigueTotal = FatigueFast + FatigueSlow +FatigueFast_new = FatigueFast_prev + (load_input - FatigueFast_prev) / 4 ``` ---- - -### 3.6. Freshness - -`Freshness` считается как разница между долгосрочной и краткосрочной компонентой. - -Формула: +#### 3.1.4 Fatigue Slow ```text -Freshness = Fitness - FatigueTotal +FatigueSlow_new = FatigueSlow_prev + (load_input - FatigueSlow_prev) / 9 ``` -Интерпретация: - -- положительное значение означает более свежее состояние; -- отрицательное значение означает накопленную усталость. - -Это уже не единственный вход в readiness. В model v2 `Freshness` описывает load state, но не заменяет recovery state. - ---- - -### 3.7. Readiness - -`Readiness` — производная прикладная метрика Human Engine, переводящая текущее состояние в шкалу готовности. +#### 3.1.5 Fatigue Total -На текущем этапе `Readiness` опирается на сочетание `Freshness` и простого recovery score. - -Общий принцип: - -- низкий `Freshness` снижает `Readiness`; -- слабый recovery signal также снижает `Readiness`, даже если load state выглядит приемлемо; -- хорошее восстановление поддерживает высокий `Readiness`. - -Базовая формула: +В текущей model v2: ```text -Readiness_raw = - w1 * Freshness + - w2 * RecoveryScoreSimple +FatigueTotal = 0.65 * FatigueFast + 0.35 * FatigueSlow ``` -### Важно - -Точная нормализация `Readiness_raw -> Readiness` должна быть зафиксирована в коде отдельно. В рамках текущей методики важно, что `Readiness` является **производной пользовательской метрикой**, а не прямой физиологической величиной. - ---- +Это важно: в backend это **взвешенная смесь**, а не простая сумма. -### 3.8. Good Day Probability - -Для вероятностного слоя model v2 используется: +#### 3.1.6 Freshness ```text -GoodDayProbability = sigmoid(Readiness_raw) +Freshness = Fitness - FatigueTotal ``` -Эта метрика нужна для: - -- более мягкого маппинга в рекомендации; -- снижения зависимости от жестких порогов; -- дальнейшей калибровки decision layer. +`Freshness` описывает load contour, но не равен readiness. --- -### 3.9. Текстовый статус +### 3.2 Recovery layer -Текстовый статус является интерпретацией `Readiness` для интерфейса. +`health_recovery_daily` агрегируется из: -Примеры статусов: +- sleep +- resting HR +- HRV +- latest known weight -- высокая усталость -- умеренная усталость -- нейтральное состояние -- хорошая готовность +Текущие поля: -Границы между статусами задаются порогами по `Readiness` и могут быть уточнены позже. +- `sleep_minutes` +- `awake_minutes` +- `rem_minutes` +- `deep_minutes` +- `resting_hr_bpm` +- `hrv_daily_median_ms` +- `weight_kg` +- `recovery_score_simple` ---- +#### 3.2.1 Recovery Score Simple -## 4. Что важно понимать про текущую модель +Это baseline heuristic score `0..100`. -### Это простая baseline-модель +Свойства: -Текущая схема нужна как первый рабочий слой. Она: +- строится из сна, resting HR и HRV +- не использует персональный baseline +- является временным baseline-слоем, а не финальной recovery model -- проста для проверки; -- понятна математически; -- позволяет валидировать данные и поток расчета; -- подходит как базовая модель перед дальнейшим усложнением. - -### Ограничения текущего подхода +--- -1. Recovery-контур пока использует простой aggregated score и еще не раскладывается полностью на: - - `sleep_score_simple` - - `hrv_dev` - - `rhr_dev` - - расширенные contextual signals +### 3.3 Readiness -2. `Readiness` пока не является физиологически полной моделью готовности. Это прикладная оценка на базе нагрузки и базового recovery layer. +`Readiness` — отдельный прикладной слой, который объединяет load contour и recovery contour. -3. При отсутствии сырого ряда мощности точность `NP`, `IF` и `TSS` зависит от внешнего источника. +#### 3.3.1 Freshness normalization -4. Сравнение с Garmin корректно только при одинаковом: - - FTP - - duration basis - - power input +Перед объединением: ---- +```text +freshness_norm = clamp(50 + Freshness, 0, 100) +``` -## 5. Интерпретация текущих метрик в системе +#### 3.3.2 Baseline formula -### Тренировка +```text +Readiness_raw = 0.6 * Freshness_norm + 0.4 * RecoveryScoreSimple +``` -После обработки тренировки Human Engine должен сохранять как минимум: +Fallback behavior: -- `duration_sec` -- `avg_power_w` -- `np_w` -- `if_value` -- `tss` -- `avg_hr`, если есть +- если нет recovery score, используется `Freshness_norm` +- если нет load score, используется `RecoveryScoreSimple` -### Состояние после обновления +#### 3.3.3 Final readiness score -После применения новой тренировки система обновляет: +```text +Readiness = clamp(round(Readiness_raw, 1), 0, 100) +``` -- `fitness` -- `fatigue_fast` -- `fatigue_slow` -- `fatigue_total` -- `freshness` -- `readiness` -- `good_day_probability` -- `status_label` +#### 3.3.4 Good Day Probability -Именно эти значения затем используются в интерфейсе и в будущих моделях рекомендаций. +Текущая backend-реализация: ---- +```text +GoodDayProbability = Readiness / 100 +``` -## 6. Что считать источником истины на текущем этапе +Это baseline probability-like mapping, а не откалиброванная статистическая вероятность. -На текущем этапе источником истины для Human Engine является **собственный расчет системы**, если: +#### 3.3.5 Текстовый статус -- зафиксирован используемый `FTP`; -- понятен источник мощности; -- расчеты воспроизводимы. +Текущий status mapping: -Garmin и другие внешние системы используются как **эталон для верификации**, но не как абсолютный источник истины, потому что они частично являются черными ящиками. +- `0..24` -> `Высокая усталость` +- `25..44` -> `Нагрузка` +- `45..64` -> `Нормальная готовность` +- `65..84` -> `Хорошая готовность` +- `85..100` -> `Очень свежий` --- -## 7. Что нужно уточнить в следующих версиях документа +## 4. Что важно понимать про текущую модель -В следующих итерациях методику нужно дополнить: +### Это рабочий baseline -1. точной формулой нормализации `Readiness_raw` и `GoodDayProbability`; -2. конкретными коэффициентами `A` и `B` для `load_input`; -3. правилами расчета `RecoveryScoreSimple` и его компонентов; -4. правилами обработки пропусков и нулей в power stream; -5. политикой выбора и обновления `FTP`; -6. протоколом верификации против Garmin. +Текущая схема нужна как проверяемый и воспроизводимый слой. Она: ---- +- проста для проверки +- понятна математически +- уже реализована в backend +- отделяет load и recovery -## Краткая схема расчета +### Что уже реализовано -```text -power data + FTP - -> Avg Power - -> NP - -> IF - -> TSS - -> load_input - -> Fitness - -> Fatigue Fast - -> Fatigue Slow - -> Fatigue Total - -> Freshness - + RecoveryScoreSimple - -> Readiness - -> GoodDayProbability - -> Status -``` - ---- +- HealthKit ingestion +- normalized health tables +- `health_recovery_daily` +- `load_state_daily_v2` +- `readiness_daily` +- `good_day_probability` -## Статус документа +### Ограничения текущего подхода -Версия: draft v2 baseline -Назначение: зафиксировать текущую baseline-логику расчета метрик в Human Engine -Язык: русский +1. `load_input_nonlinear` пока фактически линейный. +2. Recovery-контур пока использует простой aggregated score без baseline deviations. +3. `GoodDayProbability` пока является простым mapping от readiness score. +4. Decision layer поверх readiness еще не откалиброван окончательно. diff --git a/docs/models/model_v2_architecture.md b/docs/models/model_v2_architecture.md index a209436..a081a16 100644 --- a/docs/models/model_v2_architecture.md +++ b/docs/models/model_v2_architecture.md @@ -2,22 +2,19 @@ ## Контекст -Текущая модель (V1) основана на load-only логике: +Model v2 уже реализована в backend как baseline-архитектура. -TSS → fitness / fatigue → freshness → readiness +Переход выполнен от load-only readiness к двухконтурной схеме: -На практике выявлены ограничения: +```text +LoadState + RecoveryState -> Readiness -> GoodDayProbability +``` -- readiness слишком инертен -- отсутствует учет восстановления -- модель плохо отражает реальные ощущения после сна / recovery -- используется только один канал fatigue +Это означает: -## Цель Model V2 - -Перейти к двухконтурной модели: - -LoadState + RecoveryState → Readiness → GoodDayProbability +- readiness больше не равен freshness +- recovery является отдельным контуром +- readiness хранится как отдельный слой `readiness_daily` --- @@ -26,73 +23,89 @@ LoadState + RecoveryState → Readiness → GoodDayProbability ## 1.1 Load и Recovery — независимые контуры Load: + - отражает тренировочную нагрузку -- формируется из TSS / NP +- формируется из `daily_training_load` +- материализуется в `load_state_daily_v2` Recovery: + - отражает восстановление организма -- формируется из: - - sleep - - HRV - - resting HR +- формируется из HealthKit-derived tables +- материализуется в `health_recovery_daily` + +Важно: -❗ Важно: -Recovery не заменяет fatigue, а корректирует итоговую readiness. +Recovery не заменяет fatigue, а корректирует итоговую readiness поверх load model. --- ## 1.2 Readiness ≠ Freshness -В Model V1: -readiness ≈ freshness +В legacy load-only подходе readiness мог использоваться как proxy от freshness. + +В текущей model v2: -В Model V2: +```text readiness = f(load_state, recovery_state) +``` --- ## 1.3 Fast / Slow fatigue -Вместо одного fatigue: +В model v2 используются: -- fatigue_fast (τ ≈ 2 дня) -- fatigue_slow (τ ≈ 7 дней) +- `fatigue_fast` +- `fatigue_slow` +- `fatigue_total` -Итого: +Где: -fatigue_total = fatigue_fast + fatigue_slow +```text +fatigue_total = 0.65 * fatigue_fast + 0.35 * fatigue_slow freshness = fitness - fatigue_total +``` -Это дает: -- быстрый отклик после восстановления -- накопление усталости при серии тренировок +--- + +## 1.4 Calendar-continuous load state + +`load_state_daily_v2` считается по непрерывной календарной оси. + +Это означает: + +- расчет идет не только по тренировочным дням +- в дни без тренировок используется `tss = 0` --- -## 1.4 Нелинейность нагрузки +## 1.5 Current load input -Вход нагрузки должен быть нелинейным: +Поле называется `load_input_nonlinear`, но текущий backend baseline использует линейный input: -load_input = A * (1 - exp(-B * TSS)) +```text +load_input_nonlinear = TSS +``` -Смысл: -- 200 TSS ≠ 2 × 100 TSS -- предотвращает «раздувание» модели +Нелинейная трансформация пока не реализована. --- -## 1.5 Probability вместо только score +## 1.6 Probability layer + +Model v2 вводит: -Model V2 вводит: +- `readiness_score` +- `good_day_probability` -- readiness_score -- good_day_probability +Текущий baseline: -good_day_probability = sigmoid(readiness_score_raw) +```text +good_day_probability = readiness_score / 100 +``` -Это позволяет: -- убрать жесткие пороги -- сделать рекомендации более гибкими +Это probability-like layer для decision support, а не финально откалиброванная вероятность. --- @@ -101,205 +114,212 @@ good_day_probability = sigmoid(readiness_score_raw) ## 2.1 Ingestion layer Источники: + - Strava - HealthKit ---- - -## 2.2 Normalized layer +Реализовано: -Таблицы: - -Load: -- activity_metrics -- daily_training_load - -Recovery: -- health_sleep_night -- health_resting_hr_daily -- health_hrv_sample -- health_weight_measurement +- Strava ingestion baseline +- HealthKit raw ingest +- HealthKit full-sync orchestration --- -## 2.3 Daily feature layer +## 2.2 Raw layer + +Ключевые raw сущности: -- daily_training_load -- daily_fitness_state (V1, legacy) -- health_recovery_daily +- `strava_activity_raw` +- `healthkit_ingest_raw` --- -## 2.4 Model layer (V2) +## 2.3 Normalized layer -Новые таблицы: +Текущие normalized health tables: -- load_state_daily_v2 -- readiness_daily +- `health_sleep_night` +- `health_resting_hr_daily` +- `health_hrv_sample` +- `health_weight_measurement` ---- +Load-side daily aggregate: -## 2.5 User insight layer +- `daily_training_load` -Использует Model V2: +--- -- daily readiness summary -- ride briefing -- training feedback -- recommendations +## 2.4 Recovery layer ---- +Текущая таблица: -# 3. Load Model V2 +- `health_recovery_daily` -## 3.1 Параметры +Содержит: -- tau_fitness ≈ 40 -- tau_fatigue_fast ≈ 2 -- tau_fatigue_slow ≈ 7 +- sleep metrics +- resting HR +- HRV daily median +- latest weight +- `recovery_score_simple` -## 3.2 Формулы +--- -fitness[d] = - fitness[d-1] + (load_input[d] - fitness[d-1]) / tau_fitness +## 2.5 Load model layer -fatigue_fast[d] = - fatigue_fast[d-1] + (load_input[d] - fatigue_fast[d-1]) / tau_fatigue_fast +Текущая таблица: -fatigue_slow[d] = - fatigue_slow[d-1] + (load_input[d] - fatigue_slow[d-1]) / tau_fatigue_slow +- `load_state_daily_v2` -fatigue_total[d] = fatigue_fast[d] + fatigue_slow[d] +Содержит: -freshness[d] = fitness[d] - fatigue_total[d] +- `tss` +- `load_input_nonlinear` +- `fitness` +- `fatigue_fast` +- `fatigue_slow` +- `fatigue_total` +- `freshness` --- -# 4. Recovery Model +## 2.6 Readiness layer -Источник: HealthKit +Текущая таблица: -Данные: +- `readiness_daily` -- sleep -- HRV -- resting HR -- weight +Содержит: -## 4.1 Текущая реализация (V1) +- `freshness` +- `recovery_score_simple` +- `readiness_score_raw` +- `readiness_score` +- `good_day_probability` +- `status_text` +- `explanation_json` -health_recovery_daily: +--- + +# 3. HealthKit full-sync architecture -- sleep_minutes -- resting_hr_bpm -- hrv_daily_median_ms -- weight_kg -- recovery_score_simple +Текущий iOS sync endpoint: -## 4.2 План расширения +```text +POST /api/v1/healthkit/full-sync/{user_id} +``` -Добавить: +Pipeline: -- sleep_score_simple -- hrv_dev (относительно baseline) -- rhr_dev +```text +raw ingest +-> latest raw -> normalized +-> recompute health_recovery_daily +-> recompute readiness_daily +``` --- -# 5. Readiness Model V2 +# 4. Load Model V2 -## 5.1 Базовая формула (V1) +## 4.1 Параметры -readiness_score_raw = - w1 * freshness + - w2 * recovery_score_simple +- `tau_fitness = 40` +- `tau_fatigue_fast = 4` +- `tau_fatigue_slow = 9` +- `weight_fatigue_fast = 0.65` +- `weight_fatigue_slow = 0.35` -## 5.2 Расширенная формула (V2+) +## 4.2 Формулы -readiness_score_raw = - w1 * freshness - + w2 * recovery_score - + w3 * hrv_dev - - w4 * rhr_dev - + w5 * sleep_score +```text +fitness[d] = + fitness[d-1] + (load_input[d] - fitness[d-1]) / 40 ---- +fatigue_fast[d] = + fatigue_fast[d-1] + (load_input[d] - fatigue_fast[d-1]) / 4 -# 6. Probability Layer +fatigue_slow[d] = + fatigue_slow[d-1] + (load_input[d] - fatigue_slow[d-1]) / 9 -good_day_probability = sigmoid(readiness_score_raw) +fatigue_total[d] = + 0.65 * fatigue_fast[d] + 0.35 * fatigue_slow[d] -Назначение: -- оценка вероятности успешной тренировки -- основа для рекомендаций +freshness[d] = + fitness[d] - fatigue_total[d] +``` --- -# 7. Таблицы Model V2 +# 5. Recovery Model + +Источник: HealthKit. + +Текущая реализация: -## 7.1 load_state_daily_v2 +- raw payload сохраняется +- latest raw раскладывается в normalized tables +- `health_recovery_daily` агрегирует day-level recovery state -- tss -- load_input_nonlinear -- fitness -- fatigue_fast -- fatigue_slow -- fatigue_total -- freshness +Текущий recovery output: -## 7.2 readiness_daily +- `recovery_score_simple` -- freshness -- recovery_score -- readiness_score -- good_day_probability -- explanation_json +Это baseline heuristic score, не финальная recovery model. --- -# 8. Roadmap +# 6. Readiness Model V2 -- стабилизировать `load_state_daily_v2` -- зафиксировать формулу `readiness_score_raw` -- откалибровать `good_day_probability` -- добавить `sleep_score_simple`, `hrv_dev`, `rhr_dev` -- синхронизировать recommendation / ride briefing с probability layer +## 6.1 Baseline formula -## Phase 1 -- load_state_daily_v2 -- fast / slow fatigue +```text +freshness_norm = clamp(50 + freshness, 0, 100) +readiness_score_raw = 0.6 * freshness_norm + 0.4 * recovery_score_simple +readiness_score = clamp(round(readiness_score_raw, 1), 0, 100) +good_day_probability = readiness_score / 100 +``` -## Phase 2 -- расширение health_recovery_daily -- readiness_daily (freshness + recovery) +## 6.2 Storage -## Phase 3 -- probability layer -- интеграция в user layer +Readiness хранится отдельно в `readiness_daily`. -## Phase 4 -- baseline (HRV_dev, RHR_dev) -- улучшение модели +Это важно архитектурно: + +- readiness не является просто полем load state +- readiness — отдельный daily layer поверх двух контуров --- -# 9. Что сознательно НЕ делаем сейчас +# 7. Product-level consequence -- ML модели -- ARIMA / forecasting -- сложные нелинейные системы -- personalization через обучение +Текущая продуктовая схема: -Причина: -приоритет — прозрачность и интерпретируемость +```text +LoadState + RecoveryState -> Readiness -> GoodDayProbability +``` + +Это и есть текущий baseline Human Engine. --- -# 10. Ключевой принцип +# 8. Planned next steps -Model V2: +- калибровка readiness / probability +- расширение recovery signals +- уточнение decision mapping +- синхронизация recommendation / ride briefing с readiness layer -Сначала объясняет нагрузку -Потом корректируется сигналами восстановления +--- + +# 9. Что сознательно не делаем сейчас + +- ML-модели в core +- black-box probability layer +- скрытую интерпретацию readiness +- LLM-based decision logic + +Причина: -Это базовая архитектура Human Engine. +приоритет — прозрачность, воспроизводимость и инженерная проверяемость.