""" SLP views for the Tenhal Multidisciplinary Healthcare Platform. This module contains views for Speech-Language Pathology documentation including: - SLP consultations (SLP-F-1) - variants for ASD/LanguageDelay/Fluency - SLP assessments (SLP-F-2) - SLP interventions (SLP-F-3) - SLP progress reports (SLP-F-4) """ from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib import messages from django.db.models import Q, Avg from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django.views.generic import ListView, DetailView, CreateView, UpdateView, View from django.urls import reverse_lazy from core.mixins import ( SignedDocumentEditPreventionMixin, TenantFilterMixin, RolePermissionMixin, AuditLogMixin, HTMXResponseMixin, SuccessMessageMixin, PaginationMixin, ConsentRequiredMixin, SignedDocumentEditPreventionMixin, ) from core.models import User, Patient from appointments.models import Appointment from .models import SLPConsult, SLPAssessment, SLPIntervention, SLPProgressReport, SLPTarget from .forms import SLPConsultForm, SLPAssessmentForm, SLPInterventionForm, SLPProgressReportForm # Sign Views class SLPConsultSignView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View): """Sign an SLP consultation.""" allowed_roles = [User.Role.ADMIN, User.Role.SLP] def post(self, request, pk): consult = get_object_or_404(SLPConsult, pk=pk, tenant=request.user.tenant) if consult.signed_by: messages.warning(request, _("This consultation has already been signed.")) return HttpResponseRedirect(reverse_lazy('slp:consult_detail', kwargs={'pk': pk})) if consult.provider != request.user and request.user.role != User.Role.ADMIN: messages.error(request, _("Only the consultation provider or an administrator can sign this consultation.")) return HttpResponseRedirect(reverse_lazy('slp:consult_detail', kwargs={'pk': pk})) consult.signed_by = request.user consult.signed_at = timezone.now() consult.save() messages.success(request, _("Consultation signed successfully!")) return HttpResponseRedirect(reverse_lazy('slp:consult_detail', kwargs={'pk': pk})) class SLPAssessmentSignView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View): """Sign an SLP assessment.""" allowed_roles = [User.Role.ADMIN, User.Role.SLP] def post(self, request, pk): assessment = get_object_or_404(SLPAssessment, pk=pk, tenant=request.user.tenant) if assessment.signed_by: messages.warning(request, _("This assessment has already been signed.")) return HttpResponseRedirect(reverse_lazy('slp:assessment_detail', kwargs={'pk': pk})) if assessment.provider != request.user and request.user.role != User.Role.ADMIN: messages.error(request, _("Only the assessment provider or an administrator can sign this assessment.")) return HttpResponseRedirect(reverse_lazy('slp:assessment_detail', kwargs={'pk': pk})) assessment.signed_by = request.user assessment.signed_at = timezone.now() assessment.save() messages.success(request, _("Assessment signed successfully!")) return HttpResponseRedirect(reverse_lazy('slp:assessment_detail', kwargs={'pk': pk})) class SLPInterventionSignView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View): """Sign an SLP intervention.""" allowed_roles = [User.Role.ADMIN, User.Role.SLP] def post(self, request, pk): intervention = get_object_or_404(SLPIntervention, pk=pk, tenant=request.user.tenant) if intervention.signed_by: messages.warning(request, _("This intervention has already been signed.")) return HttpResponseRedirect(reverse_lazy('slp:intervention_detail', kwargs={'pk': pk})) if intervention.provider != request.user and request.user.role != User.Role.ADMIN: messages.error(request, _("Only the intervention provider or an administrator can sign this intervention.")) return HttpResponseRedirect(reverse_lazy('slp:intervention_detail', kwargs={'pk': pk})) intervention.signed_by = request.user intervention.signed_at = timezone.now() intervention.save() messages.success(request, _("Intervention signed successfully!")) return HttpResponseRedirect(reverse_lazy('slp:intervention_detail', kwargs={'pk': pk})) class SLPProgressReportSignView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, View): """Sign an SLP progress report.""" allowed_roles = [User.Role.ADMIN, User.Role.SLP] def post(self, request, pk): report = get_object_or_404(SLPProgressReport, pk=pk, tenant=request.user.tenant) if report.signed_by: messages.warning(request, _("This progress report has already been signed.")) return HttpResponseRedirect(reverse_lazy('slp:progress_report_detail', kwargs={'pk': pk})) if report.provider != request.user and request.user.role != User.Role.ADMIN: messages.error(request, _("Only the report provider or an administrator can sign this progress report.")) return HttpResponseRedirect(reverse_lazy('slp:progress_report_detail', kwargs={'pk': pk})) report.signed_by = request.user report.signed_at = timezone.now() report.save() messages.success(request, _("Progress report signed successfully!")) return HttpResponseRedirect(reverse_lazy('slp:progress_report_detail', kwargs={'pk': pk})) class SLPConsultListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin, HTMXResponseMixin, ListView): """ SLP consultation list view (SLP-F-1). Features: - Filter by patient, provider, date range, consult type - Search by patient name/MRN - Role-based filtering """ model = SLPConsult template_name = 'slp/consultation_list.html' htmx_template_name = 'slp/partials/consult_list_partial.html' context_object_name = 'consultations' 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.SLP: 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) consult_type = self.request.GET.get('consult_type') if consult_type: if consult_type == 'ASD': queryset = queryset.filter(is_asd_variant=True) elif consult_type == 'LANGUAGE': queryset = queryset.filter(is_language_delay_variant=True) elif consult_type == 'FLUENCY': queryset = queryset.filter(is_fluency_variant=True) 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', 'signed_by').order_by('-consultation_date') def get_context_data(self, **kwargs): """Add unsigned consultations count for current user.""" context = super().get_context_data(**kwargs) user = self.request.user unsigned_query = SLPConsult.objects.filter(tenant=user.tenant, signed_by__isnull=True) if user.role == User.Role.SLP: unsigned_query = unsigned_query.filter(provider=user) context['unsigned_count'] = unsigned_query.count() context['unsigned_items'] = unsigned_query.select_related('patient', 'provider').order_by('-consultation_date')[:10] return context # ============================================================================ # PDF Generation Views # ============================================================================ from core.pdf_service import BasePDFGenerator from django.shortcuts import redirect class SLPConsultPDFGenerator(BasePDFGenerator): """PDF generator for SLP Consultation (SLP-F-1).""" def get_document_title(self): """Return document title in English and Arabic.""" consult = self.document return ( f"SLP Consultation (SLP-F-1) - {consult.patient.mrn}", "استشارة علاج النطق واللغة" ) def get_pdf_filename(self): """Return PDF filename.""" consult = self.document date_str = consult.consultation_date.strftime('%Y%m%d') return f"slp_consultation_{consult.patient.mrn}_{date_str}.pdf" def get_document_sections(self): """Return document sections to render.""" consult = self.document patient = consult.patient sections = [] # Patient Information patient_name_ar = f"{patient.first_name_ar} {patient.last_name_ar}" if patient.first_name_ar else "" sections.append({ 'heading_en': 'Patient Information', 'heading_ar': 'معلومات المريض', 'type': 'table', 'content': [ ('Name', 'الاسم', f"{patient.first_name_en} {patient.last_name_en}", patient_name_ar), ('MRN', 'رقم السجل الطبي', patient.mrn, ""), ('Age', 'العمر', f"{patient.age} years", ""), ] }) # Consultation Details sections.append({ 'heading_en': 'Consultation Details', 'heading_ar': 'تفاصيل الاستشارة', 'type': 'table', 'content': [ ('Date', 'التاريخ', consult.consultation_date.strftime('%Y-%m-%d'), ""), ('Provider', 'مقدم الخدمة', consult.provider.get_full_name() if consult.provider else 'N/A', ""), ('Variant', 'النوع', consult.get_consult_variant_display(), ""), ('Service Type', 'نوع الخدمة', consult.get_type_of_service_display(), ""), ('Screen Time', 'وقت الشاشة', f"{consult.screen_time_hours} hours/day" if consult.screen_time_hours else 'N/A', ""), ('Signed By', 'موقع من قبل', consult.signed_by.get_full_name() if consult.signed_by else 'Not signed', ""), ] }) # Primary Concern if consult.primary_concern: sections.append({ 'heading_en': 'Primary Concern', 'heading_ar': 'القلق الأساسي', 'type': 'text', 'content': [consult.primary_concern] }) # Suspected Areas if consult.suspected_areas: sections.append({ 'heading_en': 'Suspected Areas', 'heading_ar': 'المجالات المشتبه بها', 'type': 'text', 'content': [consult.suspected_areas] }) # Communication Modes if consult.communication_modes: sections.append({ 'heading_en': 'Communication Modes', 'heading_ar': 'أنماط التواصل', 'type': 'text', 'content': [consult.communication_modes] }) # Skills to Observe if consult.skills_to_observe: sections.append({ 'heading_en': 'Skills to Observe', 'heading_ar': 'المهارات المراقبة', 'type': 'text', 'content': [consult.skills_to_observe] }) # Oral Motor Screening if consult.oral_motor_screening: sections.append({ 'heading_en': 'Oral Motor Screening', 'heading_ar': 'فحص الحركة الفموية', 'type': 'text', 'content': [consult.oral_motor_screening] }) # Recommendations if consult.recommendations: sections.append({ 'heading_en': 'Recommendations', 'heading_ar': 'التوصيات', 'type': 'text', 'content': [consult.recommendations] }) return sections class SLPConsultPDFView(LoginRequiredMixin, TenantFilterMixin, View): """Generate PDF for SLP consultation.""" def get(self, request, pk): consult = get_object_or_404( SLPConsult.objects.select_related('patient', 'provider', 'tenant', 'signed_by'), pk=pk, tenant=request.user.tenant ) pdf_generator = SLPConsultPDFGenerator(consult, request) return pdf_generator.generate_pdf(request.GET.get('view', 'download')) class SLPConsultEmailPDFView(LoginRequiredMixin, TenantFilterMixin, View): """Email SLP consultation PDF.""" def post(self, request, pk): consult = get_object_or_404(SLPConsult, pk=pk, tenant=request.user.tenant) email_address = request.POST.get('email_address', '').strip() if not email_address: messages.error(request, _('Email address is required.')) return redirect('slp:consult_detail', pk=pk) pdf_generator = SLPConsultPDFGenerator(consult, request) subject = f"SLP Consultation - {consult.patient.mrn}" body = f"""Dear {consult.patient.first_name_en} {consult.patient.last_name_en}, Please find attached your Speech-Language Pathology consultation details. Best regards, {consult.tenant.name}""" success, msg = pdf_generator.send_email(email_address, subject, body, request.POST.get('email_message', '')) if success: messages.success(request, _('PDF sent successfully!')) else: messages.error(request, _('Failed to send email: %(error)s') % {'error': msg}) return redirect('slp:consult_detail', pk=pk) class SLPAssessmentPDFGenerator(BasePDFGenerator): """PDF generator for SLP Assessment (SLP-F-2).""" def get_document_title(self): return (f"SLP Assessment (SLP-F-2) - {self.document.patient.mrn}", "تقييم علاج النطق واللغة") def get_pdf_filename(self): date_str = self.document.assessment_date.strftime('%Y%m%d') return f"slp_assessment_{self.document.patient.mrn}_{date_str}.pdf" def get_document_sections(self): assessment = self.document patient = assessment.patient sections = [] patient_name_ar = f"{patient.first_name_ar} {patient.last_name_ar}" if patient.first_name_ar else "" sections.append({ 'heading_en': 'Patient Information', 'heading_ar': 'معلومات المريض', 'type': 'table', 'content': [ ('Name', 'الاسم', f"{patient.first_name_en} {patient.last_name_en}", patient_name_ar), ('MRN', 'رقم السجل الطبي', patient.mrn, ""), ('Age', 'العمر', f"{patient.age} years", ""), ] }) sections.append({ 'heading_en': 'Assessment Details', 'heading_ar': 'تفاصيل التقييم', 'type': 'table', 'content': [ ('Date', 'التاريخ', assessment.assessment_date.strftime('%Y-%m-%d'), ""), ('Provider', 'مقدم الخدمة', assessment.provider.get_full_name() if assessment.provider else 'N/A', ""), ('Frequency', 'التكرار', f"{assessment.frequency_per_week}/week" if assessment.frequency_per_week else 'N/A', ""), ('Duration', 'المدة', f"{assessment.session_duration_minutes} min" if assessment.session_duration_minutes else 'N/A', ""), ] }) if assessment.diagnosis_statement: sections.append({'heading_en': 'Diagnosis', 'heading_ar': 'التشخيص', 'type': 'text', 'content': [assessment.diagnosis_statement]}) if assessment.clinical_summary: sections.append({'heading_en': 'Clinical Summary', 'heading_ar': 'الملخص السريري', 'type': 'text', 'content': [assessment.clinical_summary]}) if assessment.recommendations: sections.append({'heading_en': 'Recommendations', 'heading_ar': 'التوصيات', 'type': 'text', 'content': [assessment.recommendations]}) return sections class SLPAssessmentPDFView(LoginRequiredMixin, TenantFilterMixin, View): """Generate PDF for SLP assessment.""" def get(self, request, pk): assessment = get_object_or_404(SLPAssessment, pk=pk, tenant=request.user.tenant) pdf_generator = SLPAssessmentPDFGenerator(assessment, request) return pdf_generator.generate_pdf(request.GET.get('view', 'download')) class SLPAssessmentEmailPDFView(LoginRequiredMixin, TenantFilterMixin, View): """Email SLP assessment PDF.""" def post(self, request, pk): assessment = get_object_or_404(SLPAssessment, pk=pk, tenant=request.user.tenant) email_address = request.POST.get('email_address', '').strip() if not email_address: messages.error(request, _('Email address is required.')) return redirect('slp:assessment_detail', pk=pk) pdf_generator = SLPAssessmentPDFGenerator(assessment, request) subject = f"SLP Assessment - {assessment.patient.mrn}" body = f"""Dear {assessment.patient.first_name_en}, Please find attached your SLP assessment details. Best regards, {assessment.tenant.name}""" success, msg = pdf_generator.send_email(email_address, subject, body, request.POST.get('email_message', '')) messages.success(request, _('PDF sent successfully!')) if success else messages.error(request, f'Error: {msg}') return redirect('slp:assessment_detail', pk=pk) class SLPInterventionPDFGenerator(BasePDFGenerator): """PDF generator for SLP Intervention (SLP-F-3).""" def get_document_title(self): return (f"SLP Intervention Session #{self.document.session_number} - {self.document.patient.mrn}", "جلسة تدخل علاج النطق") def get_pdf_filename(self): date_str = self.document.session_date.strftime('%Y%m%d') return f"slp_intervention_{self.document.patient.mrn}_session{self.document.session_number}_{date_str}.pdf" def get_document_sections(self): intervention = self.document patient = intervention.patient sections = [] patient_name_ar = f"{patient.first_name_ar} {patient.last_name_ar}" if patient.first_name_ar else "" sections.append({ 'heading_en': 'Patient Information', 'heading_ar': 'معلومات المريض', 'type': 'table', 'content': [ ('Name', 'الاسم', f"{patient.first_name_en} {patient.last_name_en}", patient_name_ar), ('MRN', 'رقم السجل الطبي', patient.mrn, ""), ] }) sections.append({ 'heading_en': 'Session Details', 'heading_ar': 'تفاصيل الجلسة', 'type': 'table', 'content': [ ('Session Number', 'رقم الجلسة', str(intervention.session_number), ""), ('Date', 'التاريخ', intervention.session_date.strftime('%Y-%m-%d'), ""), ('Time', 'الوقت', intervention.session_time.strftime('%H:%M'), ""), ('Provider', 'مقدم الخدمة', intervention.provider.get_full_name() if intervention.provider else 'N/A', ""), ] }) # Targets targets = intervention.targets.all() if targets: for target in targets: target_content = [] if target.subjective: target_content.append(f"Subjective: {target.subjective}") if target.objective: target_content.append(f"Objective: {target.objective}") if target.assessment: target_content.append(f"Assessment: {target.assessment}") if target.plan: target_content.append(f"Plan: {target.plan}") if target.prompt_strategies: target_content.append(f"Prompt Strategies: {target.prompt_strategies}") sections.append({ 'heading_en': f'Target #{target.target_number}', 'heading_ar': f'الهدف #{target.target_number}', 'type': 'text', 'content': target_content }) return sections class SLPInterventionPDFView(LoginRequiredMixin, TenantFilterMixin, View): """Generate PDF for SLP intervention.""" def get(self, request, pk): intervention = get_object_or_404( SLPIntervention.objects.select_related('patient', 'provider', 'tenant').prefetch_related('targets'), pk=pk, tenant=request.user.tenant ) pdf_generator = SLPInterventionPDFGenerator(intervention, request) return pdf_generator.generate_pdf(request.GET.get('view', 'download')) class SLPInterventionEmailPDFView(LoginRequiredMixin, TenantFilterMixin, View): """Email SLP intervention PDF.""" def post(self, request, pk): intervention = get_object_or_404(SLPIntervention, pk=pk, tenant=request.user.tenant) email_address = request.POST.get('email_address', '').strip() if not email_address: messages.error(request, _('Email address is required.')) return redirect('slp:intervention_detail', pk=pk) pdf_generator = SLPInterventionPDFGenerator(intervention, request) subject = f"SLP Intervention Session #{intervention.session_number}" body = f"""Dear {intervention.patient.first_name_en}, Please find attached your SLP intervention session details. Best regards, {intervention.tenant.name}""" success, msg = pdf_generator.send_email(email_address, subject, body, request.POST.get('email_message', '')) messages.success(request, _('PDF sent successfully!')) if success else messages.error(request, f'Error: {msg}') return redirect('slp:intervention_detail', pk=pk) class SLPProgressReportPDFGenerator(BasePDFGenerator): """PDF generator for SLP Progress Report (SLP-F-4).""" def get_document_title(self): return (f"SLP Progress Report (SLP-F-4) - {self.document.patient.mrn}", "تقرير تقدم علاج النطق") def get_pdf_filename(self): date_str = self.document.report_date.strftime('%Y%m%d') return f"slp_progress_report_{self.document.patient.mrn}_{date_str}.pdf" def get_document_sections(self): report = self.document patient = report.patient sections = [] patient_name_ar = f"{patient.first_name_ar} {patient.last_name_ar}" if patient.first_name_ar else "" sections.append({ 'heading_en': 'Patient Information', 'heading_ar': 'معلومات المريض', 'type': 'table', 'content': [ ('Name', 'الاسم', f"{patient.first_name_en} {patient.last_name_en}", patient_name_ar), ('MRN', 'رقم السجل الطبي', patient.mrn, ""), ] }) sections.append({ 'heading_en': 'Report Details', 'heading_ar': 'تفاصيل التقرير', 'type': 'table', 'content': [ ('Report Date', 'تاريخ التقرير', report.report_date.strftime('%Y-%m-%d'), ""), ('Provider', 'مقدم الخدمة', report.provider.get_full_name() if report.provider else 'N/A', ""), ('Sessions Scheduled', 'الجلسات المجدولة', str(report.sessions_scheduled), ""), ('Sessions Attended', 'الجلسات المحضورة', str(report.sessions_attended), ""), ('Attendance Rate', 'معدل الحضور', f"{report.attendance_rate}%" if report.attendance_rate else 'N/A', ""), ] }) if report.final_diagnosis: sections.append({'heading_en': 'Final Diagnosis', 'heading_ar': 'التشخيص النهائي', 'type': 'text', 'content': [report.final_diagnosis]}) if report.objectives_progress: sections.append({'heading_en': 'Objectives Progress', 'heading_ar': 'تقدم الأهداف', 'type': 'text', 'content': [report.objectives_progress]}) if report.overall_progress: sections.append({'heading_en': 'Overall Progress', 'heading_ar': 'التقدم العام', 'type': 'text', 'content': [report.overall_progress]}) if report.recommendations: sections.append({'heading_en': 'Recommendations', 'heading_ar': 'التوصيات', 'type': 'text', 'content': [report.recommendations]}) return sections class SLPProgressReportPDFView(LoginRequiredMixin, TenantFilterMixin, View): """Generate PDF for SLP progress report.""" def get(self, request, pk): report = get_object_or_404(SLPProgressReport, pk=pk, tenant=request.user.tenant) pdf_generator = SLPProgressReportPDFGenerator(report, request) return pdf_generator.generate_pdf(request.GET.get('view', 'download')) class SLPProgressReportEmailPDFView(LoginRequiredMixin, TenantFilterMixin, View): """Email SLP progress report PDF.""" def post(self, request, pk): report = get_object_or_404(SLPProgressReport, pk=pk, tenant=request.user.tenant) email_address = request.POST.get('email_address', '').strip() if not email_address: messages.error(request, _('Email address is required.')) return redirect('slp:progress_report_detail', pk=pk) pdf_generator = SLPProgressReportPDFGenerator(report, request) subject = f"SLP Progress Report - {report.patient.mrn}" body = f"""Dear {report.patient.first_name_en}, Please find attached your SLP progress report. Best regards, {report.tenant.name}""" success, msg = pdf_generator.send_email(email_address, subject, body, request.POST.get('email_message', '')) messages.success(request, _('PDF sent successfully!')) if success else messages.error(request, f'Error: {msg}') return redirect('slp:progress_report_detail', pk=pk) class SLPConsultDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView): """ SLP consultation detail view (SLP-F-1). Features: - Full consultation details - Primary concern, suspected areas - Type of service (Consult/Eval/Intervention/ParentTraining) - Communication modes, screen time - Variant-specific questionnaires (ASD/LanguageDelay/Fluency) - Skills-to-observe matrices - Oral-motor screening """ model = SLPConsult template_name = 'slp/consultation_detail.html' context_object_name = 'consultation' def get_context_data(self, **kwargs): """Add related assessments and interventions.""" context = super().get_context_data(**kwargs) # Get related assessments context['assessments'] = SLPAssessment.objects.filter( patient=self.object.patient ).order_by('-assessment_date')[:5] # Get related interventions context['interventions'] = SLPIntervention.objects.filter( patient=self.object.patient ).order_by('-session_date')[:10] return context class SLPConsultCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin, AuditLogMixin, SuccessMessageMixin, CreateView): """ SLP consultation creation view (SLP-F-1). Features: - Base consultation + subtype flags (ASD/LanguageDelay/Fluency) - Primary concern, suspected areas - Type of service - Communication modes, screen time - Variant-specific questionnaires - Skills-to-observe matrices - Oral-motor screening - Consent verification enforced before creation """ model = SLPConsult form_class = SLPConsultForm template_name = 'slp/consult_form.html' success_message = _("SLP consultation recorded successfully!") allowed_roles = [User.Role.ADMIN, User.Role.SLP] # Consent enforcement consent_service_type = 'SLP' consent_error_message = _( "Patient must sign SLP therapy consent before consultation can be documented." ) def get_success_url(self): """Redirect to consult detail.""" return reverse_lazy('slp: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'] = _('SLP Consultation (SLP-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 SLPConsultUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, AuditLogMixin, SuccessMessageMixin, UpdateView): """SLP consultation update view (SLP-F-1).""" model = SLPConsult form_class = SLPConsultForm template_name = 'slp/consult_form.html' success_message = _("SLP consultation updated successfully!") allowed_roles = [User.Role.ADMIN, User.Role.SLP] def get_success_url(self): return reverse_lazy('slp:consult_detail', kwargs={'pk': self.object.pk}) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['form_title'] = _('Update SLP Consultation - %(mrn)s') % {'mrn': self.object.patient.mrn} context['submit_text'] = _('Update Consultation') if hasattr(self.object, 'history'): context['history'] = self.object.history.all()[:10] return context class SLPAssessmentListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin, HTMXResponseMixin, ListView): """ SLP assessment list view (SLP-F-2). Features: - Filter by patient, provider, date range - Search by patient name/MRN - Role-based filtering """ model = SLPAssessment template_name = 'slp/assessment_list.html' htmx_template_name = 'slp/partials/assessment_list_partial.html' context_object_name = 'assessments' paginate_by = 25 def get_queryset(self): """Get filtered queryset.""" queryset = super().get_queryset() user = self.request.user if user.role == User.Role.SLP: queryset = queryset.filter(provider__user=user) 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) ) 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(assessment_date__gte=date_from) date_to = self.request.GET.get('date_to') if date_to: queryset = queryset.filter(assessment_date__lte=date_to) return queryset.select_related('patient', 'provider', 'appointment', 'signed_by').order_by('-assessment_date') def get_context_data(self, **kwargs): """Add unsigned assessments count for current user.""" context = super().get_context_data(**kwargs) user = self.request.user unsigned_query = SLPAssessment.objects.filter(tenant=user.tenant, signed_by__isnull=True) if user.role == User.Role.SLP: unsigned_query = unsigned_query.filter(provider=user) context['unsigned_count'] = unsigned_query.count() context['unsigned_items'] = unsigned_query.select_related('patient', 'provider').order_by('-assessment_date')[:10] return context class SLPAssessmentDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView): """ SLP assessment detail view (SLP-F-2). Features: - Diagnosis statement, case history - Prenatal/perinatal/postnatal history - Developmental history - Medical status - Speech/language detail (GFTA-3/JAT, SSI scores) - Oral mechanism - Language skills via Rossetti scale (domain/age levels) - Joint-attention skills (present/absent) summaries - Clinical summary - Recommendations (frequency 1-2x/wk, 45-60 mins) - Plan & referral rules """ model = SLPAssessment template_name = 'slp/assessment_detail.html' context_object_name = 'assessment' def get_context_data(self, **kwargs): """Add related interventions.""" context = super().get_context_data(**kwargs) # Get related interventions context['interventions'] = SLPIntervention.objects.filter( patient=self.object.patient, session_date__gte=self.object.assessment_date ).order_by('-session_date')[:10] return context class SLPAssessmentCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin, AuditLogMixin, SuccessMessageMixin, CreateView): """ SLP assessment creation view (SLP-F-2). Features: - Complete assessment/reassessment form - All sections from diagnosis to recommendations - Consent verification enforced before creation """ model = SLPAssessment form_class = SLPAssessmentForm template_name = 'slp/assessment_form.html' success_message = _("SLP assessment recorded successfully!") allowed_roles = [User.Role.ADMIN, User.Role.SLP] # Consent enforcement consent_service_type = 'SLP' consent_error_message = _( "Patient must sign SLP therapy consent before assessment can be documented." ) def get_success_url(self): return reverse_lazy('slp:assessment_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 get_initial(self): """Pre-populate form fields from appointment.""" initial = super().get_initial() appointment_id = self.request.GET.get('appointment_id') if appointment_id: try: appointment = Appointment.objects.get(pk=appointment_id, tenant=self.request.user.tenant) initial['patient'] = appointment.patient.pk initial['appointment'] = appointment.pk initial['assessment_date'] = appointment.appointment_date initial['provider'] = self.request.user.pk except Appointment.DoesNotExist: pass patient_id = self.request.GET.get('patient') if patient_id and not appointment_id: initial['patient'] = patient_id initial['provider'] = self.request.user.pk initial['assessment_date'] = timezone.now().date() return initial def form_valid(self, form): form.instance.tenant = self.request.user.tenant if not form.instance.provider: form.instance.provider = self.request.user if not form.instance.assessment_date: form.instance.assessment_date = timezone.now().date() return super().form_valid(form) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['form_title'] = _('SLP Assessment (SLP-F-2)') context['submit_text'] = _('Save Assessment') 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 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 SLPAssessmentUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, AuditLogMixin, SuccessMessageMixin, UpdateView): """SLP assessment update view (SLP-F-2).""" model = SLPAssessment form_class = SLPAssessmentForm template_name = 'slp/assessment_form.html' success_message = _("SLP assessment updated successfully!") allowed_roles = [User.Role.ADMIN, User.Role.SLP] def get_success_url(self): return reverse_lazy('slp:assessment_detail', kwargs={'pk': self.object.pk}) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['form_title'] = _('Update SLP Assessment - %(mrn)s') % {'mrn': self.object.patient.mrn} context['submit_text'] = _('Update Assessment') if hasattr(self.object, 'history'): context['history'] = self.object.history.all()[:10] return context class SLPInterventionListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin, HTMXResponseMixin, ListView): """ SLP intervention list view (SLP-F-3). Features: - Filter by patient, provider, date range - Search by patient name/MRN - Role-based filtering """ model = SLPIntervention template_name = 'slp/intervention_list.html' htmx_template_name = 'slp/partials/intervention_list_partial.html' context_object_name = 'interventions' paginate_by = 25 def get_queryset(self): queryset = super().get_queryset() user = self.request.user if user.role == User.Role.SLP: queryset = queryset.filter(provider__user=user) 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) ) 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(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', 'previous_session', 'signed_by').order_by('-session_date') def get_context_data(self, **kwargs): """Add unsigned interventions count for current user.""" context = super().get_context_data(**kwargs) user = self.request.user unsigned_query = SLPIntervention.objects.filter(tenant=user.tenant, signed_by__isnull=True) if user.role == User.Role.SLP: unsigned_query = unsigned_query.filter(provider=user) context['unsigned_count'] = unsigned_query.count() context['unsigned_items'] = unsigned_query.select_related('patient', 'provider').order_by('-session_date')[:10] return context class SLPInterventionDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView): """ SLP intervention detail view (SLP-F-3). Features: - Session number/time - Target#1/2 entries with SOAP (Subjective/Objective/Assessment/Plan) - Prompt strategies - Preview previous SLP-F-3 relation """ model = SLPIntervention template_name = 'slp/intervention_detail.html' context_object_name = 'intervention' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Get targets context['targets'] = self.object.targets.all() # Get previous session if linked if self.object.previous_session: context['previous_session'] = self.object.previous_session context['previous_targets'] = self.object.previous_session.targets.all() return context class SLPInterventionCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin, AuditLogMixin, SuccessMessageMixin, CreateView): """ SLP intervention creation view (SLP-F-3). Features: - Session number/time - Target entries with SOAP + prompt strategies - Link to previous session - Consent verification enforced before creation """ model = SLPIntervention form_class = SLPInterventionForm template_name = 'slp/intervention_form.html' success_message = _("SLP intervention recorded successfully!") allowed_roles = [User.Role.ADMIN, User.Role.SLP] # Consent enforcement consent_service_type = 'SLP' consent_error_message = _( "Patient must sign SLP therapy consent before intervention can be documented." ) def get_success_url(self): return reverse_lazy('slp:intervention_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 get_initial(self): """Pre-populate form fields from appointment.""" initial = super().get_initial() appointment_id = self.request.GET.get('appointment_id') if appointment_id: try: appointment = Appointment.objects.get(pk=appointment_id, tenant=self.request.user.tenant) initial['patient'] = appointment.patient.pk initial['appointment'] = appointment.pk initial['session_date'] = appointment.appointment_date initial['session_time'] = appointment.start_time initial['provider'] = self.request.user.pk # Auto-calculate session number based on previous sessions previous_count = SLPIntervention.objects.filter( patient=appointment.patient, tenant=self.request.user.tenant ).count() initial['session_number'] = previous_count + 1 except Appointment.DoesNotExist: pass patient_id = self.request.GET.get('patient') if patient_id and not appointment_id: initial['patient'] = patient_id initial['provider'] = self.request.user.pk initial['session_date'] = timezone.now().date() initial['session_time'] = timezone.now().time() # Auto-calculate session number try: patient = Patient.objects.get(pk=patient_id, tenant=self.request.user.tenant) previous_count = SLPIntervention.objects.filter( patient=patient, tenant=self.request.user.tenant ).count() initial['session_number'] = previous_count + 1 except Patient.DoesNotExist: pass return initial def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['form_title'] = _('SLP Intervention (SLP-F-3)') context['submit_text'] = _('Save Intervention') patient_id = self.request.GET.get('patient') if patient_id: try: patient = Patient.objects.get(pk=patient_id, tenant=self.request.user.tenant) context['patient'] = patient # Get previous sessions for linking context['previous_sessions'] = SLPIntervention.objects.filter( patient=patient ).order_by('-session_date')[:10] except Patient.DoesNotExist: pass 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 # Get previous sessions for this patient context['previous_sessions'] = SLPIntervention.objects.filter( patient=context['appointment'].patient ).order_by('-session_date')[:10] except Appointment.DoesNotExist: pass return context def form_valid(self, form): form.instance.tenant = self.request.user.tenant if not form.instance.provider: form.instance.provider = self.request.user if not form.instance.session_date: form.instance.session_date = timezone.now().date() return super().form_valid(form) class SLPInterventionUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, AuditLogMixin, SuccessMessageMixin, UpdateView): """SLP intervention update view (SLP-F-3).""" model = SLPIntervention form_class = SLPInterventionForm template_name = 'slp/intervention_form.html' success_message = _("SLP intervention updated successfully!") allowed_roles = [User.Role.ADMIN, User.Role.SLP] def get_success_url(self): return reverse_lazy('slp:intervention_detail', kwargs={'pk': self.object.pk}) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['form_title'] = _('Update SLP Intervention - %(mrn)s') % {'mrn': self.object.patient.mrn} context['submit_text'] = _('Update Intervention') if hasattr(self.object, 'history'): context['history'] = self.object.history.all()[:10] return context class SLPProgressReportListView(LoginRequiredMixin, TenantFilterMixin, PaginationMixin, ListView): """ SLP progress report list view (SLP-F-4). Features: - Filter by patient, provider, date range - Search by patient name/MRN """ model = SLPProgressReport template_name = 'slp/progress_list.html' context_object_name = 'progress_reports' paginate_by = 25 def get_queryset(self): queryset = super().get_queryset() user = self.request.user if user.role == User.Role.SLP: queryset = queryset.filter(provider__user=user) 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) ) patient_id = self.request.GET.get('patient') if patient_id: queryset = queryset.filter(patient_id=patient_id) return queryset.select_related('patient', 'provider', 'signed_by').order_by('-report_date') def get_context_data(self, **kwargs): """Add unsigned progress reports count for current user.""" context = super().get_context_data(**kwargs) user = self.request.user unsigned_query = SLPProgressReport.objects.filter(tenant=user.tenant, signed_by__isnull=True) if user.role == User.Role.SLP: unsigned_query = unsigned_query.filter(provider=user) context['unsigned_count'] = unsigned_query.count() context['unsigned_items'] = unsigned_query.select_related('patient', 'provider').order_by('-report_date')[:10] return context class SLPProgressReportDetailView(LoginRequiredMixin, TenantFilterMixin, DetailView): """ SLP progress report detail view (SLP-F-4). Features: - Demographic snapshot - Sessions scheduled/attended - Final diagnosis - Objectives & progress (% accuracy) - Plan (continue/add/fade prompts/generalization) - Overall progress, participation, attendance, carryover - Prognosis, recommendations with package & sessions count - Reassessment tick """ model = SLPProgressReport template_name = 'slp/progress_detail.html' context_object_name = 'report' class SLPProgressReportCreateView(LoginRequiredMixin, RolePermissionMixin, AuditLogMixin, SuccessMessageMixin, CreateView): """ SLP progress report creation view (SLP-F-4). Features: - Complete progress report - Aggregates sessions and produces goals with % accuracy """ model = SLPProgressReport form_class = SLPProgressReportForm template_name = 'slp/progress_form.html' success_message = _("SLP progress report created successfully!") allowed_roles = [User.Role.ADMIN, User.Role.SLP] def get_success_url(self): return reverse_lazy('slp:progress_report_detail', kwargs={'pk': self.object.pk}) def form_valid(self, form): form.instance.tenant = self.request.user.tenant form.instance.provider = self.request.user if not form.instance.report_date: form.instance.report_date = timezone.now().date() return super().form_valid(form) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['form_title'] = _('SLP Progress Report (SLP-F-4)') context['submit_text'] = _('Save Report') patient_id = self.request.GET.get('patient') if patient_id: try: patient = Patient.objects.get(pk=patient_id, tenant=self.request.user.tenant) context['patient'] = patient # Get session statistics interventions = SLPIntervention.objects.filter(patient=patient) context['session_stats'] = { 'total_sessions': interventions.count(), 'date_range': { 'start': interventions.order_by('session_date').first().session_date if interventions.exists() else None, 'end': interventions.order_by('-session_date').first().session_date if interventions.exists() else None, } } except Patient.DoesNotExist: pass return context class SLPProgressReportUpdateView(LoginRequiredMixin, RolePermissionMixin, TenantFilterMixin, AuditLogMixin, SuccessMessageMixin, UpdateView): """SLP progress report update view (SLP-F-4).""" model = SLPProgressReport form_class = SLPProgressReportForm template_name = 'slp/progress_form.html' success_message = _("SLP progress report updated successfully!") allowed_roles = [User.Role.ADMIN, User.Role.SLP] def get_success_url(self): return reverse_lazy('slp:progress_report_detail', kwargs={'pk': self.object.pk}) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['form_title'] = _('Update SLP Progress Report - %(mrn)s') % {'mrn': self.object.patient.mrn} context['submit_text'] = _('Update Report') if hasattr(self.object, 'history'): context['history'] = self.object.history.all()[:10] return context class PatientProgressView(LoginRequiredMixin, TenantFilterMixin, DetailView): """ Patient progress overview view. Shows all SLP documentation for a specific patient: - Progress reports - Assessments - Interventions - Consultations """ model = Patient template_name = 'slp/patient_progress.html' context_object_name = 'patient' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) patient = self.object # Get all SLP documentation for this patient context['progress_reports'] = SLPProgressReport.objects.filter( patient=patient ).select_related('provider').order_by('-report_date') context['assessments'] = SLPAssessment.objects.filter( patient=patient ).select_related('provider').order_by('-assessment_date') context['interventions'] = SLPIntervention.objects.filter( patient=patient ).select_related('provider').order_by('-session_date')[:20] context['consults'] = SLPConsult.objects.filter( patient=patient ).select_related('provider').order_by('-consultation_date') # Calculate statistics interventions_all = SLPIntervention.objects.filter(patient=patient) context['stats'] = { 'total_sessions': interventions_all.count(), 'total_reports': context['progress_reports'].count(), 'total_assessments': context['assessments'].count(), 'total_consults': context['consults'].count(), } return context