agdar/CONSENT_IMPLEMENTATION_ANALYSIS.md
2025-11-02 14:35:35 +03:00

24 KiB

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:

    # 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

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:

# 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.


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.


Current Consent Types:

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

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.


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.


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

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:

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:

# 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)

File: aba/views.py

Changes:

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)

File: core/models.py

Migration Required: Yes

Changes to Consent Model:

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:

@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

File: core/models.py

Migration Required: Yes

Changes to Consent Model:

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:

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)

File: core/models.py

Migration Required: Yes

Changes:

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)

Features:

  • Overall consent compliance rate
  • Missing consents by patient
  • Expiring consents (next 30 days)
  • Consent coverage by service type
  • Withdrawal statistics

Features:

  • Email/SMS reminders for expiring consents
  • Notifications for missing consents
  • Scheduled tasks to check compliance

Features:

  • Sign multiple consents at once
  • Family consent management
  • Batch consent renewal

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

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

# 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

# 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