# Survey Analytics and Tracking Implementation ## Overview This document describes the comprehensive survey tracking and analytics system that tracks patient survey engagement throughout their journey. The system provides detailed metrics on survey delivery, opens, completion rates, time to complete, and abandonment patterns. ## Features Implemented ### 1. Survey Instance Tracking #### Tracking Fields Added to `SurveyInstance` Model - **`open_count`**: Number of times the survey link was opened - **`last_opened_at`**: Timestamp of the most recent survey open - **`time_spent_seconds`**: Total time patient spent on the survey in seconds - **Enhanced status field**: Now includes additional statuses: - `sent` - Survey has been sent to patient - `viewed` - Patient opened the survey - `in_progress` - Patient is actively completing the survey - `completed` - Patient completed the survey - `abandoned` - Patient opened but didn't complete - `expired` - Survey token has expired - `cancelled` - Survey was cancelled ### 2. Detailed Event Tracking #### New `SurveyTracking` Model Tracks granular events throughout the survey lifecycle: **Event Types:** - `page_view` - Patient opened a survey page - `survey_started` - Patient began answering questions - `question_answered` - Patient answered a specific question - `survey_completed` - Patient submitted the survey - `survey_abandoned` - Patient left without completing - `reminder_sent` - Reminder was sent to patient **Tracking Data:** - `time_on_page` - Time spent on current page (seconds) - `total_time_spent` - Cumulative time in survey (seconds) - `current_question` - Question number being viewed - `user_agent` - Browser/device information - `ip_address` - Patient's IP address - `device_type` - Mobile, tablet, desktop - `browser` - Chrome, Safari, Firefox, etc. - `country` - Geographic location (if available) - `city` - Geographic location (if available) - `metadata` - Flexible JSON for additional data ### 3. Analytics Functions Located in `apps/surveys/analytics.py`: #### `get_survey_engagement_stats()` Returns comprehensive engagement metrics: ```python { "total_sent": 100, "total_opened": 75, "total_completed": 60, "total_abandoned": 15, "open_rate": 75.0, # percentage "completion_rate": 60.0, # percentage "abandonment_rate": 20.0, # percentage of opened but not completed "avg_completion_time_minutes": 12.5, "delivery_breakdown": { "sms": {"sent": 60, "opened": 45, "completed": 38}, "email": {"sent": 40, "opened": 30, "completed": 22} } } ``` #### `get_patient_survey_timeline(patient_id)` Returns detailed timeline for a specific patient: ```python [ { "survey_instance_id": "...", "survey_name": "Post-Discharge Survey", "sent_at": "2025-01-20T10:00:00Z", "opened_at": "2025-01-20T14:30:00Z", "completed_at": "2025-01-20T14:45:00Z", "time_to_complete_minutes": 4.5, "delivery_channel": "sms", "status": "completed", "open_count": 1 } ] ``` #### `get_survey_completion_times()` Returns individual completion times: ```python [ { "patient_id": 123, "patient_name": "John Doe", "survey_name": "Post-Discharge Survey", "sent_at": "2025-01-20T10:00:00Z", "completed_at": "2025-01-20T14:45:00Z", "time_to_complete_minutes": 4.5, "delivery_channel": "sms" } ] ``` #### `get_survey_abandonment_analysis()` Analyzes abandonment patterns: ```python { "total_abandoned": 15, "avg_time_before_abandonment_minutes": 3.2, "abandonment_by_question": { 1: 5, # 5 abandoned at question 1 2: 7, 3: 3 }, "abandonment_by_device": { "mobile": 10, "desktop": 4, "tablet": 1 }, "abandonment_by_channel": { "sms": 8, "email": 7 } } ``` #### `get_hourly_survey_activity()` Shows activity by hour of day: ```python [ {"hour": 0, "opens": 5, "completions": 2}, {"hour": 1, "opens": 3, "completions": 1}, ... ] ``` ### 4. API Endpoints All endpoints require authentication (except public survey views). #### Survey Analytics Base path: `/api/surveys/api/analytics/` **GET `engagement_stats/`** - Query params: - `survey_template_id` (optional) - Filter by survey template - `hospital_id` (optional) - Filter by hospital - `days` (optional, default: 30) - Lookback period **GET `patient_timeline/`** - Query params: - `patient_id` (required) - Patient identifier **GET `completion_times/`** - Query params: - `survey_template_id` (optional) - `hospital_id` (optional) - `days` (optional, default: 30) **GET `abandonment_analysis/`** - Query params: - `survey_template_id` (optional) - `hospital_id` (optional) - `days` (optional, default: 30) **GET `hourly_activity/`** - Query params: - `hospital_id` (optional) - `days` (optional, default: 7) **GET `summary_dashboard/`** - Query params: - `hospital_id` (optional) - `days` (optional, default: 30) - Returns comprehensive dashboard with all key metrics #### Survey Tracking Base path: `/api/surveys/api/tracking/` **GET `by_survey/`** - Query params: - `survey_instance_id` (required) - Get tracking events for specific survey Standard list views with filtering: - `survey_instance` - Filter by survey instance - `event_type` - Filter by event type - `device_type` - Filter by device type - `browser` - Filter by browser ### 5. Admin Interface #### SurveyInstance Admin - Enhanced list display showing: - Open count - Time spent (human-readable format) - Color-coded status badges - Inline tracking events view - Detailed fieldsets for tracking data #### SurveyTracking Admin - New admin page for tracking events - Filters by event type, device, browser - Search by IP address, patient name - Links back to survey instance ### 6. Public Survey Tracking When patients access surveys via public links: 1. **First open**: Creates `page_view` event, updates `opened_at`, sets status to `viewed` 2. **First interaction**: Automatically detected via JavaScript, creates `survey_started` event, sets status to `in_progress` 3. **Answering questions**: Creates `question_answered` events 4. **Completion**: Creates `survey_completed` event, sets status to `completed`, records `completed_at` 5. **Abandonment**: Automatically detected via scheduled task, creates `survey_abandoned` event Device/browser detection using `user-agents` library. ### 7. Automatic Status Detection #### in_progress Status (Automatic) When a patient starts interacting with the survey, the system automatically detects this and updates the status to `in_progress`: **Implementation Details:** - **Frontend**: JavaScript tracking in `templates/surveys/public_form.html` - **Endpoint**: `POST /surveys/s/{access_token}/track-start/` - **Trigger**: First interaction with any question (click, input, or change) - **Tracking**: Records `survey_started` event with timestamp **How it works:** 1. Patient opens survey link 2. JavaScript monitors form for first interaction 3. On first interaction, sends POST request to tracking endpoint 4. Server updates status to `in_progress` 5. Creates `SurveyTracking` event with type `survey_started` 6. Subsequent interactions track question answers **Code Location:** - View: `apps/surveys/public_views.py:track_survey_start()` - JavaScript: `templates/surveys/public_form.html` (trackSurveyStart function) - URL: `apps/surveys/urls.py` #### abandoned Status (Automatic) When a patient opens the survey but doesn't complete it within a configurable time period, the system automatically marks it as abandoned: **Implementation Details:** - **Method**: Background task (Celery) + Management command - **Default timeframe**: 24 hours (configurable) - **Task**: `apps.surveys.tasks.mark_abandoned_surveys` - **Command**: `python manage.py mark_abandoned_surveys` **Detection Criteria:** - Survey status is `viewed` or `in_progress` - Token hasn't expired - Last opened at least X hours ago (default: 24) - Not already completed, expired, or cancelled **How it works:** 1. Scheduled task runs periodically (recommended: daily) 2. Queries surveys matching abandonment criteria 3. Updates status to `abandoned` 4. Creates `SurveyTracking` event with type `survey_abandoned` 5. Records metadata: - Time since opening (hours) - Number of questions answered - Total time spent **Usage:** ```bash # Run manually python manage.py mark_abandoned_surveys # With custom hours python manage.py mark_abandoned_surveys --hours 48 # Dry run (preview without changes) python manage.py mark_abandoned_surveys --dry-run # Via Celery (scheduled) from apps.surveys.tasks import mark_abandoned_surveys mark_abandoned_surveys.delay(hours=24) ``` **Configuration:** Add to Django settings: ```python # Number of hours before marking survey as abandoned SURVEY_ABANDONMENT_HOURS = 24 ``` **Celery Beat Schedule:** ```python # In config/celery.py app.conf.beat_schedule = { 'mark-abandoned-surveys': { 'task': 'apps.surveys.tasks.mark_abandoned_surveys', 'schedule': crontab(hour=2, minute=0), # Run daily at 2 AM 'kwargs': {'hours': 24} } } ``` **Code Locations:** - Task: `apps/surveys/tasks.py:mark_abandoned_surveys()` - Command: `apps/surveys/management/commands/mark_abandoned_surveys.py` ## Tracking Flow ### Survey Send ``` JourneyStage → create_survey_instance() ↓ SurveyInstance created with: - status = 'sent' - sent_at = now() - open_count = 0 - time_spent_seconds = 0 ``` ### Survey Open ``` Patient opens link → survey_form() view ↓ Create SurveyTracking(event_type='page_view') ↓ Update SurveyInstance: - open_count += 1 - last_opened_at = now() - status = 'viewed' ``` ### Survey Progress ``` Patient answers questions ↓ Create SurveyTracking(event_type='question_answered') ↓ Update SurveyInstance: - status = 'in_progress' - time_spent_seconds = cumulative time ``` ### Survey Completion ``` Patient submits survey → thank_you() view ↓ Create SurveyTracking(event_type='survey_completed') ↓ Update SurveyInstance: - status = 'completed' - completed_at = now() - total_score calculated ``` ### Survey Abandonment ``` Survey sent → not opened after 24h → scheduled task ↓ Update SurveyInstance: - status = 'abandoned' - Create SurveyTracking(event_type='survey_abandoned') ``` ## Usage Examples ### Get Engagement Statistics ```python from apps.surveys.analytics import get_survey_engagement_stats stats = get_survey_engagement_stats( hospital_id=1, days=30 ) print(f"Open rate: {stats['open_rate']}%") print(f"Completion rate: {stats['completion_rate']}%") print(f"Avg completion time: {stats['avg_completion_time_minutes']} minutes") ``` ### Get Patient Timeline ```python from apps.surveys.analytics import get_patient_survey_timeline timeline = get_patient_survey_timeline(patient_id=123) for entry in timeline: print(f"Survey: {entry['survey_name']}") print(f"Sent: {entry['sent_at']}") print(f"Completed: {entry['completed_at']}") print(f"Time to complete: {entry['time_to_complete_minutes']} minutes") ``` ### Access via API ```bash # Get engagement stats curl -H "Authorization: Bearer " \ "http://localhost:8000/api/surveys/api/analytics/engagement_stats/?hospital_id=1&days=30" # Get patient timeline curl -H "Authorization: Bearer " \ "http://localhost:8000/api/surveys/api/analytics/patient_timeline/?patient_id=123" # Get abandonment analysis curl -H "Authorization: Bearer " \ "http://localhost:8000/api/surveys/api/analytics/abandonment_analysis/?days=30" ``` ## Database Schema ### SurveyInstance (New Fields) ```sql ALTER TABLE surveys_surveyinstance ADD COLUMN open_count INTEGER DEFAULT 0, ADD COLUMN last_opened_at TIMESTAMP, ADD COLUMN time_spent_seconds INTEGER DEFAULT 0, MODIFY COLUMN status ENUM('sent', 'viewed', 'in_progress', 'completed', 'abandoned', 'expired', 'cancelled'); ``` ### SurveyTracking (New Table) ```sql CREATE TABLE surveys_surveytracking ( id BIGINT PRIMARY KEY AUTO_INCREMENT, survey_instance_id BIGINT NOT NULL REFERENCES surveys_surveyinstance(id), event_type VARCHAR(20) NOT NULL, time_on_page INTEGER, total_time_spent INTEGER DEFAULT 0, current_question INTEGER, user_agent TEXT, ip_address VARCHAR(45), device_type VARCHAR(50), browser VARCHAR(50), country VARCHAR(100), city VARCHAR(100), metadata JSON, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_survey_instance_created (survey_instance_id, created_at DESC), INDEX idx_event_type_created (event_type, created_at DESC), INDEX idx_ip_address (ip_address) ); ``` ## Performance Considerations ### Indexing - Composite index on `survey_instance_id` and `created_at` for fast timeline queries - Index on `event_type` for event-based filtering - Index on `ip_address` for location-based analysis ### Caching - Consider caching engagement stats for frequently accessed periods - Implement Redis caching for dashboard data ### Data Retention - SurveyTracking events can be archived after 90 days - Aggregate daily statistics for long-term reporting ## Future Enhancements 1. **Real-time Analytics**: WebSocket integration for live updates 2. **Geographic Dashboard**: Map visualization of survey responses 3. **Predictive Analytics**: ML model to predict completion likelihood 4. **A/B Testing**: Track engagement with different survey designs 5. **Integration**: Export data to analytics platforms (Google Analytics, Mixpanel) ## Testing Run the included test suite: ```bash python manage.py test apps.surveys.tests.test_analytics ``` ## Dependencies Added to `requirements.txt`: - `user-agents==2.2.0` - Browser/device detection - `ua-parser==1.0.1` - User agent parsing ## Migration Run the migration: ```bash python manage.py migrate surveys ``` ## Support For questions or issues, refer to: - API documentation: `/api/docs/` - Admin interface: `/admin/surveys/` - Code comments in `apps/surveys/analytics.py`