agdar/slp/forms.py
Marwan Alwali 3fbfccb799 update
2025-11-06 23:08:02 +03:00

476 lines
22 KiB
Python

"""
SLP (Speech-Language Pathology) forms for the Tenhal Multidisciplinary Healthcare Platform.
This module contains forms for SLP consultations, assessments, interventions, and progress reports.
Based on SLP-F-1, F-2, F-3, and F-4 forms.
"""
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 SLPConsult, SLPAssessment, SLPIntervention, SLPTarget, SLPProgressReport
class SLPConsultForm(forms.ModelForm):
"""
Form for SLP consultation (SLP-F-1).
Supports variant-specific questionnaires (ASD/LanguageDelay/Fluency).
"""
class Meta:
model = SLPConsult
fields = [
'patient', 'appointment', 'consultation_date', 'provider',
'consult_variant', 'primary_concern', 'suspected_areas',
'type_of_service', 'communication_modes', 'screen_time_hours',
'variant_questionnaire', 'skills_to_observe', 'oral_motor_screening',
'recommendations',
]
widgets = {
'consultation_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'}),
'consult_variant': forms.Select(attrs={'class': 'form-control'}),
'primary_concern': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'suspected_areas': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('e.g., Articulation, Language comprehension, Social communication')}),
'type_of_service': forms.Select(attrs={'class': 'form-control'}),
'communication_modes': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('e.g., Verbal, Gestures, AAC device, Sign language')}),
'screen_time_hours': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}),
'variant_questionnaire': forms.Textarea(attrs={'rows': 4, 'class': 'form-control', 'placeholder': _('Describe variant-specific observations and responses')}),
'skills_to_observe': forms.Textarea(attrs={'rows': 3, 'class': 'form-control', 'placeholder': _('e.g., Receptive: follows commands, understands questions. Expressive: vocabulary, sentence structure')}),
'oral_motor_screening': forms.Textarea(attrs={'rows': 3, 'class': 'form-control', 'placeholder': _('e.g., Lips: adequate, Tongue: adequate, Jaw: adequate, Palate: intact')}),
'recommendations': forms.Textarea(attrs={'rows': 4, '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(
_('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(
_('Consultation Type'),
Row(
Column('consult_variant', css_class='form-group col-md-6 mb-0'),
Column('type_of_service', css_class='form-group col-md-6 mb-0'),
css_class='form-row'
),
HTML('<small class="text-muted">{}</small>'.format(_('Variant selection will show specific questionnaire'))),
),
Fieldset(
_('Primary Concerns'),
'primary_concern',
'suspected_areas',
),
Fieldset(
_('Communication & Environment'),
'communication_modes',
'screen_time_hours',
),
Fieldset(
_('Variant-Specific Questionnaire'),
HTML('<p class="text-muted">{}</p>'.format(_('ASD: Social interaction, repetitive behaviors | Language Delay: Milestones, comprehension | Fluency: Stuttering patterns'))),
'variant_questionnaire',
),
Fieldset(
_('Skills to Observe'),
'skills_to_observe',
),
Fieldset(
_('Oral Motor Screening'),
'oral_motor_screening',
),
Fieldset(
_('Recommendations'),
'recommendations',
),
Submit('submit', _('Save SLP Consultation'), css_class='btn btn-primary')
)
class SLPAssessmentForm(forms.ModelForm):
"""
Form for SLP assessment/reassessment (SLP-F-2).
Comprehensive evaluation with standardized tests.
"""
class Meta:
model = SLPAssessment
fields = [
'patient', 'appointment', 'assessment_date', 'provider',
'diagnosis_statement', 'case_history',
'prenatal_history', 'perinatal_history', 'postnatal_history',
'developmental_history', 'medical_status',
'speech_language_detail', 'gfta3_score', 'jat_score', 'ssi_score',
'oral_mechanism', 'rossetti_domains', 'joint_attention_skills',
'clinical_summary', 'recommendations',
'frequency_per_week', 'session_duration_minutes', 'referral_rules',
]
widgets = {
'patient': forms.HiddenInput(),
'appointment': forms.HiddenInput(),
'assessment_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'}),
'diagnosis_statement': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
'case_history': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'prenatal_history': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
'perinatal_history': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
'postnatal_history': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
'developmental_history': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
'medical_status': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
'speech_language_detail': forms.Textarea(attrs={'rows': 3, 'class': 'form-control', 'placeholder': _('e.g., Articulation: details, Phonology: details, Fluency: details')}),
'gfta3_score': forms.TextInput(attrs={'class': 'form-control'}),
'jat_score': forms.TextInput(attrs={'class': 'form-control'}),
'ssi_score': forms.TextInput(attrs={'class': 'form-control'}),
'oral_mechanism': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('e.g., Structure: adequate, Function: adequate, Diadochokinesis: adequate')}),
'rossetti_domains': forms.Textarea(attrs={'rows': 3, 'class': 'form-control', 'placeholder': _('e.g., Interaction: 12-15 months, Pragmatics: 9-12 months, Gesture: 12-15 months')}),
'joint_attention_skills': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('e.g., Eye contact: present, Pointing: absent, Showing: present')}),
'clinical_summary': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
'recommendations': forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
'frequency_per_week': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}),
'session_duration_minutes': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}),
'referral_rules': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('e.g., Refer to: Audiology, OT. Reason: Sensory concerns')}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
'patient',
'appointment',
Fieldset(
_('Assessment Information'),
Row(
Column('assessment_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(
_('Diagnosis & Case History'),
'diagnosis_statement',
'case_history',
),
Fieldset(
_('History Sections'),
'prenatal_history',
'perinatal_history',
'postnatal_history',
'developmental_history',
'medical_status',
),
Fieldset(
_('Speech/Language Detail & Test Scores'),
'speech_language_detail',
Row(
Column('gfta3_score', css_class='form-group col-md-4 mb-0'),
Column('jat_score', css_class='form-group col-md-4 mb-0'),
Column('ssi_score', css_class='form-group col-md-4 mb-0'),
css_class='form-row'
),
HTML('<small class="text-muted">{}</small>'.format(_('GFTA-3: Goldman-Fristoe | JAT: Joliet Articulation | SSI: Stuttering Severity'))),
),
Fieldset(
_('Oral Mechanism'),
'oral_mechanism',
),
Fieldset(
_('Rossetti Infant-Toddler Language Scale'),
'rossetti_domains',
),
Fieldset(
_('Joint Attention Skills'),
'joint_attention_skills',
),
Fieldset(
_('Summary & Plan'),
'clinical_summary',
'recommendations',
Row(
Column('frequency_per_week', css_class='form-group col-md-6 mb-0'),
Column('session_duration_minutes', css_class='form-group col-md-6 mb-0'),
css_class='form-row'
),
),
Fieldset(
_('Referral Rules'),
'referral_rules',
),
Submit('submit', _('Save SLP Assessment'), css_class='btn btn-primary')
)
class SLPInterventionForm(forms.ModelForm):
"""
Form for SLP intervention session (SLP-F-3).
Session notes with SOAP format for targets stored as JSON.
"""
class Meta:
model = SLPIntervention
fields = [
'patient', 'appointment', 'session_number', 'session_date',
'session_time', 'provider', 'previous_session', 'intervention_targets_json',
]
widgets = {
'patient': forms.HiddenInput(),
'appointment': forms.HiddenInput(),
'session_number': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
'session_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'session_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'}),
'previous_session': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select previous session'}),
'intervention_targets_json': forms.HiddenInput(),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Filter previous_session queryset to only show sessions for the same patient
if self.instance and self.instance.patient_id:
# For existing intervention, filter by the patient
self.fields['previous_session'].queryset = SLPIntervention.objects.filter(
patient=self.instance.patient
).exclude(pk=self.instance.pk).order_by('-session_date', '-session_number')
elif self.initial.get('patient'):
# For new intervention with initial patient data
from core.models import Patient
try:
patient = Patient.objects.get(pk=self.initial['patient'])
self.fields['previous_session'].queryset = SLPIntervention.objects.filter(
patient=patient
).order_by('-session_date', '-session_number')
except Patient.DoesNotExist:
self.fields['previous_session'].queryset = SLPIntervention.objects.none()
else:
# No patient selected yet, show no options
self.fields['previous_session'].queryset = SLPIntervention.objects.none()
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
'patient',
'appointment',
Fieldset(
_('Session Information'),
Row(
Column('session_number', css_class='form-group col-md-4 mb-0'),
Column('session_date', css_class='form-group col-md-4 mb-0'),
Column('session_time', css_class='form-group col-md-4 mb-0'),
css_class='form-row'
),
'provider',
'previous_session',
HTML('<div class="alert alert-info" id="previous-session-preview"><strong>{}</strong><p class="mb-0">{}</p></div>'.format(
_('Previous Session Preview:'),
_('Select previous session to view targets and notes')
)),
),
HTML('<hr><h5>{}</h5>'.format(_('Targets (SOAP Format)'))),
HTML('<p class="text-muted">{}</p>'.format(_('Add 1-2 targets below with SOAP notes and prompt strategies'))),
Submit('submit', _('Save SLP Intervention'), css_class='btn btn-primary')
)
class SLPTargetForm(forms.ModelForm):
"""
Form for SLP intervention targets with SOAP format.
"""
class Meta:
model = SLPTarget
fields = [
'target_number', 'subjective', 'objective',
'assessment', 'plan', 'prompt_strategies',
]
widgets = {
'target_number': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
'subjective': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('Subjective observations')}),
'objective': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('Objective measurements and data')}),
'assessment': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('Assessment of progress')}),
'plan': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('Plan for next session')}),
'prompt_strategies': forms.Textarea(attrs={'rows': 2, 'class': 'form-control', 'placeholder': _('["Visual cues", "Verbal prompts", "Physical guidance"]')}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Add help text for prompt strategies
self.fields['prompt_strategies'].help_text = _('JSON array: ["Visual cues", "Verbal prompts", "Physical guidance"]')
# Inline formset for SLP targets
SLPTargetFormSet = inlineformset_factory(
SLPIntervention,
SLPTarget,
form=SLPTargetForm,
extra=2,
max_num=2,
can_delete=True,
min_num=1,
validate_min=True,
)
class SLPProgressReportForm(forms.ModelForm):
"""
Form for SLP progress report (SLP-F-4).
Comprehensive progress summary with outcomes.
"""
class Meta:
model = SLPProgressReport
fields = [
'patient', 'report_date', 'provider',
'sessions_scheduled', 'sessions_attended',
'final_diagnosis', 'objectives_progress', 'plan_details',
'overall_progress', 'participation_level', 'attendance_rate',
'carryover_level', 'prognosis', 'recommendations',
'package_sessions_count', 'reassessment_needed',
]
widgets = {
'patient': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select patient'}),
'provider': forms.Select(attrs={'class': 'form-select select2', 'data-placeholder': 'Select provider'}),
'report_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'final_diagnosis': forms.Textarea(attrs={'rows': 2}),
'overall_progress': forms.Textarea(attrs={'rows': 4}),
'participation_level': forms.Textarea(attrs={'rows': 2}),
'carryover_level': forms.Textarea(attrs={'rows': 2}),
'prognosis': forms.Textarea(attrs={'rows': 2}),
'recommendations': forms.Textarea(attrs={'rows': 4}),
}
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-4 mb-0'),
Column('sessions_attended', css_class='form-group col-md-4 mb-0'),
Column('attendance_rate', css_class='form-group col-md-4 mb-0'),
css_class='form-row'
),
HTML('<small class="text-muted">{}</small>'.format(_('Attendance rate will be auto-calculated if not provided'))),
),
Fieldset(
_('Diagnosis'),
'final_diagnosis',
),
Fieldset(
_('Objectives & Progress'),
HTML('<p class="text-muted">{}</p>'.format(_('Objective → % accuracy mapping (JSON format)'))),
HTML('<div class="form-group"><label>{}</label><textarea name="objectives_progress" class="form-control" rows="3" placeholder=\'{{"Objective 1": 85, "Objective 2": 70, "Objective 3": 90}}\'></textarea><small class="form-text text-muted">{}</small></div>'.format(
_('Objectives Progress (JSON format)'),
_('Enter each objective with percentage accuracy achieved')
)),
),
Fieldset(
_('Plan Details'),
HTML('<div class="form-group"><label>{}</label><textarea name="plan_details" class="form-control" rows="3" placeholder=\'{{"continue": ["Objective 1"], "add": ["New objective"], "fade_prompts": ["Objective 2"], "generalization": ["Objective 3"]}}\'></textarea><small class="form-text text-muted">{}</small></div>'.format(
_('Plan Details (JSON format)'),
_('Continue/Add/Fade prompts/Generalization plans')
)),
),
Fieldset(
_('Progress Summary'),
'overall_progress',
'participation_level',
'carryover_level',
'prognosis',
),
Fieldset(
_('Recommendations'),
'recommendations',
Row(
Column('package_sessions_count', css_class='form-group col-md-6 mb-0'),
Column('reassessment_needed', css_class='form-group col-md-6 mb-0'),
css_class='form-row'
),
),
Submit('submit', _('Save Progress Report'), css_class='btn btn-primary')
)
class SLPConsultSearchForm(forms.Form):
"""
Form for searching SLP consultations.
"""
search_query = forms.CharField(
required=False,
label=_('Search'),
widget=forms.TextInput(attrs={
'placeholder': _('Patient name, MRN, Primary concern...'),
'class': 'form-control'
})
)
consult_variant = forms.ChoiceField(
required=False,
label=_('Variant'),
choices=[('', _('All'))] + list(SLPConsult.ConsultVariant.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.SLP
)
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('consult_variant', 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')
)