465 lines
19 KiB
Python
465 lines
19 KiB
Python
"""
|
|
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('<p class="text-muted">{}</p>'.format(_('Sleep, appetite, medical conditions affecting behavior'))),
|
|
'physiological_factors',
|
|
HTML('<small class="form-text text-muted">{}</small>'.format(_('Enter as JSON object with relevant factors'))),
|
|
),
|
|
Fieldset(
|
|
_('Medical Factors'),
|
|
HTML('<p class="text-muted">{}</p>'.format(_('Medical conditions and medications'))),
|
|
'medical_factors',
|
|
HTML('<small class="form-text text-muted">{}</small>'.format(_('Enter as JSON object with medical information'))),
|
|
),
|
|
Fieldset(
|
|
_('Recommendations'),
|
|
'recommendations',
|
|
),
|
|
HTML('<hr><h5>{}</h5>'.format(_('Behaviors'))),
|
|
HTML('<p class="text-muted">{}</p>'.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('<small class="text-muted">{}</small>'.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('<hr><h5>{}</h5>'.format(_('Skill Targets'))),
|
|
HTML('<p class="text-muted">{}</p>'.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')
|
|
)
|