442 lines
18 KiB
Python
442 lines
18 KiB
Python
"""
|
|
Medical forms for the Tenhal Multidisciplinary Healthcare Platform.
|
|
|
|
This module contains forms for medical consultations and follow-ups.
|
|
Based on MD-F-1 and MD-F-2 forms.
|
|
"""
|
|
|
|
from django import forms
|
|
from django.forms import inlineformset_factory
|
|
from django.utils.translation import gettext_lazy as _
|
|
from crispy_forms.helper import FormHelper
|
|
from crispy_forms.layout import Layout, Fieldset, Row, Column, Submit, HTML
|
|
|
|
from .models import MedicalConsultation, MedicationPlan, MedicalFollowUp, ConsultationResponse, ConsultationFeedback
|
|
|
|
|
|
class MedicalConsultationForm(forms.ModelForm):
|
|
"""
|
|
Form for medical consultation (MD-F-1).
|
|
Comprehensive initial consultation form.
|
|
"""
|
|
|
|
class Meta:
|
|
model = MedicalConsultation
|
|
fields = [
|
|
'patient', 'appointment', 'consultation_date', 'provider',
|
|
'chief_complaint', 'present_illness_history', 'past_medical_history',
|
|
'vaccination_status', 'family_history',
|
|
'social_history', 'pregnancy_history', 'neonatal_history',
|
|
'developmental_motor_milestones', 'developmental_language_milestones',
|
|
'developmental_social_milestones', 'developmental_cognitive_milestones',
|
|
'behavioral_symptoms', 'physical_exam', 'clinical_summary', 'recommendations',
|
|
]
|
|
widgets = {
|
|
'consultation_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
|
'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'}),
|
|
'chief_complaint': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
|
|
'present_illness_history': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
|
|
'past_medical_history': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
|
|
'vaccination_status': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
|
|
'family_history': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
|
|
'social_history': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
|
|
'pregnancy_history': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
|
|
'neonatal_history': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
|
|
'developmental_motor_milestones': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
|
|
'developmental_language_milestones': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
|
|
'developmental_social_milestones': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
|
|
'developmental_cognitive_milestones': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
|
|
'behavioral_symptoms': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
|
|
'physical_exam': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
|
|
'clinical_summary': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
|
|
'recommendations': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
|
|
'patient': forms.HiddenInput(),
|
|
'appointment': forms.HiddenInput(),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'post'
|
|
self.helper.layout = Layout(
|
|
'patient',
|
|
'appointment',
|
|
Fieldset(
|
|
_('Consultation Information'),
|
|
Row(
|
|
Column('consultation_date', css_class='form-group col-md-6 mb-0'),
|
|
Column('provider', css_class='form-group col-md-6 mb-0'),
|
|
css_class='form-row'
|
|
),
|
|
),
|
|
Fieldset(
|
|
_('Chief Complaint & Present History'),
|
|
'chief_complaint',
|
|
'present_illness_history',
|
|
),
|
|
Fieldset(
|
|
_('Past History'),
|
|
'past_medical_history',
|
|
'vaccination_status',
|
|
),
|
|
Fieldset(
|
|
_('Family & Social History'),
|
|
'family_history',
|
|
'social_history',
|
|
),
|
|
Fieldset(
|
|
_('Pregnancy & Neonatal History'),
|
|
'pregnancy_history',
|
|
'neonatal_history',
|
|
),
|
|
Fieldset(
|
|
_('Developmental History'),
|
|
HTML('<p class="text-muted">{}</p>'.format(_('Motor, Language, Social, and Cognitive Milestones'))),
|
|
'developmental_motor_milestones',
|
|
'developmental_language_milestones',
|
|
'developmental_social_milestones',
|
|
'developmental_cognitive_milestones',
|
|
),
|
|
Fieldset(
|
|
_('Behavioral Symptoms'),
|
|
HTML('<p class="text-muted">{}</p>'.format(_('Checklist of behavioral observations'))),
|
|
'behavioral_symptoms',
|
|
),
|
|
Fieldset(
|
|
_('Physical Examination'),
|
|
'physical_exam',
|
|
),
|
|
Fieldset(
|
|
_('Summary & Recommendations'),
|
|
'clinical_summary',
|
|
'recommendations',
|
|
),
|
|
HTML('<hr><h5>{}</h5>'.format(_('Medications'))),
|
|
HTML('<p class="text-muted">{}</p>'.format(_('Add current medications below'))),
|
|
Submit('submit', _('Save Consultation'), css_class='btn btn-primary')
|
|
)
|
|
|
|
|
|
class MedicationPlanForm(forms.ModelForm):
|
|
"""
|
|
Form for medication plans.
|
|
"""
|
|
|
|
class Meta:
|
|
model = MedicationPlan
|
|
fields = [
|
|
'drug_name', 'dose', 'frequency', 'compliance',
|
|
'gains', 'side_effects', 'target_behavior', 'improved',
|
|
]
|
|
widgets = {
|
|
'drug_name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'dose': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'frequency': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'compliance': forms.Select(attrs={'class': 'form-control'}),
|
|
'gains': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
|
|
'side_effects': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
|
|
'target_behavior': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'improved': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
}
|
|
|
|
|
|
# Inline formset for medications
|
|
MedicationPlanFormSet = inlineformset_factory(
|
|
MedicalConsultation,
|
|
MedicationPlan,
|
|
form=MedicationPlanForm,
|
|
extra=3,
|
|
can_delete=True,
|
|
min_num=0,
|
|
)
|
|
|
|
|
|
class MedicalFollowUpForm(forms.ModelForm):
|
|
"""
|
|
Form for medical follow-up (MD-F-2).
|
|
Links to previous consultation.
|
|
"""
|
|
|
|
class Meta:
|
|
model = MedicalFollowUp
|
|
fields = [
|
|
'patient', 'appointment', 'followup_date', 'provider',
|
|
'previous_consultation', 'previous_complaints_status',
|
|
'new_complaints', 'nursing_vitals', 'assessment',
|
|
'recommendations', 'family_satisfaction',
|
|
]
|
|
widgets = {
|
|
'patient': forms.HiddenInput(),
|
|
'appointment': forms.HiddenInput(),
|
|
'followup_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
|
'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'}),
|
|
'previous_consultation': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select previous consultation'}),
|
|
'previous_complaints_status': forms.HiddenInput(),
|
|
'new_complaints': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
|
|
'nursing_vitals': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select nursing vitals'}),
|
|
'assessment': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
|
|
'recommendations': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
|
|
'family_satisfaction': forms.RadioSelect(),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# Filter previous_consultation queryset to only show consultations for the same patient
|
|
if self.instance and self.instance.patient_id:
|
|
self.fields['previous_consultation'].queryset = MedicalConsultation.objects.filter(
|
|
patient=self.instance.patient
|
|
).order_by('-consultation_date')
|
|
elif self.initial.get('patient'):
|
|
from core.models import Patient
|
|
try:
|
|
patient = Patient.objects.get(pk=self.initial['patient'])
|
|
self.fields['previous_consultation'].queryset = MedicalConsultation.objects.filter(
|
|
patient=patient
|
|
).order_by('-consultation_date')
|
|
except Patient.DoesNotExist:
|
|
self.fields['previous_consultation'].queryset = MedicalConsultation.objects.none()
|
|
else:
|
|
self.fields['previous_consultation'].queryset = MedicalConsultation.objects.none()
|
|
|
|
# Filter nursing_vitals to same patient if available
|
|
if self.instance and self.instance.patient_id:
|
|
from nursing.models import NursingEncounter
|
|
self.fields['nursing_vitals'].queryset = NursingEncounter.objects.filter(
|
|
patient=self.instance.patient
|
|
).order_by('-encounter_date')
|
|
elif self.initial.get('patient'):
|
|
from core.models import Patient
|
|
from nursing.models import NursingEncounter
|
|
try:
|
|
patient = Patient.objects.get(pk=self.initial['patient'])
|
|
self.fields['nursing_vitals'].queryset = NursingEncounter.objects.filter(
|
|
patient=patient
|
|
).order_by('-encounter_date')
|
|
except Patient.DoesNotExist:
|
|
self.fields['nursing_vitals'].queryset = NursingEncounter.objects.none()
|
|
else:
|
|
from nursing.models import NursingEncounter
|
|
self.fields['nursing_vitals'].queryset = NursingEncounter.objects.none()
|
|
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'post'
|
|
self.helper.layout = Layout(
|
|
'patient',
|
|
'appointment',
|
|
Fieldset(
|
|
_('Follow-up Information'),
|
|
Row(
|
|
Column('followup_date', css_class='form-group col-md-6 mb-0'),
|
|
Column('provider', css_class='form-group col-md-6 mb-0'),
|
|
css_class='form-row'
|
|
),
|
|
'previous_consultation',
|
|
'nursing_vitals',
|
|
),
|
|
Fieldset(
|
|
_('Previous Complaints Status'),
|
|
HTML('<p class="text-muted">{}</p>'.format(_('Status of complaints from previous consultation'))),
|
|
'previous_complaints_status',
|
|
),
|
|
Fieldset(
|
|
_('New Complaints'),
|
|
'new_complaints',
|
|
),
|
|
Fieldset(
|
|
_('Assessment & Plan'),
|
|
'assessment',
|
|
'recommendations',
|
|
),
|
|
Fieldset(
|
|
_('Family Satisfaction'),
|
|
HTML('<p class="text-muted">{}</p>'.format(_('Rate family satisfaction: 0 (Not Satisfied), 50 (Neutral), 100 (Very Satisfied)'))),
|
|
'family_satisfaction',
|
|
),
|
|
HTML('<hr><h5>{}</h5>'.format(_('Current Medications'))),
|
|
HTML('<p class="text-muted">{}</p>'.format(_('Medication snapshot from previous consultation will be displayed here'))),
|
|
Submit('submit', _('Save Follow-up'), css_class='btn btn-primary')
|
|
)
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
previous_consultation = cleaned_data.get('previous_consultation')
|
|
patient = cleaned_data.get('patient')
|
|
|
|
# Validate that previous consultation belongs to same patient
|
|
if previous_consultation and patient:
|
|
if previous_consultation.patient != patient:
|
|
raise forms.ValidationError(
|
|
_('Previous consultation must belong to the same patient.')
|
|
)
|
|
|
|
return cleaned_data
|
|
|
|
|
|
class ConsultationResponseForm(forms.ModelForm):
|
|
"""
|
|
Form for responding to medical consultation from other disciplines.
|
|
"""
|
|
|
|
class Meta:
|
|
model = ConsultationResponse
|
|
fields = [
|
|
'consultation', 'response_type', 'responder', 'response_date',
|
|
'assessment', 'recommendations', 'follow_up_needed', 'notes',
|
|
]
|
|
widgets = {
|
|
'consultation': forms.HiddenInput(),
|
|
'response_type': forms.Select(attrs={'class': 'form-control'}),
|
|
'responder': forms.HiddenInput(),
|
|
'response_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
|
'assessment': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
|
|
'recommendations': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
|
|
'follow_up_needed': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'notes': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'post'
|
|
self.helper.layout = Layout(
|
|
'consultation',
|
|
'responder',
|
|
Fieldset(
|
|
_('Response Information'),
|
|
Row(
|
|
Column('response_type', css_class='form-group col-md-6 mb-0'),
|
|
Column('response_date', css_class='form-group col-md-6 mb-0'),
|
|
css_class='form-row'
|
|
),
|
|
),
|
|
Fieldset(
|
|
_('Assessment & Recommendations'),
|
|
'assessment',
|
|
'recommendations',
|
|
),
|
|
Fieldset(
|
|
_('Follow-up'),
|
|
Row(
|
|
Column('follow_up_needed', css_class='form-group col-md-12 mb-0'),
|
|
css_class='form-row'
|
|
),
|
|
'notes',
|
|
),
|
|
Submit('submit', _('Submit Response'), css_class='btn btn-primary')
|
|
)
|
|
|
|
|
|
class ConsultationFeedbackForm(forms.ModelForm):
|
|
"""
|
|
Form for providing feedback on medical consultation.
|
|
"""
|
|
|
|
class Meta:
|
|
model = ConsultationFeedback
|
|
fields = [
|
|
'consultation', 'feedback_type', 'submitted_by', 'submitted_by_name',
|
|
'feedback_date', 'satisfaction_rating', 'communication_rating',
|
|
'care_quality_rating', 'comments', 'concerns', 'suggestions',
|
|
]
|
|
widgets = {
|
|
'consultation': forms.HiddenInput(),
|
|
'submitted_by': forms.HiddenInput(),
|
|
'feedback_type': forms.Select(attrs={'class': 'form-control'}),
|
|
'submitted_by_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Name (for family/caregiver)')}),
|
|
'feedback_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
|
'satisfaction_rating': forms.RadioSelect(),
|
|
'communication_rating': forms.RadioSelect(),
|
|
'care_quality_rating': forms.RadioSelect(),
|
|
'comments': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
|
|
'concerns': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
|
|
'suggestions': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'post'
|
|
self.helper.layout = Layout(
|
|
'consultation',
|
|
'submitted_by',
|
|
Fieldset(
|
|
_('Feedback Information'),
|
|
Row(
|
|
Column('feedback_type', css_class='form-group col-md-6 mb-0'),
|
|
Column('feedback_date', css_class='form-group col-md-6 mb-0'),
|
|
css_class='form-row'
|
|
),
|
|
'submitted_by_name',
|
|
),
|
|
Fieldset(
|
|
_('Ratings'),
|
|
HTML('<p class="text-muted">{}</p>'.format(_('Rate your experience (1=Very Dissatisfied, 5=Very Satisfied)'))),
|
|
'satisfaction_rating',
|
|
'communication_rating',
|
|
'care_quality_rating',
|
|
),
|
|
Fieldset(
|
|
_('Comments & Suggestions'),
|
|
'comments',
|
|
'concerns',
|
|
'suggestions',
|
|
),
|
|
Submit('submit', _('Submit Feedback'), css_class='btn btn-primary')
|
|
)
|
|
|
|
|
|
class MedicalConsultationSearchForm(forms.Form):
|
|
"""
|
|
Form for searching medical consultations.
|
|
"""
|
|
|
|
search_query = forms.CharField(
|
|
required=False,
|
|
label=_('Search'),
|
|
widget=forms.TextInput(attrs={
|
|
'placeholder': _('Patient name, MRN, Chief complaint...'),
|
|
'class': 'form-control'
|
|
})
|
|
)
|
|
provider = forms.ModelChoiceField(
|
|
required=False,
|
|
label=_('Provider'),
|
|
queryset=None, # Will be set in __init__
|
|
widget=forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'})
|
|
)
|
|
date_from = forms.DateField(
|
|
required=False,
|
|
label=_('From Date'),
|
|
widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'})
|
|
)
|
|
date_to = forms.DateField(
|
|
required=False,
|
|
label=_('To Date'),
|
|
widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'})
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
tenant = kwargs.pop('tenant', None)
|
|
super().__init__(*args, **kwargs)
|
|
|
|
if tenant:
|
|
from core.models import User
|
|
self.fields['provider'].queryset = User.objects.filter(
|
|
tenant=tenant,
|
|
role=User.Role.DOCTOR
|
|
)
|
|
|
|
self.helper = FormHelper()
|
|
self.helper.form_method = 'get'
|
|
self.helper.layout = Layout(
|
|
Row(
|
|
Column('search_query', css_class='form-group col-md-4 mb-0'),
|
|
Column('provider', css_class='form-group col-md-3 mb-0'),
|
|
Column('date_from', css_class='form-group col-md-2 mb-0'),
|
|
Column('date_to', css_class='form-group col-md-2 mb-0'),
|
|
css_class='form-row'
|
|
),
|
|
Submit('search', _('Search'), css_class='btn btn-primary')
|
|
)
|