mitai-jinkendo/frontend/src/utils/interpret.js

185 lines
9.3 KiB
JavaScript
Raw Normal View History

import { getBfCategory, calcDerived } from './calc.js'
export function getInterpretation(measurement, profile, prevMeasurement = null) {
const results = []
const sex = profile?.sex || 'm'
const height = profile?.height || 178
const age = profile?.dob
? Math.floor((Date.now() - new Date(profile.dob)) / (365.25 * 24 * 3600 * 1000))
: 30
const m = measurement
// lean_mass and body_fat_pct come from caliper_log
const derived = calcDerived(m, height)
// ── Körperfett ─────────────────────────────────────────────────────────────
if (m.body_fat_pct) {
const cat = getBfCategory(m.body_fat_pct, sex)
const ranges = sex === 'm'
? { essential: 6, athletic: 14, fit: 18, avg: 25 }
: { essential: 14, athletic: 21, fit: 25, avg: 32 }
let msg = '', detail = ''
if (m.body_fat_pct <= ranges.essential) {
msg = 'Sehr niedriger Körperfettanteil'; detail = 'Essenzielle Fettwerte – nur für Leistungssportler geeignet, auf Dauer nicht empfehlenswert.'
} else if (m.body_fat_pct <= ranges.athletic) {
msg = 'Athletischer Körperfettanteil'; detail = 'Ausgezeichnet. Typisch für aktive Sportler mit hohem Trainingsvolumen.'
} else if (m.body_fat_pct <= ranges.fit) {
msg = 'Guter Körperfettanteil'; detail = 'Sehr gute Fitness-Kategorie. Gesund und gut in Form.'
} else if (m.body_fat_pct <= ranges.avg) {
msg = 'Durchschnittlicher Körperfettanteil'; detail = 'Im normalen Bereich. Verbesserung durch Kombination aus Kraft- und Ausdauertraining möglich.'
} else {
msg = 'Erhöhter Körperfettanteil'; detail = 'Über dem empfohlenen Bereich. Ernährungsumstellung und regelmäßiges Training empfohlen.'
}
results.push({
category: 'Körperfett',
icon: '🫧',
status: m.body_fat_pct <= ranges.fit ? 'good' : m.body_fat_pct <= ranges.avg ? 'warn' : 'bad',
title: msg,
detail,
value: `${m.body_fat_pct}%`,
badge: cat?.label,
color: cat?.color,
})
}
// ── Waist-Hip-Ratio ────────────────────────────────────────────────────────
if (derived.whr) {
const limit = sex === 'm' ? 0.90 : 0.85
const limitHigh = sex === 'm' ? 1.0 : 0.95
let status, title, detail
if (derived.whr < limit) {
status = 'good'; title = 'Günstige Fettverteilung'
detail = `Dein WHR von ${derived.whr} liegt unter dem Grenzwert (${limit}). Birnenförmige Fettverteilung – metabolisch günstig.`
} else if (derived.whr < limitHigh) {
status = 'warn'; title = 'Grenzwertiger WHR'
detail = `Dein WHR von ${derived.whr} liegt leicht über dem Zielwert (${limit}). Apfelförmige Tendenz – Bauchfett reduzieren empfohlen.`
} else {
status = 'bad'; title = 'Erhöhtes Risiko durch Fettverteilung'
detail = `WHR von ${derived.whr} deutlich über dem Grenzwert. Erhöhtes kardiovaskuläres Risiko durch viszerales Fett.`
}
results.push({ category: 'Fettverteilung', icon: '📐', status, title, detail, value: derived.whr.toString() })
}
// ── Waist-to-Height ────────────────────────────────────────────────────────
if (derived.whtr) {
let status, title, detail
if (derived.whtr < 0.40) {
status = 'warn'; title = 'Sehr schlanke Taille'
detail = `WHtR ${derived.whtr} – möglicherweise zu wenig Körpermasse.`
} else if (derived.whtr < 0.50) {
status = 'good'; title = 'Optimale Taillen-Größen-Relation'
detail = `WHtR ${derived.whtr} – im optimalen Bereich. Geringstes kardiovaskuläres Risiko.`
} else if (derived.whtr < 0.60) {
status = 'warn'; title = 'Leicht erhöhter WHtR'
detail = `WHtR ${derived.whtr} – Ziel ist unter 0,50. Moderat erhöhtes Risiko.`
} else {
status = 'bad'; title = 'Stark erhöhter WHtR'
detail = `WHtR ${derived.whtr} – deutlich erhöhtes Risiko. Taille sollte weniger als die Hälfte der Körpergröße betragen.`
}
results.push({ category: 'Taille/Größe', icon: '📏', status, title, detail, value: derived.whtr.toString() })
}
// ── FFMI ───────────────────────────────────────────────────────────────────
if (derived.ffmi) {
const naturalLimit = sex === 'm' ? 25 : 22
let status, title, detail
if (derived.ffmi < (sex === 'm' ? 18 : 15)) {
status = 'warn'; title = 'Unterdurchschnittliche Muskelmasse'
detail = `FFMI ${derived.ffmi} – Krafttraining kann die Muskelmasse und den Grundumsatz deutlich verbessern.`
} else if (derived.ffmi < (sex === 'm' ? 22 : 19)) {
status = 'good'; title = 'Durchschnittliche Muskelmasse'
detail = `FFMI ${derived.ffmi} – gute Basis. Mit regelmäßigem Krafttraining weiter ausbaubar.`
} else if (derived.ffmi <= naturalLimit) {
status = 'good'; title = 'Überdurchschnittliche Muskelmasse'
detail = `FFMI ${derived.ffmi} – sehr gut. Oberes natürliches Spektrum für Kraftsportler.`
} else {
status = 'warn'; title = 'Außergewöhnlich hohe Muskelmasse'
detail = `FFMI ${derived.ffmi} – oberhalb der natürlichen Grenze (~${naturalLimit}). Selten ohne unterstützende Mittel erreichbar.`
}
results.push({ category: 'Muskelmasse', icon: '💪', status, title, detail, value: derived.ffmi.toString() })
}
// ── BMI (aus Gewicht + Größe) ──────────────────────────────────────────────
if (m.weight && height) {
const bmi = Math.round(m.weight / ((height / 100) ** 2) * 10) / 10
let status, title, detail
if (bmi < 18.5) {
status = 'warn'; title = 'Untergewicht (BMI)'
detail = `BMI ${bmi} – unter 18,5. Auf ausreichende Kalorienzufuhr und Nährstoffversorgung achten.`
} else if (bmi < 25) {
status = 'good'; title = 'Normalgewicht (BMI)'
detail = `BMI ${bmi} – im optimalen Bereich (18,5–24,9).`
} else if (bmi < 30) {
status = 'warn'; title = 'Übergewicht (BMI)'
detail = `BMI ${bmi} – leichtes Übergewicht. BMI allein ist wenig aussagekräftig bei Muskelmasse – Körperfett-% beachten.`
} else {
status = 'bad'; title = 'Adipositas (BMI)'
detail = `BMI ${bmi} – deutliches Übergewicht. Ärztliche Beratung empfohlen.`
}
results.push({ category: 'BMI', icon: '⚖️', status, title, detail, value: bmi.toString() })
}
// ── Vergleich zur letzten Messung ──────────────────────────────────────────
if (prevMeasurement) {
const changes = []
const p = prevMeasurement
const days = Math.round((new Date(m.date) - new Date(p.date)) / (1000 * 60 * 60 * 24))
if (m.body_fat_pct && p.body_fat_pct) {
const diff = Math.round((m.body_fat_pct - p.body_fat_pct) * 10) / 10
if (Math.abs(diff) >= 0.3) changes.push({ label: 'Körperfett', diff, unit: '%', invert: true })
}
if (m.weight && p.weight) {
const diff = Math.round((m.weight - p.weight) * 10) / 10
if (Math.abs(diff) >= 0.2) changes.push({ label: 'Gewicht', diff, unit: 'kg', invert: true })
}
if (m.lean_mass && p.lean_mass) {
const diff = Math.round((m.lean_mass - p.lean_mass) * 10) / 10
if (Math.abs(diff) >= 0.2) changes.push({ label: 'Magermasse', diff, unit: 'kg', invert: false })
}
if (m.c_waist && p.c_waist) {
const diff = Math.round((m.c_waist - p.c_waist) * 10) / 10
if (Math.abs(diff) >= 0.5) changes.push({ label: 'Taille', diff, unit: 'cm', invert: true })
}
if (m.c_belly && p.c_belly) {
const diff = Math.round((m.c_belly - p.c_belly) * 10) / 10
if (Math.abs(diff) >= 0.5) changes.push({ label: 'Bauch', diff, unit: 'cm', invert: true })
}
if (changes.length > 0) {
const positiveChanges = changes.filter(c => c.invert ? c.diff < 0 : c.diff > 0)
const negativeChanges = changes.filter(c => c.invert ? c.diff > 0 : c.diff < 0)
const detail = changes.map(c => {
const sign = c.diff > 0 ? '+' : ''
const good = c.invert ? c.diff < 0 : c.diff > 0
return `${c.label}: ${sign}${c.diff} ${c.unit} ${good ? '✓' : '↑'}`
}).join(' · ')
results.push({
category: `Seit letzter Messung (${days} Tage)`,
icon: '📊',
status: positiveChanges.length >= negativeChanges.length ? 'good' : 'warn',
title: positiveChanges.length > negativeChanges.length
? 'Positive Entwicklung seit letzter Messung'
: negativeChanges.length > positiveChanges.length
? 'Verschlechterung seit letzter Messung'
: 'Gemischte Entwicklung seit letzter Messung',
detail,
value: `${days}d`,
})
}
}
return results
}
export function getStatusColor(status) {
return status === 'good' ? '#1D9E75' : status === 'warn' ? '#EF9F27' : '#D85A30'
}
export function getStatusBg(status) {
return status === 'good' ? '#E1F5EE' : status === 'warn' ? '#FAEEDA' : '#FCEBEB'
}