Spaces:
Runtime error
Runtime error
| """ | |
| Pydantic Models for Health Data | |
| Provides automatic validation and parsing | |
| """ | |
| from datetime import datetime | |
| from typing import Optional, List, Dict, Any, Union | |
| from pydantic import BaseModel, Field, field_validator, model_validator | |
| from enum import Enum | |
| from .validators import HealthDataParser, HealthDataValidator | |
| class Gender(str, Enum): | |
| """Gender enum""" | |
| MALE = "male" | |
| FEMALE = "female" | |
| OTHER = "other" | |
| class ActivityLevel(str, Enum): | |
| """Activity level enum""" | |
| SEDENTARY = "sedentary" # Ít vận động | |
| LIGHT = "light" # Vận động nhẹ | |
| MODERATE = "moderate" # Vận động vừa | |
| ACTIVE = "active" # Vận động nhiều | |
| VERY_ACTIVE = "very_active" # Vận động rất nhiều | |
| class FitnessLevel(str, Enum): | |
| """Fitness level enum""" | |
| BEGINNER = "beginner" | |
| INTERMEDIATE = "intermediate" | |
| ADVANCED = "advanced" | |
| class RecordType(str, Enum): | |
| """Health record type""" | |
| NUTRITION = "nutrition" | |
| EXERCISE = "exercise" | |
| SYMPTOM = "symptom" | |
| MENTAL_HEALTH = "mental_health" | |
| GENERAL_HEALTH = "general_health" | |
| class HealthRecord(BaseModel): | |
| """ | |
| Health Record with Pydantic validation | |
| Automatically validates and normalizes health data | |
| """ | |
| record_id: str = Field(default_factory=lambda: str(__import__('uuid').uuid4())) | |
| user_id: str | |
| record_type: RecordType | |
| data: Dict[str, Any] = Field(default_factory=dict) | |
| timestamp: datetime = Field(default_factory=datetime.now) | |
| agent_name: Optional[str] = None | |
| confidence: float = Field(default=0.5, ge=0.0, le=1.0) | |
| # Health metrics (optional, extracted from data) | |
| height: Optional[float] = Field(None, description="Height in cm") | |
| weight: Optional[float] = Field(None, description="Weight in kg") | |
| age: Optional[int] = Field(None, description="Age in years") | |
| gender: Optional[Gender] = None | |
| bmi: Optional[float] = Field(None, description="BMI") | |
| class Config: | |
| use_enum_values = True | |
| json_encoders = { | |
| datetime: lambda v: v.isoformat() | |
| } | |
| def parse_height(cls, v): | |
| """Parse height from various formats""" | |
| if v is None: | |
| return None | |
| parsed = HealthDataParser.parse_height(v) | |
| if parsed is not None: | |
| is_valid, error = HealthDataValidator.validate_height(parsed) | |
| if not is_valid: | |
| raise ValueError(error) | |
| return parsed | |
| def parse_weight(cls, v): | |
| """Parse weight from various formats""" | |
| if v is None: | |
| return None | |
| parsed = HealthDataParser.parse_weight(v) | |
| if parsed is not None: | |
| is_valid, error = HealthDataValidator.validate_weight(parsed) | |
| if not is_valid: | |
| raise ValueError(error) | |
| return parsed | |
| def parse_age(cls, v): | |
| """Parse age from various formats""" | |
| if v is None: | |
| return None | |
| parsed = HealthDataParser.parse_age(v) | |
| if parsed is not None: | |
| is_valid, error = HealthDataValidator.validate_age(parsed) | |
| if not is_valid: | |
| raise ValueError(error) | |
| return parsed | |
| def parse_bmi(cls, v): | |
| """Parse BMI""" | |
| if v is None: | |
| return None | |
| parsed = HealthDataParser.parse_bmi(v) | |
| if parsed is not None: | |
| is_valid, error = HealthDataValidator.validate_bmi(parsed) | |
| if not is_valid: | |
| raise ValueError(error) | |
| return parsed | |
| def calculate_bmi_if_missing(self): | |
| """Auto-calculate BMI if weight and height are provided""" | |
| if self.bmi is None and self.weight and self.height: | |
| self.bmi = HealthDataValidator.calculate_bmi(self.weight, self.height) | |
| return self | |
| class UserHealthProfile(BaseModel): | |
| """ | |
| User Health Profile with Pydantic validation | |
| """ | |
| user_id: str | |
| age: Optional[int] = Field(None, ge=13, le=150) | |
| gender: Optional[Gender] = None | |
| weight: Optional[float] = Field(None, ge=20, le=300, description="Weight in kg") | |
| height: Optional[float] = Field(None, ge=50, le=300, description="Height in cm") | |
| bmi: Optional[float] = Field(None, ge=10, le=60) | |
| activity_level: Optional[ActivityLevel] = None | |
| fitness_level: Optional[FitnessLevel] = None | |
| health_conditions: List[str] = Field(default_factory=list) | |
| medications: List[str] = Field(default_factory=list) | |
| allergies: List[str] = Field(default_factory=list) | |
| dietary_restrictions: List[str] = Field(default_factory=list) | |
| created_at: datetime = Field(default_factory=datetime.now) | |
| updated_at: datetime = Field(default_factory=datetime.now) | |
| class Config: | |
| use_enum_values = True | |
| json_encoders = { | |
| datetime: lambda v: v.isoformat() | |
| } | |
| def parse_height(cls, v): | |
| """Parse height from various formats""" | |
| if v is None: | |
| return None | |
| return HealthDataParser.parse_height(v) | |
| def parse_weight(cls, v): | |
| """Parse weight from various formats""" | |
| if v is None: | |
| return None | |
| return HealthDataParser.parse_weight(v) | |
| def parse_age(cls, v): | |
| """Parse age from various formats""" | |
| if v is None: | |
| return None | |
| return HealthDataParser.parse_age(v) | |
| def calculate_bmi_if_missing(self): | |
| """Auto-calculate BMI if weight and height are provided""" | |
| if self.bmi is None and self.weight and self.height: | |
| self.bmi = HealthDataValidator.calculate_bmi(self.weight, self.height) | |
| return self | |
| def get_bmi_category(self) -> str: | |
| """Get BMI category""" | |
| return HealthDataValidator.get_bmi_category(self.bmi) | |
| def is_complete(self) -> bool: | |
| """Check if profile has all essential data""" | |
| return all([ | |
| self.age is not None, | |
| self.gender is not None, | |
| self.weight is not None, | |
| self.height is not None | |
| ]) | |
| def get_missing_fields(self) -> List[str]: | |
| """Get list of missing essential fields""" | |
| missing = [] | |
| if self.age is None: | |
| missing.append('age') | |
| if self.gender is None: | |
| missing.append('gender') | |
| if self.weight is None: | |
| missing.append('weight') | |
| if self.height is None: | |
| missing.append('height') | |
| return missing | |
| class NutritionRecord(HealthRecord): | |
| """Nutrition-specific health record""" | |
| record_type: RecordType = Field(default=RecordType.NUTRITION, frozen=True) | |
| calories: Optional[float] = Field(None, ge=0, le=10000) | |
| protein: Optional[float] = Field(None, ge=0, le=500) | |
| carbs: Optional[float] = Field(None, ge=0, le=1000) | |
| fat: Optional[float] = Field(None, ge=0, le=500) | |
| meal_type: Optional[str] = None # breakfast/lunch/dinner/snack | |
| class ExerciseRecord(HealthRecord): | |
| """Exercise-specific health record""" | |
| record_type: RecordType = Field(default=RecordType.EXERCISE, frozen=True) | |
| exercise_type: Optional[str] = None # cardio/strength/flexibility/sports | |
| duration_minutes: Optional[int] = Field(None, ge=0, le=600) | |
| intensity: Optional[str] = None # low/medium/high | |
| calories_burned: Optional[float] = Field(None, ge=0, le=5000) | |
| class SymptomRecord(HealthRecord): | |
| """Symptom-specific health record""" | |
| record_type: RecordType = Field(default=RecordType.SYMPTOM, frozen=True) | |
| symptoms: List[str] = Field(default_factory=list) | |
| severity: Optional[int] = Field(None, ge=1, le=10) | |
| duration_days: Optional[int] = Field(None, ge=0, le=365) | |
| body_part: Optional[str] = None | |
| class MentalHealthRecord(HealthRecord): | |
| """Mental health-specific health record""" | |
| record_type: RecordType = Field(default=RecordType.MENTAL_HEALTH, frozen=True) | |
| mood: Optional[str] = None | |
| stress_level: Optional[int] = Field(None, ge=1, le=10) | |
| sleep_hours: Optional[float] = Field(None, ge=0, le=24) | |
| sleep_quality: Optional[int] = Field(None, ge=1, le=10) | |