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

808 lines
24 KiB
Markdown

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