const { getJkesSettings } = require('./jkesSettings') const { MANUAL_PACE_MIN_SEC, MANUAL_PACE_MAX_SEC } = require('./paceUtils') const DEFAULT_AUTO_RUN_DISTANCE_KM = 2 function roundToStepKm(value, stepKm) { const s = Number(stepKm) const v = Number(value) if (!Number.isFinite(v)) return DEFAULT_AUTO_RUN_DISTANCE_KM if (!Number.isFinite(s) || s <= 0) return Math.round(v * 100) / 100 return Math.round(Math.ceil(v / s) * s * 100) / 100 } /** * 在 [minKm, maxKm] 内按步进离散均匀随机取一值(用于每次自动乐跑) */ function randomAutoRunDistanceKmBetween(minKm, maxKm, cfg) { const step = Number(cfg.autoDistanceStepKm) || 0.1 const maxCap = Math.max(2, Number(cfg.autoSingleRunMaxKm) || 10) let lo = Number(minKm) let hi = Number(maxKm) if (!Number.isFinite(lo) || !Number.isFinite(hi)) { lo = DEFAULT_AUTO_RUN_DISTANCE_KM hi = DEFAULT_AUTO_RUN_DISTANCE_KM } if (lo > hi) { const t = lo lo = hi hi = t } lo = roundToStepKm(lo, step) hi = roundToStepKm(hi, step) if (lo > hi) { const t = lo lo = hi hi = t } lo = Math.max(1, Math.min(lo, maxCap)) hi = Math.max(1, Math.min(hi, maxCap)) if (lo > hi) { const t = lo lo = hi hi = t } const span = hi - lo const stepCount = Math.max(0, Math.round(span / step + 1e-9)) const pick = Math.floor(Math.random() * (stepCount + 1)) const v = lo + pick * step return Math.min(hi, Math.round(v * 100) / 100) } function normalizePaceRangeSecPerKm(rawMin, rawMax, cfg) { const floor = MANUAL_PACE_MIN_SEC const ceil = MANUAL_PACE_MAX_SEC let lo = rawMin === undefined || rawMin === null || rawMin === '' ? Number(cfg.paceRandomMinSecPerKm) : Number(rawMin) let hi = rawMax === undefined || rawMax === null || rawMax === '' ? Number(cfg.paceRandomMaxSecPerKm) : Number(rawMax) if (!Number.isFinite(lo)) lo = floor if (!Number.isFinite(hi)) hi = ceil lo = Math.round(lo) hi = Math.round(hi) if (lo > hi) { const t = lo lo = hi hi = t } lo = Math.max(floor, Math.min(lo, ceil)) hi = Math.max(floor, Math.min(hi, ceil)) if (lo > hi) { const t = lo lo = hi hi = t } return { paceMinSecPerKm: lo, paceMaxSecPerKm: hi } } /** * 添加/编辑账号:校验自动距离区间与配速区间(body 应已合并库内旧值;兼容仅传 auto_run_distance_km 视为 min=max) * @returns {{ ok: true, autoRunDistanceMinKm: number, autoRunDistanceMaxKm: number, paceMinSecPerKm: number, paceMaxSecPerKm: number } | { ok: false, msg: string }} */ function validateAutoRunPresetForSave(body) { const cfg = getJkesSettings() const maxKm = Math.max(2, Number(cfg.autoSingleRunMaxKm) || 10) const stepKm = Number(cfg.autoDistanceStepKm) || 0.1 const legacy = body.auto_run_distance_km const hasMin = body.auto_run_distance_min_km !== undefined && body.auto_run_distance_min_km !== null && body.auto_run_distance_min_km !== '' const hasMax = body.auto_run_distance_max_km !== undefined && body.auto_run_distance_max_km !== null && body.auto_run_distance_max_km !== '' let rawMin let rawMax if (!hasMin && !hasMax && legacy !== undefined && legacy !== null && legacy !== '') { rawMin = Number(legacy) rawMax = Number(legacy) } else { rawMin = !hasMin ? DEFAULT_AUTO_RUN_DISTANCE_KM : Number(body.auto_run_distance_min_km) rawMax = !hasMax ? DEFAULT_AUTO_RUN_DISTANCE_KM : Number(body.auto_run_distance_max_km) if (hasMin && !hasMax) rawMax = rawMin if (!hasMin && hasMax) rawMin = rawMax } if (!Number.isFinite(rawMin) || !Number.isFinite(rawMax)) { return { ok: false, msg: '自动乐跑距离区间无效' } } if (rawMin < 1 || rawMin > maxKm || rawMax < 1 || rawMax > maxKm) { return { ok: false, msg: `自动乐跑距离区间需在 1–${maxKm} 公里` } } if (rawMin > rawMax) { return { ok: false, msg: '自动乐跑距离下限不能大于上限' } } let autoRunDistanceMinKm = Math.min(maxKm, Math.max(1, roundToStepKm(rawMin, stepKm))) let autoRunDistanceMaxKm = Math.min(maxKm, Math.max(1, roundToStepKm(rawMax, stepKm))) if (autoRunDistanceMinKm > autoRunDistanceMaxKm) { const t = autoRunDistanceMinKm autoRunDistanceMinKm = autoRunDistanceMaxKm autoRunDistanceMaxKm = t } const rawLo = body.pace_min_sec_per_km const rawHi = body.pace_max_sec_per_km const lo = rawLo === undefined || rawLo === null || rawLo === '' ? Number(cfg.paceRandomMinSecPerKm) : Number(rawLo) const hi = rawHi === undefined || rawHi === null || rawHi === '' ? Number(cfg.paceRandomMaxSecPerKm) : Number(rawHi) if (!Number.isFinite(lo) || !Number.isFinite(hi)) { return { ok: false, msg: '配速区间无效(请填写每公里秒数,如 300、540)' } } if ( lo < MANUAL_PACE_MIN_SEC || lo > MANUAL_PACE_MAX_SEC || hi < MANUAL_PACE_MIN_SEC || hi > MANUAL_PACE_MAX_SEC ) { return { ok: false, msg: '配速区间需在 3:00–10:00/km(180–600 秒/公里)' } } if (lo > hi) { return { ok: false, msg: '配速区间下限不能大于上限' } } return { ok: true, autoRunDistanceMinKm, autoRunDistanceMaxKm, paceMinSecPerKm: Math.round(lo), paceMaxSecPerKm: Math.round(hi) } } function buildAutoRunTaskExtrasFromAccountRow(row) { const cfg = getJkesSettings() const targetKm = randomAutoRunDistanceKmBetween( row?.auto_run_distance_min_km, row?.auto_run_distance_max_km, cfg ) const { paceMinSecPerKm, paceMaxSecPerKm } = normalizePaceRangeSecPerKm( row?.pace_min_sec_per_km, row?.pace_max_sec_per_km, cfg ) return { targetKm, autoDoubleSlot: targetKm >= 2, paceRandomMinSecPerKm: paceMinSecPerKm, paceRandomMaxSecPerKm: paceMaxSecPerKm } } module.exports = { DEFAULT_AUTO_RUN_DISTANCE_KM, validateAutoRunPresetForSave, buildAutoRunTaskExtrasFromAccountRow, randomAutoRunDistanceKmBetween, normalizePaceRangeSecPerKm }