Files
fitness-coach/backend/src/exercises/utils/normalize.ts
wm b3b58e66fb feat: init fitness-coach (backend + miniprogram)
- backend: NestJS service with exercise/plan data
- miniprogram: WeChat mini program client
- exclude node_modules, dist, runtime data-store, local env
2026-07-22 00:35:40 +08:00

87 lines
2.4 KiB
TypeScript

import {
RawExercise,
Exercise,
ExerciseSummary,
ExerciseType,
} from '../exercises.interface';
import {
BODY_PART_LABELS,
EQUIPMENT_LABELS,
MUSCLE_LABELS,
labelOf,
} from '../data/labels';
const MEDIA_BASE_URL = (
process.env.MEDIA_BASE_URL || 'https://cdn.jsdelivr.net/gh/hasaneyldrm/exercises-dataset@main'
).replace(/\/$/, '');
function toMediaUrl(path: string): string {
if (!path) return '';
if (/^https?:\/\//.test(path)) return path;
return `${MEDIA_BASE_URL}/${path}`;
}
function deriveType(raw: RawExercise): ExerciseType {
if (raw.body_part === 'cardio') return 'cardio';
const hay = `${raw.name} ${raw.equipment} ${raw.category || ''}`.toLowerCase();
const flexKeywords = ['stretch', 'yoga', 'mobility', 'pilates', 'flexibility', 'foam roll'];
if (flexKeywords.some((k) => hay.includes(k))) return 'flexibility';
return 'strength';
}
const TYPE_LABELS: Record<ExerciseType, string> = {
strength: '力量',
cardio: '有氧',
flexibility: '柔韧',
};
export function normalize(raw: RawExercise): Exercise {
const type = deriveType(raw);
const zh = raw.instructions?.zh || '';
const en = raw.instructions?.en || '';
const zhSteps = raw.instruction_steps?.zh || [];
const enSteps = raw.instruction_steps?.en || [];
return {
id: raw.id,
name: raw.name,
bodyPart: raw.body_part,
bodyPartLabel: labelOf(BODY_PART_LABELS, raw.body_part),
equipment: raw.equipment,
equipmentLabel: labelOf(EQUIPMENT_LABELS, raw.equipment),
target: raw.target,
targetLabel: labelOf(MUSCLE_LABELS, raw.target),
muscleGroup: raw.muscle_group,
muscleGroupLabel: labelOf(MUSCLE_LABELS, raw.muscle_group),
secondaryMuscles: raw.secondary_muscles || [],
secondaryMusclesLabels: (raw.secondary_muscles || []).map(
(m) => labelOf(MUSCLE_LABELS, m),
),
type,
typeLabel: TYPE_LABELS[type],
image: toMediaUrl(raw.image),
gifUrl: toMediaUrl(raw.gif_url),
instructions: { zh, en },
instructionSteps: { zh: zhSteps, en: enSteps },
attribution: raw.attribution,
mediaId: raw.media_id,
};
}
export function toSummary(ex: Exercise): ExerciseSummary {
return {
id: ex.id,
name: ex.name,
bodyPart: ex.bodyPart,
bodyPartLabel: ex.bodyPartLabel,
equipment: ex.equipment,
equipmentLabel: ex.equipmentLabel,
target: ex.target,
targetLabel: ex.targetLabel,
type: ex.type,
typeLabel: ex.typeLabel,
image: ex.image,
gifUrl: ex.gifUrl,
};
}