Marwan Alwali 263292f6be update
2025-11-04 00:50:06 +03:00

296 lines
10 KiB
Python

"""
API endpoints for smart scheduling features.
"""
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from django.utils import timezone
from datetime import datetime, timedelta
from appointments.scheduling.smart_scheduler import SmartScheduler
from appointments.scheduling.utils import SchedulingAnalytics, ConflictDetector
from core.mixins import TenantMixin
class SmartSchedulingViewSet(TenantMixin, viewsets.ViewSet):
"""API endpoints for smart scheduling features."""
permission_classes = [IsAuthenticated]
@action(detail=False, methods=['post'])
def find_optimal_slots(self, request):
"""
Find optimal appointment slots using AI.
POST /api/appointments/scheduling/find-optimal-slots/
{
"patient_id": 123,
"provider_id": 456,
"appointment_type_id": 789,
"preferred_dates": ["2025-01-15", "2025-01-16"],
"duration_minutes": 30
}
"""
# Extract parameters
patient_id = request.data.get('patient_id')
provider_id = request.data.get('provider_id')
appointment_type_id = request.data.get('appointment_type_id')
preferred_dates = request.data.get('preferred_dates', [])
duration_minutes = request.data.get('duration_minutes', 30)
# Validate inputs
if not all([patient_id, provider_id, appointment_type_id]):
return Response(
{'error': 'Missing required parameters: patient_id, provider_id, appointment_type_id'},
status=status.HTTP_400_BAD_REQUEST
)
# Get objects
from patients.models import PatientProfile
from accounts.models import User
from appointments.models import AppointmentTemplate
try:
patient = PatientProfile.objects.get(id=patient_id, tenant=request.user.tenant)
provider = User.objects.get(id=provider_id)
appointment_type = AppointmentTemplate.objects.get(id=appointment_type_id, tenant=request.user.tenant)
except PatientProfile.DoesNotExist:
return Response(
{'error': 'Patient not found'},
status=status.HTTP_404_NOT_FOUND
)
except User.DoesNotExist:
return Response(
{'error': 'Provider not found'},
status=status.HTTP_404_NOT_FOUND
)
except AppointmentTemplate.DoesNotExist:
return Response(
{'error': 'Appointment type not found'},
status=status.HTTP_404_NOT_FOUND
)
# Convert date strings to datetime objects
try:
date_objects = [
datetime.strptime(date_str, '%Y-%m-%d')
for date_str in preferred_dates
]
except ValueError as e:
return Response(
{'error': f'Invalid date format: {str(e)}'},
status=status.HTTP_400_BAD_REQUEST
)
# Find optimal slots
scheduler = SmartScheduler(request.user.tenant)
slots = scheduler.find_optimal_slots(
patient=patient,
provider=provider,
appointment_type=appointment_type,
preferred_dates=date_objects,
duration_minutes=duration_minutes
)
# Format response
return Response({
'success': True,
'slots': [
{
'datetime': slot['datetime'].isoformat(),
'end_datetime': slot['end_datetime'].isoformat(),
'provider_id': slot['provider'].id,
'provider_name': slot['provider'].get_full_name(),
'score': slot['score']['total'],
'score_breakdown': slot['score']['breakdown']
}
for slot in slots
],
'count': len(slots)
})
@action(detail=False, methods=['post'])
def check_conflicts(self, request):
"""
Check for scheduling conflicts.
POST /api/appointments/scheduling/check-conflicts/
{
"provider_id": 456,
"start_time": "2025-01-15T10:00:00",
"end_time": "2025-01-15T10:30:00",
"exclude_appointment_id": 123
}
"""
provider_id = request.data.get('provider_id')
start_time = request.data.get('start_time')
end_time = request.data.get('end_time')
exclude_id = request.data.get('exclude_appointment_id')
# Validate inputs
if not all([provider_id, start_time, end_time]):
return Response(
{'error': 'Missing required parameters: provider_id, start_time, end_time'},
status=status.HTTP_400_BAD_REQUEST
)
# Get provider
from accounts.models import User
try:
provider = User.objects.get(id=provider_id)
except User.DoesNotExist:
return Response(
{'error': 'Provider not found'},
status=status.HTTP_404_NOT_FOUND
)
# Parse times
try:
start_dt = datetime.fromisoformat(start_time)
end_dt = datetime.fromisoformat(end_time)
except ValueError as e:
return Response(
{'error': f'Invalid datetime format: {str(e)}'},
status=status.HTTP_400_BAD_REQUEST
)
# Check conflicts
conflicts = ConflictDetector.check_conflicts(
provider, start_dt, end_dt, exclude_id
)
if conflicts:
# Suggest alternatives
alternatives = ConflictDetector.suggest_alternative_slots(
provider, start_dt,
duration_minutes=int((end_dt - start_dt).total_seconds() / 60)
)
return Response({
'has_conflict': True,
'conflicts': [
{
'id': c.id,
'patient': c.patient.get_full_name(),
'start': c.scheduled_datetime.isoformat(),
'end': c.scheduled_end_datetime.isoformat()
}
for c in conflicts
],
'alternative_slots': [
{
'start': alt['start'].isoformat(),
'end': alt['end'].isoformat()
}
for alt in alternatives
]
})
return Response({
'has_conflict': False,
'message': 'No conflicts found'
})
@action(detail=False, methods=['get'])
def provider_analytics(self, request):
"""
Get scheduling analytics for a provider.
GET /api/appointments/scheduling/provider-analytics/?provider_id=456&days=30
"""
provider_id = request.query_params.get('provider_id')
days = int(request.query_params.get('days', 30))
if not provider_id:
return Response(
{'error': 'Missing required parameter: provider_id'},
status=status.HTTP_400_BAD_REQUEST
)
from accounts.models import User
try:
provider = User.objects.get(id=provider_id)
except User.DoesNotExist:
return Response(
{'error': 'Provider not found'},
status=status.HTTP_404_NOT_FOUND
)
# Calculate date range
end_date = timezone.now().date()
start_date = end_date - timedelta(days=days)
# Get analytics
utilization = SchedulingAnalytics.calculate_provider_utilization(
provider, start_date, end_date
)
no_show_rate = SchedulingAnalytics.calculate_no_show_rate(
provider, start_date, end_date
)
return Response({
'success': True,
'provider_id': provider.id,
'provider_name': provider.get_full_name(),
'period': {
'start': start_date.isoformat(),
'end': end_date.isoformat(),
'days': days
},
'metrics': {
'utilization_rate': utilization,
'no_show_rate': no_show_rate
}
})
@action(detail=False, methods=['post'])
def update_patient_preferences(self, request):
"""
Update patient scheduling preferences based on history.
POST /api/appointments/scheduling/update-patient-preferences/
{
"patient_id": 123
}
"""
patient_id = request.data.get('patient_id')
if not patient_id:
return Response(
{'error': 'Missing required parameter: patient_id'},
status=status.HTTP_400_BAD_REQUEST
)
from patients.models import PatientProfile
try:
patient = PatientProfile.objects.get(id=patient_id, tenant=request.user.tenant)
except PatientProfile.DoesNotExist:
return Response(
{'error': 'Patient not found'},
status=status.HTTP_404_NOT_FOUND
)
# Update preferences
prefs = SchedulingAnalytics.update_patient_scheduling_preferences(patient)
if prefs:
return Response({
'success': True,
'message': 'Patient preferences updated successfully',
'preferences': {
'preferred_days': prefs.preferred_days,
'preferred_times': prefs.preferred_times,
'total_appointments': prefs.total_appointments,
'completed_appointments': prefs.completed_appointments,
'average_no_show_rate': float(prefs.average_no_show_rate),
'completion_rate': prefs.completion_rate
}
})
else:
return Response({
'success': False,
'message': 'No appointment history found for patient'
})