This commit is contained in:
ismail 2026-02-25 08:15:25 +03:00
parent b3d9bd17cb
commit d787d41bc9
21 changed files with 2550768 additions and 449 deletions

View File

@ -0,0 +1 @@
,ismail,ismail-Latitude-5500,25.02.2026 04:28,/home/ismail/.local/share/onlyoffice;

View File

@ -0,0 +1,131 @@
"""
Management command to export database data to JSON files.
Usage:
python manage.py export_data --output-dir=./exported_data
"""
import json
import os
from django.core.management.base import BaseCommand, CommandError
from django.apps import apps
from django.core.serializers import serialize
from datetime import datetime
class Command(BaseCommand):
help = 'Export database data to JSON files'
def add_arguments(self, parser):
parser.add_argument(
'--output-dir',
type=str,
default='./exported_data',
help='Directory to export JSON files to'
)
parser.add_argument(
'--apps',
type=str,
nargs='+',
help='Specific apps to export (e.g., complaints organizations accounts)'
)
parser.add_argument(
'--exclude-apps',
type=str,
nargs='+',
default=['contenttypes', 'auth', 'admin', 'sessions', 'django_celery_beat'],
help='Apps to exclude from export'
)
def handle(self, *args, **options):
output_dir = options['output_dir']
include_apps = options['apps']
exclude_apps = options['exclude_apps']
# Create output directory
os.makedirs(output_dir, exist_ok=True)
# Get all models
all_models = apps.get_models()
# Group models by app
models_by_app = {}
for model in all_models:
app_label = model._meta.app_label
# Skip excluded apps
if app_label in exclude_apps:
continue
# Skip if specific apps requested and this isn't one of them
if include_apps and app_label not in include_apps:
continue
if app_label not in models_by_app:
models_by_app[app_label] = []
models_by_app[app_label].append(model)
# Export data
total_exported = 0
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
self.stdout.write(self.style.SUCCESS(f'Exporting data to: {output_dir}'))
self.stdout.write(f'Found {len(models_by_app)} apps to export\n')
for app_label, models in models_by_app.items():
app_dir = os.path.join(output_dir, app_label)
os.makedirs(app_dir, exist_ok=True)
app_data = {}
app_count = 0
for model in models:
model_name = model._meta.model_name
# Skip models with no records
count = model.objects.count()
if count == 0:
continue
try:
# Serialize to JSON
data = serialize('json', model.objects.all())
app_data[model_name] = {
'count': count,
'data': json.loads(data)
}
app_count += count
total_exported += count
self.stdout.write(f'{app_label}.{model_name}: {count} records')
except Exception as e:
self.stdout.write(self.style.WARNING(f'{app_label}.{model_name}: Error - {str(e)}'))
# Write app data to file
if app_data:
output_file = os.path.join(app_dir, f'{app_label}_data.json')
with open(output_file, 'w', encoding='utf-8') as f:
json.dump({
'app': app_label,
'exported_at': timestamp,
'total_records': app_count,
'models': app_data
}, f, indent=2, ensure_ascii=False)
self.stdout.write(self.style.SUCCESS(f' → Exported to: {output_file}\n'))
# Create summary file
summary_file = os.path.join(output_dir, 'export_summary.json')
with open(summary_file, 'w', encoding='utf-8') as f:
json.dump({
'exported_at': timestamp,
'total_records': total_exported,
'apps_exported': list(models_by_app.keys()),
'output_directory': output_dir
}, f, indent=2)
self.stdout.write(self.style.SUCCESS('\n' + '=' * 60))
self.stdout.write(self.style.SUCCESS(f'EXPORT COMPLETE'))
self.stdout.write(self.style.SUCCESS(f'Total records exported: {total_exported}'))
self.stdout.write(self.style.SUCCESS(f'Output directory: {output_dir}'))
self.stdout.write(self.style.SUCCESS(f'Summary file: {summary_file}'))
self.stdout.write(self.style.SUCCESS('=' * 60))

View File

