""" OT (Occupational Therapy) forms for the Tenhal Multidisciplinary Healthcare Platform. This module contains forms for OT consultations, sessions, and progress tracking. Based on OT-F-1 and OT-F-3 forms with comprehensive field-level data capture. """ from django import forms from django.forms import inlineformset_factory, modelformset_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, Div from .models import ( OTConsult, OTDifficultyArea, OTMilestone, OTSelfHelpSkill, OTInfantBehavior, OTCurrentBehavior, OTSession, OTTargetSkill, OTProgressReport, OTScoringConfig ) class OTConsultForm(forms.ModelForm): """ Main form for OT consultation (OT-F-1). Initial occupational therapy assessment. """ class Meta: model = OTConsult fields = [ 'patient', 'appointment', 'consultation_date', 'provider', 'referral_reason', 'motor_learning_difficulty', 'motor_learning_details', 'motor_skill_regression', 'regression_details', 'eats_healthy_variety', 'eats_variety_textures', 'participates_family_meals', 'eating_comments', 'infant_behavior_comments', 'current_behavior_comments', 'recommendation', 'recommendation_notes', 'clinician_name', 'clinician_signature', ] widgets = { 'patient': forms.HiddenInput(), 'appointment': forms.HiddenInput(), 'consultation_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': _('Select provider')}), 'referral_reason': forms.Select(attrs={'class': 'form-select'}), 'motor_learning_difficulty': forms.Select(choices=[(None, '---'), (True, _('Yes')), (False, _('No'))], attrs={'class': 'form-select'}), 'motor_learning_details': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('If yes, describe the difficulties')}), 'motor_skill_regression': forms.Select(choices=[(None, '---'), (True, _('Yes')), (False, _('No'))], attrs={'class': 'form-select'}), 'regression_details': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('If yes, describe the regression')}), 'eats_healthy_variety': forms.Select(choices=[(None, '---'), (True, _('Yes')), (False, _('No'))], attrs={'class': 'form-select'}), 'eats_variety_textures': forms.Select(choices=[(None, '---'), (True, _('Yes')), (False, _('No'))], attrs={'class': 'form-select'}), 'participates_family_meals': forms.Select(choices=[(None, '---'), (True, _('Yes')), (False, _('No'))], attrs={'class': 'form-select'}), 'eating_comments': forms.Textarea(attrs={'rows': 3, 'class': 'form-control', 'placeholder': _('Comments, concerns, or questions related to eating')}), 'infant_behavior_comments': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}), 'current_behavior_comments': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}), 'recommendation': forms.Select(attrs={'class': 'form-select'}), 'recommendation_notes': forms.Textarea(attrs={'rows': 4, 'class': 'form-control border-success border-2'}), 'clinician_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Enter full name')}), 'clinician_signature': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Type full name as signature')}), } class OTDifficultyAreaForm(forms.ModelForm): """ Form for difficulty areas (max 3 selections). """ class Meta: model = OTDifficultyArea fields = ['area', 'details', 'order'] widgets = { 'area': forms.Select(attrs={'class': 'form-select difficulty-area-select'}), 'details': forms.TextInput(attrs={'class': 'form-control form-control-sm', 'placeholder': _('Brief details')}), 'order': forms.HiddenInput(), } class OTMilestoneForm(forms.ModelForm): """ Form for developmental milestones. """ class Meta: model = OTMilestone fields = ['milestone', 'age_achieved', 'notes', 'is_required'] widgets = { 'milestone': forms.Select(attrs={'class': 'form-select form-select-sm'}), 'age_achieved': forms.TextInput(attrs={'class': 'form-control form-control-sm', 'placeholder': _('e.g., 6 months')}), 'notes': forms.TextInput(attrs={'class': 'form-control form-control-sm', 'placeholder': _('Optional notes')}), 'is_required': forms.HiddenInput(), } class OTSelfHelpSkillForm(forms.ModelForm): """ Form for self-help skills assessment. """ class Meta: model = OTSelfHelpSkill fields = ['age_range', 'skill_name', 'response', 'comments'] widgets = { 'age_range': forms.Select(attrs={'class': 'form-select form-select-sm'}), 'skill_name': forms.TextInput(attrs={'class': 'form-control form-control-sm', 'placeholder': _('e.g., Feeds self with spoon')}), 'response': forms.RadioSelect(attrs={'class': 'form-check-input'}), 'comments': forms.TextInput(attrs={'class': 'form-control form-control-sm', 'placeholder': _('Optional comments')}), } class OTInfantBehaviorForm(forms.ModelForm): """ Form for infant behavior assessment. """ class Meta: model = OTInfantBehavior fields = ['behavior', 'response'] widgets = { 'behavior': forms.HiddenInput(), 'response': forms.RadioSelect(attrs={'class': 'form-check-input'}), } class OTCurrentBehaviorForm(forms.ModelForm): """ Form for current behavior assessment. """ class Meta: model = OTCurrentBehavior fields = ['behavior', 'response'] widgets = { 'behavior': forms.HiddenInput(), 'response': forms.RadioSelect(attrs={'class': 'form-check-input'}), } # Formsets for related models OTDifficultyAreaFormSet = inlineformset_factory( OTConsult, OTDifficultyArea, form=OTDifficultyAreaForm, extra=3, max_num=3, can_delete=True, validate_max=True, ) OTMilestoneFormSet = inlineformset_factory( OTConsult, OTMilestone, form=OTMilestoneForm, extra=1, can_delete=True, ) OTSelfHelpSkillFormSet = inlineformset_factory( OTConsult, OTSelfHelpSkill, form=OTSelfHelpSkillForm, extra=1, can_delete=True, ) OTInfantBehaviorFormSet = inlineformset_factory( OTConsult, OTInfantBehavior, form=OTInfantBehaviorForm, extra=0, can_delete=False, ) OTCurrentBehaviorFormSet = inlineformset_factory( OTConsult, OTCurrentBehavior, form=OTCurrentBehaviorForm, extra=0, can_delete=False, ) # ============================================================================ # OT Session Forms (OT-F-3) # ============================================================================ class OTSessionForm(forms.ModelForm): """ Form for OT session notes (OT-F-3). Session documentation with target skills. """ class Meta: model = OTSession fields = [ 'patient', 'appointment', 'session_date', 'provider', 'session_type', 'cooperative_level', 'distraction_tolerance', 'activities_checklist', 'observations', 'activities_performed', 'recommendations', ] 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'}), 'cooperative_level': forms.NumberInput(attrs={'class': 'form-control', 'min': '1', 'max': '4'}), 'distraction_tolerance': forms.NumberInput(attrs={'class': 'form-control', 'min': '1', 'max': '4'}), 'activities_checklist': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('e.g., Fine motor skills, Gross motor skills, Sensory integration')}), 'observations': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), 'activities_performed': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), 'recommendations': 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( _('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' ), 'provider', ), Fieldset( _('Session Metrics'), Row( Column('cooperative_level', css_class='form-group col-md-6 mb-0'), Column('distraction_tolerance', css_class='form-group col-md-6 mb-0'), css_class='form-row' ), HTML('{}'.format(_('Rate from 1 (Poor) to 4 (Excellent)'))), ), Fieldset( _('Today We Work On...'), 'activities_checklist', ), Fieldset( _('Session Notes'), 'observations', 'activities_performed', 'recommendations', ), HTML('
{}
'.format(_('Target Skills (0-10 Scoring)'))), HTML('

{}

'.format(_('Add target skills below with baseline and current scores'))), Submit('submit', _('Save OT Session'), css_class='btn btn-primary') ) def clean(self): cleaned_data = super().clean() cooperative_level = cleaned_data.get('cooperative_level') distraction_tolerance = cleaned_data.get('distraction_tolerance') # Validate levels are within range if cooperative_level and (cooperative_level < 1 or cooperative_level > 4): self.add_error('cooperative_level', _('Level must be between 1 and 4')) if distraction_tolerance and (distraction_tolerance < 1 or distraction_tolerance > 4): self.add_error('distraction_tolerance', _('Level must be between 1 and 4')) return cleaned_data class OTTargetSkillForm(forms.ModelForm): """ Form for OT target skills with 0-10 scoring. """ class Meta: model = OTTargetSkill fields = [ 'skill_name', 'score', 'notes', 'order', ] widgets = { 'skill_name': forms.TextInput(attrs={'placeholder': _('e.g., Pencil grip'), 'class': 'form-control'}), 'score': forms.NumberInput(attrs={'class': 'form-control', 'min': '0', 'max': '10'}), 'notes': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}), 'order': forms.HiddenInput(), } def clean(self): cleaned_data = super().clean() score = cleaned_data.get('score') # Validate score is within 0-10 range if score is not None and (score < 0 or score > 10): self.add_error('score', _('Score must be between 0 and 10')) return cleaned_data # Inline formset for target skills OTTargetSkillFormSet = inlineformset_factory( OTSession, OTTargetSkill, form=OTTargetSkillForm, extra=3, can_delete=True, min_num=1, validate_min=True, ) class OTProgressReportForm(forms.ModelForm): """ Form for OT progress reports. """ class Meta: model = OTProgressReport fields = [ 'patient', 'report_date', 'provider', 'sessions_scheduled', 'sessions_attended', 'goals_progress', 'overall_progress', 'recommendations', 'continue_treatment', ] widgets = { 'patient': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select patient'}), 'report_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), 'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'}), 'sessions_scheduled': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}), 'sessions_attended': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}), 'goals_progress': forms.Textarea(attrs={'rows': 3, 'class': 'form-control', 'placeholder': _('e.g., Goal 1: Achieved, Goal 2: In Progress, Goal 3: Not Started')}), 'overall_progress': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}), 'recommendations': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), 'continue_treatment': forms.CheckboxInput(attrs={'class': 'form-check-input'}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_method = 'post' self.helper.layout = Layout( 'patient', Row( Column('report_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( _('Session Summary'), Row( Column('sessions_scheduled', css_class='form-group col-md-6 mb-0'), Column('sessions_attended', css_class='form-group col-md-6 mb-0'), css_class='form-row' ), ), Fieldset( _('Progress Summary'), 'goals_progress', 'overall_progress', ), Fieldset( _('Recommendations'), 'recommendations', 'continue_treatment', ), Submit('submit', _('Save Progress Report'), css_class='btn btn-primary') ) class OTSessionSearchForm(forms.Form): """ Form for searching OT 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(OTSession.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.OT ) 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') ) class OTScoringConfigForm(forms.ModelForm): """ Form for OT scoring configuration. """ class Meta: model = OTScoringConfig fields = [ 'name', 'is_active', 'self_help_max', 'behavior_max', 'developmental_max', 'eating_max', 'immediate_attention_threshold', 'moderate_difficulty_threshold', 'immediate_attention_label', 'moderate_difficulty_label', 'age_appropriate_label', 'immediate_attention_recommendation', 'moderate_difficulty_recommendation', 'age_appropriate_recommendation', ] widgets = { 'name': forms.TextInput(attrs={'class': 'form-control'}), 'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'self_help_max': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}), 'behavior_max': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}), 'developmental_max': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}), 'eating_max': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}), 'immediate_attention_threshold': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}), 'moderate_difficulty_threshold': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}), 'immediate_attention_label': forms.TextInput(attrs={'class': 'form-control'}), 'moderate_difficulty_label': forms.TextInput(attrs={'class': 'form-control'}), 'age_appropriate_label': forms.TextInput(attrs={'class': 'form-control'}), 'immediate_attention_recommendation': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), 'moderate_difficulty_recommendation': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), 'age_appropriate_recommendation': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), }