# Consent Implementation Analysis Report **Date:** October 30, 2025 **Project:** AgdarCentre - Tenhal Multidisciplinary Healthcare Platform **Analysis Scope:** Consent enforcement across all clinical applications --- ## Executive Summary The consent implementation in AgdarCentre is **NOT PERFECT**. While the core infrastructure is robust, there are critical gaps in enforcement across clinical applications. Consent verification is only enforced at appointment check-in, but NOT when creating clinical documentation (consultations, sessions, assessments). **Risk Level:** 🔴 **HIGH** - Providers can document services without verifying patient consent, creating legal and compliance risks. --- ## Current Implementation Status ### ✅ Strengths #### 1. Core Infrastructure (core app) - **Models:** - `Consent` model with comprehensive fields - `ConsentTemplate` model for reusable templates - E-signature support (drawn, typed, uploaded, external) - Version control via `HistoricalRecords` - Audit trail with IP address, user agent tracking - Signature hash for integrity verification - **Service Layer:** - `ConsentService` class with verification methods - `verify_consent_for_service()` - checks general + service-specific + photo/video consents - `get_missing_consents()` - identifies what's missing - `get_active_consents()` - retrieves patient's active consents - `sign_consent()` - handles consent signing workflow - `create_consent()` - creates new consent forms - **API & Views:** - RESTful API endpoints via `ConsentViewSet` - CRUD views for consent templates - CRUD views for patient consents - Serializers for API responses #### 2. Appointment Integration - **Enforcement at Check-in:** - `AppointmentService.mark_arrival()` enforces consent verification - State machine prevents check-in without consent - `check_arrival_prerequisites()` provides clear feedback - Blocks patient arrival if consents missing - **Prerequisites Checking:** ```python # From appointments/services.py consent_verified, consent_message = ConsentService.verify_consent_for_service( appointment.patient, appointment.service_type ) if not consent_verified: raise ValueError(f"Consent verification required: {consent_message}") ``` --- ## ❌ Critical Gaps Identified ### 1. NO Consent Enforcement in Clinical Apps #### ABA App (aba/) **Files Examined:** `aba/views.py`, `aba/forms.py`, `aba/models.py` **Findings:** - ❌ `ABAConsultCreateView` - No consent verification - ❌ `ABASessionCreateView` - No consent verification - ❌ `ABAConsultUpdateView` - No consent verification - ❌ `ABASessionUpdateView` - No consent verification **Impact:** ABA therapists can create consultations and sessions without verifying: - General treatment consent - ABA-specific consent (if required) - Photo/Video consent (ABA often involves recording) #### Medical App (medical/) **Files Examined:** `medical/views.py`, `medical/forms.py`, `medical/models.py` **Findings:** - ❌ No consent verification found in any views - Medical consultations can be created without consent checks #### OT App (ot/) **Files Examined:** Search results showed no consent references **Findings:** - ❌ No consent verification implemented - OT sessions can be documented without consent #### SLP App (slp/) **Files Examined:** Search results showed no consent references **Findings:** - ❌ No consent verification implemented - SLP sessions can be documented without consent #### Nursing App (nursing/) **Status:** Not examined but likely missing based on pattern --- ### 2. Incomplete Service Type Coverage **Current Implementation in `ConsentService`:** ```python # From core/services.py @staticmethod def _requires_service_specific_consent(service_type: str) -> bool: specific_consent_services = [ 'SURGERY', 'PROCEDURE', 'ANESTHESIA', 'BLOOD_TRANSFUSION', 'EXPERIMENTAL_TREATMENT', ] return service_type.upper() in specific_consent_services @staticmethod def _requires_photo_video_consent(service_type: str) -> bool: recording_services = [ 'ABA', # ABA therapy often involves video recording 'BEHAVIORAL_THERAPY', 'RESEARCH', ] return service_type.upper() in recording_services ``` **Missing Service Types:** - ❌ MEDICAL (general medical consultations) - ❌ OT (occupational therapy) - ❌ SLP (speech-language pathology) - ❌ NURSING (nursing procedures) - ❌ PHYSIOTHERAPY - ❌ PSYCHOLOGY - ❌ NUTRITION **Problem:** The service type mapping is hardcoded and incomplete. Most clinical services are not recognized. --- ### 3. No Consent Checks Before Clinical Documentation **Current Flow:** 1. Patient books appointment ✅ 2. Patient arrives → Consent checked ✅ 3. Provider creates consultation/session → **NO CONSENT CHECK** ❌ **Expected Flow:** 1. Patient books appointment ✅ 2. Patient arrives → Consent checked ✅ 3. Provider creates consultation/session → **CONSENT VERIFIED** ✅ **Gap:** Providers can bypass the appointment system and create documentation directly without consent verification. --- ### 4. Limited Consent Types **Current Consent Types:** ```python class ConsentType(models.TextChoices): GENERAL_TREATMENT = 'GENERAL_TREATMENT', _('General Treatment') SERVICE_SPECIFIC = 'SERVICE_SPECIFIC', _('Service Specific') PHOTO_VIDEO = 'PHOTO_VIDEO', _('Photo/Video') DATA_SHARING = 'DATA_SHARING', _('Data Sharing') ``` **Missing Consent Types:** - ❌ ABA_THERAPY - Specific consent for ABA services - ❌ OCCUPATIONAL_THERAPY - Specific consent for OT services - ❌ SPEECH_THERAPY - Specific consent for SLP services - ❌ MEDICAL_PROCEDURE - Specific consent for medical procedures - ❌ TELEHEALTH - Consent for remote services - ❌ RESEARCH_PARTICIPATION - Research consent - ❌ STUDENT_OBSERVATION - Consent for student observers - ❌ EMERGENCY_TREATMENT - Emergency consent --- ### 5. No Consent Expiration Logic **Current Implementation:** - Consents have no expiration date - Once signed, valid indefinitely - No mechanism to require renewal **Missing Fields:** - `expires_at` - When consent expires - `renewal_required` - Flag for renewal - `renewal_period_days` - How often to renew **Risk:** Outdated consents remain valid, potentially violating regulations requiring periodic renewal. --- ### 6. No Consent Withdrawal Mechanism **Current Implementation:** - Patients cannot withdraw consent - No workflow for consent revocation - `is_active` flag exists but no withdrawal process **Missing Features:** - Consent withdrawal workflow - `withdrawn_at` timestamp - `withdrawn_by` user reference - `withdrawal_reason` text field - Audit trail for withdrawals **Risk:** Violates patient rights to withdraw consent at any time. --- ### 7. No Consent Dependency Management **Problem:** Some consents depend on others: - Service-specific consent requires general treatment consent - Photo/video consent may require general consent - Research consent may require treatment consent **Missing:** - Dependency validation - Cascade withdrawal (withdrawing general consent should affect specific consents) - Prerequisite checking --- ### 8. No Consent Compliance Reporting **Missing Features:** - Dashboard showing consent compliance rates - Alerts for missing consents - Reports on unsigned consents - Expiring consent notifications - Consent coverage by service type --- ## Detailed Recommendations ### 🔴 HIGH PRIORITY (Immediate Action Required) #### 1. Create ConsentRequiredMixin for Clinical Views **File:** `core/mixins.py` **Purpose:** Reusable mixin to enforce consent verification in all clinical CreateViews **Implementation:** ```python class ConsentRequiredMixin: """ Mixin to enforce consent verification before creating clinical documentation. Usage: class MyConsultCreateView(ConsentRequiredMixin, CreateView): consent_service_type = 'ABA' # Required consent_types_required = ['GENERAL_TREATMENT', 'ABA_THERAPY'] # Optional """ consent_service_type = None # Must be set by subclass consent_types_required = None # Optional specific consent types def dispatch(self, request, *args, **kwargs): # Get patient from form or URL patient = self.get_patient() if patient: # Verify consent from core.services import ConsentService has_consent, message = ConsentService.verify_consent_for_service( patient, self.consent_service_type ) if not has_consent: messages.error(request, f"Cannot create documentation: {message}") return redirect('core:consent_create', patient_id=patient.id) return super().dispatch(request, *args, **kwargs) def get_patient(self): """Override to specify how to get patient.""" raise NotImplementedError("Subclass must implement get_patient()") ``` #### 2. Update ConsentService with Complete Service Type Coverage **File:** `core/services.py` **Changes:** ```python # Service type configuration SERVICE_CONSENT_REQUIREMENTS = { 'MEDICAL': { 'requires_specific': False, 'requires_photo_video': False, }, 'ABA': { 'requires_specific': True, 'requires_photo_video': True, # Often involves recording }, 'OT': { 'requires_specific': True, 'requires_photo_video': False, }, 'SLP': { 'requires_specific': True, 'requires_photo_video': False, }, 'NURSING': { 'requires_specific': False, 'requires_photo_video': False, }, 'SURGERY': { 'requires_specific': True, 'requires_photo_video': False, }, 'PROCEDURE': { 'requires_specific': True, 'requires_photo_video': False, }, # ... add more as needed } @staticmethod def _requires_service_specific_consent(service_type: str) -> bool: """Check if service requires service-specific consent.""" config = SERVICE_CONSENT_REQUIREMENTS.get(service_type.upper(), {}) return config.get('requires_specific', False) @staticmethod def _requires_photo_video_consent(service_type: str) -> bool: """Check if service requires photo/video consent.""" config = SERVICE_CONSENT_REQUIREMENTS.get(service_type.upper(), {}) return config.get('requires_photo_video', False) ``` #### 3. Add Consent Verification to ABA Views **File:** `aba/views.py` **Changes:** ```python class ABAConsultCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin, AuditLogMixin, SuccessMessageMixin, CreateView): """ABA consultation creation with consent enforcement.""" consent_service_type = 'ABA' def get_patient(self): """Get patient from form or URL.""" patient_id = self.request.GET.get('patient') if patient_id: return Patient.objects.get(pk=patient_id, tenant=self.request.user.tenant) return None # ... rest of implementation class ABASessionCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin, AuditLogMixin, SuccessMessageMixin, CreateView): """ABA session creation with consent enforcement.""" consent_service_type = 'ABA' def get_patient(self): """Get patient from form or URL.""" patient_id = self.request.GET.get('patient') if patient_id: return Patient.objects.get(pk=patient_id, tenant=self.request.user.tenant) return None # ... rest of implementation ``` #### 4. Apply Same Pattern to Medical, OT, SLP Apps **Files:** `medical/views.py`, `ot/views.py`, `slp/views.py` **Pattern:** Add `ConsentRequiredMixin` to all consultation/session CreateViews with appropriate `consent_service_type`. --- ### 🟡 MEDIUM PRIORITY (Next Sprint) #### 5. Add Consent Expiration Support **File:** `core/models.py` **Migration Required:** Yes **Changes to Consent Model:** ```python class Consent(UUIDPrimaryKeyMixin, TimeStampedMixin, TenantOwnedMixin): # ... existing fields ... # NEW FIELDS expires_at = models.DateField( null=True, blank=True, verbose_name=_("Expires At"), help_text=_("Date when this consent expires and requires renewal") ) renewal_required = models.BooleanField( default=False, verbose_name=_("Renewal Required"), help_text=_("Whether this consent requires periodic renewal") ) renewal_period_days = models.PositiveIntegerField( null=True, blank=True, verbose_name=_("Renewal Period (Days)"), help_text=_("Number of days before consent expires and requires renewal") ) def is_expired(self): """Check if consent has expired.""" if self.expires_at: from django.utils import timezone return timezone.now().date() > self.expires_at return False def days_until_expiration(self): """Get days until expiration.""" if self.expires_at: from django.utils import timezone delta = self.expires_at - timezone.now().date() return delta.days return None ``` **Update ConsentService:** ```python @staticmethod def verify_consent_for_service(patient: Patient, service_type: str) -> Tuple[bool, str]: """Check if patient has valid (non-expired) consent for service.""" # Check general consent general_consent = Consent.objects.filter( patient=patient, consent_type='GENERAL_TREATMENT', is_active=True, signed_at__isnull=False ).first() if not general_consent: return False, "General treatment consent required." # Check if expired if general_consent.is_expired(): return False, "General treatment consent has expired. Please renew." # ... rest of verification logic ``` #### 6. Add Consent Withdrawal Workflow **File:** `core/models.py` **Migration Required:** Yes **Changes to Consent Model:** ```python class Consent(UUIDPrimaryKeyMixin, TimeStampedMixin, TenantOwnedMixin): # ... existing fields ... # NEW FIELDS withdrawn_at = models.DateTimeField( null=True, blank=True, verbose_name=_("Withdrawn At") ) withdrawn_by = models.ForeignKey( User, on_delete=models.SET_NULL, null=True, blank=True, related_name='withdrawn_consents', verbose_name=_("Withdrawn By") ) withdrawal_reason = models.TextField( blank=True, verbose_name=_("Withdrawal Reason") ) def withdraw(self, user, reason): """Withdraw this consent.""" from django.utils import timezone self.withdrawn_at = timezone.now() self.withdrawn_by = user self.withdrawal_reason = reason self.is_active = False self.save() ``` **Add Withdrawal View:** ```python class ConsentWithdrawView(LoginRequiredMixin, RolePermissionMixin, UpdateView): """Withdraw a consent.""" model = Consent template_name = 'clinic/consent_withdraw.html' fields = ['withdrawal_reason'] def form_valid(self, form): self.object.withdraw( user=self.request.user, reason=form.cleaned_data['withdrawal_reason'] ) messages.success(self.request, "Consent withdrawn successfully.") return redirect('core:patient_detail', pk=self.object.patient.id) ``` #### 7. Expand Consent Types **File:** `core/models.py` **Migration Required:** Yes **Changes:** ```python class ConsentType(models.TextChoices): GENERAL_TREATMENT = 'GENERAL_TREATMENT', _('General Treatment') SERVICE_SPECIFIC = 'SERVICE_SPECIFIC', _('Service Specific') PHOTO_VIDEO = 'PHOTO_VIDEO', _('Photo/Video') DATA_SHARING = 'DATA_SHARING', _('Data Sharing') # NEW TYPES ABA_THERAPY = 'ABA_THERAPY', _('ABA Therapy') OCCUPATIONAL_THERAPY = 'OCCUPATIONAL_THERAPY', _('Occupational Therapy') SPEECH_THERAPY = 'SPEECH_THERAPY', _('Speech-Language Therapy') MEDICAL_PROCEDURE = 'MEDICAL_PROCEDURE', _('Medical Procedure') TELEHEALTH = 'TELEHEALTH', _('Telehealth Services') RESEARCH = 'RESEARCH', _('Research Participation') STUDENT_OBSERVATION = 'STUDENT_OBSERVATION', _('Student Observation') EMERGENCY_TREATMENT = 'EMERGENCY_TREATMENT', _('Emergency Treatment') ``` --- ### 🟢 LOW PRIORITY (Future Enhancement) #### 8. Consent Compliance Dashboard **Features:** - Overall consent compliance rate - Missing consents by patient - Expiring consents (next 30 days) - Consent coverage by service type - Withdrawal statistics #### 9. Automated Consent Reminders **Features:** - Email/SMS reminders for expiring consents - Notifications for missing consents - Scheduled tasks to check compliance #### 10. Bulk Consent Operations **Features:** - Sign multiple consents at once - Family consent management - Batch consent renewal #### 11. Consent Dependency Management **Features:** - Define consent prerequisites - Cascade withdrawal logic - Dependency validation --- ## Implementation Timeline ### Week 1: Critical Fixes - [ ] Create `ConsentRequiredMixin` - [ ] Update `ConsentService` with complete service type coverage - [ ] Add consent verification to ABA views - [ ] Add consent verification to Medical views ### Week 2: Remaining Apps - [ ] Add consent verification to OT views - [ ] Add consent verification to SLP views - [ ] Add consent verification to Nursing views - [ ] Testing and bug fixes ### Week 3: Expiration & Withdrawal - [ ] Add expiration fields to Consent model - [ ] Implement expiration checking logic - [ ] Add withdrawal workflow - [ ] Create withdrawal views and templates ### Week 4: Enhanced Types & Reporting - [ ] Expand consent types - [ ] Create consent compliance dashboard - [ ] Add automated reminders - [ ] Documentation and training --- ## Testing Requirements ### Unit Tests - [ ] Test `ConsentRequiredMixin` blocks unauthorized access - [ ] Test `ConsentService.verify_consent_for_service()` with all service types - [ ] Test consent expiration logic - [ ] Test consent withdrawal workflow ### Integration Tests - [ ] Test ABA consultation creation with/without consent - [ ] Test Medical consultation creation with/without consent - [ ] Test appointment check-in with expired consent - [ ] Test consent withdrawal cascade effects ### User Acceptance Tests - [ ] Provider attempts to create consultation without consent - [ ] Patient signs consent and provider creates consultation - [ ] Patient withdraws consent and provider cannot create documentation - [ ] Admin views consent compliance dashboard --- ## Compliance & Legal Considerations ### Regulatory Requirements - **HIPAA:** Consent for treatment and data sharing - **Saudi MOH:** Informed consent requirements - **GDPR (if applicable):** Right to withdraw consent ### Audit Trail - All consent actions must be logged - IP address and user agent tracking - Historical records via `simple-history` ### Documentation - Consent templates must be reviewed by legal - Multi-language support required - Clear withdrawal process documented --- ## Conclusion The consent implementation has a solid foundation but critical enforcement gaps. The highest priority is adding `ConsentRequiredMixin` to all clinical CreateViews to prevent documentation without consent. This can be accomplished in 1-2 weeks with proper testing. **Recommendation:** Implement HIGH PRIORITY items immediately to close legal and compliance gaps. --- ## Appendix: Code Examples ### Example: Complete ConsentRequiredMixin Implementation ```python # core/mixins.py from django.contrib import messages from django.shortcuts import redirect from django.urls import reverse from core.services import ConsentService class ConsentRequiredMixin: """ Mixin to enforce consent verification before creating clinical documentation. Attributes: consent_service_type (str): Required. The service type to check consent for. consent_redirect_url (str): Optional. URL to redirect to if consent missing. consent_error_message (str): Optional. Custom error message. Usage: class ABAConsultCreateView(ConsentRequiredMixin, CreateView): consent_service_type = 'ABA' def get_patient(self): patient_id = self.request.GET.get('patient') return Patient.objects.get(pk=patient_id) """ consent_service_type = None consent_redirect_url = None consent_error_message = None def dispatch(self, request, *args, **kwargs): """Check consent before allowing access.""" # Validate configuration if not self.consent_service_type: raise ImproperlyConfigured( f"{self.__class__.__name__} must define consent_service_type" ) # Get patient try: patient = self.get_patient() except Exception as e: messages.error(request, f"Error retrieving patient: {e}") return redirect('core:patient_list') if patient: # Verify consent has_consent, message = ConsentService.verify_consent_for_service( patient, self.consent_service_type ) if not has_consent: # Get missing consents missing = ConsentService.get_missing_consents( patient, self.consent_service_type ) # Custom or default error message error_msg = self.consent_error_message or ( f"Cannot create {self.consent_service_type} documentation: {message}. " f"Missing consents: {', '.join(missing)}" ) messages.error(request, error_msg) # Redirect to consent creation or custom URL redirect_url = self.consent_redirect_url or reverse( 'core:consent_create' ) + f'?patient={patient.id}&service_type={self.consent_service_type}' return redirect(redirect_url) return super().dispatch(request, *args, **kwargs) def get_patient(self): """ Get the patient for consent verification. Must be implemented by subclass. Returns: Patient: The patient instance Raises: NotImplementedError: If not implemented by subclass """ raise NotImplementedError( f"{self.__class__.__name__} must implement get_patient()" ) ``` ### Example: Updated ABA Views ```python # aba/views.py class ABAConsultCreateView(ConsentRequiredMixin, LoginRequiredMixin, RolePermissionMixin, AuditLogMixin, SuccessMessageMixin, CreateView): """ABA consultation creation with consent enforcement.""" model = ABAConsult form_class = ABAConsultForm template_name = 'aba/consult_form.html' success_message = "ABA consultation recorded successfully!" allowed_roles = [User.Role.ADMIN, User.Role.ABA] # Consent enforcement consent_service_type = 'ABA' consent_error_message = ( "Patient must sign ABA therapy consent before consultation can be documented." ) def get_patient(self): """Get patient from URL parameter or appointment.""" 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 # ... rest of implementation ``` --- **End of Report**