This commit is contained in:
247
services/calendar_service/schemas.py
Normal file
247
services/calendar_service/schemas.py
Normal file
@@ -0,0 +1,247 @@
|
||||
from datetime import date, datetime
|
||||
from enum import Enum
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class EntryType(str, Enum):
|
||||
PERIOD = "period"
|
||||
OVULATION = "ovulation"
|
||||
SYMPTOMS = "symptoms"
|
||||
MEDICATION = "medication"
|
||||
MOOD = "mood"
|
||||
EXERCISE = "exercise"
|
||||
APPOINTMENT = "appointment"
|
||||
|
||||
|
||||
# Мобильное приложение использует другие названия типов
|
||||
class MobileEntryType(str, Enum):
|
||||
MENSTRUATION = "MENSTRUATION"
|
||||
OVULATION = "OVULATION"
|
||||
SPOTTING = "SPOTTING"
|
||||
DISCHARGE = "DISCHARGE"
|
||||
PAIN = "PAIN"
|
||||
MOOD = "MOOD"
|
||||
|
||||
def to_server_type(self) -> EntryType:
|
||||
"""Конвертировать тип из мобильного приложения в тип сервера"""
|
||||
mapping = {
|
||||
self.MENSTRUATION: EntryType.PERIOD,
|
||||
self.OVULATION: EntryType.OVULATION,
|
||||
self.SPOTTING: EntryType.SYMPTOMS,
|
||||
self.DISCHARGE: EntryType.SYMPTOMS,
|
||||
self.PAIN: EntryType.SYMPTOMS,
|
||||
self.MOOD: EntryType.MOOD,
|
||||
}
|
||||
return mapping.get(self, EntryType.SYMPTOMS)
|
||||
|
||||
|
||||
class FlowIntensity(str, Enum):
|
||||
LIGHT = "light"
|
||||
MEDIUM = "medium"
|
||||
HEAVY = "heavy"
|
||||
SPOTTING = "spotting"
|
||||
|
||||
@classmethod
|
||||
def from_int(cls, value: int) -> 'FlowIntensity':
|
||||
"""Конвертировать числовое значение в enum"""
|
||||
if value <= 1:
|
||||
return cls.LIGHT
|
||||
elif value <= 3:
|
||||
return cls.MEDIUM
|
||||
else:
|
||||
return cls.HEAVY
|
||||
|
||||
|
||||
class MobileMoodType(str, Enum):
|
||||
"""Типы настроения из мобильного приложения"""
|
||||
HAPPY = "HAPPY"
|
||||
SAD = "SAD"
|
||||
NORMAL = "NORMAL"
|
||||
STRESSED = "STRESSED"
|
||||
ANXIOUS = "ANXIOUS"
|
||||
IRRITATED = "IRRITATED"
|
||||
|
||||
|
||||
class MoodType(str, Enum):
|
||||
HAPPY = "happy"
|
||||
SAD = "sad"
|
||||
ANXIOUS = "anxious"
|
||||
IRRITATED = "irritated"
|
||||
ENERGETIC = "energetic"
|
||||
TIRED = "tired"
|
||||
|
||||
@classmethod
|
||||
def from_mobile_mood(cls, mood_type: 'MobileMoodType') -> Optional['MoodType']:
|
||||
"""Конвертировать из мобильного типа настроения"""
|
||||
if mood_type == MobileMoodType.NORMAL:
|
||||
return None
|
||||
mapping = {
|
||||
MobileMoodType.HAPPY: cls.HAPPY,
|
||||
MobileMoodType.SAD: cls.SAD,
|
||||
MobileMoodType.ANXIOUS: cls.ANXIOUS,
|
||||
MobileMoodType.IRRITATED: cls.IRRITATED,
|
||||
MobileMoodType.STRESSED: cls.ANXIOUS,
|
||||
}
|
||||
return mapping.get(mood_type)
|
||||
|
||||
|
||||
class CalendarEntryBase(BaseModel):
|
||||
entry_date: date
|
||||
entry_type: EntryType
|
||||
flow_intensity: Optional[FlowIntensity] = None
|
||||
period_symptoms: Optional[str] = Field(None, max_length=500)
|
||||
mood: Optional[MoodType] = None
|
||||
energy_level: Optional[int] = Field(None, ge=1, le=5)
|
||||
sleep_hours: Optional[int] = Field(None, ge=0, le=24)
|
||||
symptoms: Optional[str] = Field(None, max_length=1000)
|
||||
medications: Optional[str] = Field(None, max_length=500)
|
||||
notes: Optional[str] = Field(None, max_length=1000)
|
||||
|
||||
|
||||
class MobileSymptom(str, Enum):
|
||||
"""Симптомы, используемые в мобильном приложении"""
|
||||
CRAMPS = "CRAMPS"
|
||||
HEADACHE = "HEADACHE"
|
||||
BLOATING = "BLOATING"
|
||||
FATIGUE = "FATIGUE"
|
||||
NAUSEA = "NAUSEA"
|
||||
BREAST_TENDERNESS = "BREAST_TENDERNESS"
|
||||
ACNE = "ACNE"
|
||||
BACKACHE = "BACKACHE"
|
||||
|
||||
|
||||
class CalendarEventCreate(BaseModel):
|
||||
"""Модель создания события календаря из мобильного приложения"""
|
||||
date: date
|
||||
type: MobileEntryType
|
||||
flow_intensity: Optional[int] = Field(None, ge=1, le=5)
|
||||
mood: Optional[MobileMoodType] = None
|
||||
symptoms: Optional[List[MobileSymptom]] = None
|
||||
notes: Optional[str] = Field(None, max_length=1000)
|
||||
|
||||
def to_server_format(self) -> 'CalendarEntryCreate':
|
||||
"""Преобразовать в серверный формат"""
|
||||
symptoms_str = None
|
||||
if self.symptoms:
|
||||
symptoms_str = ", ".join([s.value for s in self.symptoms])
|
||||
|
||||
flow = None
|
||||
if self.flow_intensity is not None:
|
||||
flow = FlowIntensity.from_int(self.flow_intensity)
|
||||
|
||||
mood_val = None
|
||||
if self.mood:
|
||||
mood_val = MoodType.from_mobile_mood(self.mood)
|
||||
|
||||
return CalendarEntryCreate(
|
||||
entry_date=self.date,
|
||||
entry_type=self.type.to_server_type(),
|
||||
flow_intensity=flow,
|
||||
mood=mood_val,
|
||||
symptoms=symptoms_str,
|
||||
notes=self.notes,
|
||||
period_symptoms=None,
|
||||
energy_level=None,
|
||||
sleep_hours=None,
|
||||
medications=None
|
||||
)
|
||||
|
||||
|
||||
class CalendarEntryCreate(CalendarEntryBase):
|
||||
pass
|
||||
|
||||
|
||||
class CalendarEntryResponse(BaseModel):
|
||||
id: int
|
||||
uuid: str
|
||||
entry_date: date
|
||||
entry_type: str
|
||||
flow_intensity: Optional[str]
|
||||
period_symptoms: Optional[str]
|
||||
mood: Optional[str]
|
||||
energy_level: Optional[int]
|
||||
sleep_hours: Optional[int]
|
||||
symptoms: Optional[str]
|
||||
medications: Optional[str]
|
||||
notes: Optional[str]
|
||||
is_predicted: bool
|
||||
confidence_score: Optional[int]
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Модель ответа для мобильного приложения
|
||||
class CalendarEvent(BaseModel):
|
||||
id: int
|
||||
uuid: str
|
||||
date: date
|
||||
type: str
|
||||
flow_intensity: Optional[int] = None
|
||||
mood: Optional[str] = None
|
||||
symptoms: Optional[List[str]] = None
|
||||
notes: Optional[str] = None
|
||||
created_at: datetime
|
||||
|
||||
@classmethod
|
||||
def from_server_response(cls, entry: CalendarEntryResponse) -> 'CalendarEvent':
|
||||
"""Преобразовать из серверной модели в модель для мобильного приложения"""
|
||||
# Преобразование flow_intensity из строки в число
|
||||
flow_int = None
|
||||
if entry.flow_intensity:
|
||||
if entry.flow_intensity == "light":
|
||||
flow_int = 1
|
||||
elif entry.flow_intensity == "medium":
|
||||
flow_int = 3
|
||||
elif entry.flow_intensity == "heavy":
|
||||
flow_int = 5
|
||||
|
||||
# Преобразование symptoms из строки в список
|
||||
symptoms_list = None
|
||||
if entry.symptoms:
|
||||
symptoms_list = [s.strip() for s in entry.symptoms.split(",")]
|
||||
|
||||
return cls(
|
||||
id=entry.id,
|
||||
uuid=entry.uuid,
|
||||
date=entry.entry_date,
|
||||
type=entry.entry_type.upper(),
|
||||
flow_intensity=flow_int,
|
||||
mood=entry.mood.upper() if entry.mood else None,
|
||||
symptoms=symptoms_list,
|
||||
notes=entry.notes,
|
||||
created_at=entry.created_at
|
||||
)
|
||||
|
||||
|
||||
class CycleDataResponse(BaseModel):
|
||||
id: int
|
||||
cycle_start_date: date
|
||||
cycle_length: Optional[int]
|
||||
period_length: Optional[int]
|
||||
ovulation_date: Optional[date]
|
||||
|
||||
|
||||
class CycleOverview(BaseModel):
|
||||
current_cycle_day: Optional[int]
|
||||
current_phase: str # menstrual, follicular, ovulation, luteal
|
||||
next_period_date: Optional[date]
|
||||
days_until_period: Optional[int]
|
||||
cycle_regularity: str # very_regular, regular, irregular, very_irregular
|
||||
avg_cycle_length: Optional[int]
|
||||
|
||||
|
||||
class HealthInsightResponse(BaseModel):
|
||||
id: int
|
||||
insight_type: str
|
||||
title: str
|
||||
description: str
|
||||
recommendation: Optional[str]
|
||||
confidence_level: str
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
Reference in New Issue
Block a user