Track detail: reference calibrated-HR kcal figure (Keytel x personal factor)

HR methods over-read vs physics (ride) and weight (sedentary day); a rough
single factor ~0.65 reconciles both. Add config.hrCalibFactor (0.65) and show a
'ЧСС откалибр. (грубо)' figure next to the Keytel one in the calories block,
clearly labelled as a rough reference. Activity balance still uses physics.
This commit is contained in:
4aiman 2026-10-21 11:22:46 +03:00
commit 1cb6efbee3
3 changed files with 40 additions and 2 deletions

View file

@ -0,0 +1,35 @@
# Сессия 2026-10-21 - база покоя из ночной записи + справочная калибровка ЧСС
## Ночь записалась чисто (нативный лог сработал)
Воркаут `1792529811087` (20.10 23:56 -> 21.10 09:20): 9.4ч непрерывно, 0 дыр. Нативный лог
ЧСС (foreground-сервис, ble_hr_log.jsonl) переживает гаснущий экран - прошлый 21ч-прогон имел
11.6ч дыр из-за заморозки JS-поллинга, теперь починено.
## База покоя
Истинный пульс сна **~48 (46-50)**, устойчивый 10-мин минимум 50 в 05:46, общий минимум 46.
Всплеск 112 в 05:00 = подъём в туалет. Прежняя прикидка 65-85 завышена.
## Прикидка моделей (разбор, объяснено пользователю словами)
Поездка 90км по-всякому: физика 2756, Keytel-net 4283, резерв-ЧСС/Карвонен (от покоя 48,
VO2max 56 по Uth) ~4841. Пульсовые ~1.5-1.8x физики. Якорь на покой поездку не уменьшил.
Сутки (сон этой ночью + работа 10-17 вчера, 16ч бодрств.): по пульсу ~2300 против веса
BMR1670 x1.2=2005 (x1.3=2172). Пульс завышает и день (+10-15%), и поездку. Чисто НЕ сходятся;
грубый множитель ~0.65 мирит обе точки (поездка 2756/4283=0.64; день 335/632=0.53..0.79).
Физика у велосипеда занижает (неэффективный односкоростной складной - реально жжёшь больше
идеала); пульс завышает (жара/обезвоживание/еда + неспособность отделить их от усилия).
## Сделано (reference-only, баланс не трогаем)
- Память `project_hr_calibration_experiment.md` дополнена результатами ночи; статус - база
получена. Напоминалка снята (state.json status=done).
- `gps.js`: `config.hrCalibFactor = 0.65` (грубый персональный множитель, правится вручную).
- `main.js` showSessionDetail блок "Калории": добавлена строка "ЧСС откалибр. (грубо)" =
Keytel x hrCalibFactor, рядом с "По ЧСС (Keytel)"; подпись "грубая прикидка". В баланс
по-прежнему идёт физика.
## Остатки (не срочно)
Собрать ещё 1-2 чистых дня для устаканивания множителя; скидка на переваривание (TEF);
батарея ремня - ждём первого шага вниз (9.4ч @ 100%, ноль шагов).
## Проверка
Синтаксис JS ок. На телефоне: открыть трек с ЧСС -> в "Калории" три числа (физика,
По ЧСС Keytel, ЧСС откалибр.); баланс дня не изменился. Живую проверку делает пользователь.

View file

@ -14,6 +14,7 @@ GPS.config = {
airDrag: 0.6, // аэродинамическое сопротивление (CdA, м2). Город=0.6, спорт=0.4
airDensity: 1.225, // плотность воздуха (кг/м3). Уровень моря=1.225, горы=1.0
pedalEfficiency: 25, // КПД педалирования (%). Человек ~25%
hrCalibFactor: 0.65, // грубый персональный множитель для справочной "ЧСС откалибр." (Keytel x этого). Пульс завышает ~в 1.5x vs физика/вес; 0.65 мирит по 2 точкам (поездка 90км + сидячий день). НЕ истина, для справки.
altSmoothHalf: 2, // полуокно сглаживания высоты (точек). 2 = 5pt, 5 = 11pt, 7 = 15pt
walkMaxSpeedKmh: 12, // макс. скорость ходьбы/бега (выше = глитч GPS)
walkMinSpeedMs: 0.3, // мин. скорость ходьбы (ниже = стоит). 0.3 м/с = 1 км/ч

View file

@ -5483,6 +5483,7 @@ async function showSessionDetail(sessionId) {
heightCm: parseFloat(state.profile?.height_cm) || 178,
};
let hrKcal = hasHr ? GPS.hrKcalNet(hrSamples, hrProfile) : null;
let hrKcalCalib = hasHr ? Math.round(hrKcal * (GPS.config.hrCalibFactor ?? 0.65)) : null; // грубая справочная прикидка
let hrAvg = hasHr ? Math.round(hrSamples.reduce((a, s) => a + s[1], 0) / hrSamples.length) : null;
let hrMax = hasHr ? Math.max(...hrSamples.map(s => s[1])) : null;
@ -5527,8 +5528,9 @@ async function showSessionDetail(sessionId) {
<div class="sd-stat"><span class="sd-val">${session.total_kcal ?? stats.kcal} ккал</span><span class="sd-lbl">${hasHr ? "Физика (в баланс)" : "Всего"}</span></div>
<div class="sd-stat"><span class="sd-val">${kcalMin} ккал/мин</span><span class="sd-lbl">Расход</span></div>
</div>
${hasHr ? `<div class="sd-row"><div class="sd-stat"><span class="sd-val">${hrKcal} ккал</span><span class="sd-lbl">По ЧСС (сравнение)</span></div></div>
<div class="prod-hint" style="text-align:center;margin-top:4px">В баланс идёт «физика»; цифра по ЧСС (Keytel) - для сравнения.</div>` : ""}
${hasHr ? `<div class="sd-row"><div class="sd-stat"><span class="sd-val">${hrKcal} ккал</span><span class="sd-lbl">По ЧСС (Keytel)</span></div>
<div class="sd-stat"><span class="sd-val">${hrKcalCalib} ккал</span><span class="sd-lbl">ЧСС откалибр. (грубо)</span></div></div>
<div class="prod-hint" style="text-align:center;margin-top:4px">В баланс идёт «физика». По ЧСС (Keytel) завышает; «откалибр.» = Keytel x ${(GPS.config.hrCalibFactor ?? 0.65)} - грубая прикидка для сравнения.</div>` : ""}
</div>
${elevations.length > 0 ? `