@ -61,7 +61,7 @@ class Command(BaseCommand):
survey = SurveyTemplate.objects.create( survey = SurveyTemplate.objects.create(
name=name, name=name,
description=f'{name} for patient feedback collection', name_ar=name,
hospital=hospital, hospital=hospital,
survey_type='general', survey_type='general',
is_active=True is_active=True

View File

@ -212,8 +212,11 @@ class Command(BaseCommand):
subsections_to_create = [] subsections_to_create = []
# Clear existing data to prevent old ID conflicts # Clear existing data to prevent old ID conflicts (skip if referenced)
SubSection.objects.all().delete() try:
SubSection.objects.all().delete()
except Exception:
self.stdout.write(self.style.WARNING('Skipping SubSection deletion - some are referenced by complaints'))
for item in subsections_data: for item in subsections_data:
subsections_to_create.append( subsections_to_create.append(

View File

@ -322,9 +322,18 @@ def doctor_rating_job_status(request, job_id):
messages.error(request, "You don't have permission to view this job.") messages.error(request, "You don't have permission to view this job.")
return redirect('physicians:physician_list') return redirect('physicians:physician_list')
# Calculate progress circle stroke-dashoffset
# Circle circumference is 326.73 (2 * pi * r, where r=52)
# When progress is 0%, offset should be 326.73 (empty)
# When progress is 100%, offset should be 0 (full)
circumference = 2 * 3.14159 * 52 # ~326.73
progress = job.progress_percentage
stroke_dashoffset = circumference * (1 - progress / 100)
context = { context = {
'job': job, 'job': job,
'progress': job.progress_percentage, 'progress': progress,
'stroke_dashoffset': stroke_dashoffset,
'is_complete': job.is_complete, 'is_complete': job.is_complete,
'results': job.results, 'results': job.results,
} }

View File

@ -0,0 +1,205 @@
{
"app": "accounts",
"exported_at": "20260225_081256",
"total_records": 5,
"models": {
"user": {
"count": 5,
"data": [
{
"model": "accounts.user",
"pk": "e4a79042-cd54-45ff-9bf5-d775bcb9fb92",
"fields": {
"password": "pbkdf2_sha256$1200000$jlJ1yPpFCioF510zF41nvd$LZeZlcG2wHPippD8ugCE6D00UbvwtlWniIfRFwY6gd8=",
"last_login": "2026-02-25T03:59:57.279Z",
"is_superuser": false,
"first_name": "",
"last_name": "",
"is_staff": false,
"date_joined": "2026-02-25T03:58:30.702Z",
"created_at": "2026-02-25T03:58:31.405Z",
"updated_at": "2026-02-25T03:59:32.986Z",
"email": "admin@hh.sa",
"username": "",
"phone": "",
"employee_id": "",
"hospital": "4ed13883-9632-4534-a007-4942258a5943",
"department": null,
"avatar": "",
"bio": "",
"language": "en",
"notification_email_enabled": true,
"notification_sms_enabled": false,
"preferred_notification_channel": "email",
"explanation_notification_channel": "email",
"is_active": true,
"is_provisional": false,
"invitation_token": null,
"invitation_expires_at": null,
"acknowledgement_completed": false,
"acknowledgement_completed_at": null,
"current_wizard_step": 0,
"wizard_completed_steps": [],
"groups": [
1,
2
],
"user_permissions": []
}
},
{
"model": "accounts.user",
"pk": "f6d00141-47c6-4c96-863e-e91c39e2da87",
"fields": {
"password": "pbkdf2_sha256$1200000$bDscjx2gPBbHeAPFwRIi8b$Hgj8Ce+H5TeCquyweT9MA7WlyJOCtwYdN26NmMJ3EEM=",
"last_login": "2026-02-25T04:12:09.315Z",
"is_superuser": true,
"first_name": "",
"last_name": "",
"is_staff": true,
"date_joined": "2026-02-25T03:57:07.977Z",
"created_at": "2026-02-25T03:57:08.653Z",
"updated_at": "2026-02-25T03:57:08.653Z",
"email": "ismail@tenhal.sa",
"username": "",
"phone": "",
"employee_id": "",
"hospital": null,
"department": null,
"avatar": "",
"bio": "",
"language": "en",
"notification_email_enabled": true,
"notification_sms_enabled": false,
"preferred_notification_channel": "email",
"explanation_notification_channel": "email",
"is_active": true,
"is_provisional": false,
"invitation_token": null,
"invitation_expires_at": null,
"acknowledgement_completed": false,
"acknowledgement_completed_at": null,
"current_wizard_step": 0,
"wizard_completed_steps": [],
"groups": [],
"user_permissions": []
}
},
{
"model": "accounts.user",
"pk": "a5d0077d-f2a7-4069-93fd-d35bdc20d314",
"fields": {
"password": "pbkdf2_sha256$1200000$aTXoF4T51QdQgp1QCw9apP$h5sEYLfp3Re9H2dWEc4aHjLPHwnlKgG9PFol0oE18Us=",
"last_login": null,
"is_superuser": false,
"first_name": "Amaal",
"last_name": "Al Otaibi",
"is_staff": true,
"date_joined": "2026-02-25T03:54:37.064Z",
"created_at": "2026-02-25T03:54:37.065Z",
"updated_at": "2026-02-25T03:54:37.742Z",
"email": "amaal@example.com",
"username": "amaal",
"phone": "",
"employee_id": "",
"hospital": null,
"department": null,
"avatar": "",
"bio": "",
"language": "en",
"notification_email_enabled": true,
"notification_sms_enabled": false,
"preferred_notification_channel": "email",
"explanation_notification_channel": "email",
"is_active": true,
"is_provisional": false,
"invitation_token": null,
"invitation_expires_at": null,
"acknowledgement_completed": false,
"acknowledgement_completed_at": null,
"current_wizard_step": 0,
"wizard_completed_steps": [],
"groups": [],
"user_permissions": []
}
},
{
"model": "accounts.user",
"pk": "d3919bb3-37a2-4cfb-9efa-5ca2b67ba91d",
"fields": {
"password": "pbkdf2_sha256$1200000$Sv7iDXpGVYzXw4BpFqXqjE$Ioq0F6Y2nly/eFwBI22KAk44qZqbhjkwSaN3hIhhxMQ=",
"last_login": null,
"is_superuser": false,
"first_name": "Abrar",
"last_name": "Al Qahtani",
"is_staff": true,
"date_joined": "2026-02-25T03:54:36.403Z",
"created_at": "2026-02-25T03:54:36.403Z",
"updated_at": "2026-02-25T03:54:37.056Z",
"email": "abrar@example.com",
"username": "abrar",
"phone": "",
"employee_id": "",
"hospital": null,
"department": null,
"avatar": "",
"bio": "",
"language": "en",
"notification_email_enabled": true,
"notification_sms_enabled": false,
"preferred_notification_channel": "email",
"explanation_notification_channel": "email",
"is_active": true,
"is_provisional": false,
"invitation_token": null,
"invitation_expires_at": null,
"acknowledgement_completed": false,
"acknowledgement_completed_at": null,
"current_wizard_step": 0,
"wizard_completed_steps": [],
"groups": [],
"user_permissions": []
}
},
{
"model": "accounts.user",
"pk": "09a84636-411a-4cd2-aa26-80abd29b1dfd",
"fields": {
"password": "pbkdf2_sha256$1200000$B6QHfxbMf2KC4kwdU4sVTf$3CLPDxTIhk6HujEaBJqTA9XdUWjqiywnrqXSsoi+ZJI=",
"last_login": null,
"is_superuser": false,
"first_name": "Rahaf",
"last_name": "Al Saud",
"is_staff": true,
"date_joined": "2026-02-25T03:54:35.738Z",
"created_at": "2026-02-25T03:54:35.738Z",
"updated_at": "2026-02-25T03:54:36.382Z",
"email": "rahaf@example.com",
"username": "rahaf",
"phone": "",
"employee_id": "",
"hospital": null,
"department": null,
"avatar": "",
"bio": "",
"language": "en",
"notification_email_enabled": true,
"notification_sms_enabled": false,
"preferred_notification_channel": "email",
"explanation_notification_channel": "email",
"is_active": true,
"is_provisional": false,
"invitation_token": null,
"invitation_expires_at": null,
"acknowledgement_completed": false,
"acknowledgement_completed_at": null,
"current_wizard_step": 0,
"wizard_completed_steps": [],
"groups": [],
"user_permissions": []
}
}
]
}
}
}

View File

@ -0,0 +1,786 @@
{
"app": "ai_engine",
"exported_at": "20260225_081256",
"total_records": 26,
"models": {
"sentimentresult": {
"count": 26,
"data": [
{
"model": "ai_engine.sentimentresult",
"pk": "ea4d89ac-33ee-4101-8f83-611c493ee679",
"fields": {
"created_at": "2026-02-25T05:09:40.733Z",
"updated_at": "2026-02-25T05:09:40.733Z",
"content_type": 57,
"object_id": "44e5ce09-a956-474a-a46e-9b79508c6633",
"text": "Complaint activated and assigned to ",
"language": "en",
"sentiment": "negative",
"sentiment_score": "-1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"complaint"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "fa5878fb-afc3-4ea0-9027-fd26bf53f9fd",
"fields": {
"created_at": "2026-02-25T05:09:40.722Z",
"updated_at": "2026-02-25T05:09:40.722Z",
"content_type": 42,
"object_id": "8bbf4733-34c8-4dc0-a440-50ac4ab90a22",
"text": "Despite pressing the call button multiple times, nurse Fahd Al-Shehri did not respond for over 30 minutes. When she finally arrived, she was annoyed and unhelpful. This level of neglect is unacceptable in a healthcare setting.",
"language": "en",
"sentiment": "positive",
"sentiment_score": "1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 1,
"keywords": [
"helpful"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "72c5e3cc-fd0f-4c6b-b78a-2a7685cba595",
"fields": {
"created_at": "2026-02-25T03:55:42.061Z",
"updated_at": "2026-02-25T03:55:42.061Z",
"content_type": 57,
"object_id": "cfdae30f-9ce2-4484-9363-b98cc19a1418",
"text": "Complaint created and registered",
"language": "en",
"sentiment": "negative",
"sentiment_score": "-1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"complaint"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "95322885-d654-4b2b-96b8-bebd006b6eda",
"fields": {
"created_at": "2026-02-25T03:55:42.059Z",
"updated_at": "2026-02-25T03:55:42.059Z",
"content_type": 42,
"object_id": "8bbf4733-34c8-4dc0-a440-50ac4ab90a22",
"text": "Despite pressing the call button multiple times, nurse Fahd Al-Shehri did not respond for over 30 minutes. When she finally arrived, she was annoyed and unhelpful. This level of neglect is unacceptable in a healthcare setting.",
"language": "en",
"sentiment": "positive",
"sentiment_score": "1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"helpful"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "d84a5a6c-f657-4d18-8e26-b77cda67284d",
"fields": {
"created_at": "2026-02-25T03:55:42.045Z",
"updated_at": "2026-02-25T03:55:42.045Z",
"content_type": 57,
"object_id": "7295f18f-83c8-4542-94ad-39efca68d7fe",
"text": "Complaint created and registered",
"language": "en",
"sentiment": "negative",
"sentiment_score": "-1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"complaint"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "382d0da3-1fd3-40b7-8b6d-7265f71795ec",
"fields": {
"created_at": "2026-02-25T03:55:42.042Z",
"updated_at": "2026-02-25T03:55:42.042Z",
"content_type": 42,
"object_id": "1f76952f-4fb6-4eaf-9fd8-2cc441e4ca07",
"text": "Dr. Noura Al-Harbi misdiagnosed my condition and prescribed wrong medication. I had to suffer for 3 more days before another doctor caught the error. This negligence is unacceptable and needs to be addressed immediately.",
"language": "en",
"sentiment": "neutral",
"sentiment_score": "0.0000",
"confidence": "0.5000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "b3ed5f77-0be3-437a-ac22-72c96a40a346",
"fields": {
"created_at": "2026-02-25T03:55:42.017Z",
"updated_at": "2026-02-25T03:55:42.017Z",
"content_type": 57,
"object_id": "56287023-2c03-4eda-b168-788a1a079cea",
"text": "Complaint created and registered",
"language": "en",
"sentiment": "negative",
"sentiment_score": "-1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"complaint"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "8337d5c0-5e53-4f51-86c0-f0a7954139ea",
"fields": {
"created_at": "2026-02-25T03:55:42.009Z",
"updated_at": "2026-02-25T03:55:42.009Z",
"content_type": 42,
"object_id": "f4096f2b-81ba-4eee-8ac8-3ad0bded1797",
"text": "Dr. Mishari Al-Fahad provided exceptional care and took the time to thoroughly explain my condition and treatment options. His expertise and bedside manner were outstanding.",
"language": "en",
"sentiment": "positive",
"sentiment_score": "1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"outstanding"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "71863e9f-5168-44e6-bc11-f2880645a301",
"fields": {
"created_at": "2026-02-25T03:55:42.006Z",
"updated_at": "2026-02-25T03:55:42.006Z",
"content_type": 57,
"object_id": "482902ef-7711-43a3-9ed9-a3b56369e591",
"text": "SMS notification sent to complainant: Your complaint has been received",
"language": "en",
"sentiment": "negative",
"sentiment_score": "-1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"complaint"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "f5ab6f53-6075-42c4-a08f-0274d33b8cb6",
"fields": {
"created_at": "2026-02-25T03:55:36.962Z",
"updated_at": "2026-02-25T03:55:36.962Z",
"content_type": 57,
"object_id": "0db0aac0-bbfb-4aaa-b648-e0aff5b3479d",
"text": "Complaint created and registered",
"language": "en",
"sentiment": "negative",
"sentiment_score": "-1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"complaint"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "adb28a27-56df-4ee2-a12b-01208399fb5d",
"fields": {
"created_at": "2026-02-25T03:55:36.957Z",
"updated_at": "2026-02-25T03:55:36.957Z",
"content_type": 42,
"object_id": "1fec51ec-ab1c-4c04-9cc4-7d3cf371940d",
"text": "هناك مواقف سيارات محدودة جداً للزوار. اضطررت للدوران عدة مرات لإيجاد مكان وتأخرت عن موعدي. هذا يجب معالجته.",
"language": "ar",
"sentiment": "neutral",
"sentiment_score": "0.0000",
"confidence": "0.5000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "4151d62a-1de3-47f7-bf32-e8bd29293853",
"fields": {
"created_at": "2026-02-25T03:55:36.941Z",
"updated_at": "2026-02-25T03:55:36.941Z",
"content_type": 57,
"object_id": "4fd1fc88-7e8e-4602-ae4c-bd7a6778438a",
"text": "Complaint created and registered",
"language": "en",
"sentiment": "negative",
"sentiment_score": "-1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"complaint"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "b0848bb7-2868-4706-b308-f6147ebba138",
"fields": {
"created_at": "2026-02-25T03:55:36.938Z",
"updated_at": "2026-02-25T03:55:36.938Z",
"content_type": 42,
"object_id": "9da3b845-c425-4bf1-b413-1d10fc1a150d",
"text": "أريد أن أعبر عن تقديري للممرضة جواهر الحربي التي بذلت ما هو أبعد من المتوقع لجعلي مرتاحاً خلال إقامتي. كلمتها اللطيفة والراعية جعلت الموقف الصعب أكثر قابلية للتحمل.",
"language": "ar",
"sentiment": "positive",
"sentiment_score": "1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"لطيف"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "88b78567-89a1-4513-a792-4796b892dde2",
"fields": {
"created_at": "2026-02-25T03:55:36.937Z",
"updated_at": "2026-02-25T03:55:36.937Z",
"content_type": 57,
"object_id": "7a094b69-80bf-4779-8dea-a0164186df14",
"text": "SMS notification sent to complainant: Your complaint has been received",
"language": "en",
"sentiment": "negative",
"sentiment_score": "-1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"complaint"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "4e4b9d1c-b790-4b44-9f2f-3b072f0495a9",
"fields": {
"created_at": "2026-02-25T03:55:31.892Z",
"updated_at": "2026-02-25T03:55:31.893Z",
"content_type": 57,
"object_id": "4512b975-e55c-4a6b-9d97-710e855ba791",
"text": "Complaint created and registered",
"language": "en",
"sentiment": "negative",
"sentiment_score": "-1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"complaint"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "19883722-8435-4e3a-9edf-b36eb65455a3",
"fields": {
"created_at": "2026-02-25T03:55:31.883Z",
"updated_at": "2026-02-25T03:55:31.883Z",
"content_type": 42,
"object_id": "c674b99c-06b3-40c2-9812-0fec40d43ea5",
"text": "جودة طعام المستشفى انخفضت بشكل كبير. الوجبات غالباً باردة وغير شهية ولا تلبي المتطلبات الغذائية. هذا يؤثر على رضا المرضى.",
"language": "ar",
"sentiment": "neutral",
"sentiment_score": "0.0000",
"confidence": "0.5000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "f47bbd0f-3f0b-400f-8b56-b8fb3bbb83e0",
"fields": {
"created_at": "2026-02-25T03:55:31.867Z",
"updated_at": "2026-02-25T03:55:31.867Z",
"content_type": 57,
"object_id": "7237e1e7-0a25-4057-98ec-b1ac5e218c4f",
"text": "Complaint created and registered",
"language": "en",
"sentiment": "negative",
"sentiment_score": "-1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"complaint"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "0f6095d8-0db3-4782-ab0e-50fb6da57077",
"fields": {
"created_at": "2026-02-25T03:55:31.863Z",
"updated_at": "2026-02-25T03:55:31.863Z",
"content_type": 42,
"object_id": "c020e66f-0f9a-42ac-9b86-a740d340cd2d",
"text": "عندما تم قبولي في غرفتي، لم تكن نظيفة بشكل صحيح. كان هناك غبار على الأسطح وحمام غير صحي. هذا مصدر قلق لسلامة المرضى.",
"language": "ar",
"sentiment": "positive",
"sentiment_score": "1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"نظيف"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.2,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "726b916a-7804-4d32-9fef-6cfcb9518599",
"fields": {
"created_at": "2026-02-25T03:55:31.851Z",
"updated_at": "2026-02-25T03:55:31.852Z",
"content_type": 57,
"object_id": "09cd1726-8993-4ae4-bae9-40702333d800",
"text": "Complaint created and registered",
"language": "en",
"sentiment": "negative",
"sentiment_score": "-1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"complaint"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "c5e1627d-825b-4ea0-b0b7-26bb0008c721",
"fields": {
"created_at": "2026-02-25T03:55:31.848Z",
"updated_at": "2026-02-25T03:55:31.848Z",
"content_type": 42,
"object_id": "312155d3-b384-4e6f-b2e7-f8bd21e7817b",
"text": "عندما تم قبولي في غرفتي، لم تكن نظيفة بشكل صحيح. كان هناك غبار على الأسطح وحمام غير صحي. هذا مصدر قلق لسلامة المرضى.",
"language": "ar",
"sentiment": "positive",
"sentiment_score": "1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"نظيف"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.2,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "f0c8d663-8f7b-40d4-8429-deb6b88e0556",
"fields": {
"created_at": "2026-02-25T03:55:31.846Z",
"updated_at": "2026-02-25T03:55:31.846Z",
"content_type": 57,
"object_id": "f226476d-c5e0-4726-8ece-1cfab6bfa0ff",
"text": "SMS notification sent to complainant: Your complaint has been received",
"language": "en",
"sentiment": "negative",
"sentiment_score": "-1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"complaint"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "cef2911a-be2c-46a7-9d2a-67d524e6d880",
"fields": {
"created_at": "2026-02-25T03:55:26.807Z",
"updated_at": "2026-02-25T03:55:26.807Z",
"content_type": 57,
"object_id": "d5fb457f-7d7b-4485-b0fe-e0b6ce52880c",
"text": "Complaint created and registered",
"language": "en",
"sentiment": "negative",
"sentiment_score": "-1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"complaint"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "66e6d7dd-0dbc-4467-8c41-bda4e6a20563",
"fields": {
"created_at": "2026-02-25T03:55:26.796Z",
"updated_at": "2026-02-25T03:55:26.796Z",
"content_type": 42,
"object_id": "4f11f759-8061-427d-899b-e0c360d11a20",
"text": "على الرغم من الضغط على زر الاستدعاء عدة مرات، لم تستجب الممرضة نورة الفالح لأكثر من 30 دقيقة. عندما وصلت أخيراً، كانت منزعجة وغير مفيدة. هذا مستوى من الإهمال غير مقبول في بيئة الرعاية الصحية.",
"language": "ar",
"sentiment": "positive",
"sentiment_score": "1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"مفيد"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "9a213f8f-02c7-4ad3-9b29-2fe35d09b40d",
"fields": {
"created_at": "2026-02-25T03:55:26.790Z",
"updated_at": "2026-02-25T03:55:26.791Z",
"content_type": 57,
"object_id": "51ec2c79-a504-437c-97a3-ddce7f1d65d8",
"text": "SMS notification sent to complainant: Your complaint has been received",
"language": "en",
"sentiment": "negative",
"sentiment_score": "-1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 2,
"keywords": [
"complaint"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "603917ee-a90b-4cd1-a8b6-06ee32e9f8ab",
"fields": {
"created_at": "2026-02-25T03:55:21.596Z",
"updated_at": "2026-02-25T03:55:21.596Z",
"content_type": 57,
"object_id": "dbb70f16-8f80-4a63-8207-cd55bf2e8226",
"text": "Complaint created and registered",
"language": "en",
"sentiment": "negative",
"sentiment_score": "-1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"complaint"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
},
{
"model": "ai_engine.sentimentresult",
"pk": "7b7440ee-5de5-46b3-81b4-37ac9d5d0680",
"fields": {
"created_at": "2026-02-25T03:55:21.592Z",
"updated_at": "2026-02-25T03:55:21.592Z",
"content_type": 42,
"object_id": "75699d55-58b8-42d2-94c3-ff479a005dad",
"text": "أريد أن أعبر عن تقديري للممرضة علي العنزي التي بذلت ما هو أبعد من المتوقع لجعلي مرتاحاً خلال إقامتي. كلمتها اللطيفة والراعية جعلت الموقف الصعب أكثر قابلية للتحمل.",
"language": "ar",
"sentiment": "positive",
"sentiment_score": "1.0000",
"confidence": "0.3000",
"ai_service": "stub",
"ai_model": "keyword_matching_v1",
"processing_time_ms": 0,
"keywords": [
"لطيف"
],
"entities": [],
"emotions": {
"joy": 0.0,
"anger": 0.0,
"sadness": 0.0,
"fear": 0.0,
"surprise": 0.0
},
"metadata": {}
}
}
]
}
}
}

View File

@ -0,0 +1,914 @@
{
"app": "analytics",
"exported_at": "20260225_081256",
"total_records": 47,
"models": {
"kpireport": {
"count": 3,
"data": [
{
"model": "analytics.kpireport",
"pk": "56af32e0-a5b2-4d38-8b18-bd0a06805237",
"fields": {
"created_at": "2026-02-25T05:06:53.918Z",
"updated_at": "2026-02-25T05:06:54.114Z",
"report_type": "activation_2h",
"hospital": "4ed13883-9632-4534-a007-4942258a5943",
"year": 2026,
"month": 2,
"report_date": "2026-02-25",
"status": "completed",
"generated_by": "e4a79042-cd54-45ff-9bf5-d775bcb9fb92",
"generated_at": "2026-02-25T05:06:54.114Z",
"target_percentage": "95.00",
"category": "Organizational",
"kpi_type": "Outcome",
"risk_level": "High",
"data_collection_method": "Retrospective",
"data_collection_frequency": "Monthly",
"reporting_frequency": "Monthly",
"dimension": "Efficiency",
"collector_name": "",
"analyzer_name": "",
"total_numerator": 0,
"total_denominator": 10,
"overall_result": "0.00",
"error_message": "",
"ai_analysis": null,
"ai_analysis_generated_at": null
}
},
{
"model": "analytics.kpireport",
"pk": "88795c74-4a05-4030-bd81-24b844564b3c",
"fields": {
"created_at": "2026-02-25T05:05:17.626Z",
"updated_at": "2026-02-25T05:05:17.892Z",
"report_type": "resolution_72h",
"hospital": "4ed13883-9632-4534-a007-4942258a5943",
"year": 2026,
"month": 2,
"report_date": "2026-02-25",
"status": "completed",
"generated_by": "e4a79042-cd54-45ff-9bf5-d775bcb9fb92",
"generated_at": "2026-02-25T05:05:17.892Z",
"target_percentage": "95.00",
"category": "Organizational",
"kpi_type": "Outcome",
"risk_level": "High",
"data_collection_method": "Retrospective",
"data_collection_frequency": "Monthly",
"reporting_frequency": "Monthly",
"dimension": "Efficiency",
"collector_name": "",
"analyzer_name": "",
"total_numerator": 0,
"total_denominator": 10,
"overall_result": "0.00",
"error_message": "",
"ai_analysis": {
"executive_summary": "The 72-Hour Complaint Resolution KPI for Olaya Hospital in February 2026 shows a complete failure to meet targets, with a 0.00% resolution rate against a 95.00% target (0 out of 10 complaints resolved within 72 hours). All 10 complaints (from sources including Social Media 40%, Call Center 30%, Family Member 20%, and Staff 10%) remain unresolved, with no resolution times recorded in the breakdown (0 in Within 24h, Within 48h, Within 72h, or After 72h). This indicates a systemic issue in complaint handling, likely due to process gaps, lack of escalation, or resource constraints. Immediate intervention is required to address the backlog and prevent further deterioration.",
"performance_analysis": {
"overall_resolution_rate": "0.00%",
"target_resolution_rate": "95.00%",
"variance": "-95.00%",
"status": "below target",
"total_complaints": 10,
"resolved_within_72h": 0,
"performance_trend": {
"current_month": "0.00% (0/10)",
"previous_months_data": "Not provided, but current month shows no improvement from baseline",
"improvement_needed": "100% resolution rate required to meet target next month"
}
},
"key_findings": [
"Zero resolutions: No complaints (0 out of 10) were closed within or after 72 hours, indicating a complete process breakdown.",
"Uniform delay: All resolution time buckets (Within 24h, 48h, 72h, After 72h) show 0.00%, with no partial progress.",
"Source distribution: Top sources are Social Media (4 complaints, 40%) and Call Center (3 complaints, 30%), suggesting high visibility complaints are piling up.",
"Department ambiguity: Despite categories, all departments show 0 complaints, 0 resolved; no 'slow departments' flagged, implying no active tracking or misclassification.",
"Escalation and closure gaps: 0 escalated and 0 closed cases highlight lack of follow-up mechanisms.",
"Month-over-month stagnation: February 2026 mirrors the 0.00% rate, with no resolved cases from prior periods shown."
],
"reasons_for_delays": [
"Process failure: No recorded resolutions suggest absent or ineffective complaint intake, assignment, or tracking workflows, potentially due to unclear ownership.",
"Resource constraints: With 10 total complaints and zero actions, likely understaffing or lack of dedicated complaint resolution teams across departments.",
"Escalation inaction: 0 escalated cases indicate no escalation protocols are being followed, leading to indefinite hold on complaints.",
"Source-specific challenges: High Social Media (40%) complaints may require PR/Communication involvement, which appears unaddressed; Call Center (30%) might overload front-line staff without backend support.",
"Departmental silos: Zero counts in all departments (Medical, Nursing, Admin, Support) suggest complaints are not routed correctly or are being ignored at the department level.",
"External factors: No data on holidays/overload, but the uniform 0% resolution points to internal inefficiencies rather than volume spikes."
],
"resolution_time_analysis": {
"within_24h": {
"count": 0,
"percentage": "0.00%"
},
"within_48h": {
"count": 0,
"percentage": "0.00%"
},
"within_72h": {
"count": 0,
"percentage": "0.00%"
},
"after_72h": {
"count": 0,
"percentage": "0.00%"
},
"insights": "No complaints have progressed through any time stage, resulting in a frozen backlog. Without interventions, this will lead to increasing complaint ages and potential regulatory non-compliance."
},
"department_analysis": {
"non_medical_admin": {
"complaints": 0,
"resolved": 0,
"status": "No activity"
},
"medical_department": {
"complaints": 0,
"resolved": 0,
"status": "No activity"
},
"nursing_department": {
"complaints": 0,
"resolved": 0,
"status": "No activity"
},
"support_services": {
"complaints": 0,
"resolved": 0,
"status": "No activity"
},
"slow_departments": "None identified, but all departments show zero complaints, suggesting underreporting or lack of integration with complaint system.",
"overall_department_insight": "Cross-departmental inaction indicates a hospital-wide issue in complaint capture and resolution, rather than isolated inefficiencies."
},
"source_analysis": {
"social_media": {
"count": 4,
"percentage": "40.00%",
"insight": "Highest volume; requires immediate social media monitoring and response team to mitigate reputational risk."
},
"call_center": {
"count": 3,
"percentage": "30.00%",
"insight": "Significant portion; likely tied to patient access or service issues, needing better call center-to-department handoffs."
},
"family_member": {
"count": 2,
"percentage": "20.00%",
"insight": "Indirect complaints; may involve communication breakdowns with families, suggesting needs for family liaison protocols."
},
"staff": {
"count": 1,
"percentage": "10.00%",
"insight": "Internal complaints; points to potential employee satisfaction issues, which could impact morale if unresolved."
},
"source_summary": "Social Media and Call Center dominate (70% combined), highlighting external visibility of unresolved issues. No sources resolved, indicating universal process gap."
},
"recommendations": [
"Immediate backlog clearance: Assign a dedicated cross-functional team to triage and resolve all 10 pending complaints within 7 days, starting with high-visibility Social Media (4) and Call Center (3) cases.",
"Process enhancement: Implement automated complaint tracking with clear SLA alerts (e.g., alerts at 24h/48h) and mandatory escalation after 48 hours; train staff on intake protocols to ensure 100% capture and routing.",
"Resource allocation: Audit staffing levels in complaint handling; add temporary resources or redistribute admin/Nursing staff to handle peak volumes, aiming for at least 50% resolution within 48h next month.",
"Department accountability: Assign complaint owners per department (e.g., Medical for clinical complaints, Admin for non-medical); integrate complaint data into department KPIs to ensure visibility and drive accountability.",
"Source-specific actions: For Social Media, establish a 24/7 monitoring team; for Call Center, improve handoff checklists; for Family Member/Staff, introduce feedback loops and response templates.",
"Monitoring and review: Conduct weekly reviews of resolution rates; set interim target of 50% for March 2026, with root cause analysis for any complaints exceeding 72h.",
"Technology upgrade: If not in place, deploy a centralized CRM system to consolidate sources and automate resolution tracking, reducing manual errors."
],
"_metadata": {
"generated_at": "2026-02-25T05:05:39.218450+00:00",
"report_id": "88795c74-4a05-4030-bd81-24b844564b3c",
"report_type": "resolution_72h",
"hospital": "Olaya",
"year": 2026,
"month": 2
}
},
"ai_analysis_generated_at": "2026-02-25T05:05:39.218Z"
}
},
{
"model": "analytics.kpireport",
"pk": "3f450a3f-036b-4be6-b368-fc1c3d2dc2ff",
"fields": {
"created_at": "2026-02-25T05:06:34.229Z",
"updated_at": "2026-02-25T05:06:34.442Z",
"report_type": "satisfaction_resolution",
"hospital": "4ed13883-9632-4534-a007-4942258a5943",
"year": 2026,
"month": 2,
"report_date": "2026-02-25",
"status": "completed",
"generated_by": "e4a79042-cd54-45ff-9bf5-d775bcb9fb92",
"generated_at": "2026-02-25T05:06:34.442Z",
"target_percentage": "95.00",
"category": "Organizational",
"kpi_type": "Outcome",
"risk_level": "High",
"data_collection_method": "Retrospective",
"data_collection_frequency": "Monthly",
"reporting_frequency": "Monthly",
"dimension": "Efficiency",
"collector_name": "",
"analyzer_name": "",
"total_numerator": 0,
"total_denominator": 0,
"overall_result": "0.00",
"error_message": "",
"ai_analysis": null,
"ai_analysis_generated_at": null
}
}
]
},
"kpireportmonthlydata": {
"count": 36,
"data": [
{
"model": "analytics.kpireportmonthlydata",
"pk": "4d9202e0-cc4c-47ff-9014-ac36d6e761ca",
"fields": {
"created_at": "2026-02-25T05:05:17.640Z",
"updated_at": "2026-02-25T05:05:17.644Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"month": 1,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {
"source_breakdown": {}
}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "7cce81c6-a7a9-4004-becf-c3b830ecbacf",
"fields": {
"created_at": "2026-02-25T05:06:34.243Z",
"updated_at": "2026-02-25T05:06:34.246Z",
"kpi_report": "3f450a3f-036b-4be6-b368-fc1c3d2dc2ff",
"month": 1,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "394c89ab-56e3-4220-bfe8-5f7f070145c4",
"fields": {
"created_at": "2026-02-25T05:06:53.931Z",
"updated_at": "2026-02-25T05:06:53.934Z",
"kpi_report": "56af32e0-a5b2-4d38-8b18-bd0a06805237",
"month": 1,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "ec879001-3daf-4147-b1b8-98d2302309a2",
"fields": {
"created_at": "2026-02-25T05:05:17.659Z",
"updated_at": "2026-02-25T05:05:17.680Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"month": 2,
"numerator": 0,
"denominator": 10,
"percentage": "0.00",
"is_below_target": true,
"details": {
"source_breakdown": {
"Social Media": 4,
"Call Center": 3,
"Staff": 1,
"Family Member": 2
}
}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "156e3c95-9211-4d10-a4e1-725333e9576d",
"fields": {
"created_at": "2026-02-25T05:06:34.258Z",
"updated_at": "2026-02-25T05:06:34.265Z",
"kpi_report": "3f450a3f-036b-4be6-b368-fc1c3d2dc2ff",
"month": 2,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "4b5c87df-1245-477f-99c1-6f1ad450460f",
"fields": {
"created_at": "2026-02-25T05:06:53.949Z",
"updated_at": "2026-02-25T05:06:53.954Z",
"kpi_report": "56af32e0-a5b2-4d38-8b18-bd0a06805237",
"month": 2,
"numerator": 0,
"denominator": 10,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "34706e2e-01ae-4ae9-94ee-c91a2ffede86",
"fields": {
"created_at": "2026-02-25T05:05:17.694Z",
"updated_at": "2026-02-25T05:05:17.697Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"month": 3,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {
"source_breakdown": {}
}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "5a30b9a3-4a02-4762-b0bc-b16a6fbe9308",
"fields": {
"created_at": "2026-02-25T05:06:34.278Z",
"updated_at": "2026-02-25T05:06:34.281Z",
"kpi_report": "3f450a3f-036b-4be6-b368-fc1c3d2dc2ff",
"month": 3,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "7ebc65a9-7688-4bde-af71-da45eea94bf0",
"fields": {
"created_at": "2026-02-25T05:06:53.970Z",
"updated_at": "2026-02-25T05:06:53.974Z",
"kpi_report": "56af32e0-a5b2-4d38-8b18-bd0a06805237",
"month": 3,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "483dec68-8f6c-4ccb-9bb4-793daf28026f",
"fields": {
"created_at": "2026-02-25T05:05:17.711Z",
"updated_at": "2026-02-25T05:05:17.714Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"month": 4,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {
"source_breakdown": {}
}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "18e69243-abfb-4800-a7b4-18a09b44b188",
"fields": {
"created_at": "2026-02-25T05:06:34.293Z",
"updated_at": "2026-02-25T05:06:34.296Z",
"kpi_report": "3f450a3f-036b-4be6-b368-fc1c3d2dc2ff",
"month": 4,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "76edc093-a757-4da3-b083-dfd52c5f6f45",
"fields": {
"created_at": "2026-02-25T05:06:53.984Z",
"updated_at": "2026-02-25T05:06:53.990Z",
"kpi_report": "56af32e0-a5b2-4d38-8b18-bd0a06805237",
"month": 4,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "f18b64c2-40ea-4ee9-aef0-74af82cb4402",
"fields": {
"created_at": "2026-02-25T05:05:17.726Z",
"updated_at": "2026-02-25T05:05:17.730Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"month": 5,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {
"source_breakdown": {}
}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "48ad553d-bc29-48da-9f9d-ae6946724c28",
"fields": {
"created_at": "2026-02-25T05:06:34.308Z",
"updated_at": "2026-02-25T05:06:34.313Z",
"kpi_report": "3f450a3f-036b-4be6-b368-fc1c3d2dc2ff",
"month": 5,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "411a6f5f-dd61-4a98-88f2-b5908d666d22",
"fields": {
"created_at": "2026-02-25T05:06:53.998Z",
"updated_at": "2026-02-25T05:06:54.000Z",
"kpi_report": "56af32e0-a5b2-4d38-8b18-bd0a06805237",
"month": 5,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "dff20507-54c1-4f83-acf9-d81914de7a59",
"fields": {
"created_at": "2026-02-25T05:05:17.743Z",
"updated_at": "2026-02-25T05:05:17.745Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"month": 6,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {
"source_breakdown": {}
}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "5b010a15-818c-4183-b1a1-4fa39ed3ac8d",
"fields": {
"created_at": "2026-02-25T05:06:34.328Z",
"updated_at": "2026-02-25T05:06:34.339Z",
"kpi_report": "3f450a3f-036b-4be6-b368-fc1c3d2dc2ff",
"month": 6,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "776c9665-80b2-4091-b84f-49eebc577862",
"fields": {
"created_at": "2026-02-25T05:06:54.012Z",
"updated_at": "2026-02-25T05:06:54.015Z",
"kpi_report": "56af32e0-a5b2-4d38-8b18-bd0a06805237",
"month": 6,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "0a26b2d2-0270-474c-9e07-120cc330c7af",
"fields": {
"created_at": "2026-02-25T05:05:17.755Z",
"updated_at": "2026-02-25T05:05:17.761Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"month": 7,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {
"source_breakdown": {}
}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "4a14e2da-d727-49f2-a6e9-8d5e3ffb5f03",
"fields": {
"created_at": "2026-02-25T05:06:34.356Z",
"updated_at": "2026-02-25T05:06:34.362Z",
"kpi_report": "3f450a3f-036b-4be6-b368-fc1c3d2dc2ff",
"month": 7,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "4d623492-b260-4ee4-a478-5a43599a1b18",
"fields": {
"created_at": "2026-02-25T05:06:54.032Z",
"updated_at": "2026-02-25T05:06:54.035Z",
"kpi_report": "56af32e0-a5b2-4d38-8b18-bd0a06805237",
"month": 7,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "c35b57c4-43ba-482f-8000-44ac099435df",
"fields": {
"created_at": "2026-02-25T05:05:17.774Z",
"updated_at": "2026-02-25T05:05:17.778Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"month": 8,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {
"source_breakdown": {}
}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "0a1d71e1-758b-4f88-849b-32e6d73d079b",
"fields": {
"created_at": "2026-02-25T05:06:34.376Z",
"updated_at": "2026-02-25T05:06:34.380Z",
"kpi_report": "3f450a3f-036b-4be6-b368-fc1c3d2dc2ff",
"month": 8,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "32a42d17-8ac6-40d7-9285-bca7dded9faa",
"fields": {
"created_at": "2026-02-25T05:06:54.047Z",
"updated_at": "2026-02-25T05:06:54.050Z",
"kpi_report": "56af32e0-a5b2-4d38-8b18-bd0a06805237",
"month": 8,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "a24ca72c-6399-4e3a-9e78-a89cfc324411",
"fields": {
"created_at": "2026-02-25T05:05:17.789Z",
"updated_at": "2026-02-25T05:05:17.792Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"month": 9,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {
"source_breakdown": {}
}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "3963f4fc-6fdd-4a20-9be6-9d2da2bd8af1",
"fields": {
"created_at": "2026-02-25T05:06:34.394Z",
"updated_at": "2026-02-25T05:06:34.398Z",
"kpi_report": "3f450a3f-036b-4be6-b368-fc1c3d2dc2ff",
"month": 9,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "3f77f04e-7477-4328-9054-84b135b016f3",
"fields": {
"created_at": "2026-02-25T05:06:54.063Z",
"updated_at": "2026-02-25T05:06:54.065Z",
"kpi_report": "56af32e0-a5b2-4d38-8b18-bd0a06805237",
"month": 9,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "4f12ec3b-29ec-4664-99b1-44a9418bcbd1",
"fields": {
"created_at": "2026-02-25T05:05:17.798Z",
"updated_at": "2026-02-25T05:05:17.801Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"month": 10,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {
"source_breakdown": {}
}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "a4c33859-5031-4410-be37-340115739fc1",
"fields": {
"created_at": "2026-02-25T05:06:34.407Z",
"updated_at": "2026-02-25T05:06:34.411Z",
"kpi_report": "3f450a3f-036b-4be6-b368-fc1c3d2dc2ff",
"month": 10,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "b7fb063c-0f9f-4d4f-9948-ed77b0ddd486",
"fields": {
"created_at": "2026-02-25T05:06:54.077Z",
"updated_at": "2026-02-25T05:06:54.081Z",
"kpi_report": "56af32e0-a5b2-4d38-8b18-bd0a06805237",
"month": 10,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "0102b319-57e9-485e-9dd4-6a4b3e8aa01b",
"fields": {
"created_at": "2026-02-25T05:05:17.807Z",
"updated_at": "2026-02-25T05:05:17.810Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"month": 11,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {
"source_breakdown": {}
}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "98bb11e1-32d1-4fd1-bf0c-7c0bb1906488",
"fields": {
"created_at": "2026-02-25T05:06:34.418Z",
"updated_at": "2026-02-25T05:06:34.422Z",
"kpi_report": "3f450a3f-036b-4be6-b368-fc1c3d2dc2ff",
"month": 11,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "6435a7b2-1c24-4b75-9d92-44f7eb28e273",
"fields": {
"created_at": "2026-02-25T05:06:54.094Z",
"updated_at": "2026-02-25T05:06:54.096Z",
"kpi_report": "56af32e0-a5b2-4d38-8b18-bd0a06805237",
"month": 11,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "a616bf00-880d-4839-ad2c-db841c3196cb",
"fields": {
"created_at": "2026-02-25T05:05:17.816Z",
"updated_at": "2026-02-25T05:05:17.818Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"month": 12,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {
"source_breakdown": {}
}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "7488dc68-b2ae-42a6-a962-0cfe40346444",
"fields": {
"created_at": "2026-02-25T05:06:34.429Z",
"updated_at": "2026-02-25T05:06:34.434Z",
"kpi_report": "3f450a3f-036b-4be6-b368-fc1c3d2dc2ff",
"month": 12,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
},
{
"model": "analytics.kpireportmonthlydata",
"pk": "ddc6ad75-756a-4935-b335-55b86646b15f",
"fields": {
"created_at": "2026-02-25T05:06:54.106Z",
"updated_at": "2026-02-25T05:06:54.109Z",
"kpi_report": "56af32e0-a5b2-4d38-8b18-bd0a06805237",
"month": 12,
"numerator": 0,
"denominator": 0,
"percentage": "0.00",
"is_below_target": true,
"details": {}
}
}
]
},
"kpireportdepartmentbreakdown": {
"count": 4,
"data": [
{
"model": "analytics.kpireportdepartmentbreakdown",
"pk": "9137144c-1b44-4d6e-be4d-0430f154f718",
"fields": {
"created_at": "2026-02-25T05:05:17.876Z",
"updated_at": "2026-02-25T05:05:17.876Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"department_category": "admin",
"complaint_count": 0,
"resolved_count": 0,
"avg_resolution_days": null,
"top_areas": "",
"details": {}
}
},
{
"model": "analytics.kpireportdepartmentbreakdown",
"pk": "4d5eb559-6036-4185-9acd-6d3625922483",
"fields": {
"created_at": "2026-02-25T05:05:17.858Z",
"updated_at": "2026-02-25T05:05:17.858Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"department_category": "medical",
"complaint_count": 0,
"resolved_count": 0,
"avg_resolution_days": null,
"top_areas": "",
"details": {}
}
},
{
"model": "analytics.kpireportdepartmentbreakdown",
"pk": "456bf2ad-1c56-41ef-80f1-d719b70e53d2",
"fields": {
"created_at": "2026-02-25T05:05:17.868Z",
"updated_at": "2026-02-25T05:05:17.868Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"department_category": "nursing",
"complaint_count": 0,
"resolved_count": 0,
"avg_resolution_days": null,
"top_areas": "",
"details": {}
}
},
{
"model": "analytics.kpireportdepartmentbreakdown",
"pk": "cc391455-444a-4d92-92d9-4c9cb39763d4",
"fields": {
"created_at": "2026-02-25T05:05:17.890Z",
"updated_at": "2026-02-25T05:05:17.890Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"department_category": "support",
"complaint_count": 0,
"resolved_count": 0,
"avg_resolution_days": null,
"top_areas": "",
"details": {}
}
}
]
},
"kpireportsourcebreakdown": {
"count": 4,
"data": [
{
"model": "analytics.kpireportsourcebreakdown",
"pk": "02d33996-ba97-4efe-b6d3-37c6b2e818ff",
"fields": {
"created_at": "2026-02-25T05:05:17.835Z",
"updated_at": "2026-02-25T05:05:17.835Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"source_name": "Social Media",
"complaint_count": 4,
"percentage": "40.00"
}
},
{
"model": "analytics.kpireportsourcebreakdown",
"pk": "fc458559-803f-47bf-af84-d7a4ca922bb0",
"fields": {
"created_at": "2026-02-25T05:05:17.838Z",
"updated_at": "2026-02-25T05:05:17.838Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"source_name": "Call Center",
"complaint_count": 3,
"percentage": "30.00"
}
},
{
"model": "analytics.kpireportsourcebreakdown",
"pk": "00b2b0dc-1652-4eed-bd47-f954106345ca",
"fields": {
"created_at": "2026-02-25T05:05:17.842Z",
"updated_at": "2026-02-25T05:05:17.842Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"source_name": "Family Member",
"complaint_count": 2,
"percentage": "20.00"
}
},
{
"model": "analytics.kpireportsourcebreakdown",
"pk": "48a31227-46f2-4274-98bd-a112dc068682",
"fields": {
"created_at": "2026-02-25T05:05:17.840Z",
"updated_at": "2026-02-25T05:05:17.840Z",
"kpi_report": "88795c74-4a05-4030-bd81-24b844564b3c",
"source_name": "Staff",
"complaint_count": 1,
"percentage": "10.00"
}
}
]
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,73 @@
{
"app": "core",
"exported_at": "20260225_081256",
"total_records": 3,
"models": {
"auditevent": {
"count": 3,
"data": [
{
"model": "core.auditevent",
"pk": "cb0145b5-839a-4d42-b8b7-15d7c5a5f92b",
"fields": {
"created_at": "2026-02-25T05:09:40.739Z",
"updated_at": "2026-02-25T05:09:40.739Z",
"event_type": "activation",
"user": "e4a79042-cd54-45ff-9bf5-d775bcb9fb92",
"description": "Complaint activated and assigned to ",
"content_type": 42,
"object_id": "8bbf4733-34c8-4dc0-a440-50ac4ab90a22",
"metadata": {
"assigned_to_user_id": "e4a79042-cd54-45ff-9bf5-d775bcb9fb92",
"assigned_to_user_name": "",
"previous_assignee_id": null,
"previous_assignee_name": null
},
"ip_address": null,
"user_agent": ""
}
},
{
"model": "core.auditevent",
"pk": "5a4f4bf3-9447-46cf-b886-966ea694f297",
"fields": {
"created_at": "2026-02-25T04:17:40.060Z",
"updated_at": "2026-02-25T04:17:40.060Z",
"event_type": "doctor_rating_import_queued",
"user": "e4a79042-cd54-45ff-9bf5-d775bcb9fb92",
"description": "Queued 38082 doctor ratings for import",
"content_type": null,
"object_id": null,
"metadata": {
"job_id": "ac3ebc22-da1a-486a-96cc-c4146b6d3d25",
"hospital": "Olaya",
"total_records": 38082
},
"ip_address": null,
"user_agent": ""
}
},
{
"model": "core.auditevent",
"pk": "e44a60aa-2c8c-4b21-868f-9c609fabd7bf",
"fields": {
"created_at": "2026-02-25T04:14:05.305Z",
"updated_at": "2026-02-25T04:14:05.305Z",
"event_type": "doctor_rating_csv_import",
"user": "e4a79042-cd54-45ff-9bf5-d775bcb9fb92",
"description": "Parsed 38082 doctor ratings from CSV by ",
"content_type": null,
"object_id": null,
"metadata": {
"hospital": "Olaya",
"total_count": 38082,
"error_count": 0
},
"ip_address": null,
"user_agent": ""
}
}
]
}
}
}

View File

@ -0,0 +1,29 @@
{
"exported_at": "20260225_081256",
"total_records": 58175,
"apps_exported": [
"core",
"accounts",
"organizations",
"journeys",
"surveys",
"complaints",
"feedback",
"callcenter",
"social",
"px_action_center",
"analytics",
"physicians",
"projects",
"integrations",
"notifications",
"ai_engine",
"appreciation",
"observations",
"px_sources",
"references",
"standards",
"simulator"
],
"output_directory": "./database_export"
}

View File

@ -0,0 +1,48 @@
{
"app": "integrations",
"exported_at": "20260225_081256",
"total_records": 3,
"models": {
"surveytemplatemapping": {
"count": 3,
"data": [
{
"model": "integrations.surveytemplatemapping",
"pk": "6e7f6f8c-bd68-490b-b67f-0756e610c787",
"fields": {
"created_at": "2026-02-25T04:01:58.060Z",
"updated_at": "2026-02-25T04:01:58.060Z",
"patient_type": "APPOINTMENT",
"survey_template": "5e8f95fd-2e84-4640-8d67-e549507684a5",
"hospital": null,
"is_active": true
}
},
{
"model": "integrations.surveytemplatemapping",
"pk": "81074873-2bdd-44c6-ad41-4fb71a5c2058",
"fields": {
"created_at": "2026-02-25T04:01:58.052Z",
"updated_at": "2026-02-25T04:01:58.052Z",
"patient_type": "INPATIENT",
"survey_template": "6104fa70-0768-45b7-9f12-6816d91c0e18",
"hospital": null,
"is_active": true
}
},
{
"model": "integrations.surveytemplatemapping",
"pk": "032cc742-98e8-47de-b4aa-48506342665b",
"fields": {
"created_at": "2026-02-25T04:01:58.057Z",
"updated_at": "2026-02-25T04:01:58.057Z",
"patient_type": "OUTPATIENT",
"survey_template": "0e175a49-a502-4d46-9c5f-c98894e316cf",
"hospital": null,
"is_active": true
}
}
]
}
}
}

View File

@ -0,0 +1,289 @@
{
"app": "journeys",
"exported_at": "20260225_081256",
"total_records": 16,
"models": {
"patientjourneytemplate": {
"count": 3,
"data": [
{
"model": "journeys.patientjourneytemplate",
"pk": "2b243c19-d7de-4313-917e-de33bea24031",
"fields": {
"created_at": "2026-02-25T04:00:58.509Z",
"updated_at": "2026-02-25T04:00:58.509Z",
"name": "EMS Patient Journey",
"name_ar": "رحلة المريض للطوارئ",
"journey_type": "ems",
"description": "Emergency medical services patient journey",
"hospital": "d7221fdb-a0f7-4460-8d69-14a3bfe627fb",
"is_active": true,
"is_default": true,
"send_post_discharge_survey": true,
"post_discharge_survey_delay_hours": 1
}
},
{
"model": "journeys.patientjourneytemplate",
"pk": "63f8256b-c463-4d63-b56e-90b585703b7d",
"fields": {
"created_at": "2026-02-25T04:00:58.535Z",
"updated_at": "2026-02-25T04:00:58.535Z",
"name": "Inpatient Patient Journey",
"name_ar": "رحلة المريض الداخلي",
"journey_type": "inpatient",
"description": "Inpatient journey from admission to discharge",
"hospital": "d7221fdb-a0f7-4460-8d69-14a3bfe627fb",
"is_active": true,
"is_default": true,
"send_post_discharge_survey": true,
"post_discharge_survey_delay_hours": 24
}
},
{
"model": "journeys.patientjourneytemplate",
"pk": "953c1fe5-1bb4-4583-a3b9-b2eeec1d278f",
"fields": {
"created_at": "2026-02-25T04:00:58.563Z",
"updated_at": "2026-02-25T04:00:58.563Z",
"name": "OPD Patient Journey",
"name_ar": "رحلة المريض للعيادات الخارجية",
"journey_type": "opd",
"description": "Outpatient department patient journey",
"hospital": "d7221fdb-a0f7-4460-8d69-14a3bfe627fb",
"is_active": true,
"is_default": true,
"send_post_discharge_survey": true,
"post_discharge_survey_delay_hours": 2
}
}
]
},
"patientjourneystagetemplate": {
"count": 13,
"data": [
{
"model": "journeys.patientjourneystagetemplate",
"pk": "dacb47c5-fbeb-4239-96e7-8103827ed6d8",
"fields": {
"created_at": "2026-02-25T04:00:58.511Z",
"updated_at": "2026-02-25T04:00:58.511Z",
"journey_template": "2b243c19-d7de-4313-917e-de33bea24031",
"name": "Ambulance Dispatch",
"name_ar": "إرسال الإسعاف",
"code": "EMS_STAGE_1_DISPATCHED",
"order": 1,
"trigger_event_code": "EMS_STAGE_1_DISPATCHED",
"survey_template": "733b4d11-2a88-4875-bb33-fb3d7efc3734",
"is_optional": false,
"is_active": true
}
},
{
"model": "journeys.patientjourneystagetemplate",
"pk": "9128dcaa-ea20-4d1c-953f-08180e12e1d6",
"fields": {
"created_at": "2026-02-25T04:00:58.513Z",
"updated_at": "2026-02-25T04:00:58.513Z",
"journey_template": "2b243c19-d7de-4313-917e-de33bea24031",
"name": "On Scene Care",
"name_ar": "الرعاية في الموقع",
"code": "EMS_STAGE_2_ON_SCENE",
"order": 2,
"trigger_event_code": "EMS_STAGE_2_ON_SCENE",
"survey_template": "733b4d11-2a88-4875-bb33-fb3d7efc3734",
"is_optional": false,
"is_active": true
}
},
{
"model": "journeys.patientjourneystagetemplate",
"pk": "e59b6e21-caf9-4e20-9925-30693232bfb7",
"fields": {
"created_at": "2026-02-25T04:00:58.514Z",
"updated_at": "2026-02-25T04:00:58.514Z",
"journey_template": "2b243c19-d7de-4313-917e-de33bea24031",
"name": "Patient Transport",
"name_ar": "نقل المريض",
"code": "EMS_STAGE_3_TRANSPORT",
"order": 3,
"trigger_event_code": "EMS_STAGE_3_TRANSPORT",
"survey_template": "733b4d11-2a88-4875-bb33-fb3d7efc3734",
"is_optional": false,
"is_active": true
}
},
{
"model": "journeys.patientjourneystagetemplate",
"pk": "c86143de-01a4-4503-8c43-33bde911d2da",
"fields": {
"created_at": "2026-02-25T04:00:58.537Z",
"updated_at": "2026-02-25T04:00:58.537Z",
"journey_template": "63f8256b-c463-4d63-b56e-90b585703b7d",
"name": "Admission",
"name_ar": "القبول",
"code": "INPATIENT_STAGE_1_ADMISSION",
"order": 1,
"trigger_event_code": "INPATIENT_STAGE_1_ADMISSION",
"survey_template": "d5d337e2-c2ba-44f5-8f35-6aa7f7e89c02",
"is_optional": false,
"is_active": true
}
},
{
"model": "journeys.patientjourneystagetemplate",
"pk": "eabc55aa-9ece-4755-ae2b-abd75765dad0",
"fields": {
"created_at": "2026-02-25T04:00:58.539Z",
"updated_at": "2026-02-25T04:00:58.539Z",
"journey_template": "63f8256b-c463-4d63-b56e-90b585703b7d",
"name": "Treatment",
"name_ar": "العلاج",
"code": "INPATIENT_STAGE_2_TREATMENT",
"order": 2,
"trigger_event_code": "INPATIENT_STAGE_2_TREATMENT",
"survey_template": "d5d337e2-c2ba-44f5-8f35-6aa7f7e89c02",
"is_optional": false,
"is_active": true
}
},
{
"model": "journeys.patientjourneystagetemplate",
"pk": "372dd6df-7667-4a4a-be26-13dc8957ae52",
"fields": {
"created_at": "2026-02-25T04:00:58.541Z",
"updated_at": "2026-02-25T04:00:58.541Z",
"journey_template": "63f8256b-c463-4d63-b56e-90b585703b7d",
"name": "Nursing Care",
"name_ar": "الرعاية التمريضية",
"code": "INPATIENT_STAGE_3_NURSING",
"order": 3,
"trigger_event_code": "INPATIENT_STAGE_3_NURSING",
"survey_template": "d5d337e2-c2ba-44f5-8f35-6aa7f7e89c02",
"is_optional": false,
"is_active": true
}
},
{
"model": "journeys.patientjourneystagetemplate",
"pk": "94fd0498-ad0d-4452-a954-622d232a174a",
"fields": {
"created_at": "2026-02-25T04:00:58.543Z",
"updated_at": "2026-02-25T04:00:58.543Z",
"journey_template": "63f8256b-c463-4d63-b56e-90b585703b7d",
"name": "Lab Tests",
"name_ar": "الفحوصات المخبرية",
"code": "INPATIENT_STAGE_4_LAB",
"order": 4,
"trigger_event_code": "INPATIENT_STAGE_4_LAB",
"survey_template": "d5d337e2-c2ba-44f5-8f35-6aa7f7e89c02",
"is_optional": false,
"is_active": true
}
},
{
"model": "journeys.patientjourneystagetemplate",
"pk": "fc7b3fe7-8e47-4947-b082-8f9c384b7137",
"fields": {
"created_at": "2026-02-25T04:00:58.545Z",
"updated_at": "2026-02-25T04:00:58.545Z",
"journey_template": "63f8256b-c463-4d63-b56e-90b585703b7d",
"name": "Radiology",
"name_ar": "الأشعة",
"code": "INPATIENT_STAGE_5_RADIOLOGY",
"order": 5,
"trigger_event_code": "INPATIENT_STAGE_5_RADIOLOGY",
"survey_template": "d5d337e2-c2ba-44f5-8f35-6aa7f7e89c02",
"is_optional": false,
"is_active": true
}
},
{
"model": "journeys.patientjourneystagetemplate",
"pk": "eb293246-9d84-46f2-abda-461a21c7c60d",
"fields": {
"created_at": "2026-02-25T04:00:58.565Z",
"updated_at": "2026-02-25T04:00:58.565Z",
"journey_template": "953c1fe5-1bb4-4583-a3b9-b2eeec1d278f",
"name": "Registration",
"name_ar": "التسجيل",
"code": "OPD_STAGE_1_REGISTRATION",
"order": 1,
"trigger_event_code": "OPD_STAGE_1_REGISTRATION",
"survey_template": "4ac2ae6b-87bc-4519-85d5-afc27d7685f6",
"is_optional": false,
"is_active": true
}
},
{
"model": "journeys.patientjourneystagetemplate",
"pk": "2014d3dc-c38c-401e-a5ec-12312de05c1e",
"fields": {
"created_at": "2026-02-25T04:00:58.571Z",
"updated_at": "2026-02-25T04:00:58.571Z",
"journey_template": "953c1fe5-1bb4-4583-a3b9-b2eeec1d278f",
"name": "Consultation",
"name_ar": "الاستشارة",
"code": "OPD_STAGE_2_CONSULTATION",
"order": 2,
"trigger_event_code": "OPD_STAGE_2_CONSULTATION",
"survey_template": "4ac2ae6b-87bc-4519-85d5-afc27d7685f6",
"is_optional": false,
"is_active": true
}
},
{
"model": "journeys.patientjourneystagetemplate",
"pk": "3e0f67ea-18a9-460b-872a-6d47e0b2906c",
"fields": {
"created_at": "2026-02-25T04:00:58.574Z",
"updated_at": "2026-02-25T04:00:58.574Z",
"journey_template": "953c1fe5-1bb4-4583-a3b9-b2eeec1d278f",
"name": "Lab Tests",
"name_ar": "الفحوصات المخبرية",
"code": "OPD_STAGE_3_LAB",
"order": 3,
"trigger_event_code": "OPD_STAGE_3_LAB",
"survey_template": "4ac2ae6b-87bc-4519-85d5-afc27d7685f6",
"is_optional": false,
"is_active": true
}
},
{
"model": "journeys.patientjourneystagetemplate",
"pk": "75860c85-ab25-4bf1-aa88-442a8b520144",
"fields": {
"created_at": "2026-02-25T04:00:58.577Z",
"updated_at": "2026-02-25T04:00:58.577Z",
"journey_template": "953c1fe5-1bb4-4583-a3b9-b2eeec1d278f",
"name": "Radiology",
"name_ar": "الأشعة",
"code": "OPD_STAGE_4_RADIOLOGY",
"order": 4,
"trigger_event_code": "OPD_STAGE_4_RADIOLOGY",
"survey_template": "4ac2ae6b-87bc-4519-85d5-afc27d7685f6",
"is_optional": false,
"is_active": true
}
},
{
"model": "journeys.patientjourneystagetemplate",
"pk": "5546f87f-85ab-441a-9898-cbc9e8bb46f9",
"fields": {
"created_at": "2026-02-25T04:00:58.580Z",
"updated_at": "2026-02-25T04:00:58.580Z",
"journey_template": "953c1fe5-1bb4-4583-a3b9-b2eeec1d278f",
"name": "Pharmacy",
"name_ar": "الصيدلية",
"code": "OPD_STAGE_5_PHARMACY",
"order": 5,
"trigger_event_code": "OPD_STAGE_5_PHARMACY",
"survey_template": "4ac2ae6b-87bc-4519-85d5-afc27d7685f6",
"is_optional": false,
"is_active": true
}
}
]
}
}
}

View File

@ -0,0 +1,132 @@
{
"app": "notifications",
"exported_at": "20260225_081256",
"total_records": 4,
"models": {
"notificationlog": {
"count": 4,
"data": [
{
"model": "notifications.notificationlog",
"pk": "60ff0efd-53d5-443f-b799-3ca311e3fd62",
"fields": {
"created_at": "2026-02-25T03:55:36.978Z",
"updated_at": "2026-02-25T03:55:36.978Z",
"channel": "sms",
"recipient": "+966537702040",
"subject": "",
"message": "PX360: Your complaint #CMP-HAM-RIY-2026-787134BD has been received. Track: https://localhost:8000/complaints/public/track/?reference=CMP-HAM-RIY-2026-787134BD",
"content_type": 42,
"object_id": "f4096f2b-81ba-4eee-8ac8-3ad0bded1797",
"status": "sent",
"sent_at": "2026-02-25T03:55:42.002Z",
"delivered_at": null,
"provider": "api",
"provider_message_id": "",
"provider_response": {},
"error": "",
"retry_count": 0,
"metadata": {
"api_url": "http://localhost:8000/api/simulator/send-sms/",
"auth_method": "bearer",
"notification_type": "complaint_created",
"reference_number": "CMP-HAM-RIY-2026-787134BD",
"tracking_url": "https://localhost:8000/complaints/public/track/?reference=CMP-HAM-RIY-2026-787134BD",
"language": "en"
}
}
},
{
"model": "notifications.notificationlog",
"pk": "eff41c4f-b6e7-4691-9faa-f1b707e517a4",
"fields": {
"created_at": "2026-02-25T03:55:31.907Z",
"updated_at": "2026-02-25T03:55:31.907Z",
"channel": "sms",
"recipient": "+966541424622",
"subject": "",
"message": "PX360: Your complaint #CMP-HAM-RIY-2026-C2897964 has been received. Track: https://localhost:8000/complaints/public/track/?reference=CMP-HAM-RIY-2026-C2897964",
"content_type": 42,
"object_id": "9da3b845-c425-4bf1-b413-1d10fc1a150d",
"status": "sent",
"sent_at": "2026-02-25T03:55:36.935Z",
"delivered_at": null,
"provider": "api",
"provider_message_id": "",
"provider_response": {},
"error": "",
"retry_count": 0,
"metadata": {
"api_url": "http://localhost:8000/api/simulator/send-sms/",
"auth_method": "bearer",
"notification_type": "complaint_created",
"reference_number": "CMP-HAM-RIY-2026-C2897964",
"tracking_url": "https://localhost:8000/complaints/public/track/?reference=CMP-HAM-RIY-2026-C2897964",
"language": "en"
}
}
},
{
"model": "notifications.notificationlog",
"pk": "de39ff56-dd07-400b-ac8f-717d4e9aa8cf",
"fields": {
"created_at": "2026-02-25T03:55:26.817Z",
"updated_at": "2026-02-25T03:55:26.817Z",
"channel": "sms",
"recipient": "+966526753309",
"subject": "",
"message": "PX360: Your complaint #CMP-HAM-RIY-2026-50004505 has been received. Track: https://localhost:8000/complaints/public/track/?reference=CMP-HAM-RIY-2026-50004505",
"content_type": 42,
"object_id": "312155d3-b384-4e6f-b2e7-f8bd21e7817b",
"status": "sent",
"sent_at": "2026-02-25T03:55:31.844Z",
"delivered_at": null,
"provider": "api",
"provider_message_id": "",
"provider_response": {},
"error": "",
"retry_count": 0,
"metadata": {
"api_url": "http://localhost:8000/api/simulator/send-sms/",
"auth_method": "bearer",
"notification_type": "complaint_created",
"reference_number": "CMP-HAM-RIY-2026-50004505",
"tracking_url": "https://localhost:8000/complaints/public/track/?reference=CMP-HAM-RIY-2026-50004505",
"language": "en"
}
}
},
{
"model": "notifications.notificationlog",
"pk": "1704e6bc-8c19-4d1b-9538-4091189495dd",
"fields": {
"created_at": "2026-02-25T03:55:21.759Z",
"updated_at": "2026-02-25T03:55:21.759Z",
"channel": "sms",
"recipient": "+966592722909",
"subject": "",
"message": "PX360: Your complaint #CMP-HAM-RIY-2026-5420F582 has been received. Track: https://localhost:8000/complaints/public/track/?reference=CMP-HAM-RIY-2026-5420F582",
"content_type": 42,
"object_id": "4f11f759-8061-427d-899b-e0c360d11a20",
"status": "sent",
"sent_at": "2026-02-25T03:55:26.781Z",
"delivered_at": null,
"provider": "api",
"provider_message_id": "",
"provider_response": {},
"error": "",
"retry_count": 0,
"metadata": {
"api_url": "http://localhost:8000/api/simulator/send-sms/",
"auth_method": "bearer",
"notification_type": "complaint_created",
"reference_number": "CMP-HAM-RIY-2026-5420F582",
"tracking_url": "https://localhost:8000/complaints/public/track/?reference=CMP-HAM-RIY-2026-5420F582",
"language": "en"
}
}
}
]
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,299 @@
{
"app": "px_sources",
"exported_at": "20260225_081256",
"total_records": 18,
"models": {
"pxsource": {
"count": 8,
"data": [
{
"model": "px_sources.pxsource",
"pk": "9affe720-438b-43dd-b570-25a7f6e8a5aa",
"fields": {
"created_at": "2026-02-25T03:55:21.561Z",
"updated_at": "2026-02-25T03:55:21.561Z",
"code": "CALL-CENT",
"name_en": "Call Center",
"name_ar": "مركز الاتصال",
"description": "Call Center source for complaints and inquiries",
"source_type": "internal",
"contact_email": "",
"contact_phone": "",
"is_active": true,
"metadata": {},
"total_complaints": 3,
"total_inquiries": 0
}
},
{
"model": "px_sources.pxsource",
"pk": "88060ad8-c771-48a6-ae9d-31fdb993e2f9",
"fields": {
"created_at": "2026-02-25T03:55:21.557Z",
"updated_at": "2026-02-25T03:55:21.558Z",
"code": "FAMI-MEMB",
"name_en": "Family Member",
"name_ar": "عضو العائلة",
"description": "Family Member source for complaints and inquiries",
"source_type": "internal",
"contact_email": "",
"contact_phone": "",
"is_active": true,
"metadata": {},
"total_complaints": 2,
"total_inquiries": 0
}
},
{
"model": "px_sources.pxsource",
"pk": "642825ce-5e1f-4900-875d-6bb3c67e9f16",
"fields": {
"created_at": "2026-02-25T03:55:21.564Z",
"updated_at": "2026-02-25T03:55:21.564Z",
"code": "IN-PERS",
"name_en": "In Person",
"name_ar": "شخصياً",
"description": "In Person source for complaints and inquiries",
"source_type": "internal",
"contact_email": "",
"contact_phone": "",
"is_active": true,
"metadata": {},
"total_complaints": 0,
"total_inquiries": 0
}
},
{
"model": "px_sources.pxsource",
"pk": "e4b6d570-5166-42b5-be13-04774a65a0dc",
"fields": {
"created_at": "2026-02-25T03:55:21.563Z",
"updated_at": "2026-02-25T03:55:21.563Z",
"code": "ONLI-FORM",
"name_en": "Online Form",
"name_ar": "نموذج عبر الإنترنت",
"description": "Online Form source for complaints and inquiries",
"source_type": "internal",
"contact_email": "",
"contact_phone": "",
"is_active": true,
"metadata": {},
"total_complaints": 0,
"total_inquiries": 0
}
},
{
"model": "px_sources.pxsource",
"pk": "db13b2e7-5169-40b9-a588-f8570d390af6",
"fields": {
"created_at": "2026-02-25T03:55:21.551Z",
"updated_at": "2026-02-25T03:55:21.551Z",
"code": "PATIENT",
"name_en": "Patient",
"name_ar": "مريض",
"description": "Patient source for complaints and inquiries",
"source_type": "internal",
"contact_email": "",
"contact_phone": "",
"is_active": true,
"metadata": {},
"total_complaints": 0,
"total_inquiries": 0
}
},
{
"model": "px_sources.pxsource",
"pk": "31ff949d-4e1b-419d-a0de-f988c41fcc3a",
"fields": {
"created_at": "2026-02-25T03:55:21.569Z",
"updated_at": "2026-02-25T03:55:21.569Z",
"code": "SOCI-MEDI",
"name_en": "Social Media",
"name_ar": "وسائل التواصل الاجتماعي",
"description": "Social Media source for complaints and inquiries",
"source_type": "internal",
"contact_email": "",
"contact_phone": "",
"is_active": true,
"metadata": {},
"total_complaints": 4,
"total_inquiries": 0
}
},
{
"model": "px_sources.pxsource",
"pk": "0aacdcd8-7aa7-4351-ad52-73a394c0848e",
"fields": {
"created_at": "2026-02-25T03:55:21.559Z",
"updated_at": "2026-02-25T03:55:21.559Z",
"code": "STAFF",
"name_en": "Staff",
"name_ar": "موظف",
"description": "Staff source for complaints and inquiries",
"source_type": "internal",
"contact_email": "",
"contact_phone": "",
"is_active": true,
"metadata": {},
"total_complaints": 1,
"total_inquiries": 0
}
},
{
"model": "px_sources.pxsource",
"pk": "83f23608-2288-4d86-ae43-4db31a91c1bf",
"fields": {
"created_at": "2026-02-25T03:55:21.566Z",
"updated_at": "2026-02-25T03:55:21.567Z",
"code": "SURVEY",
"name_en": "Survey",
"name_ar": "استبيان",
"description": "Survey source for complaints and inquiries",
"source_type": "internal",
"contact_email": "",
"contact_phone": "",
"is_active": true,
"metadata": {},
"total_complaints": 0,
"total_inquiries": 0
}
}
]
},
"sourceusage": {
"count": 10,
"data": [
{
"model": "px_sources.sourceusage",
"pk": "6ea35b90-42eb-4b5b-aab4-3ae29579feac",
"fields": {
"created_at": "2026-02-25T03:55:42.060Z",
"updated_at": "2026-02-25T03:55:42.060Z",
"source": "31ff949d-4e1b-419d-a0de-f988c41fcc3a",
"content_type": 42,
"object_id": "8bbf4733-34c8-4dc0-a440-50ac4ab90a22",
"hospital": "4ed13883-9632-4534-a007-4942258a5943",
"user": null
}
},
{
"model": "px_sources.sourceusage",
"pk": "9f01a1f8-7a07-4f4e-9309-8eb8b3655336",
"fields": {
"created_at": "2026-02-25T03:55:42.044Z",
"updated_at": "2026-02-25T03:55:42.044Z",
"source": "9affe720-438b-43dd-b570-25a7f6e8a5aa",
"content_type": 42,
"object_id": "1f76952f-4fb6-4eaf-9fd8-2cc441e4ca07",
"hospital": "4ed13883-9632-4534-a007-4942258a5943",
"user": null
}
},
{
"model": "px_sources.sourceusage",
"pk": "860a79b2-f7db-40ba-85d2-ebdc6bc92c9b",
"fields": {
"created_at": "2026-02-25T03:55:42.014Z",
"updated_at": "2026-02-25T03:55:42.014Z",
"source": "0aacdcd8-7aa7-4351-ad52-73a394c0848e",
"content_type": 42,
"object_id": "f4096f2b-81ba-4eee-8ac8-3ad0bded1797",
"hospital": "4ed13883-9632-4534-a007-4942258a5943",
"user": null
}
},
{
"model": "px_sources.sourceusage",
"pk": "9b253aae-b0ed-4d7c-bbb4-107529e8fa9a",
"fields": {
"created_at": "2026-02-25T03:55:36.960Z",
"updated_at": "2026-02-25T03:55:36.960Z",
"source": "88060ad8-c771-48a6-ae9d-31fdb993e2f9",
"content_type": 42,
"object_id": "1fec51ec-ab1c-4c04-9cc4-7d3cf371940d",
"hospital": "4ed13883-9632-4534-a007-4942258a5943",
"user": null
}
},
{
"model": "px_sources.sourceusage",
"pk": "0fba819d-6316-43b5-a9db-1e164bddff79",
"fields": {
"created_at": "2026-02-25T03:55:36.940Z",
"updated_at": "2026-02-25T03:55:36.940Z",
"source": "31ff949d-4e1b-419d-a0de-f988c41fcc3a",
"content_type": 42,
"object_id": "9da3b845-c425-4bf1-b413-1d10fc1a150d",
"hospital": "4ed13883-9632-4534-a007-4942258a5943",
"user": null
}
},
{
"model": "px_sources.sourceusage",
"pk": "35e36159-d08f-4af4-bc25-8672ba4e40dc",
"fields": {
"created_at": "2026-02-25T03:55:31.890Z",
"updated_at": "2026-02-25T03:55:31.890Z",
"source": "9affe720-438b-43dd-b570-25a7f6e8a5aa",
"content_type": 42,
"object_id": "c674b99c-06b3-40c2-9812-0fec40d43ea5",
"hospital": "4ed13883-9632-4534-a007-4942258a5943",
"user": null
}
},
{
"model": "px_sources.sourceusage",
"pk": "c0c9d0a9-a754-4500-8985-f487b7c8811c",
"fields": {
"created_at": "2026-02-25T03:55:31.865Z",
"updated_at": "2026-02-25T03:55:31.865Z",
"source": "9affe720-438b-43dd-b570-25a7f6e8a5aa",
"content_type": 42,
"object_id": "c020e66f-0f9a-42ac-9b86-a740d340cd2d",
"hospital": "4ed13883-9632-4534-a007-4942258a5943",
"user": null
}
},
{
"model": "px_sources.sourceusage",
"pk": "ed5a581f-3d4e-4500-a332-1f816bffaa43",
"fields": {
"created_at": "2026-02-25T03:55:31.850Z",
"updated_at": "2026-02-25T03:55:31.850Z",
"source": "31ff949d-4e1b-419d-a0de-f988c41fcc3a",
"content_type": 42,
"object_id": "312155d3-b384-4e6f-b2e7-f8bd21e7817b",
"hospital": "4ed13883-9632-4534-a007-4942258a5943",
"user": null
}
},
{
"model": "px_sources.sourceusage",
"pk": "0062eefc-e55c-40ff-8ab3-6ea56ca170d7",
"fields": {
"created_at": "2026-02-25T03:55:26.805Z",
"updated_at": "2026-02-25T03:55:26.805Z",
"source": "31ff949d-4e1b-419d-a0de-f988c41fcc3a",
"content_type": 42,
"object_id": "4f11f759-8061-427d-899b-e0c360d11a20",
"hospital": "4ed13883-9632-4534-a007-4942258a5943",
"user": null
}
},
{
"model": "px_sources.sourceusage",
"pk": "a6b5e484-28ef-4c65-8fd4-50505042ede7",
"fields": {
"created_at": "2026-02-25T03:55:21.594Z",
"updated_at": "2026-02-25T03:55:21.594Z",
"source": "88060ad8-c771-48a6-ae9d-31fdb993e2f9",
"content_type": 42,
"object_id": "75699d55-58b8-42d2-94c3-ff479a005dad",
"hospital": "4ed13883-9632-4534-a007-4942258a5943",
"user": null
}
}
]
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -4,220 +4,231 @@
{% block title %}{% trans "Generate KPI Report" %} - PX360{% endblock %} {% block title %}{% trans "Generate KPI Report" %} - PX360{% endblock %}
{% block content %} {% block content %}
<div class="max-w-3xl mx-auto"> <div class="max-w-6xl mx-auto">
<!-- Header --> <!-- Header -->
<header class="mb-6"> <header class="mb-6">
<a href="{% url 'analytics:kpi_report_list' %}" <div class="flex items-center gap-2 text-sm text-slate mb-3">
class="inline-flex items-center gap-2 text-sm font-bold text-blue hover:text-navy mb-3 transition"> <a href="{% url 'analytics:kpi_report_list' %}" class="hover:text-navy">{% trans "KPI Reports" %}</a>
<i data-lucide="arrow-left" class="w-4 h-4"></i> <i data-lucide="chevron-right" class="w-4 h-4"></i>
{% trans "Back to Reports" %} <span class="font-bold text-navy">{% trans "Generate Report" %}</span>
</a> </div>
<h1 class="text-2xl font-bold text-navy flex items-center gap-3"> <h1 class="text-2xl font-bold text-navy flex items-center gap-3">
<i data-lucide="file-plus" class="w-7 h-7"></i> <div class="w-10 h-10 bg-blue-50 rounded-xl flex items-center justify-center">
<i data-lucide="file-plus" class="w-5 h-5 text-blue"></i>
</div>
{% trans "Generate KPI Report" %} {% trans "Generate KPI Report" %}
</h1> </h1>
<p class="text-sm text-slate mt-1">{% trans "Create a new monthly KPI report for a specific hospital and period." %}</p> <p class="text-slate text-sm mt-1">{% trans "Create a new monthly KPI report for a specific hospital and period." %}</p>
</header> </header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Main Form --> <!-- Main Form -->
<div class="lg:col-span-2"> <div class="lg:col-span-2">
<div class="card"> <div class="bg-white rounded-2xl shadow-sm border border-slate-100 overflow-hidden">
<div class="card-header"> <div class="px-6 py-4 border-b border-slate-100 bg-slate-50">
<h2 class="card-title flex items-center gap-2"> <h2 class="font-bold text-navy flex items-center gap-2">
<i data-lucide="settings-2" class="w-4 h-4"></i> <i data-lucide="settings" class="w-5 h-5 text-blue"></i>
{% trans "Report Configuration" %} {% trans "Report Configuration" %}
</h2> </h2>
</div> </div>
<form method="post" action="{% url 'analytics:kpi_report_generate_submit' %}" <div class="p-6">
hx-post="{% url 'analytics:kpi_report_generate_submit' %}" <form method="post" action="{% url 'analytics:kpi_report_generate_submit' %}"
hx-target="#form-result" hx-post="{% url 'analytics:kpi_report_generate_submit' %}"
hx-swap="innerHTML"> hx-target="#form-result"
{% csrf_token %} hx-swap="innerHTML">
{% csrf_token %}
<div class="space-y-6"> <div class="space-y-6">
<!-- Report Type --> <!-- Report Type -->
<div>
<label for="report_type" class="block text-xs font-bold text-slate uppercase tracking-wider mb-2">
{% trans "Report Type" %} <span class="text-red-500">*</span>
</label>
<select name="report_type" id="report_type" required
class="w-full px-4 py-2.5 bg-slate-100 border border-transparent rounded-lg text-sm focus:bg-white focus:border-navy focus:ring-2 focus:ring-navy/10 outline-none transition">
<option value="">{% trans "Select Report Type" %}</option>
{% for type_value, type_label in report_types %}
<option value="{{ type_value }}">{{ type_label }}</option>
{% endfor %}
</select>
<p class="text-xs text-slate mt-1.5 flex items-center gap-1">
<i data-lucide="info" class="w-3 h-3"></i>
{% trans "Choose the type of KPI report to generate." %}
</p>
</div>
<!-- Hospital -->
<div>
<label for="hospital" class="block text-xs font-bold text-slate uppercase tracking-wider mb-2">
{% trans "Hospital" %} <span class="text-red-500">*</span>
</label>
<select name="hospital" id="hospital" required
class="w-full px-4 py-2.5 bg-slate-100 border border-transparent rounded-lg text-sm focus:bg-white focus:border-navy focus:ring-2 focus:ring-navy/10 outline-none transition">
<option value="">{% trans "Select Hospital" %}</option>
{% for hospital in hospitals %}
<option value="{{ hospital.id }}">{{ hospital.name }}</option>
{% endfor %}
</select>
</div>
<!-- Year and Month -->
<div class="grid grid-cols-2 gap-4">
<div> <div>
<label for="year" class="block text-xs font-bold text-slate uppercase tracking-wider mb-2"> <label for="report_type" class="block text-xs font-bold text-slate uppercase tracking-wider mb-2">
{% trans "Year" %} <span class="text-red-500">*</span> {% trans "Report Type" %} <span class="text-red-500">*</span>
</label> </label>
<select name="year" id="year" required <select name="report_type" id="report_type" required
class="w-full px-4 py-2.5 bg-slate-100 border border-transparent rounded-lg text-sm focus:bg-white focus:border-navy focus:ring-2 focus:ring-navy/10 outline-none transition"> class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:bg-white focus:border-navy focus:ring-2 focus:ring-navy/10 outline-none transition">
<option value="">{% trans "Select Year" %}</option> <option value="">{% trans "Select Report Type" %}</option>
{% for y in years %} {% for type_value, type_label in report_types %}
<option value="{{ y }}" {% if y == current_year %}selected{% endif %}>{{ y }}</option> <option value="{{ type_value }}">{{ type_label }}</option>
{% endfor %}
</select>
<p class="text-xs text-slate mt-1.5 flex items-center gap-1">
<i data-lucide="info" class="w-3 h-3"></i>
{% trans "Choose the type of KPI report to generate." %}
</p>
</div>
<!-- Hospital -->
<div>
<label for="hospital" class="block text-xs font-bold text-slate uppercase tracking-wider mb-2">
{% trans "Hospital" %} <span class="text-red-500">*</span>
</label>
<select name="hospital" id="hospital" required
class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:bg-white focus:border-navy focus:ring-2 focus:ring-navy/10 outline-none transition">
<option value="">{% trans "Select Hospital" %}</option>
{% for hospital in hospitals %}
<option value="{{ hospital.id }}">{{ hospital.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<div> <!-- Year and Month -->
<label for="month" class="block text-xs font-bold text-slate uppercase tracking-wider mb-2"> <div class="grid grid-cols-2 gap-4">
{% trans "Month" %} <span class="text-red-500">*</span>
</label>
<select name="month" id="month" required
class="w-full px-4 py-2.5 bg-slate-100 border border-transparent rounded-lg text-sm focus:bg-white focus:border-navy focus:ring-2 focus:ring-navy/10 outline-none transition">
<option value="">{% trans "Select Month" %}</option>
{% for m, m_label in months %}
<option value="{{ m }}" {% if m == current_month %}selected{% endif %}>{{ m_label }}</option>
{% endfor %}
</select>
</div>
</div>
<!-- Info Box -->
<div class="bg-blue-50 border border-blue-100 rounded-xl p-4">
<div class="flex items-start gap-3">
<div class="p-2 bg-blue-100 rounded-lg flex-shrink-0">
<i data-lucide="info" class="w-4 h-4 text-blue"></i>
</div>
<div> <div>
<p class="text-sm font-bold text-navy">{% trans "Report Generation Information" %}</p> <label for="year" class="block text-xs font-bold text-slate uppercase tracking-wider mb-2">
<p class="text-sm text-slate mt-1 leading-relaxed"> {% trans "Year" %} <span class="text-red-500">*</span>
{% trans "The report will be generated based on data from the selected month. This may take a few moments depending on the amount of data. If a report already exists for this period, it will be regenerated with the latest data." %} </label>
</p> <select name="year" id="year" required
class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:bg-white focus:border-navy focus:ring-2 focus:ring-navy/10 outline-none transition">
<option value="">{% trans "Select Year" %}</option>
{% for y in years %}
<option value="{{ y }}" {% if y == current_year %}selected{% endif %}>{{ y }}</option>
{% endfor %}
</select>
</div>
<div>
<label for="month" class="block text-xs font-bold text-slate uppercase tracking-wider mb-2">
{% trans "Month" %} <span class="text-red-500">*</span>
</label>
<select name="month" id="month" required
class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:bg-white focus:border-navy focus:ring-2 focus:ring-navy/10 outline-none transition">
<option value="">{% trans "Select Month" %}</option>
{% for m, m_label in months %}
<option value="{{ m }}" {% if m == current_month %}selected{% endif %}>{{ m_label }}</option>
{% endfor %}
</select>
</div> </div>
</div> </div>
</div>
<!-- Result Container --> <!-- Info Box -->
<div id="form-result"></div> <div class="bg-blue-50 border border-blue-200 rounded-2xl p-4">
<div class="flex items-start gap-3">
<div class="w-10 h-10 bg-blue-100 rounded-xl flex items-center justify-center flex-shrink-0">
<i data-lucide="info" class="w-5 h-5 text-blue-600"></i>
</div>
<div>
<p class="text-sm font-bold text-blue-800">{% trans "Report Generation Information" %}</p>
<p class="text-sm text-blue-700 mt-1 leading-relaxed">
{% trans "The report will be generated based on data from the selected month. This may take a few moments depending on the amount of data. If a report already exists for this period, it will be regenerated with the latest data." %}
</p>
</div>
</div>
</div>
<!-- Buttons --> <!-- Result Container -->
<div class="flex gap-3 pt-4 border-t"> <div id="form-result"></div>
<button type="submit" class="btn-primary flex-1 flex items-center justify-center gap-2 py-3">
<i data-lucide="play" class="w-4 h-4"></i> <!-- Buttons -->
{% trans "Generate Report" %} <div class="flex gap-3 pt-4 border-t border-slate-100">
</button> <button type="submit" class="flex-1 px-6 py-3 bg-navy text-white rounded-xl font-semibold hover:bg-blue transition flex items-center justify-center gap-2 shadow-lg shadow-navy/20">
<a href="{% url 'analytics:kpi_report_list' %}" class="btn-secondary px-6 py-3"> <i data-lucide="play" class="w-5 h-5"></i>
{% trans "Cancel" %} {% trans "Generate Report" %}
</a> </button>
<a href="{% url 'analytics:kpi_report_list' %}" class="px-6 py-3 border border-slate-200 text-slate rounded-xl font-semibold hover:bg-light transition">
{% trans "Cancel" %}
</a>
</div>
</div> </div>
</div> </form>
</form> </div>
</div> </div>
</div> </div>
<!-- Sidebar - Available Reports --> <!-- Sidebar - Available Reports -->
<div class="lg:col-span-1"> <div class="lg:col-span-1 space-y-6">
<div class="card"> <!-- Report Types Card -->
<div class="card-header"> <div class="bg-white rounded-2xl shadow-sm border border-slate-100 overflow-hidden">
<h3 class="text-sm font-bold text-navy flex items-center gap-2"> <div class="px-6 py-4 border-b border-slate-100 bg-slate-50">
<i data-lucide="list" class="w-4 h-4"></i> <h3 class="font-bold text-navy flex items-center gap-2">
<i data-lucide="list" class="w-5 h-5 text-blue"></i>
{% trans "Available KPI Reports" %} {% trans "Available KPI Reports" %}
</h3> </h3>
</div> </div>
<div class="space-y-2"> <div class="p-6 space-y-4">
<!-- MOH Reports --> <!-- MOH Reports -->
<div class="p-2.5 bg-light rounded-lg"> <div>
<p class="text-[10px] font-bold text-slate uppercase tracking-wider mb-2"> <p class="text-[10px] font-bold text-slate uppercase tracking-wider mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-navy rounded-full"></span>
{% trans "Ministry of Health" %} {% trans "Ministry of Health" %}
</p> </p>
<div class="space-y-1.5"> <div class="space-y-2">
<div class="flex items-center gap-2 text-sm"> <div class="flex items-center gap-3 p-2.5 bg-slate-50 rounded-xl hover:bg-blue-50 transition">
<span class="px-1.5 py-0.5 text-xs font-bold bg-navy text-white rounded flex-shrink-0">MOH-2</span> <span class="px-2.5 py-1 text-xs font-bold bg-navy text-white rounded-lg flex-shrink-0">MOH-2</span>
<span class="text-slate truncate">{% trans "72-Hour Resolution" %}</span> <span class="text-sm text-slate flex-1">{% trans "72-Hour Resolution" %}</span>
</div> </div>
<div class="flex items-center gap-2 text-sm"> <div class="flex items-center gap-3 p-2.5 bg-slate-50 rounded-xl hover:bg-blue-50 transition">
<span class="px-1.5 py-0.5 text-xs font-bold bg-navy text-white rounded flex-shrink-0">MOH-1</span> <span class="px-2.5 py-1 text-xs font-bold bg-navy text-white rounded-lg flex-shrink-0">MOH-1</span>
<span class="text-slate truncate">{% trans "Patient Experience" %}</span> <span class="text-sm text-slate flex-1">{% trans "Patient Experience" %}</span>
</div> </div>
<div class="flex items-center gap-2 text-sm"> <div class="flex items-center gap-3 p-2.5 bg-slate-50 rounded-xl hover:bg-blue-50 transition">
<span class="px-1.5 py-0.5 text-xs font-bold bg-navy text-white rounded flex-shrink-0">MOH-3</span> <span class="px-2.5 py-1 text-xs font-bold bg-navy text-white rounded-lg flex-shrink-0">MOH-3</span>
<span class="text-slate truncate">{% trans "Resolution Satisfaction" %}</span> <span class="text-sm text-slate flex-1">{% trans "Resolution Satisfaction" %}</span>
</div> </div>
</div> </div>
</div> </div>
<!-- Department Reports --> <!-- Department Reports -->
<div class="p-2.5 bg-light rounded-lg"> <div>
<p class="text-[10px] font-bold text-slate uppercase tracking-wider mb-2"> <p class="text-[10px] font-bold text-slate uppercase tracking-wider mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-blue rounded-full"></span>
{% trans "Departmental" %} {% trans "Departmental" %}
</p> </p>
<div class="space-y-1.5"> <div class="space-y-2">
<div class="flex items-center gap-2 text-sm"> <div class="flex items-center gap-3 p-2.5 bg-slate-50 rounded-xl hover:bg-blue-50 transition">
<span class="px-1.5 py-0.5 text-xs font-bold bg-blue text-white rounded flex-shrink-0">Dep-KPI-4</span> <span class="px-2.5 py-1 text-xs font-bold bg-blue text-white rounded-lg flex-shrink-0">Dep-KPI-4</span>
<span class="text-slate truncate">{% trans "Response Rate" %}</span> <span class="text-sm text-slate flex-1">{% trans "Response Rate" %}</span>
</div> </div>
<div class="flex items-center gap-2 text-sm"> <div class="flex items-center gap-3 p-2.5 bg-slate-50 rounded-xl hover:bg-blue-50 transition">
<span class="px-1.5 py-0.5 text-xs font-bold bg-blue text-white rounded flex-shrink-0">KPI-6</span> <span class="px-2.5 py-1 text-xs font-bold bg-blue text-white rounded-lg flex-shrink-0">KPI-6</span>
<span class="text-slate truncate">{% trans "Activation (2h)" %}</span> <span class="text-sm text-slate flex-1">{% trans "Activation (2h)" %}</span>
</div> </div>
<div class="flex items-center gap-2 text-sm"> <div class="flex items-center gap-3 p-2.5 bg-slate-50 rounded-xl hover:bg-blue-50 transition">
<span class="px-1.5 py-0.5 text-xs font-bold bg-blue text-white rounded flex-shrink-0">KPI-7</span> <span class="px-2.5 py-1 text-xs font-bold bg-blue text-white rounded-lg flex-shrink-0">KPI-7</span>
<span class="text-slate truncate">{% trans "Unactivated" %}</span> <span class="text-sm text-slate flex-1">{% trans "Unactivated" %}</span>
</div> </div>
</div> </div>
</div> </div>
<!-- N-PAD Reports --> <!-- N-PAD Reports -->
<div class="p-2.5 bg-light rounded-lg"> <div>
<p class="text-[10px] font-bold text-slate uppercase tracking-wider mb-2"> <p class="text-[10px] font-bold text-slate uppercase tracking-wider mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-navy rounded-full"></span>
{% trans "N-PAD Standards" %} {% trans "N-PAD Standards" %}
</p> </p>
<div class="space-y-1.5"> <div class="space-y-2">
<div class="flex items-center gap-2 text-sm"> <div class="flex items-center gap-3 p-2.5 bg-slate-50 rounded-xl hover:bg-blue-50 transition">
<span class="px-1.5 py-0.5 text-xs font-bold bg-navy text-white rounded flex-shrink-0">N-PAD-001</span> <span class="px-2.5 py-1 text-xs font-bold bg-navy text-white rounded-lg flex-shrink-0">N-PAD-001</span>
<span class="text-slate truncate">{% trans "Resolution" %}</span> <span class="text-sm text-slate flex-1">{% trans "Resolution" %}</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Quick Tips --> <!-- Quick Tips Card -->
<div class="mt-4 pt-4 border-t"> <div class="bg-gradient-to-br from-green-50 to-emerald-50 rounded-2xl border border-green-200 p-6">
<p class="text-[10px] font-bold text-slate uppercase tracking-wider mb-2"> <div class="flex items-center gap-2 mb-4">
{% trans "Quick Tips" %} <div class="w-8 h-8 bg-green-100 rounded-xl flex items-center justify-center">
</p> <i data-lucide="lightbulb" class="w-4 h-4 text-green-600"></i>
<ul class="space-y-2 text-sm text-slate"> </div>
<li class="flex items-start gap-2"> <h4 class="font-bold text-green-800">{% trans "Quick Tips" %}</h4>
<i data-lucide="check-circle" class="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5"></i>
<span>{% trans "Reports are generated automatically every month" %}</span>
</li>
<li class="flex items-start gap-2">
<i data-lucide="check-circle" class="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5"></i>
<span>{% trans "You can regenerate any report with latest data" %}</span>
</li>
<li class="flex items-start gap-2">
<i data-lucide="check-circle" class="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5"></i>
<span>{% trans "PDF export is available for all reports" %}</span>
</li>
</ul>
</div> </div>
<ul class="space-y-3">
<li class="flex items-start gap-3">
<i data-lucide="check-circle-2" class="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5"></i>
<span class="text-sm text-green-700">{% trans "Reports are generated automatically every month" %}</span>
</li>
<li class="flex items-start gap-3">
<i data-lucide="check-circle-2" class="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5"></i>
<span class="text-sm text-green-700">{% trans "You can regenerate any report with latest data" %}</span>
</li>
<li class="flex items-start gap-3">
<i data-lucide="check-circle-2" class="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5"></i>
<span class="text-sm text-green-700">{% trans "PDF export is available for all reports" %}</span>
</li>
</ul>
</div> </div>
</div> </div>
</div> </div>
@ -226,6 +237,8 @@
{% block extra_js %} {% block extra_js %}
<script> <script>
lucide.createIcons(); document.addEventListener('DOMContentLoaded', function() {
lucide.createIcons();
});
</script> </script>
{% endblock %} {% endblock %}

View File

@ -5,118 +5,162 @@
{% block title %}{% trans "Doctor Rating Import Jobs" %} - PX360{% endblock %} {% block title %}{% trans "Doctor Rating Import Jobs" %} - PX360{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <!-- Header -->
<!-- Header --> <div class="mb-6">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="flex items-center justify-between">
<div> <div>
<h2 class="mb-1"> <div class="flex items-center gap-2 text-sm text-slate mb-2">
<i class="bi bi-clock-history text-primary me-2"></i> <a href="{% url 'physicians:individual_ratings_list' %}" class="hover:text-navy">{% trans "Physician Ratings" %}</a>
{% trans "Import History" %} <i data-lucide="chevron-right" class="w-4 h-4"></i>
</h2> <span class="font-bold text-navy">{% trans "Import History" %}</span>
<p class="text-muted mb-0">{% trans "Track doctor rating import jobs" %}</p>
</div>
<div>
<a href="{% url 'physicians:doctor_rating_import' %}" class="btn btn-primary">
<i class="bi bi-plus-circle me-2"></i>{% trans "New Import" %}
</a>
</div>
</div>
<!-- Jobs Table -->
<div class="card">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>{% trans "Job Name" %}</th>
<th>{% trans "Hospital" %}</th>
<th>{% trans "Source" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Progress" %}</th>
<th>{% trans "Results" %}</th>
<th>{% trans "Created" %}</th>
<th>{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for job in jobs %}
<tr>
<td>
<strong>{{ job.name|truncatechars:40 }}</strong>
</td>
<td>{{ job.hospital.name }}</td>
<td>
{% if job.source == 'his_api' %}
<span class="badge bg-info">{% trans "HIS API" %}</span>
{% else %}
<span class="badge bg-secondary">{% trans "CSV Upload" %}</span>
{% endif %}
</td>
<td>
{% if job.status == 'pending' %}
<span class="badge bg-secondary">{% trans "Pending" %}</span>
{% elif job.status == 'processing' %}
<span class="badge bg-primary">
<span class="spinner-border spinner-border-sm me-1"></span>
{% trans "Processing" %}
</span>
{% elif job.status == 'completed' %}
<span class="badge bg-success">{% trans "Completed" %}</span>
{% elif job.status == 'failed' %}
<span class="badge bg-danger">{% trans "Failed" %}</span>
{% elif job.status == 'partial' %}
<span class="badge bg-warning text-dark">{% trans "Partial" %}</span>
{% endif %}
</td>
<td>
<div class="d-flex align-items-center">
<div class="progress flex-grow-1 me-2" style="height: 8px; width: 80px;">
<div class="progress-bar {% if job.status == 'failed' %}bg-danger{% elif job.status == 'completed' %}bg-success{% else %}bg-primary{% endif %}"
role="progressbar"
style="width: {{ job.progress_percentage }}%">
</div>
</div>
<small class="text-muted">{{ job.progress_percentage }}%</small>
</div>
</td>
<td>
{% if job.is_complete %}
<small>
<span class="text-success">{{ job.success_count }} <i class="bi bi-check"></i></span>
{% if job.failed_count > 0 %}
<span class="text-danger ms-2">{{ job.failed_count }} <i class="bi bi-x"></i></span>
{% endif %}
</small>
{% else %}
<small class="text-muted">{{ job.processed_count }} / {{ job.total_records }}</small>
{% endif %}
</td>
<td>
<small class="text-muted">{{ job.created_at|date:"Y-m-d H:i" }}</small>
</td>
<td>
<a href="{% url 'physicians:doctor_rating_job_status' job.id %}"
class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i>
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="8" class="text-center py-5">
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
<p class="text-muted mt-3">{% trans "No import jobs found" %}</p>
<a href="{% url 'physicians:doctor_rating_import' %}" class="btn btn-primary mt-2">
<i class="bi bi-plus-circle me-2"></i>{% trans "Import Ratings" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div> </div>
<h1 class="text-2xl font-bold text-navy flex items-center gap-3">
<div class="w-10 h-10 bg-blue-50 rounded-xl flex items-center justify-center">
<i data-lucide="clock" class="w-5 h-5 text-blue"></i>
</div>
{% trans "Import History" %}
</h1>
<p class="text-slate text-sm mt-1">{% trans "Track doctor rating import jobs" %}</p>
</div> </div>
<a href="{% url 'physicians:doctor_rating_import' %}"
class="px-6 py-3 bg-navy text-white rounded-xl font-semibold hover:bg-blue transition flex items-center gap-2 shadow-lg shadow-navy/20">
<i data-lucide="plus-circle" class="w-5 h-5"></i>
{% trans "New Import" %}
</a>
</div> </div>
</div> </div>
<!-- Jobs Table -->
<div class="bg-white rounded-2xl shadow-sm border border-slate-100 overflow-hidden">
{% if jobs %}
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-slate-50 border-b border-slate-200">
<tr>
<th class="text-left text-xs font-bold text-slate uppercase tracking-wider py-4 px-6">{% trans "Job Name" %}</th>
<th class="text-left text-xs font-bold text-slate uppercase tracking-wider py-4 px-6">{% trans "Hospital" %}</th>
<th class="text-left text-xs font-bold text-slate uppercase tracking-wider py-4 px-6">{% trans "Source" %}</th>
<th class="text-left text-xs font-bold text-slate uppercase tracking-wider py-4 px-6">{% trans "Status" %}</th>
<th class="text-left text-xs font-bold text-slate uppercase tracking-wider py-4 px-6">{% trans "Progress" %}</th>
<th class="text-left text-xs font-bold text-slate uppercase tracking-wider py-4 px-6">{% trans "Results" %}</th>
<th class="text-left text-xs font-bold text-slate uppercase tracking-wider py-4 px-6">{% trans "Created" %}</th>
<th class="text-left text-xs font-bold text-slate uppercase tracking-wider py-4 px-6">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
{% for job in jobs %}
<tr class="hover:bg-light/30 transition">
<td class="py-4 px-6">
<p class="text-sm font-semibold text-navy">{{ job.name|truncatechars:40 }}</p>
</td>
<td class="py-4 px-6">
<p class="text-sm text-slate">{{ job.hospital.name }}</p>
</td>
<td class="py-4 px-6">
{% if job.source == 'his_api' %}
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-bold bg-blue-100 text-blue-700">
<i data-lucide="database" class="w-3 h-3 mr-1"></i>
{% trans "HIS API" %}
</span>
{% else %}
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-bold bg-slate-100 text-slate-700">
<i data-lucide="upload" class="w-3 h-3 mr-1"></i>
{% trans "CSV Upload" %}
</span>
{% endif %}
</td>
<td class="py-4 px-6">
{% if job.status == 'pending' %}
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-bold bg-slate-100 text-slate-700">
<i data-lucide="clock" class="w-3 h-3 mr-1"></i>
{% trans "Pending" %}
</span>
{% elif job.status == 'processing' %}
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-bold bg-blue-100 text-blue-700">
<i data-lucide="loader-2" class="w-3 h-3 mr-1 animate-spin"></i>
{% trans "Processing" %}
</span>
{% elif job.status == 'completed' %}
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-bold bg-green-100 text-green-700">
<i data-lucide="check-circle" class="w-3 h-3 mr-1"></i>
{% trans "Completed" %}
</span>
{% elif job.status == 'failed' %}
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-bold bg-red-100 text-red-700">
<i data-lucide="x-circle" class="w-3 h-3 mr-1"></i>
{% trans "Failed" %}
</span>
{% elif job.status == 'partial' %}
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-bold bg-amber-100 text-amber-700">
<i data-lucide="alert-circle" class="w-3 h-3 mr-1"></i>
{% trans "Partial" %}
</span>
{% endif %}
</td>
<td class="py-4 px-6">
<div class="flex items-center gap-2">
<div class="flex-1 h-2 bg-slate-100 rounded-full overflow-hidden" style="min-width: 80px;">
<div class="h-full {% if job.status == 'failed' %}bg-red-500{% elif job.status == 'completed' %}bg-green-500{% else %}bg-blue-500{% endif %} rounded-full transition-all"
style="width: {{ job.progress_percentage }}%"></div>
</div>
<span class="text-xs font-semibold text-slate w-10">{{ job.progress_percentage }}%</span>
</div>
</td>
<td class="py-4 px-6">
{% if job.is_complete %}
<div class="flex items-center gap-3">
<span class="inline-flex items-center gap-1 text-xs font-semibold text-green-600">
<i data-lucide="check" class="w-3 h-3"></i>
{{ job.success_count }}
</span>
{% if job.failed_count > 0 %}
<span class="inline-flex items-center gap-1 text-xs font-semibold text-red-600">
<i data-lucide="x" class="w-3 h-3"></i>
{{ job.failed_count }}
</span>
{% endif %}
</div>
{% else %}
<span class="text-xs text-slate">{{ job.processed_count }} / {{ job.total_records }}</span>
{% endif %}
</td>
<td class="py-4 px-6">
<p class="text-sm text-slate">{{ job.created_at|date:"Y-m-d H:i" }}</p>
</td>
<td class="py-4 px-6">
<a href="{% url 'physicians:doctor_rating_job_status' job.id %}"
class="inline-flex items-center gap-1.5 px-3 py-1.5 border border-blue-200 text-blue rounded-lg text-xs font-semibold hover:bg-blue-50 transition">
<i data-lucide="eye" class="w-3.5 h-3.5"></i>
{% trans "View" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<!-- Empty State -->
<div class="py-16 text-center">
<div class="inline-flex items-center justify-center w-20 h-20 bg-slate-100 rounded-full mb-4">
<i data-lucide="inbox" class="w-10 h-10 text-slate-400"></i>
</div>
<h3 class="text-lg font-bold text-navy mb-2">{% trans "No Import Jobs" %}</h3>
<p class="text-slate text-sm mb-6">{% trans "No import jobs found. Start by importing doctor ratings." %}</p>
<a href="{% url 'physicians:doctor_rating_import' %}"
class="inline-flex items-center gap-2 px-6 py-3 bg-navy text-white rounded-xl font-semibold hover:bg-blue transition shadow-lg shadow-navy/20">
<i data-lucide="upload" class="w-5 h-5"></i>
{% trans "Import Ratings" %}
</a>
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
lucide.createIcons();
});
</script>
{% endblock %} {% endblock %}

View File

@ -7,46 +7,69 @@
{% block extra_css %} {% block extra_css %}
<style> <style>
.progress-ring { .progress-ring {
width: 120px; width: 140px;
height: 120px; height: 140px;
} }
.progress-ring-circle { .progress-ring-circle {
transition: stroke-dashoffset 0.35s; transition: stroke-dashoffset 0.35s;
transform: rotate(-90deg); transform: rotate(-90deg);
transform-origin: 50% 50%; transform-origin: 50% 50%;
} }
.stat-card {
transition: all 0.2s ease;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
</style> </style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <!-- Header -->
<!-- Header --> <div class="mb-6">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="flex items-center justify-between">
<div> <div>
<h2 class="mb-1"> <div class="flex items-center gap-2 text-sm text-slate mb-2">
<i class="bi bi-activity text-primary me-2"></i> <a href="{% url 'physicians:doctor_rating_job_list' %}" class="hover:text-navy">{% trans "Import Jobs" %}</a>
<i data-lucide="chevron-right" class="w-4 h-4"></i>
<span class="font-bold text-navy">{% trans "Job Details" %}</span>
</div>
<h1 class="text-2xl font-bold text-navy flex items-center gap-3">
<div class="w-10 h-10 bg-blue-50 rounded-xl flex items-center justify-center">
<i data-lucide="activity" class="w-5 h-5 text-blue"></i>
</div>
{% trans "Import Job Status" %} {% trans "Import Job Status" %}
</h2> </h1>
<p class="text-muted mb-0">{{ job.name }}</p> <p class="text-slate text-sm mt-1">{{ job.name }}</p>
</div> </div>
<div> <div class="flex items-center gap-3">
<a href="{% url 'physicians:doctor_rating_job_list' %}" class="btn btn-outline-secondary"> <a href="{% url 'physicians:doctor_rating_job_list' %}"
<i class="bi bi-list me-2"></i>{% trans "All Jobs" %} class="px-4 py-2 border border-slate-200 text-slate rounded-xl font-semibold hover:bg-light transition flex items-center gap-2">
<i data-lucide="list" class="w-4 h-4"></i>
{% trans "All Jobs" %}
</a>
<a href="{% url 'physicians:doctor_rating_import' %}"
class="px-4 py-2 bg-navy text-white rounded-xl font-semibold hover:bg-blue transition flex items-center gap-2">
<i data-lucide="upload" class="w-4 h-4"></i>
{% trans "Import More" %}
</a> </a>
</div> </div>
</div> </div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Status Card --> <!-- Status Card -->
<div class="row mb-4"> <div class="lg:col-span-1">
<div class="col-md-4"> <div class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
<div class="card text-center"> <div class="text-center">
<div class="card-body"> <!-- Progress Circle -->
<!-- Progress Circle --> <div class="inline-flex items-center justify-center mb-4">
<div class="position-relative d-inline-block mb-3"> <div class="relative">
<svg class="progress-ring" viewBox="0 0 120 120"> <svg class="progress-ring" viewBox="0 0 120 120">
<circle <circle
class="progress-ring-circle" class="progress-ring-circle"
stroke="#e9ecef" stroke="#f1f5f9"
stroke-width="8" stroke-width="8"
fill="transparent" fill="transparent"
r="52" r="52"
@ -55,205 +78,261 @@
/> />
<circle <circle
class="progress-ring-circle" class="progress-ring-circle"
stroke="{% if job.status == 'completed' %}#28a745{% elif job.status == 'failed' %}#dc3545{% elif job.status == 'partial' %}#ffc107{% else %}#007bbd{% endif %}" stroke="{% if job.status == 'completed' %}#22c55e{% elif job.status == 'failed' %}#ef4444{% elif job.status == 'partial' %}#f59e0b{% else %}#007bbd{% endif %}"
stroke-width="8" stroke-width="8"
fill="transparent" fill="transparent"
r="52" r="52"
cx="60" cx="60"
cy="60" cy="60"
stroke-dasharray="326.73" stroke-dasharray="326.73"
stroke-dashoffset="{{ 326.73|add:-progress|div:100|mul:326.73 }}" stroke-dashoffset="{{ stroke_dashoffset }}"
stroke-linecap="round" stroke-linecap="round"
/> />
</svg> </svg>
<div class="position-absolute top-50 start-50 translate-middle"> <div class="absolute inset-0 flex items-center justify-center">
<h3 class="mb-0">{{ progress }}%</h3> <div class="text-center">
<h3 class="text-2xl font-bold text-navy mb-0">{{ progress }}%</h3>
</div>
</div> </div>
</div> </div>
</div>
<h5 class="mb-2"> <!-- Status Badge -->
{% if job.status == 'pending' %} <div class="mb-4">
<span class="badge bg-secondary">{% trans "Pending" %}</span> {% if job.status == 'pending' %}
{% elif job.status == 'processing' %} <span class="inline-flex items-center px-4 py-2 rounded-xl text-sm font-bold bg-slate-100 text-slate-700">
<span class="badge bg-primary">{% trans "Processing" %}</span> <i data-lucide="clock" class="w-4 h-4 mr-2"></i>
{% elif job.status == 'completed' %} {% trans "Pending" %}
<span class="badge bg-success">{% trans "Completed" %}</span> </span>
{% elif job.status == 'failed' %} {% elif job.status == 'processing' %}
<span class="badge bg-danger">{% trans "Failed" %}</span> <span class="inline-flex items-center px-4 py-2 rounded-xl text-sm font-bold bg-blue-100 text-blue-700">
{% elif job.status == 'partial' %} <i data-lucide="loader-2" class="w-4 h-4 mr-2 animate-spin"></i>
<span class="badge bg-warning text-dark">{% trans "Partial Success" %}</span> {% trans "Processing" %}
{% endif %} </span>
</h5> {% elif job.status == 'completed' %}
<span class="inline-flex items-center px-4 py-2 rounded-xl text-sm font-bold bg-green-100 text-green-700">
{% if not is_complete %} <i data-lucide="check-circle" class="w-4 h-4 mr-2"></i>
<div class="spinner-border text-primary" role="status"> {% trans "Completed" %}
<span class="visually-hidden">{% trans "Processing..." %}</span> </span>
</div> {% elif job.status == 'failed' %}
<span class="inline-flex items-center px-4 py-2 rounded-xl text-sm font-bold bg-red-100 text-red-700">
<i data-lucide="x-circle" class="w-4 h-4 mr-2"></i>
{% trans "Failed" %}
</span>
{% elif job.status == 'partial' %}
<span class="inline-flex items-center px-4 py-2 rounded-xl text-sm font-bold bg-amber-100 text-amber-700">
<i data-lucide="alert-circle" class="w-4 h-4 mr-2"></i>
{% trans "Partial Success" %}
</span>
{% endif %} {% endif %}
</div> </div>
{% if not is_complete %}
<div class="flex items-center justify-center gap-2 text-blue">
<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i>
<span class="text-sm font-semibold">{% trans "Processing..." %}</span>
</div>
{% else %}
<p class="text-sm text-slate">
<i data-lucide="check" class="w-4 h-4 inline mr-1"></i>
{% trans "Job completed" %}
</p>
{% endif %}
</div> </div>
</div> </div>
</div>
<div class="col-md-8"> <!-- Job Details -->
<div class="card h-100"> <div class="lg:col-span-2">
<div class="card-header bg-white"> <div class="bg-white rounded-2xl shadow-sm border border-slate-100 overflow-hidden">
<h5 class="mb-0">{% trans "Job Details" %}</h5> <div class="px-6 py-4 border-b border-slate-100 bg-slate-50">
</div> <h3 class="font-bold text-navy flex items-center gap-2">
<div class="card-body"> <i data-lucide="file-text" class="w-5 h-5 text-blue"></i>
<div class="row"> {% trans "Job Details" %}
<div class="col-md-6"> </h3>
<table class="table table-borderless table-sm"> </div>
<tr> <div class="p-6">
<td class="text-muted">{% trans "Hospital:" %}</td> <div class="grid grid-cols-2 gap-6">
<td><strong>{{ job.hospital.name }}</strong></td> <div class="space-y-4">
</tr> <div>
<tr> <p class="text-xs font-bold text-slate uppercase mb-1">{% trans "Hospital" %}</p>
<td class="text-muted">{% trans "Source:" %}</td> <p class="text-sm font-semibold text-navy">{{ job.hospital.name }}</p>
<td>{{ job.get_source_display }}</td>
</tr>
<tr>
<td class="text-muted">{% trans "Total Records:" %}</td>
<td><strong>{{ job.total_records }}</strong></td>
</tr>
<tr>
<td class="text-muted">{% trans "Created By:" %}</td>
<td>{{ job.created_by.get_full_name|default:job.created_by.email }}</td>
</tr>
</table>
</div> </div>
<div class="col-md-6"> <div>
<table class="table table-borderless table-sm"> <p class="text-xs font-bold text-slate uppercase mb-1">{% trans "Source" %}</p>
<tr> <p class="text-sm text-slate">{{ job.get_source_display }}</p>
<td class="text-muted">{% trans "Created:" %}</td>
<td>{{ job.created_at|date:"Y-m-d H:i" }}</td>
</tr>
<tr>
<td class="text-muted">{% trans "Started:" %}</td>
<td>{{ job.started_at|date:"Y-m-d H:i"|default:"-" }}</td>
</tr>
<tr>
<td class="text-muted">{% trans "Completed:" %}</td>
<td>{{ job.completed_at|date:"Y-m-d H:i"|default:"-" }}</td>
</tr>
{% if job.duration_seconds %}
<tr>
<td class="text-muted">{% trans "Duration:" %}</td>
<td>{{ job.duration_seconds }} {% trans "seconds" %}</td>
</tr>
{% endif %}
</table>
</div> </div>
<div>
<p class="text-xs font-bold text-slate uppercase mb-1">{% trans "Total Records" %}</p>
<p class="text-lg font-bold text-navy">{{ job.total_records }}</p>
</div>
<div>
<p class="text-xs font-bold text-slate uppercase mb-1">{% trans "Created By" %}</p>
<p class="text-sm text-slate">{{ job.created_by.get_full_name|default:job.created_by.email }}</p>
</div>
</div>
<div class="space-y-4">
<div>
<p class="text-xs font-bold text-slate uppercase mb-1">{% trans "Created" %}</p>
<p class="text-sm text-slate">{{ job.created_at|date:"Y-m-d H:i" }}</p>
</div>
<div>
<p class="text-xs font-bold text-slate uppercase mb-1">{% trans "Started" %}</p>
<p class="text-sm text-slate">{{ job.started_at|date:"Y-m-d H:i"|default:"-" }}</p>
</div>
<div>
<p class="text-xs font-bold text-slate uppercase mb-1">{% trans "Completed" %}</p>
<p class="text-sm text-slate">{{ job.completed_at|date:"Y-m-d H:i"|default:"-" }}</p>
</div>
{% if job.duration_seconds %}
<div>
<p class="text-xs font-bold text-slate uppercase mb-1">{% trans "Duration" %}</p>
<p class="text-sm font-semibold text-blue">{{ job.duration_seconds }} {% trans "seconds" %}</p>
</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Results Summary --> <!-- Results Summary -->
{% if is_complete %} {% if is_complete %}
<div class="row mb-4"> <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mt-6">
<div class="col-md-3"> <div class="stat-card bg-white rounded-2xl p-6 shadow-sm border border-l-4 border-green-500">
<div class="card border-left-success"> <div class="flex items-center justify-between mb-3">
<div class="card-body text-center"> <div class="w-12 h-12 bg-green-50 rounded-xl flex items-center justify-center">
<h2 class="text-success mb-1">{{ job.success_count }}</h2> <i data-lucide="check-circle" class="w-6 h-6 text-green-600"></i>
<span class="text-muted">{% trans "Successful" %}</span>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-left-danger">
<div class="card-body text-center">
<h2 class="text-danger mb-1">{{ job.failed_count }}</h2>
<span class="text-muted">{% trans "Failed" %}</span>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-left-warning">
<div class="card-body text-center">
<h2 class="text-warning mb-1">{{ job.skipped_count }}</h2>
<span class="text-muted">{% trans "Skipped" %}</span>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-left-info">
<div class="card-body text-center">
<h2 class="text-info mb-1">{{ job.processed_count }}</h2>
<span class="text-muted">{% trans "Processed" %}</span>
</div>
</div> </div>
<span class="text-xs font-bold text-slate uppercase">{% trans "Successful" %}</span>
</div> </div>
<p class="text-3xl font-bold text-green-600">{{ job.success_count }}</p>
</div> </div>
{% endif %}
<!-- Error Message --> <div class="stat-card bg-white rounded-2xl p-6 shadow-sm border border-l-4 border-red-500">
{% if job.error_message %} <div class="flex items-center justify-between mb-3">
<div class="alert alert-danger mb-4"> <div class="w-12 h-12 bg-red-50 rounded-xl flex items-center justify-center">
<h6 class="alert-heading"> <i data-lucide="x-circle" class="w-6 h-6 text-red-600"></i>
<i class="bi bi-exclamation-triangle me-2"></i>{% trans "Error" %}
</h6>
<p class="mb-0">{{ job.error_message }}</p>
</div>
{% endif %}
<!-- Detailed Results -->
{% if results and results.errors %}
<div class="card">
<div class="card-header bg-white">
<h5 class="mb-0">
<i class="bi bi-list-ul me-2"></i>{% trans "Error Details" %}
</h5>
</div>
<div class="card-body">
<div class="table-responsive" style="max-height: 400px;">
<table class="table table-sm">
<thead>
<tr>
<th>{% trans "Row" %}</th>
<th>{% trans "Error" %}</th>
<th>{% trans "Data" %}</th>
</tr>
</thead>
<tbody>
{% for error in results.errors|slice:":50" %}
<tr>
<td>{{ error.row }}</td>
<td class="text-danger">{{ error.message }}</td>
<td><code class="small">{{ error.data|truncatechars:50 }}</code></td>
</tr>
{% endfor %}
</tbody>
</table>
</div> </div>
{% if results.errors|length > 50 %} <span class="text-xs font-bold text-slate uppercase">{% trans "Failed" %}</span>
<p class="text-muted small mt-2">
{% trans "Showing first 50 errors of" %} {{ results.errors|length }}
</p>
{% endif %}
</div> </div>
<p class="text-3xl font-bold text-red-600">{{ job.failed_count }}</p>
</div> </div>
{% endif %}
<!-- Navigation --> <div class="stat-card bg-white rounded-2xl p-6 shadow-sm border border-l-4 border-amber-500">
<div class="mt-4"> <div class="flex items-center justify-between mb-3">
<a href="{% url 'physicians:doctor_rating_import' %}" class="btn btn-primary"> <div class="w-12 h-12 bg-amber-50 rounded-xl flex items-center justify-center">
<i class="bi bi-plus-circle me-2"></i>{% trans "Import More Ratings" %} <i data-lucide="skip-forward" class="w-6 h-6 text-amber-600"></i>
</a> </div>
<a href="{% url 'physicians:individual_ratings_list' %}" class="btn btn-outline-secondary"> <span class="text-xs font-bold text-slate uppercase">{% trans "Skipped" %}</span>
<i class="bi bi-list me-2"></i>{% trans "View Imported Ratings" %} </div>
</a> <p class="text-3xl font-bold text-amber-600">{{ job.skipped_count }}</p>
</div>
<div class="stat-card bg-white rounded-2xl p-6 shadow-sm border border-l-4 border-blue-500">
<div class="flex items-center justify-between mb-3">
<div class="w-12 h-12 bg-blue-50 rounded-xl flex items-center justify-center">
<i data-lucide="layers" class="w-6 h-6 text-blue-600"></i>
</div>
<span class="text-xs font-bold text-slate uppercase">{% trans "Processed" %}</span>
</div>
<p class="text-3xl font-bold text-blue-600">{{ job.processed_count }}</p>
</div> </div>
</div> </div>
{% endif %}
<!-- Error Message -->
{% if job.error_message %}
<div class="bg-red-50 border border-red-200 rounded-2xl p-6 mt-6">
<div class="flex items-start gap-4">
<div class="w-10 h-10 bg-red-100 rounded-xl flex items-center justify-center flex-shrink-0">
<i data-lucide="alert-triangle" class="w-5 h-5 text-red-600"></i>
</div>
<div class="flex-1">
<h4 class="font-bold text-red-800 mb-2 flex items-center gap-2">
<i data-lucide="circle-alert" class="w-4 h-4"></i>
{% trans "Error" %}
</h4>
<p class="text-sm text-red-700">{{ job.error_message }}</p>
</div>
</div>
</div>
{% endif %}
<!-- Error Details -->
{% if results and results.errors %}
<div class="bg-white rounded-2xl shadow-sm border border-slate-100 mt-6 overflow-hidden">
<div class="px-6 py-4 border-b border-slate-100 bg-slate-50">
<h3 class="font-bold text-navy flex items-center gap-2">
<i data-lucide="list" class="w-5 h-5 text-blue"></i>
{% trans "Error Details" %}
</h3>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-slate-50 border-b border-slate-200">
<tr>
<th class="text-left text-xs font-bold text-slate uppercase tracking-wider py-3 px-4">{% trans "Row" %}</th>
<th class="text-left text-xs font-bold text-slate uppercase tracking-wider py-3 px-4">{% trans "Error" %}</th>
<th class="text-left text-xs font-bold text-slate uppercase tracking-wider py-3 px-4">{% trans "Data" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
{% for error in results.errors|slice:":50" %}
<tr class="hover:bg-light/30 transition">
<td class="py-3 px-4">
<span class="text-sm font-mono text-slate">{{ error.row }}</span>
</td>
<td class="py-3 px-4">
<span class="text-sm text-red-600">{{ error.message }}</span>
</td>
<td class="py-3 px-4">
<code class="text-xs bg-slate-100 px-2 py-1 rounded">{{ error.data|truncatechars:50 }}</code>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if results.errors|length > 50 %}
<div class="px-6 py-4 bg-slate-50 border-t border-slate-200">
<p class="text-sm text-slate">
{% trans "Showing first 50 errors of" %} <strong>{{ results.errors|length }}</strong>
</p>
</div>
{% endif %}
</div>
{% endif %}
<!-- Action Buttons -->
{% if is_complete %}
<div class="flex items-center gap-4 mt-6">
<a href="{% url 'physicians:individual_ratings_list' %}"
class="px-6 py-3 bg-navy text-white rounded-xl font-semibold hover:bg-blue transition flex items-center gap-2 shadow-lg shadow-navy/20">
<i data-lucide="list" class="w-5 h-5"></i>
{% trans "View Imported Ratings" %}
</a>
<a href="{% url 'physicians:doctor_rating_import' %}"
class="px-6 py-3 border border-slate-200 text-slate rounded-xl font-semibold hover:bg-light transition flex items-center gap-2">
<i data-lucide="upload" class="w-5 h-5"></i>
{% trans "Import More Ratings" %}
</a>
</div>
{% endif %}
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
{% if not is_complete %}
<script> <script>
// Auto-refresh page every 3 seconds while job is processing document.addEventListener('DOMContentLoaded', function() {
setTimeout(function() { lucide.createIcons();
window.location.reload(); });
}, 3000);
</script> {% if not is_complete %}
// Auto-refresh page every 3 seconds while job is processing
setTimeout(function() {
window.location.reload();
}, 3000);
{% endif %} {% endif %}
</script>
{% endblock %} {% endblock %}