""" Nursing forms for the Tenhal Multidisciplinary Healthcare Platform. This module contains forms for nursing encounters, vitals, and growth charts. Based on MD-N-F-1 form. """ from django import forms 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 NursingEncounter, GrowthChart class NursingEncounterForm(forms.ModelForm): """ Form for nursing encounter (MD-N-F-1). Auto-calculates BMI from height and weight. """ class Meta: model = NursingEncounter fields = [ 'patient', 'appointment', 'encounter_date', 'filled_by', 'height_cm', 'weight_kg', 'head_circumference_cm', 'hr_bpm', 'bp_systolic', 'bp_diastolic', 'respiratory_rate', 'spo2', 'crt', 'pain_score', 'temperature', 'allergy_present', 'allergy_details', 'observations', ] widgets = { 'encounter_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'filled_by': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select nurse'}), 'height_cm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1', 'min': '0'}), 'weight_kg': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1', 'min': '0'}), 'head_circumference_cm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1', 'min': '0'}), 'hr_bpm': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}), 'bp_systolic': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}), 'bp_diastolic': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}), 'respiratory_rate': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}), 'spo2': forms.NumberInput(attrs={'class': 'form-control', 'min': '0', 'max': '100'}), 'crt': forms.Select(attrs={'class': 'form-control'}), 'pain_score': forms.NumberInput(attrs={'class': 'form-control', 'min': '0', 'max': '10'}), 'temperature': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1'}), 'allergy_present': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'allergy_details': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}), 'observations': forms.Textarea(attrs={'rows': 3, '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( _('Encounter Information'), Row( Column('encounter_date', css_class='form-group col-md-6 mb-0'), Column('filled_by', css_class='form-group col-md-6 mb-0'), css_class='form-row' ), ), Fieldset( _('Anthropometric Measurements'), Row( Column('height_cm', css_class='form-group col-md-4 mb-0'), Column('weight_kg', css_class='form-group col-md-4 mb-0'), Column('head_circumference_cm', css_class='form-group col-md-4 mb-0'), css_class='form-row' ), HTML('''
{% trans "BMI:" %} --
'''), ), Fieldset( _('Vital Signs'), Row( Column('hr_bpm', css_class='form-group col-md-3 mb-0'), Column('respiratory_rate', css_class='form-group col-md-3 mb-0'), Column('spo2', css_class='form-group col-md-3 mb-0'), Column('temperature', css_class='form-group col-md-3 mb-0'), css_class='form-row' ), Row( Column('bp_systolic', css_class='form-group col-md-4 mb-0'), Column('bp_diastolic', css_class='form-group col-md-4 mb-0'), Column('crt', css_class='form-group col-md-4 mb-0'), css_class='form-row' ), 'pain_score', ), Fieldset( _('Allergies'), 'allergy_present', 'allergy_details', ), Fieldset( _('Clinical Notes'), 'observations', ), HTML(''' '''), Submit('submit', _('Save Nursing Encounter'), css_class='btn btn-primary') ) def clean(self): cleaned_data = super().clean() allergy_present = cleaned_data.get('allergy_present') allergy_details = cleaned_data.get('allergy_details') # Validate allergy details if allergy_present is True if allergy_present and not allergy_details: raise forms.ValidationError( _('Please provide allergy details when allergies are present.') ) # Validate vital signs ranges hr_bpm = cleaned_data.get('hr_bpm') if hr_bpm and (hr_bpm < 30 or hr_bpm > 250): self.add_error('hr_bpm', _('Heart rate seems unusual. Please verify.')) bp_systolic = cleaned_data.get('bp_systolic') bp_diastolic = cleaned_data.get('bp_diastolic') if bp_systolic and bp_diastolic and bp_systolic <= bp_diastolic: raise forms.ValidationError( _('Systolic blood pressure must be higher than diastolic.') ) spo2 = cleaned_data.get('spo2') if spo2 and (spo2 < 50 or spo2 > 100): self.add_error('spo2', _('SpO2 value seems unusual. Please verify.')) return cleaned_data class GrowthChartForm(forms.ModelForm): """ Form for recording growth chart data. """ class Meta: model = GrowthChart fields = [ 'patient', 'measurement_date', 'age_months', 'height_cm', 'weight_kg', 'head_circumference_cm', 'percentile_height', 'percentile_weight', 'percentile_head_circumference', ] widgets = { 'patient': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select patient'}), 'measurement_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'age_months': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}), 'height_cm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1', 'min': '0'}), 'weight_kg': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1', 'min': '0'}), 'head_circumference_cm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1', 'min': '0'}), 'percentile_height': forms.NumberInput(attrs={'class': 'form-control', 'min': '0', 'max': '100'}), 'percentile_weight': forms.NumberInput(attrs={'class': 'form-control', 'min': '0', 'max': '100'}), 'percentile_head_circumference': forms.NumberInput(attrs={'class': 'form-control', 'min': '0', 'max': '100'}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_method = 'post' self.helper.layout = Layout( 'patient', Row( Column('measurement_date', css_class='form-group col-md-6 mb-0'), Column('age_months', css_class='form-group col-md-6 mb-0'), css_class='form-row' ), Fieldset( _('Measurements'), Row( Column('height_cm', css_class='form-group col-md-4 mb-0'), Column('weight_kg', css_class='form-group col-md-4 mb-0'), Column('head_circumference_cm', css_class='form-group col-md-4 mb-0'), css_class='form-row' ), ), Fieldset( _('Percentiles'), Row( Column('percentile_height', css_class='form-group col-md-4 mb-0'), Column('percentile_weight', css_class='form-group col-md-4 mb-0'), Column('percentile_head_circumference', css_class='form-group col-md-4 mb-0'), css_class='form-row' ), HTML('''
{% trans "Percentiles are calculated based on WHO growth standards." %}
'''), ), Submit('submit', _('Save Growth Chart Entry'), css_class='btn btn-primary') ) class NursingEncounterSearchForm(forms.Form): """ Form for searching nursing encounters. """ search_query = forms.CharField( required=False, label=_('Search'), widget=forms.TextInput(attrs={ 'placeholder': _('Patient name, MRN...'), 'class': 'form-control' }) ) 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'}) ) has_alerts = forms.BooleanField( required=False, label=_('Has Vital Signs Alerts'), widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}) ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_method = 'get' self.helper.layout = Layout( Row( Column('search_query', css_class='form-group col-md-5 mb-0'), Column('date_from', css_class='form-group col-md-3 mb-0'), Column('date_to', css_class='form-group col-md-3 mb-0'), Column('has_alerts', css_class='form-group col-md-1 mb-0'), css_class='form-row' ), Submit('search', _('Search'), css_class='btn btn-primary') )