| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- export interface TelemetryConfig {
- heartRateAge: number
- restingHeartRateBpm: number
- userWeightKg: number
- }
- export type HeartRateTone = 'blue' | 'purple' | 'green' | 'yellow' | 'orange' | 'red'
- type HeartRateToneMeta = {
- label: string
- heartRateRangeText: string
- speedRangeText: string
- }
- const HEART_RATE_TONE_META: Record<HeartRateTone, HeartRateToneMeta> = {
- blue: {
- label: '激活放松',
- heartRateRangeText: '<=39%',
- speedRangeText: '<3.2 km/h',
- },
- purple: {
- label: '动态热身',
- heartRateRangeText: '40~54%',
- speedRangeText: '3.2~4.0 km/h',
- },
- green: {
- label: '脂肪燃烧',
- heartRateRangeText: '55~69%',
- speedRangeText: '4.1~5.5 km/h',
- },
- yellow: {
- label: '糖分消耗',
- heartRateRangeText: '70~79%',
- speedRangeText: '5.6~7.1 km/h',
- },
- orange: {
- label: '心肺训练',
- heartRateRangeText: '80~89%',
- speedRangeText: '7.2~8.8 km/h',
- },
- red: {
- label: '峰值锻炼',
- heartRateRangeText: '>=90%',
- speedRangeText: '>=8.9 km/h',
- },
- }
- export function clampTelemetryAge(age: number): number {
- if (!Number.isFinite(age)) {
- return 30
- }
- return Math.max(10, Math.min(85, Math.round(age)))
- }
- export function estimateRestingHeartRateBpm(age: number): number {
- const safeAge = clampTelemetryAge(age)
- const estimated = 68 + (safeAge - 30) * 0.12
- return Math.max(56, Math.min(76, Math.round(estimated)))
- }
- export function normalizeRestingHeartRateBpm(restingHeartRateBpm: number, age: number): number {
- if (!Number.isFinite(restingHeartRateBpm) || restingHeartRateBpm <= 0) {
- return estimateRestingHeartRateBpm(age)
- }
- return Math.max(40, Math.min(95, Math.round(restingHeartRateBpm)))
- }
- export function normalizeUserWeightKg(userWeightKg: number): number {
- if (!Number.isFinite(userWeightKg) || userWeightKg <= 0) {
- return 65
- }
- return Math.max(35, Math.min(180, Math.round(userWeightKg)))
- }
- export const DEFAULT_TELEMETRY_CONFIG: TelemetryConfig = {
- heartRateAge: 30,
- restingHeartRateBpm: estimateRestingHeartRateBpm(30),
- userWeightKg: 65,
- }
- export function mergeTelemetryConfig(overrides?: Partial<TelemetryConfig> | null): TelemetryConfig {
- const heartRateAge = overrides && overrides.heartRateAge !== undefined
- ? clampTelemetryAge(overrides.heartRateAge)
- : DEFAULT_TELEMETRY_CONFIG.heartRateAge
- const restingHeartRateBpm = overrides && overrides.restingHeartRateBpm !== undefined
- ? normalizeRestingHeartRateBpm(overrides.restingHeartRateBpm, heartRateAge)
- : estimateRestingHeartRateBpm(heartRateAge)
- const userWeightKg = overrides && overrides.userWeightKg !== undefined
- ? normalizeUserWeightKg(overrides.userWeightKg)
- : DEFAULT_TELEMETRY_CONFIG.userWeightKg
- return {
- heartRateAge,
- restingHeartRateBpm,
- userWeightKg,
- }
- }
- export function getHeartRateToneSampleBpm(tone: HeartRateTone, config: TelemetryConfig): number {
- const maxHeartRate = Math.max(120, 220 - config.heartRateAge)
- const restingHeartRate = Math.min(maxHeartRate - 15, config.restingHeartRateBpm)
- const reserve = Math.max(20, maxHeartRate - restingHeartRate)
- if (tone === 'blue') {
- return Math.round(restingHeartRate + reserve * 0.3)
- }
- if (tone === 'purple') {
- return Math.round(restingHeartRate + reserve * 0.47)
- }
- if (tone === 'green') {
- return Math.round(restingHeartRate + reserve * 0.62)
- }
- if (tone === 'yellow') {
- return Math.round(restingHeartRate + reserve * 0.745)
- }
- if (tone === 'orange') {
- return Math.round(restingHeartRate + reserve * 0.845)
- }
- return Math.round(restingHeartRate + reserve * 0.93)
- }
- export function getHeartRateToneLabel(tone: HeartRateTone): string {
- return HEART_RATE_TONE_META[tone].label
- }
- export function getHeartRateToneRangeText(tone: HeartRateTone): string {
- return HEART_RATE_TONE_META[tone].heartRateRangeText
- }
- export function getSpeedToneRangeText(tone: HeartRateTone): string {
- return HEART_RATE_TONE_META[tone].speedRangeText
- }
|