Files
fitness-coach/backend/scripts/analyze.mjs
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

75 lines
3.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 数据分析脚本:扫描 exercises.json输出各维度的去重取值与数量
* 并报告哪些取值尚未被中文标签映射覆盖(便于补全 labels.ts
*
* 用法: node scripts/analyze.mjs [path-to-exercises.json]
*/
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DATA = process.argv[2] || path.join(__dirname, '..', 'src', 'exercises', 'data', 'exercises.json');
// 复用 TS 标签映射(用 require 不行,这里用一份 JS 镜像做缺失检测)
const BODY_PART_LABELS = {
back: '背部', cardio: '有氧心肺', chest: '胸部', 'lower arms': '前臂',
'lower legs': '小腿', neck: '颈部', shoulders: '肩部', 'upper arms': '上臂',
'upper legs': '大腿', waist: '腰腹',
};
const EQUIPMENT_LABELS = {
'body weight': '自重', dumbbell: '哑铃', barbell: '杠铃', kettlebell: '壶铃', cable: '钢索',
machine: '固定器械', band: '弹力带', 'exercise ball': '健身球', 'medicine ball': '药球',
'ez bar': 'EZ曲杆', 'foam roll': '泡沫轴', treadmill: '跑步机', 'stationary bike': '固定单车',
rope: '战绳', tire: '轮胎', skier: '滑雪训练器', 'sled machine': '雪橇机', roller: '滚轮',
'elliptical machine': '椭圆机', 'olympic barbell': '奥杆', 'decline bench': '下斜凳',
'flat bench': '平板凳', 'pull-up bar': '单杠', 'stability ball': '瑞士球', trx: 'TRX悬挂带',
'assistance machine': '辅助器械', 'bosu ball': 'BOSU半球', 'wheel roller': '健腹轮',
none: '无器械', other: '其他',
};
const MUSCLE_LABELS = {
biceps: '肱二头肌', triceps: '肱三头肌', forearms: '前臂', abs: '腹肌', abdominals: '腹肌',
obliques: '腹斜肌', pectorals: '胸大肌', chest: '胸部', quadriceps: '股四头肌',
hamstrings: '腘绳肌', glutes: '臀大肌', calves: '小腿', traps: '斜方肌', 'upper traps': '上斜方肌',
lats: '背阔肌', 'lower back': '下背', delts: '三角肌', shoulders: '肩部', hips: '髋部',
adductors: '内收肌', neck: '颈部', 'serratus anterior': '前锯肌', 'spinal erectors': '竖脊肌',
'hip flexors': '髋屈肌', 'middle back': '中背',
};
function tally(arr, keyFn) {
const m = new Map();
for (const x of arr) {
const k = keyFn(x);
if (!k) continue;
if (Array.isArray(k)) k.forEach((v) => m.set(v, (m.get(v) || 0) + 1));
else m.set(k, (m.get(k) || 0) + 1);
}
return m;
}
function report(name, map, labels) {
const missing = [];
const rows = Array.from(map.entries()).sort((a, b) => b[1] - a[1]);
console.log(`\n=== ${name} (${rows.length} 种) ===`);
for (const [v, c] of rows) {
const has = labels[v] ? '' : ' ⚠ 缺标签';
if (!labels[v]) missing.push(v);
console.log(` ${v.padEnd(22)} ${String(c).padStart(5)} ${labels[v] || ''}${has}`);
}
return missing;
}
const raw = JSON.parse(fs.readFileSync(DATA, 'utf-8'));
console.log(`总动作数: ${raw.length}`);
const eqMiss = report('EQUIPMENT', tally(raw, (e) => e.equipment), EQUIPMENT_LABELS);
const tMiss = report('TARGET', tally(raw, (e) => e.target), MUSCLE_LABELS);
const mgMiss = report('MUSCLE_GROUP', tally(raw, (e) => e.muscle_group), MUSCLE_LABELS);
const smMiss = report('SECONDARY_MUSCLES', tally(raw, (e) => e.secondary_muscles), MUSCLE_LABELS);
console.log('\n=== 缺失中文标签汇总(需补全 labels.ts===');
console.log('equipment :', eqMiss.join(', ') || '(无)');
console.log('target :', tMiss.join(', ') || '(无)');
console.log('muscle_group:', mgMiss.join(', ') || '(无)');
console.log('secondary :', smMiss.join(', ') || '(无)');