This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
from datetime import date, datetime, timedelta
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
from fastapi import Depends, FastAPI, HTTPException, Query
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import and_, desc, select
|
||||
from sqlalchemy import and_, desc, select, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from services.calendar_service.models import CalendarEntry, CycleData, HealthInsights
|
||||
@@ -14,6 +14,10 @@ from services.calendar_service.schemas import (CalendarEntryCreate, CalendarEntr
|
||||
FlowIntensity, HealthInsightResponse, MoodType,
|
||||
CalendarEventCreate)
|
||||
from services.calendar_service.mobile_endpoint import MobileCalendarEntryCreate, mobile_create_calendar_entry
|
||||
from services.calendar_service.mobile_responses import (MobileCalendarEntryResponse,
|
||||
MobileCalendarPeriodInfo,
|
||||
MobilePredictionInfo,
|
||||
MobileCalendarResponse)
|
||||
from shared.auth import get_current_user_from_token as get_current_user
|
||||
from shared.config import settings
|
||||
from shared.database import get_db
|
||||
@@ -349,7 +353,7 @@ async def get_all_calendar_entries(
|
||||
if end_date:
|
||||
query = query.filter(CalendarEntry.entry_date <= end_date)
|
||||
if entry_type:
|
||||
query = query.filter(CalendarEntry.entry_type == entry_type)
|
||||
query = query.filter(CalendarEntry.entry_type == entry_type.value)
|
||||
|
||||
query = query.order_by(CalendarEntry.entry_date.desc()).limit(limit)
|
||||
|
||||
@@ -679,7 +683,191 @@ async def create_mobile_calendar_entry(
|
||||
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
||||
|
||||
|
||||
from .utils import safe_int, safe_str, safe_bool, safe_date, safe_datetime, safe_get_column_value
|
||||
|
||||
# Новый эндпоинт для мобильного приложения для получения записей календаря с фильтрацией по датам
|
||||
@app.get("/api/v1/calendar/entries/mobile", response_model=MobileCalendarResponse)
|
||||
async def get_mobile_calendar_entries(
|
||||
start_date: Optional[date] = Query(None, description="Начальная дата для фильтрации (включительно)"),
|
||||
end_date: Optional[date] = Query(None, description="Конечная дата для фильтрации (включительно)"),
|
||||
entry_type: Optional[str] = Query(None, description="Тип записи (MENSTRUATION, OVULATION и т.д.)"),
|
||||
current_user: Dict = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
limit: int = Query(100, ge=1, le=500, description="Максимальное количество записей"),
|
||||
):
|
||||
"""Получить записи календаря для мобильного приложения с фильтрацией по датам"""
|
||||
import logging
|
||||
logging.info(f"Запрос мобильных данных. start_date={start_date}, end_date={end_date}, entry_type={entry_type}")
|
||||
|
||||
try:
|
||||
# Получаем записи из базы данных
|
||||
query = select(CalendarEntry).filter(CalendarEntry.user_id == current_user["user_id"])
|
||||
|
||||
# Фильтрация по датам
|
||||
if start_date:
|
||||
query = query.filter(CalendarEntry.entry_date >= start_date)
|
||||
if end_date:
|
||||
query = query.filter(CalendarEntry.entry_date <= end_date)
|
||||
|
||||
# Фильтрация по типу записи
|
||||
if entry_type:
|
||||
# Преобразуем тип из мобильного формата в серверный
|
||||
server_entry_type = None
|
||||
if entry_type == "MENSTRUATION":
|
||||
server_entry_type = EntryType.PERIOD.value
|
||||
elif entry_type == "OVULATION":
|
||||
server_entry_type = EntryType.OVULATION.value
|
||||
elif entry_type == "SYMPTOMS":
|
||||
server_entry_type = EntryType.SYMPTOMS.value
|
||||
|
||||
if server_entry_type:
|
||||
query = query.filter(CalendarEntry.entry_type == server_entry_type)
|
||||
|
||||
# Сортировка и ограничение количества записей
|
||||
query = query.order_by(desc(CalendarEntry.entry_date)).limit(limit)
|
||||
|
||||
# Выполнение запроса
|
||||
result = await db.execute(query)
|
||||
entries = result.scalars().all()
|
||||
|
||||
# Преобразуем записи в формат мобильного приложения
|
||||
mobile_entries = []
|
||||
for entry in entries:
|
||||
# Преобразуем тип записи из серверного формата в мобильный
|
||||
mobile_type = "SYMPTOMS" # По умолчанию
|
||||
entry_type_value = safe_str(entry.entry_type, "")
|
||||
|
||||
if entry_type_value == EntryType.PERIOD.value:
|
||||
mobile_type = "MENSTRUATION"
|
||||
elif entry_type_value == EntryType.OVULATION.value:
|
||||
mobile_type = "OVULATION"
|
||||
|
||||
# Преобразуем симптомы из строки в список
|
||||
symptoms_list = []
|
||||
entry_symptoms = safe_str(entry.symptoms, "")
|
||||
if entry_symptoms:
|
||||
symptoms_list = [s.strip() for s in entry_symptoms.split(",")]
|
||||
|
||||
# Преобразуем flow_intensity, если есть
|
||||
flow_intensity_value = None
|
||||
entry_flow = safe_str(entry.flow_intensity, "")
|
||||
if entry_flow in ['1', '2', '3', '4', '5']:
|
||||
flow_intensity_value = int(entry_flow)
|
||||
|
||||
# Преобразуем mood, если есть
|
||||
mood_value = None
|
||||
entry_mood = safe_str(entry.mood, "")
|
||||
if entry_mood:
|
||||
mood_value = entry_mood.upper()
|
||||
|
||||
# Преобразуем notes, если есть
|
||||
notes_value = safe_str(entry.notes, "")
|
||||
|
||||
# Получаем created_at
|
||||
created_at_value = safe_datetime(
|
||||
getattr(entry, 'created_at', None),
|
||||
datetime.now()
|
||||
).isoformat()
|
||||
|
||||
# Получаем is_predicted
|
||||
is_predicted_value = safe_bool(
|
||||
getattr(entry, 'is_predicted', None),
|
||||
False
|
||||
)
|
||||
|
||||
# Создаем мобильную запись
|
||||
mobile_entry = MobileCalendarEntryResponse(
|
||||
id=safe_int(entry.id),
|
||||
uuid=str(safe_int(entry.id)), # Используем ID как UUID
|
||||
date=safe_date(entry.entry_date, date.today()).isoformat(),
|
||||
type=mobile_type,
|
||||
flow_intensity=flow_intensity_value,
|
||||
mood=mood_value,
|
||||
symptoms=symptoms_list,
|
||||
notes=notes_value,
|
||||
created_at=created_at_value,
|
||||
is_predicted=is_predicted_value
|
||||
)
|
||||
mobile_entries.append(mobile_entry)
|
||||
|
||||
# Получаем информацию о текущем цикле
|
||||
current_cycle = await db.execute(
|
||||
select(CycleData)
|
||||
.filter(CycleData.user_id == current_user["user_id"])
|
||||
.order_by(desc(CycleData.cycle_start_date))
|
||||
.limit(1)
|
||||
)
|
||||
cycle_data = current_cycle.scalars().first()
|
||||
|
||||
# Создаем информацию о периоде
|
||||
period_info = MobileCalendarPeriodInfo()
|
||||
prediction_info = MobilePredictionInfo()
|
||||
|
||||
if cycle_data:
|
||||
# Заполняем информацию о текущем цикле
|
||||
cycle_start_date = safe_date(cycle_data.cycle_start_date)
|
||||
if cycle_start_date:
|
||||
period_info.current_cycle_start = cycle_start_date.isoformat()
|
||||
|
||||
cycle_length = safe_int(cycle_data.cycle_length)
|
||||
if cycle_length > 0:
|
||||
period_info.cycle_length = cycle_length
|
||||
|
||||
avg_cycle_length = safe_int(cycle_data.avg_cycle_length)
|
||||
if avg_cycle_length > 0:
|
||||
period_info.average_cycle_length = avg_cycle_length
|
||||
|
||||
# Если есть информация о периоде, рассчитываем дату окончания
|
||||
period_length = safe_int(cycle_data.period_length)
|
||||
if period_length > 0 and cycle_start_date:
|
||||
end_date_value = cycle_start_date + timedelta(days=period_length)
|
||||
period_info.expected_period_end = end_date_value.isoformat()
|
||||
|
||||
# Заполняем информацию о фертильном окне и овуляции
|
||||
if cycle_start_date:
|
||||
ovulation_day = avg_cycle_length // 2
|
||||
ovulation_date = cycle_start_date + timedelta(days=ovulation_day)
|
||||
period_info.ovulation_date = ovulation_date.isoformat()
|
||||
|
||||
# Фертильное окно начинается за 5 дней до овуляции и заканчивается через 1 день после
|
||||
period_info.fertility_window_start = (ovulation_date - timedelta(days=5)).isoformat()
|
||||
period_info.fertility_window_end = (ovulation_date + timedelta(days=1)).isoformat()
|
||||
|
||||
# Заполняем прогноз
|
||||
next_period = safe_date(cycle_data.next_period_predicted)
|
||||
if next_period:
|
||||
prediction_info.next_period_date = next_period.isoformat()
|
||||
prediction_info.confidence_level = 80 # Приблизительное значение уверенности
|
||||
|
||||
# Рассчитываем следующее фертильное окно и овуляцию
|
||||
if avg_cycle_length > 0:
|
||||
next_ovulation = next_period - timedelta(days=avg_cycle_length // 2)
|
||||
prediction_info.next_ovulation_date = next_ovulation.isoformat()
|
||||
prediction_info.next_fertile_window_start = (next_ovulation - timedelta(days=5)).isoformat()
|
||||
prediction_info.next_fertile_window_end = (next_ovulation + timedelta(days=1)).isoformat()
|
||||
|
||||
# Собираем полный ответ
|
||||
response = MobileCalendarResponse(
|
||||
entries=mobile_entries,
|
||||
period_info=period_info,
|
||||
prediction=prediction_info
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Ошибка при получении записей календаря: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Ошибка сервера: {str(e)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
from .mobile_responses import (
|
||||
MobileCalendarEntryResponse,
|
||||
MobileCalendarPeriodInfo,
|
||||
MobilePredictionInfo,
|
||||
MobileCalendarResponse
|
||||
)
|
||||
from .schemas_mobile import MobileFlowIntensity, MobileMood
|
||||
|
||||
uvicorn.run(app, host="0.0.0.0", port=8004)
|
||||
|
||||
Reference in New Issue
Block a user