476 lines
22 KiB
Python
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')
|
|
)
|