500 lines
15 KiB
Markdown
500 lines
15 KiB
Markdown
# 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
|
|
|
|
```python
|
|
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)
|
|
|
|
```python
|
|
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
|
|
1. **CLINICAL** (سريري)
|
|
- ID: 3ab92484-840b-4f81-b50a-b51d1d807929
|
|
- Type: CLINICAL
|
|
- Active: True
|
|
|
|
2. **MANAGEMENT** (إداري)
|
|
- ID: 1dc25dd0-a550-4cbe-9af1-0a5f8a71ce69
|
|
- Type: MANAGEMENT
|
|
- Active: True
|
|
|
|
3. **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**:
|
|
```json
|
|
{
|
|
"categories": [
|
|
{
|
|
"id": "uuid",
|
|
"name_en": "CLINICAL",
|
|
"name_ar": "سريري",
|
|
"code": "CLINICAL",
|
|
"parent_id": null,
|
|
"level": 1,
|
|
"domain_type": "CLINICAL",
|
|
"description_en": "...",
|
|
"description_ar": "..."
|
|
},
|
|
...
|
|
]
|
|
}
|
|
```
|
|
|
|
### Query Logic
|
|
```python
|
|
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:
|
|
|
|
1. **Hospital Selection** → Triggers Domain load
|
|
2. **Domain Selection** → Triggers Category load (filtered by domain)
|
|
3. **Category Selection** → Triggers Subcategory load (filtered by category)
|
|
4. **Subcategory Selection** → Triggers Classification load (filtered by subcategory)
|
|
|
|
### Key JavaScript Functions
|
|
|
|
#### loadDomains(hospitalId)
|
|
```javascript
|
|
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
|
|
1. ✓ Database has 3 Level 1 domains with `is_active=True`
|
|
2. ✓ API endpoint is configured correctly: `/complaints/public/api/load-categories/`
|
|
3. ✓ API query returns 3 domains when filtered by hospital
|
|
4. ✓ jQuery 3.7.1 is loaded in base template
|
|
5. ✓ JavaScript code logic is correct
|
|
6. ✓ AJAX endpoint returns correct JSON structure
|
|
|
|
#### Potential Root Causes
|
|
|
|
**Most Likely**: JavaScript event handler not firing or console error
|
|
|
|
Possible issues:
|
|
1. **Hospital selection event not triggering**: The `$('#id_hospital').on('change', ...)` event handler might not be firing
|
|
2. **Console JavaScript error**: An error in JavaScript preventing execution
|
|
3. **Timing issue**: JavaScript code running before DOM is fully ready
|
|
4. **jQuery selector issue**: `$('#id_hospital')` selector not finding the element
|
|
5. **CSRF token issue**: Though GET requests shouldn't need CSRF
|
|
|
|
### Investigation Steps
|
|
|
|
1. **Open browser developer tools**
|
|
- Press F12 or right-click → Inspect
|
|
- Go to Console tab
|
|
- Look for any JavaScript errors (red text)
|
|
|
|
2. **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
|
|
|
|
3. **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
|
|
|
|
4. **Check console for errors**
|
|
- Common errors:
|
|
- `$ is not defined` - jQuery not loaded
|
|
- `Uncaught ReferenceError` - variable not defined
|
|
- `Failed to load resource` - network error
|
|
|
|
---
|
|
|
|
## 7. Solution Recommendations
|
|
|
|
### Immediate Fix
|
|
|
|
Add a `$(document).ready()` wrapper to ensure JavaScript runs after DOM loads:
|
|
|
|
```javascript
|
|
$(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
|
|
|
|
1. **Add error handling to AJAX calls**:
|
|
```javascript
|
|
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.'
|
|
});
|
|
}
|
|
```
|
|
|
|
2. **Add success message when domains load**:
|
|
```javascript
|
|
success: function(response) {
|
|
console.log('Loaded categories:', response.categories.length);
|
|
allCategories = response.categories;
|
|
// ... rest of code
|
|
}
|
|
```
|
|
|
|
3. **Add loading indicator**:
|
|
```javascript
|
|
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
|
|
```bash
|
|
python manage.py load_shct_taxonomy
|
|
```
|
|
|
|
### Examine Taxonomy Structure
|
|
```bash
|
|
python examine_taxonomy.py
|
|
```
|
|
|
|
### Diagnose Domain Dropdown Issue
|
|
```bash
|
|
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 structure
|
|
- `complaints_complaint` - Stores complaints with 4-level taxonomy
|
|
- `organizations_hospital_complaintcategories` - Hospital-category mapping
|
|
|
|
### Key Files
|
|
- `apps/complaints/models.py` - Database models
|
|
- `apps/complaints/ui_views.py` - UI views and API endpoints
|
|
- `templates/complaints/public_complaint_form.html` - Public form template
|
|
- `apps/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 |