15 KiB
Complaint Category and Subcategory Structure Examination
Executive Summary
This document provides a comprehensive examination of the complaint category and subcategory structure in the PX360 system, including the 4-level SHCT taxonomy implementation and a diagnosis of the domain dropdown issue.
1. Taxonomy Structure Overview
The complaint taxonomy follows a 4-level hierarchical structure based on SHCT (Saudi Health Commission for Tourism) standards:
Level 1: DOMAIN (3 domains)
- CLINICAL (سريري) - Medical and healthcare-related complaints
- MANAGEMENT (إداري) - Administrative and operational complaints
- RELATIONSHIPS (علاقات) - Staff-patient relationship complaints
Level 2: CATEGORY (8 categories)
These are specific areas within each domain. Examples include:
- Under CLINICAL: "Medical Treatment", "Diagnosis", "Medication"
- Under MANAGEMENT: "Billing", "Scheduling", "Facilities"
- Under RELATIONSHIPS: "Staff Behavior", "Communication"
Level 3: SUBCATEGORY (20 subcategories)
More detailed classifications within each category. Examples:
- Under "Medical Treatment": "Treatment Delay", "Treatment Quality"
- Under "Billing": "Incorrect Charges", "Payment Issues"
Level 4: CLASSIFICATION (75 classifications)
The most granular level, providing specific complaint types. Examples:
- Under "Treatment Delay": "Emergency Room", "Outpatient"
- Under "Incorrect Charges": "Insurance", "Self-Pay"
2. Database Schema
ComplaintCategory Model
class ComplaintCategory(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# Taxonomy fields
level = models.IntegerField(
choices=[
(1, "DOMAIN"),
(2, "CATEGORY"),
(3, "SUBCATEGORY"),
(4, "CLASSIFICATION")
],
db_index=True
)
parent_id = models.UUIDField(null=True, blank=True, db_index=True)
domain_type = models.CharField(
max_length=50,
choices=[
('CLINICAL', 'Clinical'),
('MANAGEMENT', 'Management'),
('RELATIONSHIPS', 'Relationships')
]
)
# Bilingual fields
name_en = models.CharField(max_length=200)
name_ar = models.CharField(max_length=200)
description_en = models.TextField(blank=True)
description_ar = models.TextField(blank=True)
# SHCT code
code = models.CharField(max_length=50, unique=True)
# Hospital-specific categories (optional)
hospitals = models.ManyToManyField(
Hospital,
blank=True,
related_name='complaint_categories'
)
# Ordering and status
order = models.IntegerField(default=0)
is_active = models.BooleanField(default=True)
# Metadata
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
Complaint Model (4-level taxonomy)
class Complaint(models.Model):
# Level 1: Domain (FK to ComplaintCategory)
domain = models.ForeignKey(
ComplaintCategory,
on_delete=models.PROTECT,
related_name='complaints_as_domain',
null=True,
blank=True
)
# Level 2: Category (FK to ComplaintCategory)
category = models.ForeignKey(
ComplaintCategory,
on_delete=models.PROTECT,
related_name='complaints_as_category',
null=True,
blank=True
)
# Level 3: Subcategory (stored as code)
subcategory = models.CharField(max_length=50, blank=True)
# Level 4: Classification (stored as code)
classification = models.CharField(max_length=50, blank=True)
3. Current Taxonomy Data Status
Database Statistics (as of latest diagnostic)
- Total Categories: 106
- Level 1 (Domains): 3
- Level 2 (Categories): 8
- Level 3 (Subcategories): 20
- Level 4 (Classifications): 75
- Active Hospitals: 1 (Alhammadi Hospital)
Level 1 Domains in Database
-
CLINICAL (سريري)
- ID: 3ab92484-840b-4f81-b50a-b51d1d807929
- Type: CLINICAL
- Active: True
-
MANAGEMENT (إداري)
- ID: 1dc25dd0-a550-4cbe-9af1-0a5f8a71ce69
- Type: MANAGEMENT
- Active: True
-
RELATIONSHIPS (علاقات)
- ID: 537132ad-0035-4a1e-bea5-8ebee3c7d5af
- Type: RELATIONSHIPS
- Active: True
Category Visibility
- All 106 categories are system-wide (not hospital-specific)
- All categories have
is_active=True - The hospital "Alhammadi Hospital" has 0 hospital-specific categories
4. API Endpoint Analysis
Endpoint: /complaints/public/api/load-categories/
Method: GET
Authentication: Not required (public form)
Parameters:
hospital_id(optional): UUID of selected hospital
Response Format:
{
"categories": [
{
"id": "uuid",
"name_en": "CLINICAL",
"name_ar": "سريري",
"code": "CLINICAL",
"parent_id": null,
"level": 1,
"domain_type": "CLINICAL",
"description_en": "...",
"description_ar": "..."
},
...
]
}
Query Logic
if hospital_id:
# Return hospital-specific + system-wide categories
categories_queryset = (
ComplaintCategory.objects.filter(
Q(hospitals__id=hospital_id) | Q(hospitals__isnull=True),
is_active=True
)
.distinct()
.order_by("level", "order", "name_en")
)
else:
# Return only system-wide categories
categories_queryset = ComplaintCategory.objects.filter(
hospitals__isnull=True,
is_active=True
).order_by("level", "order", "name_en")
Diagnostic Results
✓ API query returns 106 categories for Alhammadi Hospital
✓ API query returns 3 Level 1 domains for dropdown
✓ All domains are active and visible
5. Frontend Implementation
Public Complaint Form Template
Location: templates/complaints/public_complaint_form.html
JavaScript Dependencies
- jQuery 3.7.1 - Loaded via CDN in
templates/layouts/public_base.html - SweetAlert2 - Loaded for user feedback
- Bootstrap 5 - UI framework
Cascading Dropdown Logic
The form implements a 4-level cascading dropdown system:
- Hospital Selection → Triggers Domain load
- Domain Selection → Triggers Category load (filtered by domain)
- Category Selection → Triggers Subcategory load (filtered by category)
- Subcategory Selection → Triggers Classification load (filtered by subcategory)
Key JavaScript Functions
loadDomains(hospitalId)
function loadDomains(hospitalId) {
if (!hospitalId) {
// Clear all dropdowns
$('#id_domain').find('option:not(:first)').remove();
$('#category_container').hide();
$('#subcategory_container').hide();
$('#classification_container').hide();
return;
}
$.ajax({
url: '{% url "complaints:api_load_categories" %}',
type: 'GET',
data: { hospital_id: hospitalId },
success: function(response) {
allCategories = response.categories;
const domainSelect = $('#id_domain');
domainSelect.find('option:not(:first)').remove();
// Only show level 1 categories (Domains)
allCategories.forEach(function(category) {
if (category.level === 1) {
domainSelect.append($('<option>', {
value: category.id,
text: getName(category)
}));
}
});
},
error: function() {
console.error('Failed to load domains');
}
});
}
loadCategories(domainId), loadSubcategories(categoryId), loadClassifications(subcategoryId)
Similar pattern: filter allCategories by level and parent_id, then populate the appropriate dropdown.
6. Domain Dropdown Issue Diagnosis
Problem Description
The domain dropdown shows no data even though:
- Database contains 3 active domains
- API returns correct data when tested directly
- jQuery is loaded
- URL configuration is correct
Diagnostic Findings
What Works
- ✓ Database has 3 Level 1 domains with
is_active=True - ✓ API endpoint is configured correctly:
/complaints/public/api/load-categories/ - ✓ API query returns 3 domains when filtered by hospital
- ✓ jQuery 3.7.1 is loaded in base template
- ✓ JavaScript code logic is correct
- ✓ AJAX endpoint returns correct JSON structure
Potential Root Causes
Most Likely: JavaScript event handler not firing or console error
Possible issues:
- Hospital selection event not triggering: The
$('#id_hospital').on('change', ...)event handler might not be firing - Console JavaScript error: An error in JavaScript preventing execution
- Timing issue: JavaScript code running before DOM is fully ready
- jQuery selector issue:
$('#id_hospital')selector not finding the element - CSRF token issue: Though GET requests shouldn't need CSRF
Investigation Steps
-
Open browser developer tools
- Press F12 or right-click → Inspect
- Go to Console tab
- Look for any JavaScript errors (red text)
-
Check Network tab
- Go to Network tab
- Select a hospital from dropdown
- Look for AJAX request to
/complaints/public/api/load-categories/ - Check if request is being made
- If made, check response status and content
-
Test API directly
- Open URL:
http://localhost:8000/complaints/public/api/load-categories/?hospital_id=<hospital_uuid> - Should return JSON with 106 categories including 3 domains
- Open URL:
-
Check console for errors
- Common errors:
$ is not defined- jQuery not loadedUncaught ReferenceError- variable not definedFailed to load resource- network error
- Common errors:
7. Solution Recommendations
Immediate Fix
Add a $(document).ready() wrapper to ensure JavaScript runs after DOM loads:
$(document).ready(function() {
// Store all categories data globally for easy access
let allCategories = [];
let currentLanguage = 'en';
// Get CSRF token
function getCSRFToken() {
const cookieValue = document.cookie
.split('; ')
.find(row => row.startsWith('csrftoken='))
?.split('=')[1];
if (cookieValue) {
return cookieValue;
}
return $('[name="csrfmiddlewaretoken"]').val();
}
// ... rest of JavaScript code ...
// Handle hospital change
$('#id_hospital').on('change', function() {
const hospitalId = $(this).val();
console.log('Hospital changed to:', hospitalId); // Debug log
loadDomains(hospitalId);
// Clear all taxonomy dropdowns when hospital changes
$('#id_domain').val('');
$('#id_category').val('');
$('#id_subcategory').val('');
$('#id_classification').val('');
hideAllDescriptions();
});
// ... rest of event handlers ...
// Detect current language from HTML dir
currentLanguage = $('html').attr('dir') === 'rtl' ? 'ar' : 'en';
});
Additional Improvements
- Add error handling to AJAX calls:
error: function(xhr, status, error) {
console.error('Failed to load domains:', error);
console.error('Response:', xhr.responseText);
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Failed to load complaint categories. Please try again.'
});
}
- Add success message when domains load:
success: function(response) {
console.log('Loaded categories:', response.categories.length);
allCategories = response.categories;
// ... rest of code
}
- Add loading indicator:
beforeSend: function() {
$('#id_domain').prop('disabled', true);
},
complete: function() {
$('#id_domain').prop('disabled', false);
}
8. Testing Checklist
After implementing fixes, test the following:
- Open public complaint form
- Select a hospital from dropdown
- Verify domain dropdown populates with 3 options
- Select a domain
- Verify category dropdown populates with child categories
- Select a category
- Verify subcategory dropdown populates
- Select a subcategory
- Verify classification dropdown populates (if applicable)
- Submit a complaint with all 4 levels filled
- Verify complaint is created with correct taxonomy data
- Test in both English and Arabic modes
- Check browser console for no errors
- Check network tab for successful AJAX calls
9. Taxonomy Management Commands
Load SHCT Taxonomy Data
python manage.py load_shct_taxonomy
Examine Taxonomy Structure
python examine_taxonomy.py
Diagnose Domain Dropdown Issue
python diagnose_domain_dropdown.py
Create New Categories (Management Command)
Use Django admin: /admin/complaints/complaintcategory/
10. Summary
The complaint taxonomy system is well-structured with:
- ✅ 4-level hierarchical classification (Domain → Category → Subcategory → Classification)
- ✅ Bilingual support (English/Arabic)
- ✅ SHCT compliance
- ✅ 106 categories loaded in database
- ✅ Hospital-specific and system-wide category support
- ✅ RESTful API for frontend integration
- ✅ Cascading dropdown UI implementation
Known Issue: Domain dropdown not populating on hospital selection
- ✅ Backend and API are working correctly
- ⚠️ Frontend JavaScript event handler may have timing or execution issue
- 📋 Fix: Add
$(document).ready()wrapper and debug logging
Appendix: Quick Reference
Category Levels
- Level 1 (DOMAIN): Highest level - Clinical, Management, Relationships
- Level 2 (CATEGORY): Specific areas within domains
- Level 3 (SUBCATEGORY): Detailed classifications within categories
- Level 4 (CLASSIFICATION): Most granular complaint types
API Endpoints
- Load categories:
GET /complaints/public/api/load-categories/?hospital_id={uuid} - Load departments:
GET /complaints/public/api/load-departments/?hospital_id={uuid}
Database Tables
complaints_complaintcategory- Stores taxonomy structurecomplaints_complaint- Stores complaints with 4-level taxonomyorganizations_hospital_complaintcategories- Hospital-category mapping
Key Files
apps/complaints/models.py- Database modelsapps/complaints/ui_views.py- UI views and API endpointstemplates/complaints/public_complaint_form.html- Public form templateapps/complaints/management/commands/load_shct_taxonomy.py- Data loading
Document Version: 1.0
Last Updated: January 29, 2026
Status: Complete - Diagnosis provided, fix recommendations included