mitai-jinkendo/frontend/src/utils/interpret.js
Lars Stommer 89b6c0b072
Some checks are pending
Deploy to Raspberry Pi / deploy (push) Waiting to run
Build Test / build-frontend (push) Waiting to run
Build Test / lint-backend (push) Waiting to run
feat: initial commit – Mitai Jinkendo v9a
2026-03-16 13:35:11 +01:00

185 lines
9.3 KiB
JavaScript
Raw 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.

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,524,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'
}