Files
Home_assistant/backend/app/fitness/body_composition.py
T
2026-07-01 05:04:47 +00:00

120 lines
3.3 KiB
Python

import math
from typing import Any
def _is_female(sex: str) -> bool:
return sex.lower() in ("f", "female", "ж", "женский", "woman")
def _clamp_bf(value: float) -> float:
return round(max(3.0, min(50.0, value)), 1)
def navy_body_fat_pct(
*,
sex: str,
height_cm: float,
neck_cm: float,
waist_cm: float,
hip_cm: float | None = None,
) -> float | None:
if height_cm <= 0 or neck_cm <= 0 or waist_cm <= 0:
return None
if _is_female(sex):
if hip_cm is None or hip_cm <= 0:
return None
measurement_sum_cm = waist_cm + hip_cm - neck_cm
if measurement_sum_cm <= 0:
return None
denom = (
1.29579
- 0.35004 * math.log10(measurement_sum_cm)
+ 0.22100 * math.log10(height_cm)
)
else:
measurement_diff_cm = waist_cm - neck_cm
if measurement_diff_cm <= 0:
return None
denom = (
1.0324
- 0.19077 * math.log10(measurement_diff_cm)
+ 0.15456 * math.log10(height_cm)
)
if denom <= 0:
return None
return _clamp_bf(495.0 / denom - 450.0)
def whr(waist_cm: float, hip_cm: float) -> float | None:
if waist_cm <= 0 or hip_cm <= 0:
return None
return round(waist_cm / hip_cm, 2)
def lean_body_mass(weight_kg: float, body_fat_pct: float) -> float:
return round(weight_kg * (1.0 - body_fat_pct / 100.0), 1)
def ffmi(weight_kg: float, height_cm: float, body_fat_pct: float) -> float | None:
if height_cm <= 0:
return None
height_m = height_cm / 100.0
lbm = weight_kg * (1.0 - body_fat_pct / 100.0)
raw = lbm / (height_m * height_m)
normalized = raw + 6.1 * (1.8 - height_m)
return round(normalized, 1)
def compute_body_composition(
*,
sex: str,
height_cm: float,
weight_kg: float,
neck_cm: float | None = None,
waist_cm: float | None = None,
hip_cm: float | None = None,
body_fat_pct: float | None = None,
) -> dict[str, Any]:
warnings: list[str] = []
result: dict[str, Any] = {
"body_fat_pct": None,
"body_fat_method": None,
"whr": None,
"lbm_kg": None,
"ffmi": None,
"warnings": warnings,
}
bf = body_fat_pct
method: str | None = "manual" if bf is not None else None
if bf is None and neck_cm and waist_cm:
navy_bf = navy_body_fat_pct(
sex=sex,
height_cm=height_cm,
neck_cm=neck_cm,
waist_cm=waist_cm,
hip_cm=hip_cm,
)
if navy_bf is not None:
bf = navy_bf
method = "navy"
elif _is_female(sex) and not hip_cm:
warnings.append("Для Navy у женщин нужен обхват бёдер (hip_cm).")
elif neck_cm and waist_cm and waist_cm <= neck_cm:
warnings.append("Обхват талии должен быть больше шеи для Navy.")
if bf is not None:
result["body_fat_pct"] = round(float(bf), 1)
result["body_fat_method"] = method
result["lbm_kg"] = lean_body_mass(weight_kg, float(bf))
result["ffmi"] = ffmi(weight_kg, height_cm, float(bf))
if waist_cm and hip_cm:
result["whr"] = whr(waist_cm, hip_cm)
return result