File size: 8,626 Bytes
eeb0f9c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
"""
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()
        }
    
    @field_validator('height', mode='before')
    @classmethod
    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
    
    @field_validator('weight', mode='before')
    @classmethod
    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
    
    @field_validator('age', mode='before')
    @classmethod
    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
    
    @field_validator('bmi', mode='before')
    @classmethod
    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
    
    @model_validator(mode='after')
    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()
        }
    
    @field_validator('height', mode='before')
    @classmethod
    def parse_height(cls, v):
        """Parse height from various formats"""
        if v is None:
            return None
        return HealthDataParser.parse_height(v)
    
    @field_validator('weight', mode='before')
    @classmethod
    def parse_weight(cls, v):
        """Parse weight from various formats"""
        if v is None:
            return None
        return HealthDataParser.parse_weight(v)
    
    @field_validator('age', mode='before')
    @classmethod
    def parse_age(cls, v):
        """Parse age from various formats"""
        if v is None:
            return None
        return HealthDataParser.parse_age(v)
    
    @model_validator(mode='after')
    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)