export function calcBodyFat(method, skinfolds, sex, age) { const vals = Object.values(skinfolds).filter(v => v > 0) if (!vals.length) return null const siri = (D) => Math.max(3, Math.min(60, (495 / D) - 450)) if (method === 'jackson3') { const { chest=0, abdomen=0, suprailiac=0, triceps=0, thigh=0 } = skinfolds const s = sex === 'm' ? chest+abdomen+thigh : triceps+suprailiac+thigh if (s === 0) return null const D = sex === 'm' ? 1.10938 - 0.0008267*s + 0.0000016*s*s - 0.0002574*age : 1.0994921 - 0.0009929*s + 0.0000023*s*s - 0.0001392*age return siri(D) } if (method === 'jackson7') { const { chest=0, axilla=0, triceps=0, subscap=0, suprailiac=0, abdomen=0, thigh=0 } = skinfolds const s = chest+axilla+triceps+subscap+suprailiac+abdomen+thigh if (s === 0) return null const D = sex === 'm' ? 1.112 - 0.00043499*s + 0.00000055*s*s - 0.00028826*age : 1.097 - 0.00046971*s + 0.00000056*s*s - 0.00012828*age return siri(D) } if (method === 'durnin') { const { biceps=0, triceps=0, subscap=0, suprailiac=0 } = skinfolds const s = biceps+triceps+subscap+suprailiac if (s === 0) return null const logS = Math.log10(s) const tbl = sex === 'm' ? [ [20,1.1620,0.0630],[30,1.1631,0.0632],[40,1.1422,0.0544],[50,1.1620,0.0700],[99,1.1715,0.0779] ] : [ [20,1.1549,0.0678],[30,1.1599,0.0717],[40,1.1423,0.0632],[50,1.1333,0.0612],[99,1.1339,0.0645] ] const [, c, m] = tbl.find(([maxAge]) => age < maxAge) || tbl[tbl.length-1] return siri(c - m * logS) } if (method === 'parrillo') { const sum = Object.values(skinfolds).reduce((a,b) => a+(b||0), 0) return Math.max(3, Math.min(50, (sum * 27) / 1000)) } return null } export const METHOD_POINTS = { jackson3: { m: ['chest','abdomen','thigh'], f: ['triceps','suprailiac','thigh'] }, jackson7: { m: ['chest','axilla','triceps','subscap','suprailiac','abdomen','thigh'], f: ['chest','axilla','triceps','subscap','suprailiac','abdomen','thigh'] }, durnin: { m: ['biceps','triceps','subscap','suprailiac'], f: ['biceps','triceps','subscap','suprailiac'] }, parrillo: { m: ['chest','axilla','triceps','subscap','suprailiac','abdomen','thigh','calf_med','lowerback'], f: ['chest','axilla','triceps','subscap','suprailiac','abdomen','thigh','calf_med','lowerback'] }, } export const BF_CATEGORIES = { m: [ {max:6, label:'Essenziell', color:'#378ADD', desc:'Unter diesem Wert sind lebenswichtige Fette betroffen.'}, {max:14, label:'Athletisch', color:'#1D9E75', desc:'Typisch für Leistungssportler – sehr definiert.'}, {max:18, label:'Fit', color:'#639922', desc:'Gute Fitness, gesunder Bereich für aktive Menschen.'}, {max:25, label:'Durchschnitt', color:'#EF9F27', desc:'Normaler Bereich für die allgemeine Bevölkerung.'}, {max:100,label:'Übergewicht', color:'#D85A30', desc:'Erhöhtes Gesundheitsrisiko, Reduktion empfohlen.'}, ], f: [ {max:14, label:'Essenziell', color:'#378ADD', desc:'Unter diesem Wert sind lebenswichtige Fette betroffen.'}, {max:21, label:'Athletisch', color:'#1D9E75', desc:'Typisch für Leistungssportlerinnen – sehr definiert.'}, {max:25, label:'Fit', color:'#639922', desc:'Gute Fitness, gesunder Bereich für aktive Frauen.'}, {max:32, label:'Durchschnitt', color:'#EF9F27', desc:'Normaler Bereich für die allgemeine Bevölkerung.'}, {max:100,label:'Übergewicht', color:'#D85A30', desc:'Erhöhtes Gesundheitsrisiko, Reduktion empfohlen.'}, ], } export function getBfCategory(pct, sex) { return BF_CATEGORIES[sex]?.find(c => pct <= c.max) || BF_CATEGORIES[sex]?.at(-1) } export function calcDerived(m, height) { const out = {} if (m.c_waist && m.c_hip) out.whr = Math.round(m.c_waist / m.c_hip * 100) / 100 if (m.c_waist && height) out.whtr = Math.round(m.c_waist / height * 100) / 100 if (m.lean_mass && height) out.ffmi = Math.round(m.lean_mass / ((height/100)**2) * 10) / 10 return out } export function getRuleBasedAssessment(current, previous, profile) { const sex = profile?.sex || 'm' const height = profile?.height || 178 const findings = [] if (current.body_fat_pct) { const cat = getBfCategory(current.body_fat_pct, sex) findings.push({ type: ['Athletisch','Fit'].includes(cat.label) ? 'good' : cat.label === 'Durchschnitt' ? 'info' : cat.label === 'Essenziell' ? 'warn' : 'bad', icon: '🔥', text: `Körperfett: ${current.body_fat_pct}% – ${cat.label}`, detail: cat.desc }) if (previous?.body_fat_pct) { const delta = Math.round((current.body_fat_pct - previous.body_fat_pct) * 10) / 10 if (Math.abs(delta) >= 0.5) findings.push({ type: delta < 0 ? 'good' : 'warn', icon: delta < 0 ? '📉' : '📈', text: `Körperfett ${delta > 0?'+':''}${delta}% seit letzter Messung`, detail: delta < 0 ? 'Positive Entwicklung – weiter so!' : 'Leichter Anstieg – Ernährung und Training überprüfen.' }) } } if (current.lean_mass && previous?.lean_mass) { const delta = Math.round((current.lean_mass - previous.lean_mass) * 10) / 10 if (Math.abs(delta) >= 0.3) findings.push({ type: delta > 0 ? 'good' : 'warn', icon: delta > 0 ? '💪' : '⚠️', text: `Magermasse ${delta > 0?'+':''}${delta} kg`, detail: delta > 0 ? 'Muskelaufbau detektiert – Training zeigt Wirkung.' : 'Rückgang der Magermasse – auf ausreichend Protein und Krafttraining achten.' }) } if (current.lean_mass && height) { const ffmi = Math.round(current.lean_mass / ((height/100)**2) * 10) / 10 const limit = sex === 'm' ? 25 : 22 findings.push({ type: ffmi >= 20 && ffmi <= limit ? 'good' : ffmi < 17 ? 'info' : 'warn', icon: '📐', text: `FFMI: ${ffmi}`, detail: ffmi < 17 ? 'Unterdurchschnittliche Muskelmasse – Krafttraining empfohlen.' : ffmi < 20 ? 'Durchschnittliche Muskelmasse.' : ffmi < 23 ? 'Gute Muskelmasse – gut trainierter Körper.' : ffmi <= limit ? 'Sehr hohe Muskelmasse – Leistungssportler-Niveau.' : `Sehr hoher FFMI – bei Werten über ${limit} kritisch hinterfragen.` }) } if (current.c_waist && current.c_hip) { const whr = Math.round(current.c_waist / current.c_hip * 100) / 100 const limit = sex === 'm' ? 0.90 : 0.85 findings.push({ type: whr < limit ? 'good' : whr < limit+0.05 ? 'warn' : 'bad', icon: '⚖️', text: `Waist-Hip-Ratio: ${whr} (Ziel: <${limit})`, detail: whr < limit ? 'Gesunde Fettverteilung – kein erhöhtes kardiovaskuläres Risiko.' : whr < limit+0.05 ? 'Grenzwertiger Bereich – Taillenumfang reduzieren empfohlen.' : 'Erhöhtes kardiovaskuläres Risiko durch abdominelle Fettverteilung.' }) } if (current.c_waist && height) { const whtr = Math.round(current.c_waist / height * 100) / 100 findings.push({ type: whtr < 0.50 ? 'good' : whtr < 0.60 ? 'warn' : 'bad', icon: '📏', text: `Waist-to-Height-Ratio: ${whtr} (Ziel: <0,50)`, detail: whtr < 0.50 ? 'Optimales Verhältnis – Taille halb so groß wie Körpergröße.' : whtr < 0.60 ? 'Leicht erhöhtes Risiko – Taillenumfang sollte reduziert werden.' : 'Deutlich erhöhtes Gesundheitsrisiko – ärztliche Beratung empfohlen.' }) } if (current.c_waist) { const limit = sex === 'm' ? 94 : 80 const limitHigh = sex === 'm' ? 102 : 88 if (current.c_waist > limit) findings.push({ type: current.c_waist > limitHigh ? 'bad' : 'warn', icon: '🔴', text: `Taillenumfang ${current.c_waist} cm (WHO-Grenzwert: ${limit} cm)`, detail: current.c_waist > limitHigh ? `Stark erhöhtes Risiko (WHO: über ${limitHigh} cm = hohes Risiko).` : `Leicht erhöhtes metabolisches Risiko laut WHO-Kriterien.` }) if (previous?.c_waist) { const delta = Math.round((current.c_waist - previous.c_waist) * 10) / 10 if (Math.abs(delta) >= 1) findings.push({ type: delta < 0 ? 'good' : 'warn', icon: delta < 0 ? '✅' : '📊', text: `Taille ${delta > 0?'+':''}${delta} cm seit letzter Messung`, detail: delta < 0 ? 'Taillenumfang nimmt ab – gute Entwicklung!' : 'Taille ist gewachsen – Ernährung überprüfen.' }) } } if (current.weight && previous?.weight) { const delta = Math.round((current.weight - previous.weight) * 10) / 10 if (Math.abs(delta) >= 0.3) findings.push({ type: 'info', icon: '⚖️', text: `Gewicht ${delta > 0?'+':''}${delta} kg seit letzter Messung`, detail: 'Kombination mit Körperfett-Verlauf beachten – Gewicht allein ist wenig aussagekräftig.' }) } const goods = findings.filter(f => f.type === 'good').length const bads = findings.filter(f => f.type === 'bad').length const warns = findings.filter(f => f.type === 'warn').length let summary, summaryType if (findings.length === 0) { summary = 'Zu wenig Daten. Bitte Umfänge und Körperfett ergänzen.'; summaryType = 'info' } else if (bads >= 2) { summary = 'Mehrere Werte im kritischen Bereich – gezielte Maßnahmen empfohlen.'; summaryType = 'bad' } else if (bads === 1 || warns >= 2) { summary = 'Einige Werte außerhalb des optimalen Bereichs – Verbesserungspotenzial vorhanden.'; summaryType = 'warn' } else if (goods >= 2) { summary = 'Gute Körperzusammensetzung – weiter so!'; summaryType = 'good' } else { summary = 'Werte im normalen Bereich – regelmäßig weiter messen.'; summaryType = 'info' } return { findings, summary, summaryType } } export const CIRCUMFERENCE_GUIDE = [ { id:'c_neck', name:'Hals', color:'#1D9E75', where:'Direkt unterhalb des Adamsapfels, schmalste Stelle des Halses', posture:'Gerade stehen, Kopf neutral, nicht nach vorne beugen', how:'Waagerecht, 1 Finger Luft zwischen Band und Hals', tip:'Morgens nüchtern messen für konsistente Werte' }, { id:'c_chest', name:'Brust', color:'#378ADD', where:'Breiteste Stelle des Brustkorbs, über den Brustmuskeln (Männer) bzw. vollsten Stelle der Brust (Frauen)', posture:'Aufrecht, Arme locker seitlich – am Ende normaler Ausatmung messen', how:'Waagerecht, parallel zum Boden, fest aber nicht einschneidend', tip:'Nicht einatmen beim Messen – Werte ändern sich um bis zu 5 cm!' }, { id:'c_waist', name:'Taille', color:'#EF9F27', where:'Schmalste Stelle des Rumpfes – meist 2–3 cm oberhalb des Bauchnabels', posture:'Aufrecht, Bauch entspannen, Arme locker hängen lassen', how:'Waagerecht, eng aber nicht zusammenpressend', tip:'Beim seitlichen Beugen wird die schmalste Stelle gut sichtbar' }, { id:'c_belly', name:'Bauch', color:'#D85A30', where:'Exakt auf Höhe des Bauchnabels', posture:'Stehend, Bauch vollständig entspannen – nicht einziehen!', how:'Waagerecht, ohne Druck', tip:'Wichtigster Einzelwert für viszerales Fett und Gesundheitsrisiko' }, { id:'c_hip', name:'Hüfte', color:'#D4537E', where:'Breiteste Stelle des Gesäßes, ca. 15–20 cm unterhalb des Bauchnabels', posture:'Aufrecht, Füße zusammen, Gewicht gleichmäßig', how:'Über die breiteste Stelle des Gesäßes, waagerecht', tip:'Für WHR: Taille ÷ Hüfte (Ziel: <0,85 Frauen / <0,90 Männer)' }, { id:'c_thigh', name:'Oberschenkel', color:'#7F77DD', where:'Dickste Stelle des Oberschenkels, 5 cm unterhalb des Schritts', posture:'Aufrecht, Gewicht gleichmäßig – nicht auf ein Bein verlagern!', how:'Waagerecht, immer rechte Seite, gleicher Abstand vom Schritt', tip:'Mit Lineal vorher markieren für reproduzierbare Werte' }, { id:'c_calf', name:'Wade', color:'#639922', where:'Dickste Stelle der Wade, Mitte zwischen Knöchel und Kniebeuge', posture:'Aufrecht, Gewicht gleichmäßig verteilt', how:'Waagerecht, Muskel entspannt', tip:'Morgens messen – abends schwellen Beine durch Wassereinlagerungen an' }, { id:'c_arm', name:'Oberarm', color:'#1D9E75', where:'Dickste Stelle, Mitte zwischen Schultergelenk und Ellenbogen', posture:'Arm locker hängen lassen und entspannen', how:'Waagerecht, senkrecht zur Längsachse', tip:'Immer denselben Arm (rechts); auch angespannt messen und notieren' }, ] export const CALIPER_GUIDE = { chest: { name:'Brust', color:'#378ADD', where:'Diagonale Falte, halb zwischen Achselhöhle und Brustwarze (Männer); 1/3 des Abstands (Frauen)', posture:'Aufrecht, Arm leicht angehoben', how:'Diagonale Falte (45°)', tip:'Liegt medial der Achselfalte – nicht zu weit nach außen greifen' }, axilla: { name:'Achsel', color:'#D4537E', where:'Mittlere Achsellinie, auf Höhe des Xiphoids (Brustbeinansatz)', posture:'Arm leicht nach vorne', how:'Vertikale Falte', tip:'Schwieriger Punkt – Helfer sinnvoll' }, triceps: { name:'Trizeps', color:'#EF9F27', where:'Rückseite Oberarm, Mitte zwischen Schultergelenk und Ellenbogen', posture:'Arm hängt entspannt seitlich', how:'Vertikale Falte, parallel zur Längsachse', tip:'Wichtigster Punkt in Frauen-Formeln – Arm vollständig entspannen' }, subscap: { name:'Schulterblatt', color:'#7F77DD', where:'1–2 cm unterhalb der unteren Schulterblatt-Ecke', posture:'Arm hängt locker, leicht nach hinten', how:'Diagonale Falte (45°) entlang natürlicher Hautlinien', tip:'Arm nach hinten halten lassen für besseren Zugang' }, suprailiac: { name:'Hüftkamm', color:'#D85A30', where:'Direkt oberhalb des Hüftkamms (Crista iliaca), vordere Achsellinie', posture:'Aufrecht, Arme leicht angehoben', how:'Diagonale Falte (45° nach innen-unten)', tip:'Liegt ÜBER dem Hüftknochen – nicht mit Bauch-Punkt verwechseln' }, abdomen: { name:'Bauch', color:'#D85A30', where:'2 cm rechts neben dem Bauchnabel', posture:'Stehend, Bauch entspannen', how:'Horizontale Falte', tip:'Bauch vollständig entspannen – nicht einziehen!' }, thigh: { name:'Oberschenkel', color:'#1D9E75', where:'Vorderseite, Mitte zwischen Leiste und Kniescheibe', posture:'Gewicht auf linkes Bein verlagern (rechter Muskel entspannt)', how:'Vertikale Falte', tip:'Gewicht aufs andere Bein – Muskel muss entspannt sein' }, calf_med: { name:'Wade (medial)', color:'#639922', where:'Innenseite der Wade, dickste Stelle', posture:'Sitzend, Fuß flach, Knie 90°', how:'Vertikale Falte', tip:'Bein vollständig entspannen' }, biceps: { name:'Bizeps', color:'#1D9E75', where:'Vorderseite Oberarm, Mitte zwischen Schultergelenk und Ellenbogen', posture:'Arm hängt entspannt', how:'Vertikale Falte', tip:'Nur Durnin-Methode' }, lowerback: { name:'Lendenwirbel', color:'#888780', where:'Über Lendenwirbelsäule, 2 cm seitlich der Mittellinie auf Höhe L4', posture:'Leicht nach vorne gebeugt', how:'Horizontale Falte', tip:'Helfer bitten – schwer allein erreichbar' }, }