fix: PR review changes

pull/4/head
lambda 5 days ago
parent 0888056268
commit 3c06100a23
No known key found for this signature in database
GPG Key ID: CF45A8EE158BBCB6

@ -99,6 +99,8 @@ struct RawStep {
#[derive(Deserialize)]
struct RawPower {
#[serde(default)]
units: Option<String>,
#[serde(default)]
value: Option<f64>,
#[serde(default)]
@ -160,6 +162,12 @@ fn convert_step(raw: RawStep) -> WorkoutStep {
/// линейный ramp от свободного диапазона. Предполагаем `units == "%ftp"`
/// (все скачанные воркауты используют %ftp); абсолютные ватты не поддерживаются.
fn convert_power(p: RawPower, ramp: bool) -> Option<PowerTarget> {
// Отвергаем воркауты в абсолютных ваттах: перевод watts → %FTP требует FTP,
// которого нет на этапе парсинга. Принимаем только %ftp (units == None
// трактуем как %ftp — все скачанные воркауты используют %ftp).
if matches!(p.units.as_deref(), Some(u) if u != "%ftp") {
return None;
}
if let Some(v) = p.value {
return Some(PowerTarget::Fixed { percent: v });
}
@ -199,7 +207,11 @@ pub fn total_duration_secs(segments: &[Segment]) -> u64 {
}
/// Целевая мощность (%FTP) в момент `elapsed_secs` внутри шага.
/// Fixed — константа; Ramp — линейная интерполяция from→to; Range — середина.
/// Fixed — константа; Ramp — линейная интерполяция from→to.
///
/// Range (свободный диапазон low..high) сводится к середине: в ERG-режиме
/// нужна конкретная цель, середина — осознанный дефолт. Альтернативы: не слать
/// ERG вовсе (свободный ход) или слать low как «пол».
pub fn target_percent(power: &PowerTarget, elapsed_secs: f64, duration_secs: u32) -> f64 {
match power {
PowerTarget::Fixed { percent } => *percent,
@ -330,6 +342,17 @@ mod tests {
assert_eq!(w.steps[0].cadence_rpm, Some(85));
}
#[test]
fn rejects_non_ftp_power_units() {
// units:"watts" нельзя перевести в %FTP без FTP на этапе парсинга —
// шаг должен терять цель мощности, а не трактовать ватты как проценты.
let json = r#"{"name":"W","workout_doc":{"steps":[
{"text":"X","duration":60,"power":{"units":"watts","value":200}}
]}}"#;
let w = parse_workout(json).unwrap();
assert_eq!(w.steps[0].power, None);
}
#[test]
fn flatten_expands_repeats() {
let w = parse_workout(EVENT).unwrap();

@ -58,13 +58,11 @@
<span class="metric__label">мощность</span>
<span class="metric__target" id="power-target">—</span>
<span class="metric__actual" id="power-value">—</span>
<span class="metric__warn" id="power-warn"></span>
</div>
<div class="metric metric--cadence" id="cadence-metric">
<span class="metric__label">каденс</span>
<span class="metric__target" id="cadence-target">—</span>
<span class="metric__actual" id="cadence-value">—</span>
<span class="metric__warn" id="cadence-warn"></span>
</div>
<div class="metric metric--hr">
<span class="metric__label">чсс</span>

@ -211,10 +211,8 @@ const workoutStopBtn = document.querySelector("#workout-stop");
// --- Отклонение от цели ---
const powerMetric = document.querySelector("#power-metric");
const powerTarget = document.querySelector("#power-target");
const powerWarn = document.querySelector("#power-warn");
const cadenceMetric = document.querySelector("#cadence-metric");
const cadenceTarget = document.querySelector("#cadence-target");
const cadenceWarn = document.querySelector("#cadence-warn");
const POWER_TOL_PCT = 0.1;
const POWER_TOL_ABS = 15; // Вт
@ -229,11 +227,10 @@ function powerTolerance(target) {
return Math.max(POWER_TOL_ABS, Math.round(target * POWER_TOL_PCT));
}
function updateDeviation(metricEl, targetEl, warnEl, actual, target, tolerance, unit) {
function updateDeviation(metricEl, targetEl, actual, target, tolerance, unit) {
targetEl.textContent = target != null ? `${target} ${unit}` : "—";
if (actual == null || target == null) {
metricEl.classList.remove("metric--off");
warnEl.textContent = "";
return;
}
const diff = actual - target;
@ -245,11 +242,11 @@ function updateDeviation(metricEl, targetEl, warnEl, actual, target, tolerance,
}
function updatePowerDeviation() {
updateDeviation(powerMetric, powerTarget, powerWarn, actualWatts, targetWatts, powerTolerance(targetWatts ?? 0), "W");
updateDeviation(powerMetric, powerTarget, actualWatts, targetWatts, powerTolerance(targetWatts ?? 0), "W");
}
function updateCadenceDeviation() {
updateDeviation(cadenceMetric, cadenceTarget, cadenceWarn, actualCadence, targetCadence, CADENCE_TOL_ABS, "rpm");
updateDeviation(cadenceMetric, cadenceTarget, actualCadence, targetCadence, CADENCE_TOL_ABS, "rpm");
}
function clearTargets() {

@ -347,15 +347,6 @@ body {
font-weight: var(--fw-semibold);
}
/* предупреждение — резервируем строку, чтобы не прыгало */
.metric__warn {
min-height: var(--text-base);
font-size: var(--text-xs);
font-weight: var(--fw-medium);
color: var(--c-danger);
line-height: 1.2;
}
/* --- График мощности/пульса --- */
.graph canvas {

Loading…
Cancel
Save