Files
chat/services/calendar_service/schemas.py
Andrew K. Choi 91c7e04474
All checks were successful
continuous-integration/drone/push Build is passing
API refactor
2025-10-07 16:25:52 +09:00

278 lines
9.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from datetime import date, datetime
from enum import Enum
from typing import List, Optional, Union, Any
import uuid
from uuid import UUID
from pydantic import BaseModel, Field, field_serializer, ConfigDict
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_str: str) -> Optional['MoodType']:
"""Конвертировать строку настроения из мобильного приложения"""
if not mood_str:
return None
# Преобразуем строку в enum или используем исходную строку, если не удалось
try:
mood_type = MobileMoodType(mood_str)
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)
except ValueError:
# Если строка не является допустимым MobileMoodType
# Пробуем сопоставить с сервером напрямую
mood_map = {
"HAPPY": cls.HAPPY,
"SAD": cls.SAD,
"ANXIOUS": cls.ANXIOUS,
"IRRITATED": cls.IRRITATED,
"ENERGETIC": cls.ENERGETIC,
"TIRED": cls.TIRED,
"NORMAL": cls.HAPPY, # NORMAL мапится на HAPPY
}
return mood_map.get(mood_str)
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: UUID # Используем UUID из uuid модуля
entry_date: date
entry_type: str
flow_intensity: Optional[str] = None
period_symptoms: Optional[str] = None
mood: Optional[str] = None
energy_level: Optional[int] = None
sleep_hours: Optional[int] = None
symptoms: Optional[str] = None
medications: Optional[str] = None
notes: Optional[str] = None
is_predicted: bool
confidence_score: Optional[int] = None
created_at: datetime
updated_at: Optional[datetime] = None
is_active: Optional[bool] = None
user_id: Optional[int] = None
model_config = ConfigDict(from_attributes=True)
@field_serializer('uuid')
def serialize_uuid(self, uuid_val: UUID) -> str:
"""Преобразование UUID в строку для JSON-сериализации"""
return str(uuid_val)
# Модель ответа для мобильного приложения
class CalendarEvent(BaseModel):
id: int
uuid: UUID # Используем тип UUID из модуля uuid
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
model_config = ConfigDict(from_attributes=True)
@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