diff --git a/src/body_score.py b/src/body_score.py new file mode 100644 index 0000000..12f1d6d --- /dev/null +++ b/src/body_score.py @@ -0,0 +1,181 @@ + +# Reverse engineered from amazfit's app (also known as Mi Fit) +from body_scales import bodyScales +class bodyScore: + + def __init__(self, age, sex, height, weight, bmi, bodyfat, muscle, water, visceral_fat, bone, basal_metabolism, protein): + self.age = age + self.sex = sex + self.height = height + self.weight = weight + self.bmi = bmi + self.bodyfat = bodyfat + self.muscle = muscle + self.water = water + self.visceral_fat = visceral_fat + self.bone = bone + self.basal_metabolism = basal_metabolism + self.protein = protein + self.scales = bodyScales(age, height, sex, weight) + + def getBodyScore(self): + score = 100 + score -= self.getBmiDeductScore() + score -= self.getBodyFatDeductScore() + score -= self.getMuscleDeductScore() + score -= self.getWaterDeductScore() + score -= self.getVisceralFatDeductScore() + score -= self.getBoneDeductScore() + score -= self.getBasalMetabolismDeductScore() + if self.protein: + score -= self.getProteinDeductScore() + return score + + def getMalus(self, data, min_data, max_data, max_malus, min_malus): + result = ((data - max_data) / (min_data - max_data)) * float(max_malus - min_malus) + if result >= 0.0: + return result + return 0.0 + + def getBmiDeductScore(self): + if not self.height >= 90: + # "BMI is not reasonable + return 0.0 + + bmi_low = 15.0 + bmi_verylow = 14.0 + bmi_normal = 18.5 + bmi_overweight = 28.0 + bmi_obese = 32.0 + fat_scale = self.scales.getFatPercentageScale() + + # Perfect range (bmi >= 18.5 and bodyfat not high for adults, bmi >= 15.0 for kids + if self.bmi >= 18.5 and self.age >= 18 and self.bodyfat < fat_scale[2]: + return 0.0 + elif self.bmi >= bmi_verylow and self.age < 18 and self.bodyfat < fat_scale[2]: + return 0.0 + + # Extremely skinny (bmi < 14) + elif self.bmi <= bmi_verylow: + return 30.0 + # Too skinny (bmi between 14 and 15) + elif self.bmi > bmi_verylow and self.bmi < bmi_low: + return self.getMalus(self.bmi, bmi_verylow, bmi_low, 30, 15) + 15.0 + # Skinny (for adults, between 15 and 18.5) + elif self.bmi >= bmi_low and self.bmi < bmi_normal and self.age >= 18: + return self.getMalus(self.bmi, 15.0, 18.5, 15, 5) + 5.0 + + # Normal or high bmi but too much bodyfat + elif ((self.bmi >= bmi_low and self.age < 18) or (self.bmi >= bmi_normal and self.age >= 18)) and self.bodyfat >= fat_scale[2]: + # Obese + if self.bmi >= bmi_obese: + return 10.0 + # Overweight + if self.bmi > bmi_overweight: + return self.getMalus(self.bmi, 28.0, 25.0, 5, 10) + 5.0 + else: + return 0.0 + + def getBodyFatDeductScore(self): + scale = self.scales.getFatPercentageScale() + + if self.sex == 'male': + best = scale[2] - 3.0 + elif self.sex == 'female': + best = scale[2] - 2.0 + + # Slighly low in fat or low part or normal fat + if self.bodyfat >= scale[0] and self.bodyfat < best: + return 0.0 + elif self.bodyfat >= scale[3]: + return 20.0 + else: + # Sightly high body fat + if self.bodyfat < scale[3]: + return self.getMalus(self.bodyfat, scale[3], scale[2], 20, 10) + 10.0 + + # High part of normal fat + elif self.bodyfat <= normal[2]: + return self.getMalus(self.bodyfat, scale[2], best, 3, 9) + 3.0 + + # Very low in fat + elif self.bodyfat < normal[0]: + return self.getMalus(self.bodyfat, 1.0, scale[0], 3, 10) + 3.0 + + + def getMuscleDeductScore(self): + scale = self.scales.getMuscleMassScale() + + # For some reason, there's code to return self.calculate(muscle, normal[0], normal[0]+2.0, 3, 5) + 3.0 + # if your muscle is between normal[0] and normal[0] + 2.0, but it's overwritten with 0.0 before return + if self.muscle >= scale[0]: + return 0.0 + elif self.muscle < (scale[0] - 5.0): + return 10.0 + else: + return self.getMalus(self.muscle, scale[0] - 5.0, scale[0], 10, 5) + 5.0 + + # No malus = normal or good; maximum malus (10.0) = less than normal-5.0; + # malus = between 5 and 10, on your water being between normal-5.0 and normal + def getWaterDeductScore(self): + scale = self.scales.getWaterPercentageScale() + + if self.water >= scale[0]: + return 0.0 + elif self.water <= (scale[0] - 5.0): + return 10.0 + else: + return self.getMalus(self.water, scale[0] - 5.0, scale[0], 10, 5) + 5.0 + + # No malus = normal; maximum malus (15.0) = very high; malus = between 10 and 15 + # with your visceral fat in your high range + def getVisceralFatDeductScore(self): + scale = self.scales.getVisceralFatScale() + + if self.visceral_fat < scale[0]: + # For some reason, the original app would try to + # return 3.0 if vfat == 8 and 5.0 if vfat == 9 + # but i's overwritten with 0.0 anyway before return + return 0.0 + elif self.visceral_fat >= scale[1]: + return 15.0 + else: + return self.getMalus(self.visceral_fat, scale[1], scale[0], 15, 10) + 10.0 + + def getBoneDeductScore(self): + scale = self.scales.getBoneMassScale() + + if self.bone >= scale[0]: + return 0.0 + elif self.bone <= (scale[0] - 0.3): + return 10.0 + else: + return self.getMalus(self.bone, scale[0] - 0.3, scale[0], 10, 5) + 5.0 + + def getBasalMetabolismDeductScore(self): + # Get normal BMR + normal = self.scales.getBMRScale()[0] + + if self.basal_metabolism >= normal: + return 0.0 + elif self.basal_metabolism <= (normal - 300): + return 6.0 + else: + # It's really + 5.0 in the app, but it's probably a mistake, should be 3.0 + return self.getMalus(self.basal_metabolism, normal - 300, normal, 6, 3) + 5.0 + + + # Get protein percentage malus + def getProteinDeductScore(self): + # low: 10,16; normal: 16,17 + # Check limits + if self.protein > 17.0: + return 0.0 + elif self.protein < 10.0: + return 10.0 + else: + # Return values for low proteins or normal proteins + if self.protein <= 16.0: + return self.getMalus(self.protein, 10.0, 16.0, 10, 5) + 5.0 + elif self.protein <= 17.0: + return self.getMalus(self.protein, 16.0, 17.0, 5, 3) + 3.0