All checks were successful
continuous-integration/drone/push Build is passing
267 lines
9.1 KiB
Python
267 lines
9.1 KiB
Python
from datetime import date
|
|
from enum import Enum
|
|
from typing import Optional, Dict, Any, List
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
from .schemas import (
|
|
EntryType, FlowIntensity, MoodType, CalendarEntryCreate, CalendarEntryResponse
|
|
)
|
|
|
|
|
|
class MobileFlowIntensity(int, Enum):
|
|
"""Flow intensity values used in the mobile app (1-5)"""
|
|
SPOTTING = 1
|
|
LIGHT = 2
|
|
MEDIUM = 3
|
|
HEAVY = 4
|
|
VERY_HEAVY = 5
|
|
|
|
@classmethod
|
|
def to_server_intensity(cls, value: int) -> Optional[str]:
|
|
"""Convert mobile app intensity value to server format"""
|
|
if value is None:
|
|
return None
|
|
|
|
mapping = {
|
|
cls.SPOTTING.value: FlowIntensity.SPOTTING.value,
|
|
cls.LIGHT.value: FlowIntensity.LIGHT.value,
|
|
cls.MEDIUM.value: FlowIntensity.MEDIUM.value,
|
|
cls.HEAVY.value: FlowIntensity.HEAVY.value,
|
|
cls.VERY_HEAVY.value: FlowIntensity.HEAVY.value, # Server has no "very heavy"
|
|
}
|
|
return mapping.get(value)
|
|
|
|
@classmethod
|
|
def from_server_intensity(cls, value: Optional[str]) -> Optional[int]:
|
|
"""Convert server intensity value to mobile app format"""
|
|
if value is None:
|
|
return None
|
|
|
|
mapping = {
|
|
FlowIntensity.SPOTTING.value: cls.SPOTTING.value,
|
|
FlowIntensity.LIGHT.value: cls.LIGHT.value,
|
|
FlowIntensity.MEDIUM.value: cls.MEDIUM.value,
|
|
FlowIntensity.HEAVY.value: cls.HEAVY.value,
|
|
}
|
|
return mapping.get(value)
|
|
|
|
|
|
class MobileMood(str, Enum):
|
|
"""Mood values used in the mobile app"""
|
|
HAPPY = "HAPPY"
|
|
SAD = "SAD"
|
|
NORMAL = "NORMAL" # Added for mobile app support
|
|
ANXIOUS = "ANXIOUS"
|
|
IRRITATED = "IRRITATED"
|
|
ENERGETIC = "ENERGETIC"
|
|
TIRED = "TIRED"
|
|
|
|
@classmethod
|
|
def to_server_mood(cls, value: str) -> Optional[MoodType]:
|
|
"""Convert mobile app mood value to server format"""
|
|
if value is None:
|
|
return None
|
|
|
|
mapping = {
|
|
cls.HAPPY: MoodType.HAPPY,
|
|
cls.SAD: MoodType.SAD,
|
|
cls.NORMAL: MoodType.HAPPY, # NORMAL maps to HAPPY
|
|
cls.ANXIOUS: MoodType.ANXIOUS,
|
|
cls.IRRITATED: MoodType.IRRITATED, # Different spelling
|
|
cls.ENERGETIC: MoodType.ENERGETIC,
|
|
cls.TIRED: MoodType.TIRED,
|
|
}
|
|
return mapping.get(MobileMood(value))
|
|
|
|
@classmethod
|
|
def from_server_mood(cls, value: Optional[str]) -> Optional[str]:
|
|
"""Convert server mood value to mobile app format"""
|
|
if value is None:
|
|
return None
|
|
|
|
# Convert string value to MoodType enum
|
|
try:
|
|
mood_type = MoodType(value)
|
|
except ValueError:
|
|
return None
|
|
|
|
mapping = {
|
|
MoodType.HAPPY: cls.HAPPY,
|
|
MoodType.SAD: cls.SAD,
|
|
MoodType.ANXIOUS: cls.ANXIOUS,
|
|
MoodType.IRRITATED: cls.IRRITABLE, # Different spelling
|
|
MoodType.ENERGETIC: cls.ENERGETIC,
|
|
MoodType.TIRED: cls.TIRED,
|
|
}
|
|
return mapping.get(mood_type)
|
|
|
|
|
|
class MobileAppCalendarEntryCreate(BaseModel):
|
|
"""Schema for creating calendar entries from mobile app"""
|
|
entry_date: date
|
|
entry_type: str # Will be mapped to EntryType
|
|
flow_intensity: Optional[int] = None # 1-5 scale
|
|
symptoms: Optional[str] = None
|
|
mood: Optional[str] = None # Mobile mood enum as string
|
|
notes: Optional[str] = None
|
|
|
|
|
|
class MobileAppCalendarEntryResponse(BaseModel):
|
|
"""Schema for calendar entry responses to mobile app"""
|
|
id: int
|
|
user_id: int
|
|
entry_date: date
|
|
entry_type: str
|
|
flow_intensity: Optional[int] = None
|
|
symptoms: Optional[str] = None
|
|
mood: Optional[str] = None
|
|
notes: Optional[str] = None
|
|
created_at: date
|
|
updated_at: date
|
|
|
|
|
|
def convert_mobile_app_format_to_server(mobile_entry: MobileAppCalendarEntryCreate) -> CalendarEntryCreate:
|
|
"""Convert mobile app format to server format"""
|
|
# Map entry type
|
|
try:
|
|
entry_type = EntryType(mobile_entry.entry_type.lower())
|
|
except ValueError:
|
|
# Handle case where entry type doesn't match directly
|
|
if mobile_entry.entry_type == "MENSTRUATION":
|
|
entry_type = EntryType.PERIOD
|
|
elif mobile_entry.entry_type == "SPOTTING":
|
|
entry_type = EntryType.PERIOD
|
|
else:
|
|
# Default to symptoms if no direct mapping
|
|
entry_type = EntryType.SYMPTOMS
|
|
|
|
# Map flow intensity (1-5 scale to text values)
|
|
flow_intensity_str = None
|
|
if mobile_entry.flow_intensity is not None:
|
|
flow_intensity_str = MobileFlowIntensity.to_server_intensity(mobile_entry.flow_intensity)
|
|
|
|
# Map mood
|
|
mood_str = None
|
|
if mobile_entry.mood is not None:
|
|
try:
|
|
mobile_mood = MobileMood(mobile_entry.mood)
|
|
mood_type = MobileMood.to_server_mood(mobile_entry.mood)
|
|
if mood_type:
|
|
mood_str = mood_type.value
|
|
except ValueError:
|
|
# If mood doesn't match any enum value, leave it as None
|
|
pass
|
|
|
|
return CalendarEntryCreate(
|
|
entry_date=mobile_entry.entry_date,
|
|
entry_type=entry_type,
|
|
flow_intensity=flow_intensity_str,
|
|
symptoms=mobile_entry.symptoms or "",
|
|
mood=mood_str,
|
|
notes=mobile_entry.notes or "",
|
|
# Default values for server required fields that mobile app doesn't provide
|
|
period_symptoms="",
|
|
energy_level=0,
|
|
sleep_hours=0,
|
|
medications="",
|
|
)
|
|
|
|
|
|
def convert_server_response_to_mobile_app(
|
|
server_response: CalendarEntryResponse
|
|
) -> MobileAppCalendarEntryResponse:
|
|
"""Convert server response to mobile app format"""
|
|
# Map flow intensity back to 1-5 scale
|
|
flow_intensity = None
|
|
if server_response.flow_intensity is not None:
|
|
flow_intensity = MobileFlowIntensity.from_server_intensity(server_response.flow_intensity)
|
|
|
|
# Map mood back to mobile format
|
|
mood = None
|
|
if server_response.mood is not None:
|
|
mood = MobileMood.from_server_mood(server_response.mood)
|
|
|
|
# Upper case the entry type
|
|
entry_type = server_response.entry_type.upper() if server_response.entry_type else ""
|
|
|
|
return MobileAppCalendarEntryResponse(
|
|
id=server_response.id,
|
|
user_id=getattr(server_response, "user_id", 0), # Handle if field doesn't exist
|
|
entry_date=server_response.entry_date,
|
|
entry_type=entry_type,
|
|
flow_intensity=flow_intensity,
|
|
symptoms=server_response.symptoms,
|
|
mood=mood,
|
|
notes=server_response.notes,
|
|
created_at=getattr(server_response, "created_at", date.today()),
|
|
updated_at=getattr(server_response, "created_at", date.today()), # Fallback to created_at
|
|
)
|
|
|
|
|
|
def convert_mobile_entry_type(mobile_type: str) -> str:
|
|
"""Конвертирует тип записи из мобильного формата в формат API"""
|
|
mapping = {
|
|
"MENSTRUATION": "period",
|
|
"OVULATION": "ovulation",
|
|
"FERTILE": "fertile_window",
|
|
"OTHER": "other"
|
|
}
|
|
return mapping.get(mobile_type, "other")
|
|
|
|
|
|
def convert_mobile_flow_intensity(intensity: int) -> str:
|
|
"""Конвертирует интенсивность потока из мобильного формата в формат API"""
|
|
if intensity is None:
|
|
return None
|
|
|
|
mapping = {
|
|
1: "very_light",
|
|
2: "light",
|
|
3: "medium",
|
|
4: "heavy",
|
|
5: "very_heavy"
|
|
}
|
|
return mapping.get(intensity, "medium")
|
|
|
|
|
|
def convert_mobile_mood(mood: str) -> str:
|
|
"""Конвертирует настроение из мобильного формата в формат API"""
|
|
if mood is None:
|
|
return None
|
|
|
|
mapping = {
|
|
"HAPPY": "happy",
|
|
"SAD": "sad",
|
|
"NORMAL": "happy", # NORMAL мапится на happy
|
|
"ANXIOUS": "anxious",
|
|
"IRRITATED": "irritated",
|
|
"ENERGETIC": "energetic",
|
|
"TIRED": "tired"
|
|
}
|
|
return mapping.get(mood, "happy")
|
|
|
|
|
|
def convert_mobile_symptoms(symptoms: List[str]) -> str:
|
|
"""Конвертирует список симптомов из мобильного формата в строку для API"""
|
|
if not symptoms:
|
|
return ""
|
|
|
|
# Преобразуем все симптомы в нижний регистр и соединяем запятыми
|
|
return ",".join([symptom.lower() for symptom in symptoms])
|
|
|
|
|
|
def mobile_to_api_format(mobile_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Конвертирует данные из мобильного формата в формат API"""
|
|
api_data = {
|
|
"entry_date": mobile_data.get("date"),
|
|
"entry_type": convert_mobile_entry_type(mobile_data.get("type")),
|
|
"flow_intensity": convert_mobile_flow_intensity(mobile_data.get("flow_intensity")),
|
|
"mood": convert_mobile_mood(mobile_data.get("mood")),
|
|
"symptoms": convert_mobile_symptoms(mobile_data.get("symptoms")),
|
|
"notes": mobile_data.get("notes", "")
|
|
}
|
|
|
|
# Удаляем None значения
|
|
return {k: v for k, v in api_data.items() if v is not None}
|