HH/apps/surveys/serializers.py

302 lines
12 KiB
Python

"""
Surveys serializers
"""
from rest_framework import serializers
from .models import SurveyInstance, SurveyQuestion, SurveyResponse, SurveyTemplate, SurveyTracking
class SurveyQuestionSerializer(serializers.ModelSerializer):
"""Survey question serializer"""
class Meta:
model = SurveyQuestion
fields = [
'id', 'survey_template', 'text', 'text_ar',
'question_type', 'order', 'is_required',
'choices_json',
'created_at', 'updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at']
class SurveyTemplateSerializer(serializers.ModelSerializer):
"""Survey template serializer"""
hospital_name = serializers.CharField(source='hospital.name', read_only=True)
questions = SurveyQuestionSerializer(many=True, read_only=True)
question_count = serializers.SerializerMethodField()
class Meta:
model = SurveyTemplate
fields = [
'id', 'name', 'name_ar',
'hospital', 'hospital_name', 'survey_type',
'scoring_method', 'negative_threshold',
'is_active',
'questions', 'question_count',
'created_at', 'updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at']
def get_question_count(self, obj):
"""Get number of questions"""
return obj.get_question_count()
class SurveyResponseSerializer(serializers.ModelSerializer):
"""Survey response serializer"""
question_text = serializers.CharField(source='question.text', read_only=True)
question_type = serializers.CharField(source='question.question_type', read_only=True)
class Meta:
model = SurveyResponse
fields = [
'id', 'survey_instance', 'question', 'question_text', 'question_type',
'numeric_value', 'text_value', 'choice_value',
'response_time_seconds',
'created_at', 'updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at']
class SurveyInstanceSerializer(serializers.ModelSerializer):
"""Survey instance serializer"""
survey_template_name = serializers.CharField(source='survey_template.name', read_only=True)
recipient_name = serializers.CharField(source='get_recipient_name', read_only=True)
patient_name = serializers.CharField(source='patient.get_full_name', read_only=True)
patient_mrn = serializers.CharField(source='patient.mrn', read_only=True)
staff_name = serializers.CharField(source='staff.get_full_name', read_only=True)
staff_email = serializers.EmailField(source='staff.email', read_only=True)
responses = SurveyResponseSerializer(many=True, read_only=True)
survey_url = serializers.SerializerMethodField()
comment_analysis = serializers.JSONField(read_only=True)
class Meta:
model = SurveyInstance
fields = [
'id', 'survey_template', 'survey_template_name',
'patient', 'patient_name', 'patient_mrn',
'staff', 'staff_name', 'staff_email',
'recipient_name',
'journey_instance', 'encounter_id',
'delivery_channel', 'recipient_phone', 'recipient_email',
'access_token', 'token_expires_at', 'survey_url',
'status', 'sent_at', 'opened_at', 'completed_at',
'total_score', 'is_negative',
'comment', 'comment_analyzed', 'comment_analysis',
'responses', 'metadata',
'created_at', 'updated_at'
]
read_only_fields = [
'id', 'access_token', 'token_expires_at',
'sent_at', 'opened_at', 'completed_at',
'total_score', 'is_negative',
'created_at', 'updated_at'
]
def validate(self, data):
"""Validate that exactly one of patient or staff is set"""
patient = data.get('patient')
staff = data.get('staff')
# If we're updating, get existing values
if self.instance:
patient = patient if patient is not None else self.instance.patient
staff = staff if staff is not None else self.instance.staff
if patient and staff:
raise serializers.ValidationError({
'patient': 'Cannot specify both patient and staff for a survey',
'staff': 'Cannot specify both patient and staff for a survey'
})
if not patient and not staff:
raise serializers.ValidationError({
'patient': 'Must specify either a patient or staff recipient',
'staff': 'Must specify either a patient or staff recipient'
})
return data
def get_survey_url(self, obj):
"""Get survey URL"""
return obj.get_survey_url()
class SurveySubmissionSerializer(serializers.Serializer):
"""
Serializer for submitting survey responses.
Used by public survey form.
"""
responses = serializers.ListField(
child=serializers.DictField(),
help_text="Array of {question_id, numeric_value, text_value, choice_value}"
)
comment = serializers.CharField(
required=False,
allow_blank=True,
allow_null=True,
help_text="Optional patient comment about their experience"
)
def validate_responses(self, value):
"""Validate responses"""
if not value:
raise serializers.ValidationError("At least one response is required")
for response in value:
if 'question_id' not in response:
raise serializers.ValidationError("Each response must have question_id")
return value
def create(self, validated_data):
"""
Create survey responses and calculate score.
This is called when a patient submits the survey.
"""
survey_instance = self.context['survey_instance']
responses_data = validated_data['responses']
from apps.surveys.models import SurveyResponse
from django.utils import timezone
# Create responses
for response_data in responses_data:
SurveyResponse.objects.create(
survey_instance=survey_instance,
question_id=response_data['question_id'],
numeric_value=response_data.get('numeric_value'),
text_value=response_data.get('text_value', ''),
choice_value=response_data.get('choice_value', ''),
response_time_seconds=response_data.get('response_time_seconds')
)
# Update survey instance
survey_instance.status = 'completed'
survey_instance.completed_at = timezone.now()
survey_instance.save()
# Calculate score
survey_instance.calculate_score()
# Save optional comment if provided
if 'comment' in validated_data and validated_data['comment']:
survey_instance.comment = validated_data['comment'].strip()
survey_instance.save(update_fields=['comment'])
# Queue processing task
from apps.surveys.tasks import process_survey_completion
process_survey_completion.delay(str(survey_instance.id))
return survey_instance
class SurveyTrackingSerializer(serializers.ModelSerializer):
"""
Survey tracking events serializer.
Tracks detailed engagement metrics for surveys.
"""
survey_template_name = serializers.CharField(source='survey_instance.survey_template.name', read_only=True)
recipient_name = serializers.CharField(source='survey_instance.get_recipient_name', read_only=True)
patient_name = serializers.CharField(source='survey_instance.patient.get_full_name', read_only=True)
staff_name = serializers.CharField(source='survey_instance.staff.get_full_name', read_only=True)
class Meta:
model = SurveyTracking
fields = [
'id', 'survey_instance', 'survey_template_name', 'recipient_name',
'patient_name', 'staff_name',
'event_type', 'time_on_page', 'total_time_spent',
'current_question', 'user_agent', 'ip_address',
'device_type', 'browser', 'country', 'city', 'metadata',
'created_at'
]
read_only_fields = ['id', 'created_at']
class SurveyInstanceAnalyticsSerializer(serializers.ModelSerializer):
"""
Enhanced survey instance serializer with tracking analytics.
"""
survey_template_name = serializers.CharField(source='survey_template.name', read_only=True)
recipient_name = serializers.CharField(source='get_recipient_name', read_only=True)
recipient_type = serializers.SerializerMethodField()
patient_name = serializers.CharField(source='patient.get_full_name', read_only=True)
patient_mrn = serializers.CharField(source='patient.mrn', read_only=True)
staff_name = serializers.CharField(source='staff.get_full_name', read_only=True)
staff_email = serializers.EmailField(source='staff.email', read_only=True)
responses = SurveyResponseSerializer(many=True, read_only=True)
survey_url = serializers.SerializerMethodField()
tracking_events_count = serializers.SerializerMethodField()
time_to_complete_minutes = serializers.SerializerMethodField()
comment_analysis = serializers.JSONField(read_only=True)
class Meta:
model = SurveyInstance
fields = [
'id', 'survey_template', 'survey_template_name',
'recipient_name', 'recipient_type',
'patient', 'patient_name', 'patient_mrn',
'staff', 'staff_name', 'staff_email',
'journey_instance', 'encounter_id',
'delivery_channel', 'recipient_phone', 'recipient_email',
'access_token', 'token_expires_at', 'survey_url',
'status', 'sent_at', 'opened_at', 'completed_at',
'open_count', 'last_opened_at', 'time_spent_seconds',
'total_score', 'is_negative',
'comment', 'comment_analyzed', 'comment_analysis',
'responses', 'metadata',
'tracking_events_count', 'time_to_complete_minutes',
'created_at', 'updated_at'
]
read_only_fields = [
'id', 'access_token', 'token_expires_at',
'sent_at', 'opened_at', 'completed_at',
'open_count', 'last_opened_at', 'time_spent_seconds',
'total_score', 'is_negative',
'tracking_events_count', 'time_to_complete_minutes',
'created_at', 'updated_at'
]
def get_survey_url(self, obj):
"""Get survey URL"""
return obj.get_survey_url()
def get_recipient_type(self, obj):
"""Get recipient type (patient or staff)"""
return 'staff' if obj.staff else 'patient'
def get_tracking_events_count(self, obj):
"""Get count of tracking events"""
return obj.tracking_events.count()
def get_time_to_complete_minutes(self, obj):
"""Calculate time to complete in minutes"""
if obj.sent_at and obj.completed_at:
time_diff = obj.completed_at - obj.sent_at
return round(time_diff.total_seconds() / 60, 2)
return None
class PublicSurveySerializer(serializers.ModelSerializer):
"""
Public survey serializer for patient-facing survey form.
Excludes sensitive information.
"""
survey_name = serializers.CharField(source='survey_template.name', read_only=True)
survey_name_ar = serializers.CharField(source='survey_template.name_ar', read_only=True)
questions = SurveyQuestionSerializer(source='survey_template.questions', many=True, read_only=True)
class Meta:
model = SurveyInstance
fields = [
'id', 'survey_name', 'survey_name_ar',
'questions', 'status', 'completed_at'
]
read_only_fields = ['id', 'status', 'completed_at']