122 lines
4.3 KiB
Python
122 lines
4.3 KiB
Python
"""
|
|
Management command to mark surveys as abandoned.
|
|
|
|
Marks surveys that have been opened or started but not completed
|
|
within a configurable time period as abandoned.
|
|
|
|
Usage:
|
|
python manage.py mark_abandoned_surveys
|
|
python manage.py mark_abandoned_surveys --hours 24
|
|
python manage.py mark_abandoned_surveys --dry-run
|
|
"""
|
|
from django.core.management.base import BaseCommand
|
|
from django.utils import timezone
|
|
from django.conf import settings
|
|
from django.utils.dateparse import parse_duration
|
|
from datetime import timedelta
|
|
|
|
from apps.surveys.models import SurveyInstance, SurveyTracking
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = 'Mark surveys as abandoned if not completed within specified time'
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument(
|
|
'--hours',
|
|
type=int,
|
|
default=getattr(settings, 'SURVEY_ABANDONMENT_HOURS', 24),
|
|
help='Hours after which to mark survey as abandoned (default: 24)'
|
|
)
|
|
parser.add_argument(
|
|
'--dry-run',
|
|
action='store_true',
|
|
help='Show what would be done without making changes'
|
|
)
|
|
|
|
def handle(self, *args, **options):
|
|
hours = options['hours']
|
|
dry_run = options['dry_run']
|
|
|
|
self.stdout.write(self.style.SUCCESS(
|
|
f"{'[DRY RUN] ' if dry_run else ''}Marking surveys as abandoned (after {hours} hours)"
|
|
))
|
|
|
|
# Calculate cutoff time
|
|
cutoff_time = timezone.now() - timedelta(hours=hours)
|
|
|
|
# Find surveys that should be marked as abandoned
|
|
# Criteria:
|
|
# 1. Status is 'viewed' or 'in_progress'
|
|
# 2. Token hasn't expired
|
|
# 3. Last opened at least X hours ago
|
|
# 4. Not already abandoned, completed, expired, or cancelled
|
|
|
|
surveys_to_abandon = SurveyInstance.objects.filter(
|
|
status__in=['viewed', 'in_progress'],
|
|
token_expires_at__gt=timezone.now(), # Not expired
|
|
last_opened_at__lt=cutoff_time
|
|
).select_related('survey_template', 'patient')
|
|
|
|
count = surveys_to_abandon.count()
|
|
|
|
if count == 0:
|
|
self.stdout.write(self.style.WARNING('No surveys to mark as abandoned'))
|
|
return
|
|
|
|
self.stdout.write(f"Found {count} surveys to mark as abandoned:")
|
|
|
|
for survey in surveys_to_abandon:
|
|
time_since_open = timezone.now() - survey.last_opened_at
|
|
hours_since_open = time_since_open.total_seconds() / 3600
|
|
|
|
# Get question count for this survey
|
|
tracking_events = survey.tracking_events.filter(
|
|
event_type='question_answered'
|
|
).count()
|
|
|
|
self.stdout.write(
|
|
f" - {survey.survey_template.name} | "
|
|
f"Patient: {survey.patient.get_full_name()} | "
|
|
f"Status: {survey.status} | "
|
|
f"Opened: {survey.last_opened_at.strftime('%Y-%m-%d %H:%M')} | "
|
|
f"{hours_since_open:.1f} hours ago | "
|
|
f"Questions answered: {tracking_events}"
|
|
)
|
|
|
|
if dry_run:
|
|
self.stdout.write(self.style.WARNING(
|
|
f"\n[DRY RUN] Would mark {count} surveys as abandoned"
|
|
))
|
|
return
|
|
|
|
# Mark surveys as abandoned
|
|
updated = 0
|
|
for survey in surveys_to_abandon:
|
|
# Update status
|
|
survey.status = 'abandoned'
|
|
survey.save(update_fields=['status'])
|
|
|
|
# Track abandonment event
|
|
tracking_events = survey.tracking_events.filter(
|
|
event_type='question_answered'
|
|
)
|
|
|
|
SurveyTracking.objects.create(
|
|
survey_instance=survey,
|
|
event_type='survey_abandoned',
|
|
current_question=tracking_events.count(),
|
|
total_time_spent=survey.time_spent_seconds or 0,
|
|
metadata={
|
|
'time_since_open_hours': round(time_since_open.total_seconds() / 3600, 2),
|
|
'questions_answered': tracking_events.count(),
|
|
'original_status': survey.status,
|
|
}
|
|
)
|
|
|
|
updated += 1
|
|
|
|
self.stdout.write(self.style.SUCCESS(
|
|
f"\nSuccessfully marked {updated} surveys as abandoned"
|
|
))
|