579 lines
16 KiB
Python
579 lines
16 KiB
Python
"""
|
|
Admin configuration for EMR app.
|
|
"""
|
|
|
|
from django.contrib import admin
|
|
from django.utils.html import format_html
|
|
from django.urls import reverse
|
|
from django.utils.safestring import mark_safe
|
|
from .models import (
|
|
Encounter, VitalSigns, ProblemList, CarePlan,
|
|
ClinicalNote, NoteTemplate
|
|
)
|
|
|
|
|
|
@admin.register(Encounter)
|
|
class EncounterAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for Encounter model.
|
|
"""
|
|
list_display = [
|
|
'encounter_id', 'patient_name', 'provider_name', 'encounter_type',
|
|
'status', 'start_datetime', 'duration_display', 'documentation_complete'
|
|
]
|
|
list_filter = [
|
|
'tenant', 'encounter_type', 'encounter_class', 'status',
|
|
'priority', 'documentation_complete', 'signed_off'
|
|
]
|
|
search_fields = [
|
|
'patient__first_name', 'patient__last_name', 'patient__mrn',
|
|
'provider__first_name', 'provider__last_name',
|
|
'chief_complaint', 'reason_for_visit'
|
|
]
|
|
readonly_fields = ['encounter_id', 'created_at', 'updated_at', 'duration_display']
|
|
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': (
|
|
'encounter_id', 'tenant', 'patient', 'provider',
|
|
'encounter_type', 'encounter_class'
|
|
)
|
|
}),
|
|
('Timing', {
|
|
'fields': (
|
|
'start_datetime', 'end_datetime', 'duration_display'
|
|
)
|
|
}),
|
|
('Status', {
|
|
'fields': (
|
|
'status', 'priority', 'acuity_level'
|
|
)
|
|
}),
|
|
('Location', {
|
|
'fields': (
|
|
'location', 'room_number'
|
|
)
|
|
}),
|
|
('Related Records', {
|
|
'fields': (
|
|
'appointment', 'admission'
|
|
)
|
|
}),
|
|
('Clinical Information', {
|
|
'fields': (
|
|
'chief_complaint', 'reason_for_visit'
|
|
)
|
|
}),
|
|
('Documentation', {
|
|
'fields': (
|
|
'documentation_complete', 'signed_off', 'signed_by', 'signed_datetime'
|
|
)
|
|
}),
|
|
('Billing', {
|
|
'fields': (
|
|
'billable', 'billing_codes'
|
|
)
|
|
}),
|
|
('Quality', {
|
|
'fields': (
|
|
'quality_measures',
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at', 'created_by'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def patient_name(self, obj):
|
|
return obj.patient.get_full_name()
|
|
patient_name.short_description = 'Patient'
|
|
|
|
def provider_name(self, obj):
|
|
return obj.provider.get_full_name()
|
|
provider_name.short_description = 'Provider'
|
|
|
|
def duration_display(self, obj):
|
|
duration = obj.duration
|
|
if duration:
|
|
hours = duration.total_seconds() // 3600
|
|
minutes = (duration.total_seconds() % 3600) // 60
|
|
return f"{int(hours)}h {int(minutes)}m"
|
|
return "Ongoing" if obj.is_active else "Not set"
|
|
duration_display.short_description = 'Duration'
|
|
|
|
|
|
@admin.register(VitalSigns)
|
|
class VitalSignsAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for VitalSigns model.
|
|
"""
|
|
list_display = [
|
|
'measurement_id', 'patient_name', 'measured_datetime',
|
|
'temperature', 'blood_pressure_display', 'heart_rate',
|
|
'respiratory_rate', 'oxygen_saturation', 'measured_by_name'
|
|
]
|
|
list_filter = [
|
|
'measured_datetime', 'temperature_method', 'bp_position',
|
|
'oxygen_delivery', 'device_calibrated'
|
|
]
|
|
search_fields = [
|
|
'patient__first_name', 'patient__last_name', 'patient__mrn',
|
|
'measured_by__first_name', 'measured_by__last_name'
|
|
]
|
|
readonly_fields = ['measurement_id', 'bmi', 'created_at', 'updated_at']
|
|
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': (
|
|
'measurement_id', 'encounter', 'patient', 'measured_datetime'
|
|
)
|
|
}),
|
|
('Temperature', {
|
|
'fields': (
|
|
'temperature', 'temperature_method'
|
|
)
|
|
}),
|
|
('Blood Pressure', {
|
|
'fields': (
|
|
'systolic_bp', 'diastolic_bp', 'bp_position', 'bp_cuff_size'
|
|
)
|
|
}),
|
|
('Heart Rate', {
|
|
'fields': (
|
|
'heart_rate', 'heart_rhythm'
|
|
)
|
|
}),
|
|
('Respiratory', {
|
|
'fields': (
|
|
'respiratory_rate', 'oxygen_saturation', 'oxygen_delivery', 'oxygen_flow_rate'
|
|
)
|
|
}),
|
|
('Pain Assessment', {
|
|
'fields': (
|
|
'pain_scale', 'pain_location', 'pain_quality'
|
|
)
|
|
}),
|
|
('Anthropometric', {
|
|
'fields': (
|
|
'weight', 'height', 'bmi', 'head_circumference'
|
|
)
|
|
}),
|
|
('Device Information', {
|
|
'fields': (
|
|
'device_used', 'device_calibrated'
|
|
)
|
|
}),
|
|
('Staff', {
|
|
'fields': (
|
|
'measured_by', 'verified_by'
|
|
)
|
|
}),
|
|
('Quality', {
|
|
'fields': (
|
|
'critical_values', 'alerts_generated', 'notes'
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def patient_name(self, obj):
|
|
return obj.patient.get_full_name()
|
|
patient_name.short_description = 'Patient'
|
|
|
|
def blood_pressure_display(self, obj):
|
|
return obj.blood_pressure or "Not recorded"
|
|
blood_pressure_display.short_description = 'Blood Pressure'
|
|
|
|
def measured_by_name(self, obj):
|
|
return obj.measured_by.get_full_name()
|
|
measured_by_name.short_description = 'Measured By'
|
|
|
|
|
|
@admin.register(ProblemList)
|
|
class ProblemListAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for ProblemList model.
|
|
"""
|
|
list_display = [
|
|
'problem_id', 'patient_name', 'problem_name', 'problem_type',
|
|
'status', 'severity', 'priority', 'onset_date', 'diagnosing_provider_name'
|
|
]
|
|
list_filter = [
|
|
'tenant', 'problem_type', 'status', 'severity', 'priority',
|
|
'coding_system', 'verified'
|
|
]
|
|
search_fields = [
|
|
'patient__first_name', 'patient__last_name', 'patient__mrn',
|
|
'problem_name', 'problem_code',
|
|
'diagnosing_provider__first_name', 'diagnosing_provider__last_name'
|
|
]
|
|
readonly_fields = ['problem_id', 'created_at', 'updated_at']
|
|
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': (
|
|
'problem_id', 'tenant', 'patient', 'problem_name'
|
|
)
|
|
}),
|
|
('Coding', {
|
|
'fields': (
|
|
'problem_code', 'coding_system'
|
|
)
|
|
}),
|
|
('Classification', {
|
|
'fields': (
|
|
'problem_type', 'severity', 'priority'
|
|
)
|
|
}),
|
|
('Clinical Information', {
|
|
'fields': (
|
|
'onset_date', 'onset_description', 'body_site', 'laterality'
|
|
)
|
|
}),
|
|
('Status', {
|
|
'fields': (
|
|
'status', 'resolution_date', 'resolution_notes'
|
|
)
|
|
}),
|
|
('Providers', {
|
|
'fields': (
|
|
'diagnosing_provider', 'managing_provider'
|
|
)
|
|
}),
|
|
('Related Information', {
|
|
'fields': (
|
|
'related_encounter',
|
|
)
|
|
}),
|
|
('Documentation', {
|
|
'fields': (
|
|
'clinical_notes', 'patient_concerns'
|
|
)
|
|
}),
|
|
('Goals and Outcomes', {
|
|
'fields': (
|
|
'treatment_goals', 'outcome_measures'
|
|
)
|
|
}),
|
|
('Verification', {
|
|
'fields': (
|
|
'verified', 'verified_by', 'verified_date'
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at', 'created_by'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def patient_name(self, obj):
|
|
return obj.patient.get_full_name()
|
|
patient_name.short_description = 'Patient'
|
|
|
|
def diagnosing_provider_name(self, obj):
|
|
return obj.diagnosing_provider.get_full_name()
|
|
diagnosing_provider_name.short_description = 'Diagnosing Provider'
|
|
|
|
|
|
@admin.register(CarePlan)
|
|
class CarePlanAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for CarePlan model.
|
|
"""
|
|
list_display = [
|
|
'care_plan_id', 'patient_name', 'title', 'plan_type',
|
|
'status', 'priority', 'start_date', 'completion_percentage',
|
|
'primary_provider_name'
|
|
]
|
|
list_filter = [
|
|
'tenant', 'plan_type', 'category', 'status', 'priority', 'approved'
|
|
]
|
|
search_fields = [
|
|
'patient__first_name', 'patient__last_name', 'patient__mrn',
|
|
'title', 'description',
|
|
'primary_provider__first_name', 'primary_provider__last_name'
|
|
]
|
|
readonly_fields = ['care_plan_id', 'created_at', 'updated_at']
|
|
filter_horizontal = ['care_team', 'related_problems']
|
|
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': (
|
|
'care_plan_id', 'tenant', 'patient', 'title', 'description'
|
|
)
|
|
}),
|
|
('Plan Type', {
|
|
'fields': (
|
|
'plan_type', 'category', 'priority'
|
|
)
|
|
}),
|
|
('Timing', {
|
|
'fields': (
|
|
'start_date', 'end_date', 'target_completion_date'
|
|
)
|
|
}),
|
|
('Status', {
|
|
'fields': (
|
|
'status', 'completion_percentage'
|
|
)
|
|
}),
|
|
('Providers', {
|
|
'fields': (
|
|
'primary_provider', 'care_team'
|
|
)
|
|
}),
|
|
('Related Problems', {
|
|
'fields': (
|
|
'related_problems',
|
|
)
|
|
}),
|
|
('Goals and Objectives', {
|
|
'fields': (
|
|
'goals', 'objectives'
|
|
)
|
|
}),
|
|
('Interventions', {
|
|
'fields': (
|
|
'interventions', 'activities'
|
|
)
|
|
}),
|
|
('Monitoring', {
|
|
'fields': (
|
|
'monitoring_parameters', 'evaluation_criteria'
|
|
)
|
|
}),
|
|
('Patient Involvement', {
|
|
'fields': (
|
|
'patient_goals', 'patient_preferences', 'patient_barriers'
|
|
)
|
|
}),
|
|
('Resources', {
|
|
'fields': (
|
|
'resources_needed', 'support_systems'
|
|
)
|
|
}),
|
|
('Progress', {
|
|
'fields': (
|
|
'progress_notes', 'last_reviewed', 'next_review_date'
|
|
)
|
|
}),
|
|
('Outcomes', {
|
|
'fields': (
|
|
'outcomes_achieved',
|
|
)
|
|
}),
|
|
('Approval', {
|
|
'fields': (
|
|
'approved', 'approved_by', 'approved_date'
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at', 'created_by'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def patient_name(self, obj):
|
|
return obj.patient.get_full_name()
|
|
patient_name.short_description = 'Patient'
|
|
|
|
def primary_provider_name(self, obj):
|
|
return obj.primary_provider.get_full_name()
|
|
primary_provider_name.short_description = 'Primary Provider'
|
|
|
|
|
|
@admin.register(ClinicalNote)
|
|
class ClinicalNoteAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for ClinicalNote model.
|
|
"""
|
|
list_display = [
|
|
'note_id', 'patient_name', 'title', 'note_type',
|
|
'status', 'author_name', 'note_datetime', 'word_count_display',
|
|
'electronically_signed'
|
|
]
|
|
list_filter = [
|
|
'note_type', 'status', 'electronically_signed', 'confidential',
|
|
'restricted_access', 'note_datetime'
|
|
]
|
|
search_fields = [
|
|
'patient__first_name', 'patient__last_name', 'patient__mrn',
|
|
'title', 'content',
|
|
'author__first_name', 'author__last_name'
|
|
]
|
|
readonly_fields = ['note_id', 'word_count_display', 'created_at', 'updated_at']
|
|
filter_horizontal = ['co_signers', 'related_problems', 'related_care_plans']
|
|
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': (
|
|
'note_id', 'encounter', 'patient', 'note_type', 'title'
|
|
)
|
|
}),
|
|
('Content', {
|
|
'fields': (
|
|
'content', 'word_count_display'
|
|
)
|
|
}),
|
|
('Template', {
|
|
'fields': (
|
|
'template', 'structured_data'
|
|
)
|
|
}),
|
|
('Provider Information', {
|
|
'fields': (
|
|
'author', 'co_signers'
|
|
)
|
|
}),
|
|
('Status', {
|
|
'fields': (
|
|
'status', 'note_datetime'
|
|
)
|
|
}),
|
|
('Signatures', {
|
|
'fields': (
|
|
'electronically_signed', 'signed_datetime', 'signature_method'
|
|
)
|
|
}),
|
|
('Amendment', {
|
|
'fields': (
|
|
'amended_note', 'amendment_reason'
|
|
)
|
|
}),
|
|
('Quality', {
|
|
'fields': (
|
|
'quality_score', 'compliance_flags'
|
|
)
|
|
}),
|
|
('Privacy', {
|
|
'fields': (
|
|
'confidential', 'restricted_access', 'access_restrictions'
|
|
)
|
|
}),
|
|
('Related Information', {
|
|
'fields': (
|
|
'related_problems', 'related_care_plans'
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def patient_name(self, obj):
|
|
return obj.patient.get_full_name()
|
|
patient_name.short_description = 'Patient'
|
|
|
|
def author_name(self, obj):
|
|
return obj.author.get_full_name()
|
|
author_name.short_description = 'Author'
|
|
|
|
def word_count_display(self, obj):
|
|
return f"{obj.word_count} words"
|
|
word_count_display.short_description = 'Word Count'
|
|
|
|
|
|
@admin.register(NoteTemplate)
|
|
class NoteTemplateAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for NoteTemplate model.
|
|
"""
|
|
list_display = [
|
|
'template_id', 'name', 'note_type', 'specialty',
|
|
'is_active', 'is_default', 'version', 'usage_count'
|
|
]
|
|
list_filter = [
|
|
'tenant', 'note_type', 'specialty', 'is_active', 'is_default'
|
|
]
|
|
search_fields = [
|
|
'name', 'description', 'template_content'
|
|
]
|
|
readonly_fields = ['template_id', 'usage_count', 'created_at', 'updated_at']
|
|
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': (
|
|
'template_id', 'tenant', 'name', 'description'
|
|
)
|
|
}),
|
|
('Template Type', {
|
|
'fields': (
|
|
'note_type', 'specialty'
|
|
)
|
|
}),
|
|
('Content', {
|
|
'fields': (
|
|
'template_content', 'structured_fields'
|
|
)
|
|
}),
|
|
('Usage', {
|
|
'fields': (
|
|
'is_active', 'is_default', 'usage_count'
|
|
)
|
|
}),
|
|
('Version Control', {
|
|
'fields': (
|
|
'version', 'previous_version'
|
|
)
|
|
}),
|
|
('Quality', {
|
|
'fields': (
|
|
'quality_indicators', 'compliance_requirements'
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at', 'created_by'
|
|
)
|
|
}),
|
|
)
|
|
|
|
|
|
# Inline admin classes for related models
|
|
class VitalSignsInline(admin.TabularInline):
|
|
"""
|
|
Inline admin for VitalSigns.
|
|
"""
|
|
model = VitalSigns
|
|
extra = 0
|
|
readonly_fields = ['measurement_id', 'bmi']
|
|
fields = [
|
|
'measured_datetime', 'temperature', 'systolic_bp', 'diastolic_bp',
|
|
'heart_rate', 'respiratory_rate', 'oxygen_saturation', 'measured_by'
|
|
]
|
|
|
|
|
|
class ClinicalNoteInline(admin.TabularInline):
|
|
"""
|
|
Inline admin for ClinicalNote.
|
|
"""
|
|
model = ClinicalNote
|
|
extra = 0
|
|
readonly_fields = ['note_id']
|
|
fields = ['note_type', 'title', 'status', 'author', 'note_datetime']
|
|
|
|
|
|
class ProblemListInline(admin.TabularInline):
|
|
"""
|
|
Inline admin for ProblemList.
|
|
"""
|
|
model = ProblemList
|
|
extra = 0
|
|
readonly_fields = ['problem_id']
|
|
fields = ['problem_name', 'problem_type', 'status', 'severity', 'diagnosing_provider']
|
|
|
|
|
|
# Add inlines to Encounter admin
|
|
EncounterAdmin.inlines = [VitalSignsInline, ClinicalNoteInline, ProblemListInline]
|
|
|