Files
chat/services/calendar_service/schemas.py
Andrew K. Choi b98034b616
All checks were successful
continuous-integration/drone/push Build is passing
calendar
2025-09-26 14:12:49 +09:00

247 lines
7.5 KiB
Python

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