2025-08-12 13:33:25 +03:00

382 lines
17 KiB
Python

"""
Forms for EMR app.
"""
from django import forms
from django.utils import timezone
from django.core.exceptions import ValidationError
from django.contrib.auth import get_user_model
from .models import (
Encounter, VitalSigns, ProblemList, CarePlan,
ClinicalNote, NoteTemplate
)
User = get_user_model()
class EncounterForm(forms.ModelForm):
"""
Form for creating and updating encounters.
"""
class Meta:
model = Encounter
fields = [
'patient', 'provider', 'encounter_type', 'encounter_class',
'start_datetime', 'end_datetime', 'status', 'location', 'room_number',
'appointment', 'admission', 'chief_complaint', 'reason_for_visit',
'priority', 'acuity_level', 'billable'
]
widgets = {
'start_datetime': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
'end_datetime': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
'chief_complaint': forms.Textarea(attrs={'rows': 3}),
'reason_for_visit': forms.Textarea(attrs={'rows': 3}),
}
def __init__(self, *args, **kwargs):
self.tenant = kwargs.pop('tenant', None)
super().__init__(*args, **kwargs)
# Filter related objects by tenant
if self.tenant:
self.fields['patient'].queryset = self.fields['patient'].queryset.filter(tenant=self.tenant)
self.fields['provider'].queryset = User.objects.filter(tenant=self.tenant)
self.fields['appointment'].queryset = self.fields['appointment'].queryset.filter(tenant=self.tenant)
self.fields['admission'].queryset = self.fields['admission'].queryset.filter(tenant=self.tenant)
def clean(self):
cleaned_data = super().clean()
start_datetime = cleaned_data.get('start_datetime')
end_datetime = cleaned_data.get('end_datetime')
status = cleaned_data.get('status')
# Validate end_datetime is after start_datetime
if start_datetime and end_datetime and end_datetime < start_datetime:
self.add_error('end_datetime', 'End time must be after start time')
# Validate status and end_datetime consistency
if status == 'FINISHED' and not end_datetime:
cleaned_data['end_datetime'] = timezone.now()
return cleaned_data
class VitalSignsForm(forms.ModelForm):
"""
Form for recording vital signs.
"""
class Meta:
model = VitalSigns
fields = [
'encounter', 'patient', 'measured_datetime',
'temperature', 'temperature_method',
'systolic_bp', 'diastolic_bp', 'bp_position', 'bp_cuff_size',
'heart_rate', 'heart_rhythm',
'respiratory_rate',
'oxygen_saturation', 'oxygen_delivery', 'oxygen_flow_rate',
'pain_scale', 'pain_location', 'pain_quality',
'weight', 'height',
'head_circumference',
'device_used', 'device_calibrated',
'measured_by', 'notes'
]
widgets = {
'measured_datetime': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
'notes': forms.Textarea(attrs={'rows': 3}),
'pain_location': forms.TextInput(),
'pain_quality': forms.TextInput(),
}
def __init__(self, *args, **kwargs):
self.tenant = kwargs.pop('tenant', None)
super().__init__(*args, **kwargs)
# Filter related objects by tenant
if self.tenant:
self.fields['encounter'].queryset = Encounter.objects.filter(tenant=self.tenant)
self.fields['patient'].queryset = self.fields['patient'].queryset.filter(tenant=self.tenant)
self.fields['measured_by'].queryset = User.objects.filter(tenant=self.tenant)
def clean(self):
cleaned_data = super().clean()
# Validate blood pressure values
systolic_bp = cleaned_data.get('systolic_bp')
diastolic_bp = cleaned_data.get('diastolic_bp')
if (systolic_bp and not diastolic_bp) or (diastolic_bp and not systolic_bp):
self.add_error('systolic_bp', 'Both systolic and diastolic values must be provided')
if systolic_bp and diastolic_bp and systolic_bp <= diastolic_bp:
self.add_error('systolic_bp', 'Systolic pressure must be greater than diastolic pressure')
# Validate oxygen flow rate is provided when delivery method is not room air
oxygen_delivery = cleaned_data.get('oxygen_delivery')
oxygen_flow_rate = cleaned_data.get('oxygen_flow_rate')
if oxygen_delivery and oxygen_delivery != 'ROOM_AIR' and not oxygen_flow_rate:
self.add_error('oxygen_flow_rate', 'Flow rate is required for this oxygen delivery method')
return cleaned_data
class ProblemListForm(forms.ModelForm):
"""
Form for creating and updating problems.
"""
class Meta:
model = ProblemList
fields = [
'patient', 'problem_name', 'problem_code', 'coding_system',
'problem_type', 'onset_date', 'onset_description',
'severity', 'priority', 'status',
'resolution_date', 'resolution_notes',
'diagnosing_provider', 'managing_provider',
'related_encounter', 'body_site', 'laterality',
'clinical_notes', 'patient_concerns',
'treatment_goals', 'outcome_measures',
'verified', 'verified_by'
]
widgets = {
'onset_date': forms.DateInput(attrs={'type': 'date'}),
'resolution_date': forms.DateInput(attrs={'type': 'date'}),
'clinical_notes': forms.Textarea(attrs={'rows': 3}),
'patient_concerns': forms.Textarea(attrs={'rows': 3}),
'resolution_notes': forms.Textarea(attrs={'rows': 3}),
'treatment_goals': forms.Textarea(attrs={'rows': 3}),
'outcome_measures': forms.Textarea(attrs={'rows': 3}),
}
def __init__(self, *args, **kwargs):
self.tenant = kwargs.pop('tenant', None)
super().__init__(*args, **kwargs)
# Filter related objects by tenant
if self.tenant:
self.fields['patient'].queryset = self.fields['patient'].queryset.filter(tenant=self.tenant)
self.fields['diagnosing_provider'].queryset = User.objects.filter(tenant=self.tenant)
self.fields['managing_provider'].queryset = User.objects.filter(tenant=self.tenant)
self.fields['verified_by'].queryset = User.objects.filter(tenant=self.tenant)
self.fields['related_encounter'].queryset = Encounter.objects.filter(tenant=self.tenant)
def clean(self):
cleaned_data = super().clean()
status = cleaned_data.get('status')
resolution_date = cleaned_data.get('resolution_date')
# Validate resolution date is provided for resolved problems
if status in ['RESOLVED', 'REMISSION'] and not resolution_date:
self.add_error('resolution_date', 'Resolution date is required for resolved problems')
# Validate resolution date is not in the future
if resolution_date and resolution_date > timezone.now().date():
self.add_error('resolution_date', 'Resolution date cannot be in the future')
# Validate onset date is not in the future
onset_date = cleaned_data.get('onset_date')
if onset_date and onset_date > timezone.now().date():
self.add_error('onset_date', 'Onset date cannot be in the future')
# Validate resolution date is after onset date
if onset_date and resolution_date and resolution_date < onset_date:
self.add_error('resolution_date', 'Resolution date must be after onset date')
return cleaned_data
class CarePlanForm(forms.ModelForm):
"""
Form for creating and updating care plans.
"""
class Meta:
model = CarePlan
fields = [
'patient', 'title', 'description', 'plan_type', 'category',
'start_date', 'end_date', 'target_completion_date',
'status', 'priority', 'primary_provider', 'care_team',
'related_problems', 'goals', 'objectives',
'interventions', 'activities',
'monitoring_parameters', 'evaluation_criteria',
'patient_goals', 'patient_preferences', 'patient_barriers',
'resources_needed', 'support_systems',
'progress_notes', 'last_reviewed', 'next_review_date',
'outcomes_achieved', 'completion_percentage',
'approved', 'approved_by'
]
widgets = {
'start_date': forms.DateInput(attrs={'type': 'date'}),
'end_date': forms.DateInput(attrs={'type': 'date'}),
'target_completion_date': forms.DateInput(attrs={'type': 'date'}),
'last_reviewed': forms.DateInput(attrs={'type': 'date'}),
'next_review_date': forms.DateInput(attrs={'type': 'date'}),
'description': forms.Textarea(attrs={'rows': 3}),
'patient_goals': forms.Textarea(attrs={'rows': 3}),
'patient_preferences': forms.Textarea(attrs={'rows': 3}),
'patient_barriers': forms.Textarea(attrs={'rows': 3}),
'progress_notes': forms.Textarea(attrs={'rows': 3}),
'goals': forms.Textarea(attrs={'rows': 3}),
'objectives': forms.Textarea(attrs={'rows': 3}),
'interventions': forms.Textarea(attrs={'rows': 3}),
'activities': forms.Textarea(attrs={'rows': 3}),
'monitoring_parameters': forms.Textarea(attrs={'rows': 3}),
'evaluation_criteria': forms.Textarea(attrs={'rows': 3}),
'resources_needed': forms.Textarea(attrs={'rows': 3}),
'support_systems': forms.Textarea(attrs={'rows': 3}),
'outcomes_achieved': forms.Textarea(attrs={'rows': 3}),
}
def __init__(self, *args, **kwargs):
self.tenant = kwargs.pop('tenant', None)
super().__init__(*args, **kwargs)
# Filter related objects by tenant
if self.tenant:
self.fields['patient'].queryset = self.fields['patient'].queryset.filter(tenant=self.tenant)
self.fields['primary_provider'].queryset = User.objects.filter(tenant=self.tenant)
self.fields['care_team'].queryset = User.objects.filter(tenant=self.tenant)
self.fields['approved_by'].queryset = User.objects.filter(tenant=self.tenant)
self.fields['related_problems'].queryset = ProblemList.objects.filter(tenant=self.tenant)
def clean(self):
cleaned_data = super().clean()
start_date = cleaned_data.get('start_date')
end_date = cleaned_data.get('end_date')
target_completion_date = cleaned_data.get('target_completion_date')
# Validate end_date is after start_date
if start_date and end_date and end_date < start_date:
self.add_error('end_date', 'End date must be after start date')
# Validate target_completion_date is after start_date
if start_date and target_completion_date and target_completion_date < start_date:
self.add_error('target_completion_date', 'Target completion date must be after start date')
# Validate completion percentage
completion_percentage = cleaned_data.get('completion_percentage')
status = cleaned_data.get('status')
if status == 'COMPLETED' and completion_percentage < 100:
self.add_error('completion_percentage', 'Completion percentage must be 100% for completed care plans')
if status == 'DRAFT' and completion_percentage > 0:
self.add_error('completion_percentage', 'Completion percentage must be 0% for draft care plans')
return cleaned_data
class ClinicalNoteForm(forms.ModelForm):
"""
Form for creating and updating clinical notes.
"""
class Meta:
model = ClinicalNote
fields = [
'encounter', 'patient', 'note_type', 'title', 'content',
'template', 'structured_data', 'author', 'co_signers',
'status', 'note_datetime',
'electronically_signed', 'signed_datetime', 'signature_method',
'amended_note', 'amendment_reason',
'confidential', 'restricted_access', 'access_restrictions',
'related_problems', 'related_care_plans'
]
widgets = {
'note_datetime': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
'signed_datetime': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
'content': forms.Textarea(attrs={'rows': 10, 'class': 'note-editor'}),
'amendment_reason': forms.Textarea(attrs={'rows': 3}),
'structured_data': forms.Textarea(attrs={'rows': 3}),
'access_restrictions': forms.Textarea(attrs={'rows': 3}),
}
def __init__(self, *args, **kwargs):
self.tenant = kwargs.pop('tenant', None)
super().__init__(*args, **kwargs)
# Filter related objects by tenant
if self.tenant:
self.fields['encounter'].queryset = Encounter.objects.filter(tenant=self.tenant)
self.fields['patient'].queryset = self.fields['patient'].queryset.filter(tenant=self.tenant)
self.fields['author'].queryset = User.objects.filter(tenant=self.tenant)
self.fields['co_signers'].queryset = User.objects.filter(tenant=self.tenant)
self.fields['template'].queryset = NoteTemplate.objects.filter(tenant=self.tenant, is_active=True)
self.fields['amended_note'].queryset = ClinicalNote.objects.filter(
patient__tenant=self.tenant
).exclude(pk=self.instance.pk if self.instance.pk else None)
self.fields['related_problems'].queryset = ProblemList.objects.filter(tenant=self.tenant)
self.fields['related_care_plans'].queryset = CarePlan.objects.filter(tenant=self.tenant)
def clean(self):
cleaned_data = super().clean()
status = cleaned_data.get('status')
electronically_signed = cleaned_data.get('electronically_signed')
signed_datetime = cleaned_data.get('signed_datetime')
# Validate signature information
if status == 'SIGNED' and not electronically_signed:
self.add_error('electronically_signed', 'Note must be electronically signed if status is Signed')
if electronically_signed and not signed_datetime:
cleaned_data['signed_datetime'] = timezone.now()
# Validate amendment information
amended_note = cleaned_data.get('amended_note')
amendment_reason = cleaned_data.get('amendment_reason')
if amended_note and not amendment_reason:
self.add_error('amendment_reason', 'Amendment reason is required when amending a note')
return cleaned_data
class NoteTemplateForm(forms.ModelForm):
"""
Form for creating and updating note templates.
"""
class Meta:
model = NoteTemplate
fields = [
'name', 'description', 'note_type', 'specialty',
'template_content', 'structured_fields',
'is_active', 'is_default', 'version',
'previous_version', 'quality_indicators', 'compliance_requirements'
]
widgets = {
'description': forms.Textarea(attrs={'rows': 3}),
'template_content': forms.Textarea(attrs={'rows': 10, 'class': 'template-editor'}),
'structured_fields': forms.Textarea(attrs={'rows': 5}),
'quality_indicators': forms.Textarea(attrs={'rows': 3}),
'compliance_requirements': forms.Textarea(attrs={'rows': 3}),
}
def __init__(self, *args, **kwargs):
self.tenant = kwargs.pop('tenant', None)
super().__init__(*args, **kwargs)
# Filter related objects by tenant
if self.tenant:
self.fields['previous_version'].queryset = NoteTemplate.objects.filter(
tenant=self.tenant
).exclude(pk=self.instance.pk if self.instance.pk else None)
def clean(self):
cleaned_data = super().clean()
is_default = cleaned_data.get('is_default')
note_type = cleaned_data.get('note_type')
specialty = cleaned_data.get('specialty')
# Validate template content
template_content = cleaned_data.get('template_content')
if not template_content or len(template_content.strip()) < 10:
self.add_error('template_content', 'Template content is too short')
# Validate version format
version = cleaned_data.get('version')
if version:
import re
if not re.match(r'^\d+\.\d+$', version):
self.add_error('version', 'Version must be in format X.Y (e.g., 1.0)')
return cleaned_data