update survey-feedback

This commit is contained in:
Marwan Alwali 2025-12-28 20:01:22 +03:00
parent edfd1cfe2e
commit ac58f5c82f
10 changed files with 692 additions and 2 deletions

View File

@ -0,0 +1,201 @@
# Survey-Feedback Integration
## Overview
This document describes the integration between the Survey and Feedback systems, enabling a closed-loop workflow for handling negative survey responses.
## Workflow
### 1. Negative Survey Detection
When a patient completes a survey with a score below the threshold (default: 3.0/5.0):
- Survey is automatically marked as `is_negative=True`
- Staff receives notification/alert about the negative feedback
- Survey detail page displays a warning banner with follow-up actions
### 2. Patient Contact
Staff member contacts the patient to discuss the negative feedback:
- **Action**: Click "Log Patient Contact" button on survey detail page
- **Required**: Contact notes documenting the conversation
- **Optional**: Mark issue as "Resolved" or "Explained"
- **Tracked**: Who contacted, when, and what was discussed
### 3. Send Satisfaction Feedback
After contacting the patient, staff can send a satisfaction check:
- **Action**: Click "Send Satisfaction Feedback" button
- **Creates**: New Feedback record of type `SATISFACTION_CHECK`
- **Links**: Feedback is linked to the original survey via `related_survey` field
- **Sends**: Notification to patient with feedback form link (TODO: implement notification)
### 4. Patient Completes Feedback
Patient receives and completes the satisfaction feedback form:
- Rates their satisfaction with how concerns were addressed
- Provides additional comments if needed
- Feedback is tracked in the system
### 5. Close Loop
Staff reviews the satisfaction feedback:
- If satisfied → Close both survey and feedback
- If still unsatisfied → Escalate or repeat process
## Database Schema
### Feedback Model Changes
```python
# New field in Feedback model
related_survey = ForeignKey(
'surveys.SurveyInstance',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='follow_up_feedbacks'
)
# New feedback type
FeedbackType.SATISFACTION_CHECK = 'satisfaction_check', 'Satisfaction Check'
```
### SurveyInstance Model Changes
```python
# Patient contact tracking
patient_contacted = BooleanField(default=False)
patient_contacted_at = DateTimeField(null=True, blank=True)
patient_contacted_by = ForeignKey('accounts.User', ...)
contact_notes = TextField(blank=True)
issue_resolved = BooleanField(default=False)
# Satisfaction feedback tracking
satisfaction_feedback_sent = BooleanField(default=False)
satisfaction_feedback_sent_at = DateTimeField(null=True, blank=True)
```
## API Endpoints
### Survey Actions
- `POST /surveys/instances/<uuid>/log-contact/` - Log patient contact
- `POST /surveys/instances/<uuid>/send-satisfaction/` - Send satisfaction feedback
### Views
- `survey_log_patient_contact(request, pk)` - Handle patient contact logging
- `survey_send_satisfaction_feedback(request, pk)` - Trigger satisfaction feedback
## Background Tasks
### `send_satisfaction_feedback`
```python
@shared_task(bind=True, max_retries=3)
def send_satisfaction_feedback(self, survey_instance_id, user_id=None):
"""
Creates and sends satisfaction feedback form to patient.
- Validates patient was contacted
- Creates Feedback record linked to survey
- Sends notification to patient
- Updates survey tracking fields
"""
```
## UI Components
### Survey Detail Page
For negative surveys, displays:
1. **Warning Alert**: "Action Required: Contact patient to discuss negative feedback"
2. **Contact Form**:
- Contact notes textarea
- Issue resolved checkbox
- Submit button
3. **Contact Summary** (after logging):
- Who contacted and when
- Contact notes
- Resolution status
4. **Send Satisfaction Button** (after contact):
- Disabled until patient contacted
- Triggers satisfaction feedback creation
### Feedback Detail Page
For satisfaction check feedbacks, displays:
1. **Related Survey Card**:
- Original survey name and score
- Survey completion date
- Patient contact information
- Link to view original survey
## Permissions
- **Log Patient Contact**: Hospital Admin or PX Admin
- **Send Satisfaction Feedback**: Hospital Admin or PX Admin
- **View Related Survey**: Same as survey permissions
## Audit Trail
All actions are logged:
- `survey_patient_contacted` - When patient is contacted
- `satisfaction_feedback_sent` - When satisfaction feedback is sent
- Standard feedback audit events for the satisfaction check
## Migration
Run migrations to apply database changes:
```bash
python3 manage.py migrate feedback
python3 manage.py migrate surveys
```
## Future Enhancements
1. **Notifications**: Implement SMS/WhatsApp/Email notifications for satisfaction feedback
2. **Automated Reminders**: Send reminders if satisfaction feedback not completed
3. **Analytics**: Track satisfaction improvement rates
4. **Templates**: Customizable satisfaction feedback templates
5. **Multi-language**: Support for Arabic satisfaction feedback forms
## Testing Checklist
- [ ] Create survey with negative score
- [ ] Verify warning appears on survey detail
- [ ] Log patient contact with notes
- [ ] Verify contact information is saved
- [ ] Send satisfaction feedback
- [ ] Verify feedback record is created and linked
- [ ] View feedback detail and verify survey link
- [ ] View survey detail and verify feedback link
- [ ] Test permissions for different user roles
- [ ] Verify audit logs are created
## Related Files
### Models
- `apps/feedback/models.py` - Feedback model with related_survey field
- `apps/surveys/models.py` - SurveyInstance model with contact tracking
### Views
- `apps/surveys/ui_views.py` - Survey contact and satisfaction views
- `apps/feedback/views.py` - Feedback views (no changes needed)
### Tasks
- `apps/surveys/tasks.py` - send_satisfaction_feedback task
### Templates
- `templates/surveys/instance_detail.html` - Survey detail with follow-up actions
- `templates/feedback/feedback_detail.html` - Feedback detail with survey link
### URLs
- `apps/surveys/urls.py` - New URL patterns for contact and satisfaction
### Migrations
- `apps/feedback/migrations/0002_add_survey_linkage.py`
- `apps/surveys/migrations/0003_add_survey_linkage.py`
## Configuration
### Settings
```python
# Survey token expiry (default: 30 days)
SURVEY_TOKEN_EXPIRY_DAYS = 30
# Negative survey threshold (default: 3.0 out of 5.0)
# Configured per survey template in database
```
## Support
For questions or issues, contact the development team or refer to the main project documentation.

