""" ABA forms for the Tenhal Multidisciplinary Healthcare Platform. This module contains forms for ABA consultations and behavior tracking. Based on ABA-F-1 form. """ 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 ABAConsult, ABABehavior, ABAGoal, ABASession, ABASkillTarget class ABAConsultForm(forms.ModelForm): """ Form for ABA consultation (ABA-F-1). Functional behavior assessment. """ class Meta: model = ABAConsult fields = [ 'patient', 'appointment', 'consultation_date', 'provider', 'reason_of_referral', 'parental_concern', 'school_concern', 'respondents', 'interviewer', 'diagnosed_condition', 'interaction_hours_per_day', 'physiological_factors', 'medical_factors', 'recommendations', ] widgets = { 'consultation_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'reason_of_referral': forms.RadioSelect(), 'parental_concern': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), 'school_concern': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), 'recommendations': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}), 'respondents': forms.TextInput(attrs={'class': 'form-control'}), 'interviewer': forms.TextInput(attrs={'class': 'form-control'}), 'diagnosed_condition': forms.TextInput(attrs={'class': 'form-control'}), 'interaction_hours_per_day': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.5', 'min': '0'}), 'physiological_factors': forms.Textarea(attrs={ 'rows': 4, 'class': 'form-control', 'placeholder': _('e.g., Sleep: 8 hours per night, adequate. Appetite: Normal. Medical conditions: None reported.') }), 'medical_factors': forms.Textarea(attrs={ 'rows': 4, 'class': 'form-control', 'placeholder': _('e.g., Conditions: Asthma (controlled). Medications: Albuterol inhaler as needed. Allergies: Peanuts, dust.') }), 'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'}), '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( _('Referral Information'), 'reason_of_referral', 'parental_concern', 'school_concern', ), Fieldset( _('Interview Details'), Row( Column('respondents', css_class='form-group col-md-6 mb-0'), Column('interviewer', css_class='form-group col-md-6 mb-0'), css_class='form-row' ), Row( Column('diagnosed_condition', css_class='form-group col-md-6 mb-0'), Column('interaction_hours_per_day', css_class='form-group col-md-6 mb-0'), css_class='form-row' ), ), Fieldset( _('Physiological Factors'), HTML('
{}
'.format(_('Sleep, appetite, medical conditions affecting behavior'))), 'physiological_factors', HTML('{}'.format(_('Enter as JSON object with relevant factors'))), ), Fieldset( _('Medical Factors'), HTML('{}
'.format(_('Medical conditions and medications'))), 'medical_factors', HTML('{}'.format(_('Enter as JSON object with medical information'))), ), Fieldset( _('Recommendations'), 'recommendations', ), HTML('{}
'.format(_('Add specific behaviors below'))), Submit('submit', _('Save ABA Consultation'), css_class='btn btn-primary') ) class ABABehaviorForm(forms.ModelForm): """ Form for ABA behaviors. """ class Meta: model = ABABehavior fields = [ 'behavior_description', 'frequency', 'duration', 'intensity', 'antecedents_likely', 'antecedents_least_likely', 'consequences', 'order', ] widgets = { 'behavior_description': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}), 'frequency': forms.Select(attrs={'class': 'form-control'}), 'duration': forms.TextInput(attrs={'placeholder': _('e.g., 5-10 minutes'), 'class': 'form-control'}), 'intensity': forms.Select(attrs={'class': 'form-control'}), 'antecedents_likely': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}), 'antecedents_least_likely': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}), 'consequences': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}), 'order': forms.HiddenInput(), } # Inline formset for behaviors ABABehaviorFormSet = inlineformset_factory( ABAConsult, ABABehavior, form=ABABehaviorForm, extra=1, can_delete=True, min_num=1, validate_min=True, ) class ABAGoalForm(forms.ModelForm): """ Form for ABA treatment goals. """ class Meta: model = ABAGoal fields = [ 'consult', 'goal_description', 'target_date', 'status', 'progress_notes', 'achieved_date', ] widgets = { 'consult': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select consultation'}), 'goal_description': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), 'target_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'status': forms.Select(attrs={'class': 'form-control'}), 'achieved_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'progress_notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_method = 'post' self.helper.layout = Layout( 'consult', 'goal_description', Row( Column('target_date', css_class='form-group col-md-6 mb-0'), Column('status', css_class='form-group col-md-6 mb-0'), css_class='form-row' ), 'progress_notes', 'achieved_date', Submit('submit', _('Save Goal'), css_class='btn btn-primary') ) def clean(self): cleaned_data = super().clean() status = cleaned_data.get('status') achieved_date = cleaned_data.get('achieved_date') # Validate achieved date is set when status is ACHIEVED if status == ABAGoal.Status.ACHIEVED and not achieved_date: raise forms.ValidationError( _('Achieved date is required when status is "Achieved".') ) return cleaned_data class ABAConsultSearchForm(forms.Form): """ Form for searching ABA consultations. """ search_query = forms.CharField( required=False, label=_('Search'), widget=forms.TextInput(attrs={ 'placeholder': _('Patient name, MRN, Diagnosed condition...'), 'class': 'form-control' }) ) reason_of_referral = forms.ChoiceField( required=False, label=_('Reason of Referral'), choices=[('', _('All'))] + list(ABAConsult.ReasonOfReferral.choices), widget=forms.Select(attrs={'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.ABA ) self.helper = FormHelper() self.helper.form_method = 'get' self.helper.layout = Layout( Row( Column('search_query', css_class='form-group col-md-3 mb-0'), Column('reason_of_referral', css_class='form-group col-md-2 mb-0'), Column('provider', css_class='form-group col-md-2 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') ) class ABASessionForm(forms.ModelForm): """ Form for ABA session notes. Session documentation with skill targets. """ class Meta: model = ABASession fields = [ 'patient', 'appointment', 'session_date', 'provider', 'session_type', 'duration_minutes', 'engagement_level', 'cooperation_level', 'target_behaviors', 'interventions_used', 'activities_performed', 'behavioral_observations', 'progress_notes', 'recommendations', 'home_program', ] widgets = { 'session_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'}), 'session_type': forms.Select(attrs={'class': 'form-control'}), 'duration_minutes': forms.NumberInput(attrs={'class': 'form-control', 'min': '0', 'step': '5'}), 'engagement_level': forms.NumberInput(attrs={'class': 'form-control', 'min': '1', 'max': '5'}), 'cooperation_level': forms.NumberInput(attrs={'class': 'form-control', 'min': '1', 'max': '5'}), 'target_behaviors': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('e.g., Reduce tantrums, Increase eye contact, Improve turn-taking')}), 'interventions_used': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('e.g., Positive reinforcement, Visual schedules, Token economy')}), 'activities_performed': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), 'behavioral_observations': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), 'progress_notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), 'recommendations': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), 'home_program': forms.Textarea(attrs={'rows': 3, 'class': 'form-control', 'placeholder': _('Recommendations for parents to practice at home')}), '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( _('Session Information'), Row( Column('session_date', css_class='form-group col-md-6 mb-0'), Column('session_type', css_class='form-group col-md-6 mb-0'), css_class='form-row' ), Row( Column('provider', css_class='form-group col-md-6 mb-0'), Column('duration_minutes', css_class='form-group col-md-6 mb-0'), css_class='form-row' ), ), Fieldset( _('Session Metrics'), Row( Column('engagement_level', css_class='form-group col-md-6 mb-0'), Column('cooperation_level', css_class='form-group col-md-6 mb-0'), css_class='form-row' ), HTML('{}'.format(_('Rate from 1 (Very Low) to 5 (Excellent)'))), ), Fieldset( _('Session Content'), 'target_behaviors', 'interventions_used', 'activities_performed', ), Fieldset( _('Observations & Progress'), 'behavioral_observations', 'progress_notes', ), Fieldset( _('Recommendations'), 'recommendations', 'home_program', ), HTML('{}
'.format(_('Add skill targets below with mastery levels and trial data'))), Submit('submit', _('Save ABA Session'), css_class='btn btn-primary') ) def clean(self): cleaned_data = super().clean() engagement_level = cleaned_data.get('engagement_level') cooperation_level = cleaned_data.get('cooperation_level') # Validate levels are within range if engagement_level and (engagement_level < 1 or engagement_level > 5): self.add_error('engagement_level', _('Level must be between 1 and 5')) if cooperation_level and (cooperation_level < 1 or cooperation_level > 5): self.add_error('cooperation_level', _('Level must be between 1 and 5')) return cleaned_data class ABASkillTargetForm(forms.ModelForm): """ Form for ABA skill targets with mastery tracking. """ class Meta: model = ABASkillTarget fields = [ 'skill_name', 'mastery_level', 'trials_correct', 'trials_total', 'notes', 'order', ] widgets = { 'skill_name': forms.TextInput(attrs={'placeholder': _('e.g., Follows one-step directions'), 'class': 'form-control'}), 'mastery_level': forms.Select(attrs={'class': 'form-control'}), 'trials_correct': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}), 'trials_total': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}), 'notes': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}), 'order': forms.HiddenInput(), } def clean(self): cleaned_data = super().clean() trials_correct = cleaned_data.get('trials_correct') trials_total = cleaned_data.get('trials_total') # Validate trials_correct is not greater than trials_total if trials_correct is not None and trials_total is not None: if trials_correct > trials_total: self.add_error('trials_correct', _('Correct trials cannot exceed total trials')) return cleaned_data # Inline formset for skill targets ABASkillTargetFormSet = inlineformset_factory( ABASession, ABASkillTarget, form=ABASkillTargetForm, extra=1, can_delete=True, min_num=1, validate_min=True, ) class ABASessionSearchForm(forms.Form): """ Form for searching ABA sessions. """ search_query = forms.CharField( required=False, label=_('Search'), widget=forms.TextInput(attrs={ 'placeholder': _('Patient name, MRN...'), 'class': 'form-control' }) ) session_type = forms.ChoiceField( required=False, label=_('Session Type'), choices=[('', _('All'))] + list(ABASession.SessionType.choices), widget=forms.Select(attrs={'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.ABA ) self.helper = FormHelper() self.helper.form_method = 'get' self.helper.layout = Layout( Row( Column('search_query', css_class='form-group col-md-3 mb-0'), Column('session_type', css_class='form-group col-md-2 mb-0'), Column('provider', css_class='form-group col-md-2 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') )