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
This commit is contained in:
86
backend/src/exercises/utils/normalize.ts
Normal file
86
backend/src/exercises/utils/normalize.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user