View File

@ -0,0 +1,25 @@
# Generated by Django 5.0.14 on 2025-12-28 16:51
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('feedback', '0001_initial'),
('surveys', '0003_add_survey_linkage'),
]
operations = [
migrations.AddField(
model_name='feedback',
name='related_survey',
field=models.ForeignKey(blank=True, help_text='Survey that triggered this satisfaction check feedback', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='follow_up_feedbacks', to='surveys.surveyinstance'),
),
migrations.AlterField(
model_name='feedback',
name='feedback_type',
field=models.CharField(choices=[('compliment', 'Compliment'), ('suggestion', 'Suggestion'), ('general', 'General Feedback'), ('inquiry', 'Inquiry'), ('satisfaction_check', 'Satisfaction Check')], db_index=True, default='general', max_length=20),
),
]

View File

@ -20,6 +20,7 @@ class FeedbackType(models.TextChoices):
SUGGESTION = 'suggestion', 'Suggestion'
GENERAL = 'general', 'General Feedback'
INQUIRY = 'inquiry', 'Inquiry'
SATISFACTION_CHECK = 'satisfaction_check', 'Satisfaction Check'
class FeedbackStatus(models.TextChoices):
@ -84,6 +85,16 @@ class Feedback(UUIDModel, TimeStampedModel):
help_text="Related encounter ID if applicable"
)
# Survey linkage (for satisfaction checks after negative surveys)
related_survey = models.ForeignKey(
'surveys.SurveyInstance',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='follow_up_feedbacks',
help_text="Survey that triggered this satisfaction check feedback"
)
# Organization
hospital = models.ForeignKey(
'organizations.Hospital',

View File

@ -0,0 +1,51 @@
# Generated by Django 5.0.14 on 2025-12-28 16:51
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('surveys', '0002_surveyquestion_surveyresponse_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='surveyinstance',
name='contact_notes',
field=models.TextField(blank=True, help_text='Notes from patient contact'),
),
migrations.AddField(
model_name='surveyinstance',
name='issue_resolved',
field=models.BooleanField(default=False, help_text='Whether the issue was resolved/explained'),
),
migrations.AddField(
model_name='surveyinstance',
name='patient_contacted',
field=models.BooleanField(default=False, help_text='Whether patient was contacted about negative survey'),
),
migrations.AddField(
model_name='surveyinstance',
name='patient_contacted_at',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='surveyinstance',
name='patient_contacted_by',
field=models.ForeignKey(blank=True, help_text='User who contacted the patient', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='contacted_surveys', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='surveyinstance',
name='satisfaction_feedback_sent',
field=models.BooleanField(default=False, help_text='Whether satisfaction feedback form was sent'),
),
migrations.AddField(
model_name='surveyinstance',
name='satisfaction_feedback_sent_at',
field=models.DateTimeField(blank=True, null=True),
),
]

View File

@ -262,6 +262,36 @@ class SurveyInstance(UUIDModel, TimeStampedModel):
# Metadata
metadata = models.JSONField(default=dict, blank=True)
# Patient contact tracking (for negative surveys)
patient_contacted = models.BooleanField(
default=False,
help_text="Whether patient was contacted about negative survey"
)
patient_contacted_at = models.DateTimeField(null=True, blank=True)
patient_contacted_by = models.ForeignKey(
'accounts.User',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='contacted_surveys',
help_text="User who contacted the patient"
)
contact_notes = models.TextField(
blank=True,
help_text="Notes from patient contact"
)
issue_resolved = models.BooleanField(
default=False,
help_text="Whether the issue was resolved/explained"
)
# Satisfaction feedback tracking
satisfaction_feedback_sent = models.BooleanField(
default=False,
help_text="Whether satisfaction feedback form was sent"
)
satisfaction_feedback_sent_at = models.DateTimeField(null=True, blank=True)
class Meta:
ordering = ['-created_at']
indexes = [

View File

@ -248,3 +248,115 @@ def process_survey_completion(survey_instance_id):
error_msg = f"Error processing survey completion: {str(e)}"
logger.error(error_msg, exc_info=True)
return {'status': 'error', 'reason': error_msg}
@shared_task(bind=True, max_retries=3)
def send_satisfaction_feedback(self, survey_instance_id, user_id=None):
"""
Send satisfaction feedback form to patient after negative survey contact.
This creates a feedback form linked to the survey and sends it to the patient
to assess their satisfaction with the resolution.
Args:
survey_instance_id: UUID of SurveyInstance
user_id: UUID of User who initiated the feedback (optional)
Returns:
dict: Result with feedback_id and delivery status
"""
from apps.core.services import create_audit_log
from apps.feedback.models import Feedback, FeedbackType, FeedbackResponse
from apps.notifications.services import NotificationService
from apps.surveys.models import SurveyInstance
try:
# Get survey instance
survey_instance = SurveyInstance.objects.select_related(
'patient', 'survey_template', 'journey_instance__hospital'
).get(id=survey_instance_id)
# Check if feedback already sent
if survey_instance.satisfaction_feedback_sent:
logger.warning(f"Satisfaction feedback already sent for survey {survey_instance_id}")
return {'status': 'skipped', 'reason': 'already_sent'}
# Check if patient was contacted
if not survey_instance.patient_contacted:
logger.warning(f"Patient not contacted yet for survey {survey_instance_id}")
return {'status': 'skipped', 'reason': 'patient_not_contacted'}
patient = survey_instance.patient
hospital = survey_instance.journey_instance.hospital if survey_instance.journey_instance else patient.primary_hospital
# Create satisfaction feedback
with transaction.atomic():
feedback = Feedback.objects.create(
patient=patient,
hospital=hospital,
department=survey_instance.journey_stage_instance.stage_template.department if survey_instance.journey_stage_instance else None,
feedback_type=FeedbackType.SATISFACTION_CHECK,
title=f"Satisfaction Check - {survey_instance.survey_template.name}",
message=f"Please rate your satisfaction with how we addressed your concerns regarding the survey.",
category='communication',
priority='medium',
sentiment='neutral',
status='submitted',
related_survey=survey_instance,
encounter_id=survey_instance.encounter_id,
source='system',
metadata={
'survey_id': str(survey_instance.id),
'survey_score': float(survey_instance.total_score) if survey_instance.total_score else None,
'auto_generated': True
}
)
# Create initial response
FeedbackResponse.objects.create(
feedback=feedback,
response_type='note',
message=f"Satisfaction feedback automatically created following negative survey (Score: {survey_instance.total_score})",
created_by_id=user_id,
is_internal=True
)
# Update survey instance
survey_instance.satisfaction_feedback_sent = True
survey_instance.satisfaction_feedback_sent_at = timezone.now()
survey_instance.save(update_fields=['satisfaction_feedback_sent', 'satisfaction_feedback_sent_at'])
# Send notification to patient
# TODO: Implement feedback form link notification
# For now, we'll log it
logger.info(f"Satisfaction feedback {feedback.id} created for survey {survey_instance.id}")
# Log audit event
create_audit_log(
event_type='satisfaction_feedback_sent',
description=f"Satisfaction feedback sent to {patient.get_full_name()} for survey {survey_instance.survey_template.name}",
content_object=feedback,
metadata={
'survey_id': str(survey_instance.id),
'survey_score': float(survey_instance.total_score) if survey_instance.total_score else None,
'feedback_id': str(feedback.id)
}
)
return {
'status': 'sent',
'feedback_id': str(feedback.id),
'survey_id': str(survey_instance.id)
}
except SurveyInstance.DoesNotExist:
error_msg = f"Survey instance {survey_instance_id} not found"
logger.error(error_msg)
return {'status': 'error', 'reason': error_msg}
except Exception as e:
error_msg = f"Error sending satisfaction feedback: {str(e)}"
logger.error(error_msg, exc_info=True)
# Retry the task
raise self.retry(exc=e, countdown=60 * (self.request.retries + 1))

View File

@ -1,14 +1,19 @@
"""
Survey Console UI views - Server-rendered templates for survey management
"""
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.db.models import Q, Prefetch
from django.shortcuts import get_object_or_404, render
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from django.views.decorators.http import require_http_methods
from apps.core.services import AuditService
from apps.organizations.models import Department, Hospital
from .models import SurveyInstance, SurveyTemplate
from .tasks import send_satisfaction_feedback
@login_required
@ -198,3 +203,117 @@ def survey_template_list(request):
}
return render(request, 'surveys/template_list.html', context)
@login_required
@require_http_methods(["POST"])
def survey_log_patient_contact(request, pk):
"""
Log patient contact for negative survey.
This records that the user contacted the patient to discuss
the negative survey feedback.
"""
survey = get_object_or_404(SurveyInstance, pk=pk)
# Check permission
user = request.user
if not user.is_px_admin() and not user.is_hospital_admin():
if user.hospital and survey.survey_template.hospital != user.hospital:
messages.error(request, "You don't have permission to modify this survey.")
return redirect('surveys:instance_detail', pk=pk)
# Check if survey is negative
if not survey.is_negative:
messages.warning(request, "This survey is not marked as negative.")
return redirect('surveys:instance_detail', pk=pk)
# Get form data
contact_notes = request.POST.get('contact_notes', '')
issue_resolved = request.POST.get('issue_resolved') == 'on'
if not contact_notes:
messages.error(request, "Please provide contact notes.")
return redirect('surveys:instance_detail', pk=pk)
try:
# Update survey
survey.patient_contacted = True
survey.patient_contacted_at = timezone.now()
survey.patient_contacted_by = user
survey.contact_notes = contact_notes
survey.issue_resolved = issue_resolved
survey.save(update_fields=[
'patient_contacted', 'patient_contacted_at',
'patient_contacted_by', 'contact_notes', 'issue_resolved'
])
# Log audit
AuditService.log_event(
event_type='survey_patient_contacted',
description=f"Patient contacted for negative survey by {user.get_full_name()}",
user=user,
content_object=survey,
metadata={
'contact_notes': contact_notes,
'issue_resolved': issue_resolved,
'survey_score': float(survey.total_score) if survey.total_score else None
}
)
status = "resolved" if issue_resolved else "discussed"
messages.success(request, f"Patient contact logged successfully. Issue marked as {status}.")
except Exception as e:
messages.error(request, f"Error logging patient contact: {str(e)}")
return redirect('surveys:instance_detail', pk=pk)
@login_required
@require_http_methods(["POST"])
def survey_send_satisfaction_feedback(request, pk):
"""
Send satisfaction feedback form to patient.
This creates and sends a feedback form to assess patient satisfaction
with how their negative survey concerns were addressed.
"""
survey = get_object_or_404(SurveyInstance, pk=pk)
# Check permission
user = request.user
if not user.is_px_admin() and not user.is_hospital_admin():
if user.hospital and survey.survey_template.hospital != user.hospital:
messages.error(request, "You don't have permission to modify this survey.")
return redirect('surveys:instance_detail', pk=pk)
# Check if survey is negative
if not survey.is_negative:
messages.warning(request, "This survey is not marked as negative.")
return redirect('surveys:instance_detail', pk=pk)
# Check if patient was contacted
if not survey.patient_contacted:
messages.error(request, "Please log patient contact before sending satisfaction feedback.")
return redirect('surveys:instance_detail', pk=pk)
# Check if already sent
if survey.satisfaction_feedback_sent:
messages.warning(request, "Satisfaction feedback has already been sent for this survey.")
return redirect('surveys:instance_detail', pk=pk)
try:
# Trigger async task to send satisfaction feedback
send_satisfaction_feedback.delay(str(survey.id), str(user.id))
messages.success(
request,
"Satisfaction feedback form is being sent to the patient. "
"They will receive a link to provide their feedback."
)
except Exception as e:
messages.error(request, f"Error sending satisfaction feedback: {str(e)}")
return redirect('surveys:instance_detail', pk=pk)

View File

@ -27,6 +27,8 @@ urlpatterns = [
# UI Views (authenticated)
path('instances/', ui_views.survey_instance_list, name='instance_list'),
path('instances/<uuid:pk>/', ui_views.survey_instance_detail, name='instance_detail'),
path('instances/<uuid:pk>/log-contact/', ui_views.survey_log_patient_contact, name='log_patient_contact'),
path('instances/<uuid:pk>/send-satisfaction/', ui_views.survey_send_satisfaction_feedback, name='send_satisfaction_feedback'),
path('templates/', ui_views.survey_template_list, name='template_list'),
# Public API endpoints (no auth required)

View File

@ -352,6 +352,57 @@
</div>
</div>
<!-- Related Survey (if satisfaction check) -->
{% if feedback.related_survey %}
<div class="detail-card border-info">
<div class="detail-card-header bg-info text-white">
<i class="bi bi-link-45deg me-2"></i>Related Survey
</div>
<div class="detail-card-body">
<div class="alert alert-info mb-3">
<i class="bi bi-info-circle me-2"></i>
This is a <strong>satisfaction check</strong> feedback following a negative survey response.
</div>
<div class="info-row">
<div class="info-label">Survey:</div>
<div class="info-value">
<strong>{{ feedback.related_survey.survey_template.name }}</strong>
</div>
</div>
<div class="info-row">
<div class="info-label">Original Score:</div>
<div class="info-value">
<span class="badge bg-danger">{{ feedback.related_survey.total_score|floatformat:1 }}/5.0</span>
<span class="text-muted ms-2">(Negative)</span>
</div>
</div>
<div class="info-row">
<div class="info-label">Survey Date:</div>
<div class="info-value">
{{ feedback.related_survey.completed_at|date:"M d, Y H:i" }}
</div>
</div>
{% if feedback.related_survey.patient_contacted %}
<div class="info-row">
<div class="info-label">Patient Contacted:</div>
<div class="info-value">
<i class="bi bi-check-circle-fill text-success"></i>
{{ feedback.related_survey.patient_contacted_at|date:"M d, Y H:i" }}
<br>
<small class="text-muted">By {{ feedback.related_survey.patient_contacted_by.get_full_name }}</small>
</div>
</div>
{% endif %}
<div class="mt-3">
<a href="{% url 'surveys:instance_detail' feedback.related_survey.id %}"
class="btn btn-info btn-sm w-100">
<i class="bi bi-box-arrow-up-right me-1"></i>View Original Survey
</a>
</div>
</div>
</div>
{% endif %}
<!-- Timeline -->
<div class="detail-card">
<div class="detail-card-header">Timeline & Responses</div>

View File

@ -87,7 +87,7 @@
</div>
</div>
<div class="card">
<div class="card mb-3">
<div class="card-header">
<h6 class="mb-0"><i class="bi bi-person me-2"></i>Patient Information</h6>
</div>
@ -102,6 +102,94 @@
</div>
</div>
</div>
{% if survey.is_negative %}
<div class="card border-warning">
<div class="card-header bg-warning text-dark">
<h6 class="mb-0"><i class="bi bi-exclamation-triangle me-2"></i>Follow-up Actions</h6>
</div>
<div class="card-body">
{% if not survey.patient_contacted %}
<div class="alert alert-warning mb-3">
<i class="bi bi-info-circle me-2"></i>
<strong>Action Required:</strong> Contact patient to discuss negative feedback.
</div>
<form method="post" action="{% url 'surveys:log_patient_contact' survey.id %}">
{% csrf_token %}
<div class="mb-3">
<label for="contact_notes" class="form-label">Contact Notes *</label>
<textarea class="form-control" id="contact_notes" name="contact_notes" rows="4" required
placeholder="Document your conversation with the patient..."></textarea>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="issue_resolved" name="issue_resolved">
<label class="form-check-label" for="issue_resolved">
Issue resolved or explained to patient
</label>
</div>
<button type="submit" class="btn btn-warning w-100">
<i class="bi bi-telephone me-2"></i>Log Patient Contact
</button>
</form>
{% else %}
<div class="alert alert-success mb-3">
<i class="bi bi-check-circle me-2"></i>
<strong>Patient Contacted</strong><br>
<small>By {{ survey.patient_contacted_by.get_full_name }} on {{ survey.patient_contacted_at|date:"M d, Y H:i" }}</small>
</div>
<div class="mb-3">
<strong>Contact Notes:</strong>
<p class="mb-0 mt-2">{{ survey.contact_notes }}</p>
</div>
<div class="mb-3">
<strong>Status:</strong><br>
{% if survey.issue_resolved %}
<span class="badge bg-success">Issue Resolved</span>
{% else %}
<span class="badge bg-warning">Issue Discussed</span>
{% endif %}
</div>
{% if not survey.satisfaction_feedback_sent %}
<hr>
<h6 class="mb-3">Send Satisfaction Feedback</h6>
<p class="text-muted small mb-3">
Send a feedback form to the patient to assess their satisfaction with how their concerns were addressed.
</p>
<form method="post" action="{% url 'surveys:send_satisfaction_feedback' survey.id %}">
{% csrf_token %}
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-send me-2"></i>Send Satisfaction Feedback
</button>
</form>
{% else %}
<hr>
<div class="alert alert-info mb-0">
<i class="bi bi-check-circle me-2"></i>
<strong>Satisfaction Feedback Sent</strong><br>
<small>{{ survey.satisfaction_feedback_sent_at|date:"M d, Y H:i" }}</small>
</div>
{% if survey.follow_up_feedbacks.exists %}
<div class="mt-3">
<strong>Related Feedback:</strong>
{% for feedback in survey.follow_up_feedbacks.all %}
<div class="mt-2">
<a href="{% url 'feedback:feedback_detail' feedback.id %}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-chat-left-text me-1"></i>View Feedback #{{ feedback.id|slice:":8" }}
</a>
</div>
{% endfor %}
</div>
{% endif %}
{% endif %}
{% endif %}
</div>
</div>
{% endif %}
</div>
</div>
</div>