| from typing import List, Dict, Optional
|
| import json
|
| import re
|
|
|
| class HealthScoreCalculator:
|
| def __init__(self):
|
|
|
| self.risk_categories = {
|
| 'preservatives': {
|
| 'weight': 2.0,
|
| 'ingredients': [
|
| 'sodium benzoate', 'potassium sorbate', 'sulfites', 'nitrites',
|
| 'BHA', 'BHT', 'propylene glycol', 'sodium nitrate'
|
| ]
|
| },
|
| 'artificial_colors': {
|
| 'weight': 1.5,
|
| 'ingredients': [
|
| 'red 40', 'yellow 5', 'yellow 6', 'blue 1', 'blue 2',
|
| 'green 3', 'tartrazine', 'brilliant blue'
|
| ]
|
| },
|
| 'artificial_sweeteners': {
|
| 'weight': 1.8,
|
| 'ingredients': [
|
| 'aspartame', 'sucralose', 'saccharin', 'acesulfame k',
|
| 'neotame', 'advantame'
|
| ]
|
| },
|
| 'harmful_chemicals': {
|
| 'weight': 2.5,
|
| 'ingredients': [
|
| 'parabens', 'phthalates', 'formaldehyde', 'toluene',
|
| 'triclosan', 'lead acetate', 'petroleum'
|
| ]
|
| }
|
| }
|
|
|
|
|
| self.cosmetic_categories = {
|
| 'irritants': {
|
| 'weight': 1.7,
|
| 'ingredients': [
|
| 'sodium lauryl sulfate', 'alcohol denat', 'isopropyl alcohol',
|
| 'fragrance', 'essential oils'
|
| ]
|
| },
|
| 'comedogenic': {
|
| 'weight': 1.3,
|
| 'ingredients': [
|
| 'coconut oil', 'cocoa butter', 'isopropyl myristate',
|
| 'sodium chloride', 'laureth-4'
|
| ]
|
| }
|
| }
|
|
|
|
|
| self.food_categories = {
|
| 'trans_fats': {
|
| 'weight': 2.2,
|
| 'ingredients': [
|
| 'partially hydrogenated', 'hydrogenated oil',
|
| 'shortening', 'margarine'
|
| ]
|
| },
|
| 'added_sugars': {
|
| 'weight': 1.6,
|
| 'ingredients': [
|
| 'high fructose corn syrup', 'corn syrup', 'dextrose',
|
| 'maltose', 'sucrose', 'cane sugar', 'brown sugar'
|
| ]
|
| }
|
| }
|
|
|
| def calculate_ingredient_position_weight(self, position: int, total_ingredients: int) -> float:
|
| """Calculate weight based on ingredient position (earlier ingredients have higher weight)"""
|
| if total_ingredients == 0:
|
| return 1.0
|
| return 1.0 + (1.0 - (position / total_ingredients))
|
|
|
| def identify_risks(self, ingredient: str) -> List[str]:
|
| """Identify all risk categories an ingredient belongs to"""
|
| risks = []
|
| ingredient = ingredient.lower()
|
|
|
|
|
| all_categories = {
|
| **self.risk_categories,
|
| **self.cosmetic_categories,
|
| **self.food_categories
|
| }
|
|
|
| for category, data in all_categories.items():
|
| if any(risk_ingredient in ingredient for risk_ingredient in data['ingredients']):
|
| risks.append(category)
|
|
|
| return risks
|
|
|
| def calculate_score(self, ingredients: List[str], product_type: str = 'food') -> Dict:
|
| """Calculate health score and provide risk breakdown"""
|
| if not ingredients:
|
| return {
|
| 'score': 500,
|
| 'risks': {},
|
| 'explanation': "No ingredients provided for analysis"
|
| }
|
|
|
| total_ingredients = len(ingredients)
|
| risk_points = 0
|
| risk_breakdown = {}
|
|
|
|
|
| categories = {**self.risk_categories}
|
| if product_type.lower() == 'food':
|
| categories.update(self.food_categories)
|
| else:
|
| categories.update(self.cosmetic_categories)
|
|
|
|
|
| for position, ingredient in enumerate(ingredients):
|
| position_weight = self.calculate_ingredient_position_weight(position, total_ingredients)
|
| risks = self.identify_risks(ingredient)
|
|
|
| for risk in risks:
|
| if risk in categories:
|
| risk_weight = categories[risk]['weight']
|
| risk_value = risk_weight * position_weight
|
| risk_points += risk_value
|
|
|
| if risk not in risk_breakdown:
|
| risk_breakdown[risk] = {
|
| 'ingredients': [],
|
| 'total_risk': 0
|
| }
|
| risk_breakdown[risk]['ingredients'].append(ingredient)
|
| risk_breakdown[risk]['total_risk'] += risk_value
|
|
|
|
|
| base_score = 1000
|
| risk_multiplier = 100
|
| final_score = max(0, min(1000, base_score - (risk_points * risk_multiplier)))
|
|
|
|
|
| explanation = self._generate_explanation(risk_breakdown, final_score)
|
|
|
| return {
|
| 'score': int(final_score),
|
| 'risks': risk_breakdown,
|
| 'explanation': explanation
|
| }
|
|
|
| def _generate_explanation(self, risk_breakdown: Dict, score: float) -> str:
|
| """Generate a human-readable explanation of the health score"""
|
| if score > 800:
|
| status = "very healthy"
|
| elif score > 600:
|
| status = "moderately healthy"
|
| elif score > 400:
|
| status = "moderate risk"
|
| elif score > 200:
|
| status = "high risk"
|
| else:
|
| status = "very high risk"
|
|
|
| explanation = f"This product is considered {status} with a score of {int(score)}."
|
|
|
| if risk_breakdown:
|
| explanation += "\n\nKey concerns:"
|
| for risk, data in risk_breakdown.items():
|
| risk_name = risk.replace('_', ' ').title()
|
| ingredients = ', '.join(data['ingredients'])
|
| explanation += f"\n- {risk_name}: Found {len(data['ingredients'])} concerning ingredient(s): {ingredients}"
|
| else:
|
| explanation += "\n\nNo specific risk factors identified in the ingredients list."
|
|
|
| return explanation
|
|
|
| def analyze_nutrition(self, nutrition_info: Dict) -> Dict:
|
| """Analyze nutrition information and provide recommendations"""
|
| analysis = {
|
| 'concerns': [],
|
| 'positives': [],
|
| 'recommendations': []
|
| }
|
|
|
|
|
| if 'calories' in nutrition_info:
|
| if nutrition_info['calories'] > 400:
|
| analysis['concerns'].append("High calorie content")
|
| elif nutrition_info['calories'] < 50:
|
| analysis['positives'].append("Low calorie content")
|
|
|
|
|
| if 'protein' in nutrition_info:
|
| if nutrition_info['protein'] > 15:
|
| analysis['positives'].append("Good source of protein")
|
| elif nutrition_info['protein'] < 5:
|
| analysis['recommendations'].append("Consider options with more protein")
|
|
|
|
|
| if 'fat' in nutrition_info:
|
| if nutrition_info['fat'] > 15:
|
| analysis['concerns'].append("High fat content")
|
|
|
|
|
| if 'sugar' in nutrition_info:
|
| if nutrition_info['sugar'] > 10:
|
| analysis['concerns'].append("High sugar content")
|
| analysis['recommendations'].append("Look for options with less sugar")
|
|
|
|
|
| if 'fiber' in nutrition_info:
|
| if nutrition_info['fiber'] > 5:
|
| analysis['positives'].append("Good source of fiber")
|
| elif nutrition_info['fiber'] < 2:
|
| analysis['recommendations'].append("Consider options with more fiber")
|
|
|
| return analysis |