460 lines
18 KiB
Python
460 lines
18 KiB
Python
"""
|
|
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('<small class="text-muted">{}</small>'.format(_('Rate from 1 (Poor) to 4 (Excellent)'))),
|
|
),
|
|
Fieldset(
|
|
_('Today We Work On...'),
|
|
'activities_checklist',
|
|
),
|
|
Fieldset(
|
|
_('Session Notes'),
|
|
'observations',
|
|
'activities_performed',
|
|
'recommendations',
|
|
),
|
|
HTML('<hr><h5>{}</h5>'.format(_('Target Skills (0-10 Scoring)'))),
|
|
HTML('<p class="text-muted">{}</p>'.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'}),
|
|
}
|