""" OT views for the Tenhal Multidisciplinary Healthcare Platform. This module contains views for Occupational Therapy documentation including: - OT consultations (OT-F-1) - OT session notes (OT-F-3) - Target skills tracking with 0-10 scoring - Progress visualization """ from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Q, Avg from django.shortcuts import get_object_or_404 from django.utils import timezone from django.views.generic import ListView, DetailView, CreateView, UpdateView from django.urls import reverse_lazy from core.mixins import ( TenantFilterMixin, RolePermissionMixin, AuditLogMixin, HTMXResponseMixin, SuccessMessageMixin, PaginationMixin, ConsentRequiredMixin, ) from core.models import User, Patient from appointments.models import Appointment from .models import OTConsult, OTSession, OTTargetSkill from .forms import OTConsultForm, OTSessionForm class OTConsultListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin, HTMXResponseMixin, ListView): """ OT consultation list view (OT-F-1). Features: - Filter by patient, provider, date range - Search by patient name/MRN - Role-based filtering """ model = OTConsult template_name = 'ot/consult_list.html' htmx_template_name = 'ot/partials/consult_list_partial.html' context_object_name = 'consults' paginate_by = 25 def get_queryset(self): """Get filtered queryset.""" queryset = super().get_queryset() user = self.request.user # Role-based filtering if user.role == User.Role.OT: queryset = queryset.filter(provider__user=user) # Apply search search_query = self.request.GET.get('search', '').strip() if search_query: queryset = queryset.filter( Q(patient__first_name_en__icontains=search_query) | Q(patient__last_name_en__icontains=search_query) | Q(patient__mrn__icontains=search_query) ) # Apply filters patient_id = self.request.GET.get('patient') if patient_id: queryset = queryset.filter(patient_id=patient_id) date_from = self.request.GET.get('date_from') if date_from: queryset = queryset.filter(consultation_date__gte=date_from) date_to = self.request.GET.get('date_to') if date_to: queryset = queryset.filter(consultation_date__lte=date_to) return queryset.select_related('patient', 'provider', 'appointment').order_by('-consultation_date') class OTConsultDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView): """ OT consultation detail view (OT-F-1). Features: - Full consultation details - Reasons for referral (multi-select) - Top 3 difficulty areas - Developmental motor milestones - Self-help skills matrix - Feeding participation - Infant/now behavior descriptors - Recommendation (continue/discharge/referral with rules) """ model = OTConsult template_name = 'ot/consult_detail.html' context_object_name = 'consult' def get_context_data(self, **kwargs): """Add related sessions.""" context = super().get_context_data(**kwargs) # Get related sessions context['sessions'] = OTSession.objects.filter( patient=self.object.patient ).order_by('-session_date')[:10] return context class OTConsultCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin, AuditLogMixin, SuccessMessageMixin, CreateView): """ OT consultation creation view (OT-F-1). Features: - Reasons for referral (multi-select) - Top 3 difficulty areas (text) - Developmental motor milestones - Self-help skills matrix - Feeding participation - Infant/now behavior descriptors - Recommendation (continue/discharge/referral with rules) - Consent verification enforced before creation """ model = OTConsult form_class = OTConsultForm template_name = 'ot/consult_form.html' success_message = "OT consultation recorded successfully!" allowed_roles = [User.Role.ADMIN, User.Role.OT] # Consent enforcement consent_service_type = 'OT' consent_error_message = ( "Patient must sign OT therapy consent before consultation can be documented." ) def get_success_url(self): """Redirect to consult detail.""" return reverse_lazy('ot:consult_detail', kwargs={'pk': self.object.pk}) def get_patient(self): """Get patient for consent verification.""" patient_id = self.request.GET.get('patient') appointment_id = self.request.GET.get('appointment_id') if patient_id: return Patient.objects.get( pk=patient_id, tenant=self.request.user.tenant ) elif appointment_id: appointment = Appointment.objects.get( pk=appointment_id, tenant=self.request.user.tenant ) return appointment.patient return None def form_valid(self, form): """Set tenant and provider.""" # Set tenant form.instance.tenant = self.request.user.tenant # Set provider form.instance.provider = self.request.user # Set consultation date if not provided if not form.instance.consultation_date: form.instance.consultation_date = timezone.now().date() # Save consultation response = super().form_valid(form) return response def get_context_data(self, **kwargs): """Add form title and patient/appointment info.""" context = super().get_context_data(**kwargs) context['form_title'] = 'OT Consultation (OT-F-1)' context['submit_text'] = 'Save Consultation' # Get patient if provided patient_id = self.request.GET.get('patient') if patient_id: try: context['patient'] = Patient.objects.get( pk=patient_id, tenant=self.request.user.tenant ) except Patient.DoesNotExist: pass # Get appointment if provided appointment_id = self.request.GET.get('appointment_id') if appointment_id: try: context['appointment'] = Appointment.objects.get( pk=appointment_id, tenant=self.request.user.tenant ) context['patient'] = context['appointment'].patient except Appointment.DoesNotExist: pass return context class OTConsultUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, AuditLogMixin, SuccessMessageMixin, UpdateView): """ OT consultation update view (OT-F-1). Features: - Update consultation details - Version history - Audit trail """ model = OTConsult form_class = OTConsultForm template_name = 'ot/consult_form.html' success_message = "OT consultation updated successfully!" allowed_roles = [User.Role.ADMIN, User.Role.OT] def get_success_url(self): """Redirect to consult detail.""" return reverse_lazy('ot:consult_detail', kwargs={'pk': self.object.pk}) def get_context_data(self, **kwargs): """Add form title and history.""" context = super().get_context_data(**kwargs) context['form_title'] = f'Update OT Consultation - {self.object.patient.mrn}' context['submit_text'] = 'Update Consultation' # Add version history if available if hasattr(self.object, 'history'): context['history'] = self.object.history.all()[:10] return context class OTSessionListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin, HTMXResponseMixin, ListView): """ OT session list view (OT-F-3). Features: - Filter by patient, provider, date range, session type - Search by patient name/MRN - Role-based filtering """ model = OTSession template_name = 'ot/session_list.html' htmx_template_name = 'ot/partials/session_list_partial.html' context_object_name = 'sessions' paginate_by = 25 def get_queryset(self): """Get filtered queryset.""" queryset = super().get_queryset() user = self.request.user # Role-based filtering if user.role == User.Role.OT: queryset = queryset.filter(provider__user=user) # Apply search search_query = self.request.GET.get('search', '').strip() if search_query: queryset = queryset.filter( Q(patient__first_name_en__icontains=search_query) | Q(patient__last_name_en__icontains=search_query) | Q(patient__mrn__icontains=search_query) ) # Apply filters patient_id = self.request.GET.get('patient') if patient_id: queryset = queryset.filter(patient_id=patient_id) session_type = self.request.GET.get('session_type') if session_type: queryset = queryset.filter(session_type=session_type) date_from = self.request.GET.get('date_from') if date_from: queryset = queryset.filter(session_date__gte=date_from) date_to = self.request.GET.get('date_to') if date_to: queryset = queryset.filter(session_date__lte=date_to) return queryset.select_related('patient', 'provider', 'appointment').order_by('-session_date') class OTSessionDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView): """ OT session detail view (OT-F-3). Features: - Full session details - Session type (Consult/Individual/Group/ParentTraining) - Cooperative level (1-4) - Distraction tolerance (1-4) - "Today we work on..." checklist - Target skills with 0-10 scoring - Observations, activities, recommendations """ model = OTSession template_name = 'ot/session_detail.html' context_object_name = 'session' def get_context_data(self, **kwargs): """Add target skills and progress data.""" context = super().get_context_data(**kwargs) # Get target skills context['target_skills'] = self.object.target_skills.all() # Get previous sessions for comparison context['previous_sessions'] = OTSession.objects.filter( patient=self.object.patient, session_date__lt=self.object.session_date ).order_by('-session_date')[:5] return context class OTSessionCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin, AuditLogMixin, SuccessMessageMixin, CreateView): """ OT session creation view (OT-F-3). Features: - Session date, type (Consult/Individual/Group/ParentTraining) - Cooperative level (1-4) - Distraction tolerance (1-4) - "Today we work on..." checklist - Target skills with 0-10 scoring - Observations, activities, recommendations - Consent verification enforced before creation """ model = OTSession form_class = OTSessionForm template_name = 'ot/session_form.html' success_message = "OT session recorded successfully!" allowed_roles = [User.Role.ADMIN, User.Role.OT] # Consent enforcement consent_service_type = 'OT' consent_error_message = ( "Patient must sign OT therapy consent before session can be documented." ) def get_success_url(self): """Redirect to session detail.""" return reverse_lazy('ot:session_detail', kwargs={'pk': self.object.pk}) def get_patient(self): """Get patient for consent verification.""" patient_id = self.request.GET.get('patient') appointment_id = self.request.GET.get('appointment_id') if patient_id: return Patient.objects.get( pk=patient_id, tenant=self.request.user.tenant ) elif appointment_id: appointment = Appointment.objects.get( pk=appointment_id, tenant=self.request.user.tenant ) return appointment.patient return None def form_valid(self, form): """Set tenant and provider.""" # Set tenant form.instance.tenant = self.request.user.tenant # Set provider form.instance.provider = self.request.user # Set session date if not provided if not form.instance.session_date: form.instance.session_date = timezone.now().date() # Save session response = super().form_valid(form) return response def get_context_data(self, **kwargs): """Add form title and patient/appointment info.""" context = super().get_context_data(**kwargs) context['form_title'] = 'OT Session Note (OT-F-3)' context['submit_text'] = 'Save Session' # Get patient if provided patient_id = self.request.GET.get('patient') if patient_id: try: context['patient'] = Patient.objects.get( pk=patient_id, tenant=self.request.user.tenant ) except Patient.DoesNotExist: pass # Get appointment if provided appointment_id = self.request.GET.get('appointment_id') if appointment_id: try: context['appointment'] = Appointment.objects.get( pk=appointment_id, tenant=self.request.user.tenant ) context['patient'] = context['appointment'].patient except Appointment.DoesNotExist: pass return context class OTSessionUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, AuditLogMixin, SuccessMessageMixin, UpdateView): """ OT session update view (OT-F-3). Features: - Update session details - Version history - Audit trail """ model = OTSession form_class = OTSessionForm template_name = 'ot/session_form.html' success_message = "OT session updated successfully!" allowed_roles = [User.Role.ADMIN, User.Role.OT] def get_success_url(self): """Redirect to session detail.""" return reverse_lazy('ot:session_detail', kwargs={'pk': self.object.pk}) def get_context_data(self, **kwargs): """Add form title and history.""" context = super().get_context_data(**kwargs) context['form_title'] = f'Update OT Session - {self.object.patient.mrn}' context['submit_text'] = 'Update Session' # Add version history if available if hasattr(self.object, 'history'): context['history'] = self.object.history.all()[:10] return context class PatientOTProgressView(LoginRequiredMixin, TenantFilterMixin, ListView): """ Patient OT progress view. Features: - All OT sessions for specific patient - Target skills progress tracking (0-10 scoring graph) - Cooperative level trends - Distraction tolerance trends """ model = OTSession template_name = 'ot/patient_progress.html' context_object_name = 'sessions' paginate_by = 50 def get_queryset(self): """Get all sessions for patient.""" patient_id = self.kwargs.get('patient_id') return OTSession.objects.filter( patient_id=patient_id, tenant=self.request.user.tenant ).order_by('-session_date') def get_context_data(self, **kwargs): """Add patient and progress data.""" context = super().get_context_data(**kwargs) patient_id = self.kwargs.get('patient_id') context['patient'] = get_object_or_404( Patient, pk=patient_id, tenant=self.request.user.tenant ) # Get progress data for charts sessions = self.get_queryset() context['progress_data'] = self._prepare_progress_data(sessions) # Get statistics context['stats'] = self._calculate_statistics(sessions) return context def _prepare_progress_data(self, sessions): """Prepare data for progress charts.""" data = { 'dates': [], 'cooperative_level': [], 'distraction_tolerance': [], 'target_skills': {}, } for session in sessions.order_by('session_date'): data['dates'].append(session.session_date.strftime('%Y-%m-%d')) data['cooperative_level'].append(session.cooperative_level) data['distraction_tolerance'].append(session.distraction_tolerance) # Track target skills for skill in session.target_skills.all(): if skill.skill_name not in data['target_skills']: data['target_skills'][skill.skill_name] = [] data['target_skills'][skill.skill_name].append(skill.score) return data def _calculate_statistics(self, sessions): """Calculate statistics from sessions.""" if not sessions: return {} return { 'total_sessions': sessions.count(), 'avg_cooperative_level': sessions.aggregate(Avg('cooperative_level'))['cooperative_level__avg'], 'avg_distraction_tolerance': sessions.aggregate(Avg('distraction_tolerance'))['distraction_tolerance__avg'], 'latest_session': sessions.first(), } class TargetSkillProgressView(LoginRequiredMixin, TenantFilterMixin, ListView): """ Target skill progress view across all patients. Features: - Track specific target skills - 0-10 scoring trends - Skill mastery analysis """ model = OTTargetSkill template_name = 'ot/target_skill_progress.html' context_object_name = 'target_skills' paginate_by = 50 def get_queryset(self): """Get target skills with filters.""" queryset = OTTargetSkill.objects.filter( session__tenant=self.request.user.tenant ).select_related('session', 'session__patient') # Apply filters skill_name = self.request.GET.get('skill_name') if skill_name: queryset = queryset.filter(skill_name__icontains=skill_name) patient_id = self.request.GET.get('patient') if patient_id: queryset = queryset.filter(session__patient_id=patient_id) return queryset.order_by('-session__session_date') def get_context_data(self, **kwargs): """Add statistics.""" context = super().get_context_data(**kwargs) queryset = self.get_queryset() # Add statistics context['stats'] = { 'total_skills': queryset.count(), 'avg_score': queryset.aggregate(Avg('score'))['score__avg'], 'mastered_skills': queryset.filter(score__gte=8).count(), } return context