autoRunAccountOptions.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. const { getJkesSettings } = require('./jkesSettings')
  2. const { MANUAL_PACE_MIN_SEC, MANUAL_PACE_MAX_SEC } = require('./paceUtils')
  3. const DEFAULT_AUTO_RUN_DISTANCE_KM = 2
  4. function roundToStepKm(value, stepKm) {
  5. const s = Number(stepKm)
  6. const v = Number(value)
  7. if (!Number.isFinite(v)) return DEFAULT_AUTO_RUN_DISTANCE_KM
  8. if (!Number.isFinite(s) || s <= 0) return Math.round(v * 100) / 100
  9. return Math.round(Math.ceil(v / s) * s * 100) / 100
  10. }
  11. /**
  12. * 在 [minKm, maxKm] 内按步进离散均匀随机取一值(用于每次自动乐跑)
  13. */
  14. function randomAutoRunDistanceKmBetween(minKm, maxKm, cfg) {
  15. const step = Number(cfg.autoDistanceStepKm) || 0.5
  16. const maxCap = Math.max(2, Number(cfg.autoSingleRunMaxKm) || 5)
  17. let lo = Number(minKm)
  18. let hi = Number(maxKm)
  19. if (!Number.isFinite(lo) || !Number.isFinite(hi)) {
  20. lo = DEFAULT_AUTO_RUN_DISTANCE_KM
  21. hi = DEFAULT_AUTO_RUN_DISTANCE_KM
  22. }
  23. if (lo > hi) {
  24. const t = lo
  25. lo = hi
  26. hi = t
  27. }
  28. lo = roundToStepKm(lo, step)
  29. hi = roundToStepKm(hi, step)
  30. if (lo > hi) {
  31. const t = lo
  32. lo = hi
  33. hi = t
  34. }
  35. lo = Math.max(1, Math.min(lo, maxCap))
  36. hi = Math.max(1, Math.min(hi, maxCap))
  37. if (lo > hi) {
  38. const t = lo
  39. lo = hi
  40. hi = t
  41. }
  42. const span = hi - lo
  43. const stepCount = Math.max(0, Math.round(span / step + 1e-9))
  44. const pick = Math.floor(Math.random() * (stepCount + 1))
  45. const v = lo + pick * step
  46. return Math.min(hi, Math.round(v * 100) / 100)
  47. }
  48. function normalizePaceRangeSecPerKm(rawMin, rawMax, cfg) {
  49. const floor = MANUAL_PACE_MIN_SEC
  50. const ceil = MANUAL_PACE_MAX_SEC
  51. let lo =
  52. rawMin === undefined || rawMin === null || rawMin === ''
  53. ? Number(cfg.paceRandomMinSecPerKm)
  54. : Number(rawMin)
  55. let hi =
  56. rawMax === undefined || rawMax === null || rawMax === ''
  57. ? Number(cfg.paceRandomMaxSecPerKm)
  58. : Number(rawMax)
  59. if (!Number.isFinite(lo)) lo = floor
  60. if (!Number.isFinite(hi)) hi = ceil
  61. lo = Math.round(lo)
  62. hi = Math.round(hi)
  63. if (lo > hi) {
  64. const t = lo
  65. lo = hi
  66. hi = t
  67. }
  68. lo = Math.max(floor, Math.min(lo, ceil))
  69. hi = Math.max(floor, Math.min(hi, ceil))
  70. if (lo > hi) {
  71. const t = lo
  72. lo = hi
  73. hi = t
  74. }
  75. return { paceMinSecPerKm: lo, paceMaxSecPerKm: hi }
  76. }
  77. /**
  78. * 添加/编辑账号:校验自动距离区间与配速区间(body 应已合并库内旧值;兼容仅传 auto_run_distance_km 视为 min=max)
  79. * @returns {{ ok: true, autoRunDistanceMinKm: number, autoRunDistanceMaxKm: number, paceMinSecPerKm: number, paceMaxSecPerKm: number } | { ok: false, msg: string }}
  80. */
  81. function validateAutoRunPresetForSave(body) {
  82. const cfg = getJkesSettings()
  83. const maxKm = Math.max(2, Number(cfg.autoSingleRunMaxKm) || 5)
  84. const stepKm = Number(cfg.autoDistanceStepKm) || 0.5
  85. const legacy = body.auto_run_distance_km
  86. const hasMin =
  87. body.auto_run_distance_min_km !== undefined &&
  88. body.auto_run_distance_min_km !== null &&
  89. body.auto_run_distance_min_km !== ''
  90. const hasMax =
  91. body.auto_run_distance_max_km !== undefined &&
  92. body.auto_run_distance_max_km !== null &&
  93. body.auto_run_distance_max_km !== ''
  94. let rawMin
  95. let rawMax
  96. if (!hasMin && !hasMax && legacy !== undefined && legacy !== null && legacy !== '') {
  97. rawMin = Number(legacy)
  98. rawMax = Number(legacy)
  99. } else {
  100. rawMin = !hasMin ? DEFAULT_AUTO_RUN_DISTANCE_KM : Number(body.auto_run_distance_min_km)
  101. rawMax = !hasMax ? DEFAULT_AUTO_RUN_DISTANCE_KM : Number(body.auto_run_distance_max_km)
  102. if (hasMin && !hasMax) rawMax = rawMin
  103. if (!hasMin && hasMax) rawMin = rawMax
  104. }
  105. if (!Number.isFinite(rawMin) || !Number.isFinite(rawMax)) {
  106. return { ok: false, msg: '自动乐跑距离区间无效' }
  107. }
  108. if (rawMin < 1 || rawMin > maxKm || rawMax < 1 || rawMax > maxKm) {
  109. return {
  110. ok: false,
  111. msg: `自动乐跑距离区间需在 1–${maxKm} 公里`
  112. }
  113. }
  114. if (rawMin > rawMax) {
  115. return { ok: false, msg: '自动乐跑距离下限不能大于上限' }
  116. }
  117. let autoRunDistanceMinKm = Math.min(maxKm, Math.max(1, roundToStepKm(rawMin, stepKm)))
  118. let autoRunDistanceMaxKm = Math.min(maxKm, Math.max(1, roundToStepKm(rawMax, stepKm)))
  119. if (autoRunDistanceMinKm > autoRunDistanceMaxKm) {
  120. const t = autoRunDistanceMinKm
  121. autoRunDistanceMinKm = autoRunDistanceMaxKm
  122. autoRunDistanceMaxKm = t
  123. }
  124. const rawLo = body.pace_min_sec_per_km
  125. const rawHi = body.pace_max_sec_per_km
  126. const lo =
  127. rawLo === undefined || rawLo === null || rawLo === ''
  128. ? Number(cfg.paceRandomMinSecPerKm)
  129. : Number(rawLo)
  130. const hi =
  131. rawHi === undefined || rawHi === null || rawHi === ''
  132. ? Number(cfg.paceRandomMaxSecPerKm)
  133. : Number(rawHi)
  134. if (!Number.isFinite(lo) || !Number.isFinite(hi)) {
  135. return { ok: false, msg: '配速区间无效(请填写每公里秒数,如 300、540)' }
  136. }
  137. if (
  138. lo < MANUAL_PACE_MIN_SEC ||
  139. lo > MANUAL_PACE_MAX_SEC ||
  140. hi < MANUAL_PACE_MIN_SEC ||
  141. hi > MANUAL_PACE_MAX_SEC
  142. ) {
  143. return { ok: false, msg: '配速区间需在 3:00–10:00/km(180–600 秒/公里)' }
  144. }
  145. if (lo > hi) {
  146. return { ok: false, msg: '配速区间下限不能大于上限' }
  147. }
  148. return {
  149. ok: true,
  150. autoRunDistanceMinKm,
  151. autoRunDistanceMaxKm,
  152. paceMinSecPerKm: Math.round(lo),
  153. paceMaxSecPerKm: Math.round(hi)
  154. }
  155. }
  156. function buildAutoRunTaskExtrasFromAccountRow(row) {
  157. const cfg = getJkesSettings()
  158. const targetKm = randomAutoRunDistanceKmBetween(
  159. row?.auto_run_distance_min_km,
  160. row?.auto_run_distance_max_km,
  161. cfg
  162. )
  163. const { paceMinSecPerKm, paceMaxSecPerKm } = normalizePaceRangeSecPerKm(
  164. row?.pace_min_sec_per_km,
  165. row?.pace_max_sec_per_km,
  166. cfg
  167. )
  168. return {
  169. targetKm,
  170. autoDoubleSlot: targetKm >= 2,
  171. paceRandomMinSecPerKm: paceMinSecPerKm,
  172. paceRandomMaxSecPerKm: paceMaxSecPerKm
  173. }
  174. }
  175. module.exports = {
  176. DEFAULT_AUTO_RUN_DISTANCE_KM,
  177. validateAutoRunPresetForSave,
  178. buildAutoRunTaskExtrasFromAccountRow,
  179. randomAutoRunDistanceKmBetween,
  180. normalizePaceRangeSecPerKm
  181. }