185 lines
9.3 KiB
JavaScript
185 lines
9.3 KiB
JavaScript
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'
|
||
}
|