SreekarB commited on
Commit
d8e9860
·
verified ·
1 Parent(s): 57a00c7

Upload casl_analysis.py

Browse files
Files changed (1) hide show
  1. casl_analysis.py +1903 -0
casl_analysis.py ADDED
@@ -0,0 +1,1903 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import boto3
3
+ import json
4
+ import pandas as pd
5
+ import matplotlib.pyplot as plt
6
+ import numpy as np
7
+ import re
8
+ import logging
9
+ import os
10
+ import pickle
11
+ import csv
12
+ from PIL import Image
13
+ import io
14
+ import PyPDF2
15
+ import uuid
16
+ from datetime import datetime
17
+
18
+ # Configure logging
19
+ logging.basicConfig(level=logging.INFO)
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # AWS credentials for Bedrock API
23
+ # For HuggingFace Spaces, set these as secrets in the Space settings
24
+ AWS_ACCESS_KEY = os.getenv("AWS_ACCESS_KEY", "")
25
+ AWS_SECRET_KEY = os.getenv("AWS_SECRET_KEY", "")
26
+ AWS_REGION = os.getenv("AWS_REGION", "us-east-1")
27
+
28
+ # Initialize Bedrock client if credentials are available
29
+ bedrock_client = None
30
+ if AWS_ACCESS_KEY and AWS_SECRET_KEY:
31
+ try:
32
+ bedrock_client = boto3.client(
33
+ 'bedrock-runtime',
34
+ aws_access_key_id=AWS_ACCESS_KEY,
35
+ aws_secret_access_key=AWS_SECRET_KEY,
36
+ region_name=AWS_REGION
37
+ )
38
+ logger.info("Bedrock client initialized successfully")
39
+ except Exception as e:
40
+ logger.error(f"Failed to initialize Bedrock client: {str(e)}")
41
+
42
+ # Sample transcript for the demo
43
+ SAMPLE_TRANSCRIPT = """*PAR: today I would &-um like to talk about &-um a fun trip I took last &-um summer with my family.
44
+ *PAR: we went to the &-um &-um beach [//] no to the mountains [//] I mean the beach actually.
45
+ *PAR: there was lots of &-um &-um swimming and &-um sun.
46
+ *PAR: we [/] we stayed for &-um three no [//] four days in a &-um hotel near the water [: ocean] [*].
47
+ *PAR: my favorite part was &-um building &-um castles with sand.
48
+ *PAR: sometimes I forget [//] forgetted [: forgot] [*] what they call those things we built.
49
+ *PAR: my brother he [//] he helped me dig a big hole.
50
+ *PAR: we saw [/] saw fishies [: fish] [*] swimming in the water.
51
+ *PAR: sometimes I wonder [/] wonder where fishies [: fish] [*] go when it's cold.
52
+ *PAR: maybe they have [/] have houses under the water.
53
+ *PAR: after swimming we [//] I eat [: ate] [*] &-um ice cream with &-um chocolate things on top.
54
+ *PAR: what do you call those &-um &-um sprinkles! that's the word.
55
+ *PAR: my mom said to &-um that I could have &-um two scoops next time.
56
+ *PAR: I want to go back to the beach [/] beach next year."""
57
+
58
+ # ===============================
59
+ # Database and Storage Functions
60
+ # ===============================
61
+
62
+ # Create data directories if they don't exist
63
+ DATA_DIR = "patient_data"
64
+ RECORDS_FILE = os.path.join(DATA_DIR, "patient_records.csv")
65
+ ANALYSES_DIR = os.path.join(DATA_DIR, "analyses")
66
+
67
+ def ensure_data_dirs():
68
+ """Ensure data directories exist"""
69
+ os.makedirs(DATA_DIR, exist_ok=True)
70
+ os.makedirs(ANALYSES_DIR, exist_ok=True)
71
+
72
+ # Create records file if it doesn't exist
73
+ if not os.path.exists(RECORDS_FILE):
74
+ with open(RECORDS_FILE, 'w', newline='') as f:
75
+ writer = csv.writer(f)
76
+ writer.writerow([
77
+ "ID", "Name", "Record ID", "Age", "Gender",
78
+ "Assessment Date", "Clinician", "Analysis Date", "File Path"
79
+ ])
80
+
81
+ # Initialize data directories
82
+ ensure_data_dirs()
83
+
84
+ def save_patient_record(patient_info, analysis_results, transcript):
85
+ """Save patient record to storage"""
86
+ try:
87
+ # Generate unique ID for the record
88
+ record_id = str(uuid.uuid4())
89
+
90
+ # Extract patient information
91
+ name = patient_info.get("name", "")
92
+ patient_id = patient_info.get("record_id", "")
93
+ age = patient_info.get("age", "")
94
+ gender = patient_info.get("gender", "")
95
+ assessment_date = patient_info.get("assessment_date", "")
96
+ clinician = patient_info.get("clinician", "")
97
+
98
+ # Create filename for the analysis data
99
+ filename = f"analysis_{record_id}.pkl"
100
+ filepath = os.path.join(ANALYSES_DIR, filename)
101
+
102
+ # Save analysis data
103
+ with open(filepath, 'wb') as f:
104
+ pickle.dump({
105
+ "patient_info": patient_info,
106
+ "analysis_results": analysis_results,
107
+ "transcript": transcript,
108
+ "timestamp": datetime.now().isoformat(),
109
+ }, f)
110
+
111
+ # Add record to CSV file
112
+ with open(RECORDS_FILE, 'a', newline='') as f:
113
+ writer = csv.writer(f)
114
+ writer.writerow([
115
+ record_id, name, patient_id, age, gender,
116
+ assessment_date, clinician, datetime.now().strftime('%Y-%m-%d'),
117
+ filepath
118
+ ])
119
+
120
+ return record_id
121
+
122
+ except Exception as e:
123
+ logger.error(f"Error saving patient record: {str(e)}")
124
+ return None
125
+
126
+ def load_patient_record(record_id):
127
+ """Load patient record from storage"""
128
+ try:
129
+ # Find the record in the CSV file
130
+ with open(RECORDS_FILE, 'r', newline='') as f:
131
+ reader = csv.reader(f)
132
+ next(reader) # Skip header
133
+ for row in reader:
134
+ if row[0] == record_id:
135
+ file_path = row[8]
136
+
137
+ # Load and return the data
138
+ with open(file_path, 'rb') as f:
139
+ return pickle.load(f)
140
+
141
+ return None
142
+
143
+ except Exception as e:
144
+ logger.error(f"Error loading patient record: {str(e)}")
145
+ return None
146
+
147
+ def get_all_patient_records():
148
+ """Return a list of all patient records"""
149
+ try:
150
+ records = []
151
+ if os.path.exists(RECORDS_FILE):
152
+ with open(RECORDS_FILE, 'r', newline='') as f:
153
+ reader = csv.reader(f)
154
+ next(reader) # Skip header
155
+ for row in reader:
156
+ records.append({
157
+ "id": row[0],
158
+ "name": row[1],
159
+ "record_id": row[2],
160
+ "age": row[3],
161
+ "gender": row[4],
162
+ "assessment_date": row[5],
163
+ "clinician": row[6],
164
+ "analysis_date": row[7]
165
+ })
166
+ return records
167
+
168
+ except Exception as e:
169
+ logger.error(f"Error getting patient records: {str(e)}")
170
+ return []
171
+
172
+ # ===============================
173
+ # Utility Functions
174
+ # ===============================
175
+
176
+ def read_pdf(file_path):
177
+ """Read text from a PDF file"""
178
+ try:
179
+ with open(file_path, 'rb') as file:
180
+ pdf_reader = PyPDF2.PdfReader(file)
181
+ text = ""
182
+ for page in pdf_reader.pages:
183
+ text += page.extract_text()
184
+ return text
185
+ except Exception as e:
186
+ logger.error(f"Error reading PDF: {str(e)}")
187
+ return ""
188
+
189
+ def read_cha_file(file_path):
190
+ """Read and parse a .cha transcript file"""
191
+ try:
192
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
193
+ content = f.read()
194
+
195
+ # Extract participant lines (starting with *PAR:)
196
+ par_lines = []
197
+ for line in content.splitlines():
198
+ if line.startswith('*PAR:'):
199
+ par_lines.append(line)
200
+
201
+ # If no PAR lines found, just return the whole content
202
+ if not par_lines:
203
+ return content
204
+
205
+ return '\n'.join(par_lines)
206
+
207
+ except Exception as e:
208
+ logger.error(f"Error reading CHA file: {str(e)}")
209
+ return ""
210
+
211
+ def process_upload(file):
212
+ """Process an uploaded file (PDF, text, or CHA)"""
213
+ if file is None:
214
+ return ""
215
+
216
+ file_path = file.name
217
+ if file_path.endswith('.pdf'):
218
+ return read_pdf(file_path)
219
+ elif file_path.endswith('.cha'):
220
+ return read_cha_file(file_path)
221
+ else:
222
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
223
+ return f.read()
224
+
225
+ # ===============================
226
+ # AI Model Interface Functions
227
+ # ===============================
228
+
229
+ def call_bedrock(prompt, max_tokens=4096):
230
+ """Call the AWS Bedrock API to analyze text using Claude"""
231
+ if not bedrock_client:
232
+ return "AWS credentials not configured. Please set your AWS credentials as secrets in the Space settings."
233
+
234
+ try:
235
+ body = json.dumps({
236
+ "anthropic_version": "bedrock-2023-05-31",
237
+ "max_tokens": max_tokens,
238
+ "messages": [
239
+ {
240
+ "role": "user",
241
+ "content": prompt
242
+ }
243
+ ],
244
+ "temperature": 0.3,
245
+ "top_p": 0.9
246
+ })
247
+
248
+ modelId = 'anthropic.claude-3-sonnet-20240229-v1:0'
249
+ response = bedrock_client.invoke_model(
250
+ body=body,
251
+ modelId=modelId,
252
+ accept='application/json',
253
+ contentType='application/json'
254
+ )
255
+ response_body = json.loads(response.get('body').read())
256
+ return response_body['content'][0]['text']
257
+ except Exception as e:
258
+ logger.error(f"Error in call_bedrock: {str(e)}")
259
+ return f"Error: {str(e)}"
260
+
261
+ def generate_demo_response(prompt):
262
+ """Generate a simulated response for demo purposes"""
263
+ # This function generates a realistic but fake response for demo purposes
264
+ # In a real deployment, you would call an actual LLM API
265
+
266
+ random_seed = sum(ord(c) for c in prompt) % 1000 # Generate a seed based on prompt
267
+ np.random.seed(random_seed)
268
+
269
+ # Simulate speech factors with random but reasonable values
270
+ factors = [
271
+ "Difficulty producing fluent speech",
272
+ "Word retrieval issues",
273
+ "Grammatical errors",
274
+ "Repetitions and revisions",
275
+ "Neologisms",
276
+ "Perseveration",
277
+ "Comprehension issues"
278
+ ]
279
+
280
+ occurrences = np.random.randint(1, 15, size=len(factors))
281
+ percentiles = np.random.randint(30, 95, size=len(factors))
282
+
283
+ # Simulate CASL scores
284
+ domains = ["Lexical/Semantic", "Syntactic", "Supralinguistic"]
285
+ scores = np.random.randint(80, 115, size=3)
286
+ percentiles_casl = [int(np.interp(s, [70, 85, 100, 115, 130], [2, 16, 50, 84, 98])) for s in scores]
287
+
288
+ perf_levels = []
289
+ for s in scores:
290
+ if s < 70: perf_levels.append("Well Below Average")
291
+ elif s < 85: perf_levels.append("Below Average")
292
+ elif s < 115: perf_levels.append("Average")
293
+ elif s < 130: perf_levels.append("Above Average")
294
+ else: perf_levels.append("Well Above Average")
295
+
296
+ # Build response
297
+ response = "## Speech Factor Analysis\n\n"
298
+ for i, factor in enumerate(factors):
299
+ response += f"{factor}: {occurrences[i]}, {percentiles[i]}\n"
300
+
301
+ response += "\n## CASL-2 Assessment\n\n"
302
+ for i, domain in enumerate(domains):
303
+ response += f"{domain} Skills: Standard Score ({scores[i]}), Percentile Rank ({percentiles_casl[i]}%), Performance Level ({perf_levels[i]})\n"
304
+
305
+ response += "\n## Other analysis/Best plans of action:\n\n"
306
+ suggestions = [
307
+ "Implement word-finding strategies with semantic cuing",
308
+ "Practice structured narrative tasks with visual supports",
309
+ "Use sentence formulation exercises with increasing complexity",
310
+ "Incorporate self-monitoring techniques during structured conversations",
311
+ "Work on grammatical forms through structured practice"
312
+ ]
313
+ for suggestion in suggestions:
314
+ response += f"- {suggestion}\n"
315
+
316
+ response += "\n## Explanation:\n\n"
317
+ response += "Based on the analysis, this patient demonstrates moderate word-finding difficulties with compensatory strategies like filler words and repetitions. Their syntactic skills show some weakness in verb tense consistency. Treatment should focus on building vocabulary access, grammatical accuracy, and narrative structure using scaffolded support.\n"
318
+
319
+ response += "\n## Additional Analysis:\n\n"
320
+ response += "The patient shows relative strengths in conversation maintenance and topic coherence. Consider building on these strengths while addressing specific language formulation challenges. Recommended frequency: 2-3 sessions per week for 10-12 weeks with periodic reassessment."
321
+
322
+ return response
323
+
324
+ def generate_demo_transcription(audio_path):
325
+ """Generate a simulated transcription response"""
326
+ # In a real app, this would process an audio file
327
+ return "*PAR: today I want to tell you about my favorite toy.\n*PAR: it's a &-um teddy bear that I got for my birthday.\n*PAR: he has &-um brown fur and a red bow.\n*PAR: I like to sleep with him every night.\n*PAR: sometimes I take him to school in my backpack."
328
+
329
+ def generate_demo_qa_response(question):
330
+ """Generate a simulated Q&A response"""
331
+ qa_responses = {
332
+ "what is casl": "CASL-2 (Comprehensive Assessment of Spoken Language, Second Edition) is a standardized assessment tool used by Speech-Language Pathologists to evaluate a child's oral language abilities across multiple domains including lexical/semantic, syntactic, and supralinguistic skills. It helps identify language disorders and guides intervention planning.",
333
+ "how do i interpret scores": "CASL-2 scores include standard scores (mean=100, SD=15), percentile ranks, and performance levels. Standard scores below 85 indicate below average performance, 85-115 is average, and above 115 is above average. Percentile ranks show how a child performs relative to same-age peers.",
334
+ "what activities help word finding": "Activities to improve word-finding skills include semantic feature analysis (describing attributes of objects), categorization tasks, word association games, rapid naming practice, and structured conversation with gentle cueing. Visual supports and semantic mapping can also be helpful.",
335
+ "how often should therapy occur": "The recommended frequency for speech-language therapy typically ranges from 1-3 sessions per week, depending on the severity of the impairment. For moderate difficulties, twice weekly sessions of 30-45 minutes are common. Consistency is important for progress.",
336
+ "when should i reassess": "Reassessment is typically recommended every 3-6 months to track progress and adjust treatment goals. For educational settings, annual reassessment is common. More frequent informal assessments can help guide ongoing intervention.",
337
+ }
338
+
339
+ # Simple keyword matching for demo purposes
340
+ for key, response in qa_responses.items():
341
+ if key in question.lower():
342
+ return response
343
+
344
+ return "I don't have specific information about that topic. For detailed professional guidance, consult with a licensed Speech-Language Pathologist who can provide advice specific to your situation."
345
+
346
+ # ===============================
347
+ # Analysis Functions
348
+ # ===============================
349
+
350
+ def parse_casl_response(response):
351
+ """Parse the LLM response for CASL analysis into structured data"""
352
+ lines = response.split('\n')
353
+ data = {
354
+ 'Factor': [],
355
+ 'Occurrences': [],
356
+ 'Severity': [],
357
+ 'Examples': [] # Added field for error examples
358
+ }
359
+
360
+ casl_data = {
361
+ 'Domain': ['Lexical/Semantic', 'Syntactic', 'Supralinguistic'],
362
+ 'Standard Score': [0, 0, 0],
363
+ 'Percentile': [0, 0, 0],
364
+ 'Performance Level': ['', '', ''],
365
+ 'Examples': ['', '', ''] # Added field for specific examples
366
+ }
367
+
368
+ treatment_suggestions = []
369
+ explanation = ""
370
+ additional_analysis = ""
371
+ specific_errors = {} # Track specific error examples by factor
372
+ raw_response = response # Store the complete raw LLM response
373
+
374
+ # Pattern to match factor lines - updated to potentially capture examples
375
+ factor_pattern = re.compile(r'([\w\s/]+):\s*(\d+)[,\s]+(\d+)(?:\s*-\s*(.+))?')
376
+
377
+ # Pattern to match CASL data
378
+ casl_pattern = re.compile(r'(\w+/?\w*)\s+Skills:\s+Standard\s+Score\s+\((\d+)\),\s+Percentile\s+Rank\s+\((\d+)%\),\s+Performance\s+Level\s+\(([\w\s]+)\)')
379
+
380
+ # Pattern to find examples
381
+ example_pattern = re.compile(r'(?:Example|Examples|observed|observed in)[^\"\'"]*[\"\']([^\"\']*)[\"\']')
382
+ error_pattern = re.compile(r'(?:error|errors|difficulty|difficulties)[^\"\'"]*[\"\']([^\"\']*)[\"\']')
383
+
384
+ current_factor = None
385
+ current_domain = None
386
+ in_suggestions = False
387
+ in_explanation = False
388
+ in_additional = False
389
+ in_examples = False
390
+
391
+ for i, line in enumerate(lines):
392
+ line = line.strip()
393
+
394
+ # Skip empty lines
395
+ if not line:
396
+ continue
397
+
398
+ # Check for factor data
399
+ factor_match = factor_pattern.search(line)
400
+ if factor_match:
401
+ factor = factor_match.group(1).strip()
402
+ occurrences = int(factor_match.group(2))
403
+ severity = int(factor_match.group(3))
404
+ example = factor_match.group(4) if factor_match.group(4) else ""
405
+
406
+ # Look ahead to find examples for this factor
407
+ if not example:
408
+ # Check next few lines for examples
409
+ for j in range(i+1, min(i+5, len(lines))):
410
+ next_line = lines[j].strip()
411
+ if next_line and ('"' in next_line or "'" in next_line):
412
+ example_match = example_pattern.search(next_line)
413
+ if example_match:
414
+ example = example_match.group(1)
415
+ break
416
+ error_match = error_pattern.search(next_line)
417
+ if error_match:
418
+ example = error_match.group(1)
419
+ break
420
+
421
+ data['Factor'].append(factor)
422
+ data['Occurrences'].append(occurrences)
423
+ data['Severity'].append(severity)
424
+ data['Examples'].append(example)
425
+ specific_errors[factor] = example
426
+ current_factor = factor
427
+ continue
428
+
429
+ # Check for CASL data
430
+ casl_match = casl_pattern.search(line)
431
+ if casl_match:
432
+ domain = casl_match.group(1)
433
+ score = int(casl_match.group(2))
434
+ percentile = int(casl_match.group(3))
435
+ level = casl_match.group(4)
436
+
437
+ domain_examples = ""
438
+ # Look ahead for examples related to this domain
439
+ for j in range(i+1, min(i+10, len(lines))):
440
+ next_line = lines[j].strip()
441
+ if "Domain:" in next_line or casl_pattern.search(next_line):
442
+ break
443
+ if ('"' in next_line or "'" in next_line) and "example" in next_line.lower():
444
+ example_match = re.search(r'[\"\']([^\"\']*)[\"\']', next_line)
445
+ if example_match:
446
+ domain_examples = example_match.group(1)
447
+ break
448
+
449
+ if "Lexical" in domain:
450
+ casl_data['Standard Score'][0] = score
451
+ casl_data['Percentile'][0] = percentile
452
+ casl_data['Performance Level'][0] = level
453
+ casl_data['Examples'][0] = domain_examples
454
+ current_domain = "Lexical/Semantic"
455
+ elif "Syntactic" in domain:
456
+ casl_data['Standard Score'][1] = score
457
+ casl_data['Percentile'][1] = percentile
458
+ casl_data['Performance Level'][1] = level
459
+ casl_data['Examples'][1] = domain_examples
460
+ current_domain = "Syntactic"
461
+ elif "Supralinguistic" in domain:
462
+ casl_data['Standard Score'][2] = score
463
+ casl_data['Percentile'][2] = percentile
464
+ casl_data['Performance Level'][2] = level
465
+ casl_data['Examples'][2] = domain_examples
466
+ current_domain = "Supralinguistic"
467
+ continue
468
+
469
+ # Check for section headers
470
+ if "Other analysis/Best plans of action:" in line or "### Recommended Treatment Approaches" in line or "Treatment Recommendations:" in line:
471
+ in_suggestions = True
472
+ in_explanation = False
473
+ in_additional = False
474
+ in_examples = False
475
+ continue
476
+ elif "Explanation:" in line or "### Clinical Rationale" in line or "Clinical Rationale:" in line:
477
+ in_suggestions = False
478
+ in_explanation = True
479
+ in_additional = False
480
+ in_examples = False
481
+ continue
482
+ elif "Additional Analysis:" in line or "Further Observations:" in line:
483
+ in_suggestions = False
484
+ in_explanation = False
485
+ in_additional = True
486
+ in_examples = False
487
+ continue
488
+ elif "Examples:" in line or "Specific Errors:" in line:
489
+ in_suggestions = False
490
+ in_explanation = False
491
+ in_additional = False
492
+ in_examples = True
493
+ continue
494
+
495
+ # Add content to appropriate section
496
+ if in_suggestions:
497
+ if line.startswith("- "):
498
+ treatment_suggestions.append(line[2:]) # Remove the bullet point
499
+ elif line.startswith("•"):
500
+ treatment_suggestions.append(line[1:].strip()) # Remove bullet and trim
501
+ elif line and not line.startswith("#"):
502
+ # Non-empty, non-header lines might be treatment suggestions without bullets
503
+ treatment_suggestions.append(line)
504
+ elif in_explanation:
505
+ explanation += line + "\n"
506
+ elif in_additional:
507
+ additional_analysis += line + "\n"
508
+ elif in_examples and current_factor and not specific_errors.get(current_factor):
509
+ # Look for quoted examples in the examples section
510
+ if '"' in line or "'" in line:
511
+ example_match = re.search(r'[\"\']([^\"\']*)[\"\']', line)
512
+ if example_match:
513
+ specific_errors[current_factor] = example_match.group(1)
514
+ # Update the examples in the dataframe
515
+ if current_factor in data['Factor']:
516
+ idx = data['Factor'].index(current_factor)
517
+ data['Examples'][idx] = example_match.group(1)
518
+
519
+ # Continuously look for examples with quotes regardless of section
520
+ if ('"' in line or "'" in line) and current_factor:
521
+ if re.search(rf'{current_factor}.*[\"\']([^\"\']*)[\"\']', line, re.IGNORECASE):
522
+ example_match = re.search(r'[\"\']([^\"\']*)[\"\']', line)
523
+ if example_match:
524
+ specific_errors[current_factor] = example_match.group(1)
525
+ # Update in dataframe
526
+ if current_factor in data['Factor']:
527
+ idx = data['Factor'].index(current_factor)
528
+ data['Examples'][idx] = example_match.group(1)
529
+
530
+ # Process specific errors and examples if they're presented as a list later in the text
531
+ for i, line in enumerate(lines):
532
+ if "examples of errors" in line.lower() or "error examples" in line.lower():
533
+ # Look through next few lines for examples
534
+ for j in range(i+1, min(i+15, len(lines))):
535
+ example_line = lines[j].strip()
536
+ if not example_line or example_line.startswith("#"):
537
+ continue
538
+
539
+ # Look for factors mentioned with examples
540
+ for factor in data['Factor']:
541
+ if factor.lower() in example_line.lower() and ('"' in example_line or "'" in example_line):
542
+ example_match = re.search(r'[\"\']([^\"\']*)[\"\']', example_line)
543
+ if example_match:
544
+ idx = data['Factor'].index(factor)
545
+ data['Examples'][idx] = example_match.group(1)
546
+ specific_errors[factor] = example_match.group(1)
547
+
548
+ return {
549
+ 'speech_factors': pd.DataFrame(data),
550
+ 'casl_data': pd.DataFrame(casl_data),
551
+ 'treatment_suggestions': treatment_suggestions,
552
+ 'explanation': explanation,
553
+ 'additional_analysis': additional_analysis,
554
+ 'specific_errors': specific_errors,
555
+ 'raw_response': raw_response # Include the full LLM response
556
+ }
557
+
558
+ def create_casl_plots(speech_factors, casl_data):
559
+ """Create visualizations for the CASL analysis results"""
560
+
561
+ # Set a professional style for the plots
562
+ plt.style.use('seaborn-v0_8-pastel')
563
+
564
+ # Create figure with two subplots
565
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6), dpi=100)
566
+
567
+ # Plot speech factors - sorted by occurrence count
568
+ if not speech_factors.empty:
569
+ # Sort the dataframe
570
+ speech_factors_sorted = speech_factors.sort_values('Occurrences', ascending=False)
571
+
572
+ # Custom colors
573
+ speech_colors = ['#4C72B0', '#55A868', '#C44E52', '#8172B3', '#CCB974', '#64B5CD', '#4C72B0']
574
+
575
+ # Create horizontal bar chart
576
+ bars = ax1.barh(speech_factors_sorted['Factor'],
577
+ speech_factors_sorted['Occurrences'],
578
+ color=speech_colors[:len(speech_factors_sorted)])
579
+
580
+ # Add count labels at the end of each bar
581
+ for i, bar in enumerate(bars):
582
+ width = bar.get_width()
583
+ factor = speech_factors_sorted.iloc[i]['Factor']
584
+
585
+ # Get severity percentile for this factor
586
+ severity = speech_factors_sorted.iloc[i]['Severity']
587
+
588
+ # Label with both count and severity percentile
589
+ ax1.text(width + 0.3, bar.get_y() + bar.get_height()/2,
590
+ f'{width:.0f} ({severity}%)', ha='left', va='center')
591
+
592
+ # Add example as annotation if available
593
+ if 'Examples' in speech_factors_sorted.columns:
594
+ example = speech_factors_sorted.iloc[i]['Examples']
595
+ if example and len(example) > 0:
596
+ # Add a small marker to indicate example exists
597
+ ax1.text(0.5, bar.get_y() + bar.get_height()/2,
598
+ '★', ha='center', va='center', color='#C44E52',
599
+ fontsize=8, fontweight='bold')
600
+
601
+ ax1.set_title('Speech Factors Analysis', fontsize=14, fontweight='bold')
602
+ ax1.set_xlabel('Number of Occurrences', fontsize=11)
603
+ # No y-label needed for horizontal bar chart
604
+
605
+ # Remove top and right spines
606
+ ax1.spines['top'].set_visible(False)
607
+ ax1.spines['right'].set_visible(False)
608
+
609
+ # Add a footnote about the star symbol
610
+ ax1.annotate('★ = Example available in details panel', xy=(0, -0.1), xycoords='axes fraction',
611
+ fontsize=8, ha='left', va='center', color='#C44E52')
612
+
613
+ # Plot CASL domains
614
+ domain_names = casl_data['Domain']
615
+ y_scores = casl_data['Standard Score']
616
+ percentiles = casl_data['Percentile']
617
+
618
+ # Custom color scheme
619
+ casl_colors = ['#4C72B0', '#55A868', '#C44E52']
620
+
621
+ # Create bars with nice colors
622
+ bars = ax2.bar(domain_names, y_scores, color=casl_colors)
623
+
624
+ # Add score labels on top of each bar
625
+ for i, bar in enumerate(bars):
626
+ height = bar.get_height()
627
+ score = y_scores.iloc[i]
628
+ percentile = percentiles.iloc[i]
629
+
630
+ # Label with both score and percentile
631
+ ax2.text(bar.get_x() + bar.get_width()/2., height + 1,
632
+ f'{score:.0f} ({percentile}%)', ha='center', va='bottom')
633
+
634
+ # Add a star marker if example exists
635
+ if 'Examples' in casl_data.columns:
636
+ example = casl_data.iloc[i]['Examples']
637
+ if example and len(example) > 0:
638
+ ax2.text(bar.get_x() + bar.get_width()/2., height/2,
639
+ '★', ha='center', va='center', color='white',
640
+ fontsize=12, fontweight='bold')
641
+
642
+ # Add score reference lines
643
+ ax2.axhline(y=100, linestyle='--', color='gray', alpha=0.7, label='Average (100)')
644
+ ax2.axhline(y=85, linestyle=':', color='orange', alpha=0.7, label='Below Average (<85)')
645
+ ax2.axhline(y=115, linestyle=':', color='green', alpha=0.7, label='Above Average (>115)')
646
+
647
+ # Add labels and title
648
+ ax2.set_title('CASL-2 Standard Scores', fontsize=14, fontweight='bold')
649
+ ax2.set_ylabel('Standard Score', fontsize=11)
650
+ ax2.set_ylim(bottom=0, top=max(130, max(y_scores) + 15)) # Set y-axis limit with some padding
651
+
652
+ # Add legend
653
+ ax2.legend(loc='upper right', fontsize='small')
654
+
655
+ # Remove top and right spines
656
+ ax2.spines['top'].set_visible(False)
657
+ ax2.spines['right'].set_visible(False)
658
+
659
+ # Add overall figure title
660
+ fig.suptitle('Speech Analysis Results', fontsize=16, fontweight='bold', y=0.98)
661
+
662
+ # Add a subtitle with note about examples
663
+ plt.figtext(0.5, 0.01, '★ indicates specific examples available in the Error Examples panel',
664
+ ha='center', fontsize=9, fontstyle='italic')
665
+
666
+ plt.tight_layout(rect=[0, 0.03, 1, 0.95]) # Adjust layout to make room for suptitle
667
+
668
+ # Save plot to buffer
669
+ buf = io.BytesIO()
670
+ plt.savefig(buf, format='png', bbox_inches='tight')
671
+ buf.seek(0)
672
+ plt.close()
673
+
674
+ return buf
675
+
676
+ def create_casl_radar_chart(speech_factors):
677
+ """Create a radar chart for speech factors (percentiles)"""
678
+
679
+ if speech_factors.empty or 'Severity' not in speech_factors.columns:
680
+ # Create a placeholder image if no data
681
+ plt.figure(figsize=(8, 8))
682
+ plt.text(0.5, 0.5, "No data available for radar chart",
683
+ ha='center', va='center', fontsize=14)
684
+ plt.axis('off')
685
+
686
+ buf = io.BytesIO()
687
+ plt.savefig(buf, format='png')
688
+ buf.seek(0)
689
+ plt.close()
690
+ return buf
691
+
692
+ # Prepare data for radar chart
693
+ categories = speech_factors['Factor'].tolist()
694
+ percentiles = speech_factors['Severity'].tolist()
695
+
696
+ # Need to repeat first value to close the polygon
697
+ categories = categories + [categories[0]]
698
+ percentiles = percentiles + [percentiles[0]]
699
+
700
+ # Convert to radians and calculate points
701
+ N = len(categories) - 1 # Subtract 1 for the repeated point
702
+ angles = [n / float(N) * 2 * np.pi for n in range(N)]
703
+ angles += angles[:1] # Repeat the first angle to close the polygon
704
+
705
+ # Create the plot
706
+ fig = plt.figure(figsize=(8, 8))
707
+ ax = fig.add_subplot(111, polar=True)
708
+
709
+ # Draw percentile lines with labels
710
+ plt.xticks(angles[:-1], categories[:-1], size=12)
711
+ ax.set_rlabel_position(0)
712
+ plt.yticks([20, 40, 60, 80, 100], ["20", "40", "60", "80", "100"], color="grey", size=10)
713
+ plt.ylim(0, 100)
714
+
715
+ # Plot data
716
+ ax.plot(angles, percentiles, linewidth=1, linestyle='solid', color='#4C72B0')
717
+ ax.fill(angles, percentiles, color='#4C72B0', alpha=0.25)
718
+
719
+ # Add title
720
+ plt.title('Speech Factors Severity (Percentile)', size=15, fontweight='bold', pad=20)
721
+
722
+ # Save to buffer
723
+ buf = io.BytesIO()
724
+ plt.savefig(buf, format='png', bbox_inches='tight')
725
+ buf.seek(0)
726
+ plt.close()
727
+
728
+ return buf
729
+
730
+ def analyze_transcript(transcript, age, gender):
731
+ """Analyze a speech transcript using the CASL framework"""
732
+
733
+ # Instructions for the LLM analysis
734
+ instructions = """
735
+ You're a professional Speech-Language Pathologist analyzing this transcription sample.
736
+
737
+ For your analysis, count occurrences of:
738
+
739
+ 1. Difficulty producing fluent, grammatical speech - Speech that is slow, halting, with pauses while searching for words
740
+ 2. Word retrieval issues - Trouble finding specific words, using fillers like "um", circumlocution, or semantically similar substitutions
741
+ 3. Grammatical errors - Missing/incorrect function words, verb tense problems, simplified sentences
742
+ 4. Repetitions and revisions - Repeating or restating due to word-finding or sentence construction difficulties
743
+ 5. Neologisms - Creating nonexistent "new" words
744
+ 6. Perseveration - Unintentionally repeating words or phrases
745
+ 7. Comprehension issues - Difficulty understanding complex sentences or fast speech
746
+
747
+ Analyze using the CASL-2 (Comprehensive Assessment of Spoken Language) framework:
748
+
749
+ Lexical/Semantic Skills:
750
+ - Evaluate vocabulary diversity, word retrieval difficulties, and semantic precision
751
+ - Estimate Standard Score (mean=100, SD=15), percentile rank, and performance level
752
+ - Quote specific examples from the transcript to support your assessment
753
+
754
+ Syntactic Skills:
755
+ - Assess sentence structure, grammatical accuracy, and syntactic complexity
756
+ - Estimate Standard Score, percentile rank, and performance level
757
+ - Quote specific examples from the transcript to support your assessment
758
+
759
+ Supralinguistic Skills:
760
+ - Evaluate figurative language use, inferencing, and contextual understanding
761
+ - Estimate Standard Score, percentile rank, and performance level
762
+ - Quote specific examples from the transcript to support your assessment
763
+
764
+ Format your analysis with:
765
+ 1. Speech factor counts with severity percentiles - Include direct quotes for examples of each factor you identify
766
+ 2. CASL-2 domain scores with performance levels - Include direct quotes for examples in each domain
767
+ 3. Treatment recommendations based on findings
768
+ 4. Brief explanation of your rationale
769
+ 5. Any additional insights
770
+
771
+ IMPORTANT: For each factor and domain you analyze, provide direct quotes from the transcript as evidence. For example:
772
+
773
+ Word retrieval issues: 7, 65% - Example: "today I would &-um like to talk about &-um a fun trip"
774
+ Grammatical errors: 3, 45% - Example: "after swimming we [//] I eat [: ate] [*] &-um ice cream"
775
+
776
+ This specificity is critical for diagnostic accuracy and treatment planning.
777
+ """
778
+
779
+ # Prepare prompt for Claude
780
+ prompt = f"""
781
+ You are an experienced Speech-Language Pathologist analyzing this transcript for a patient who is {age} years old and {gender}.
782
+
783
+ TRANSCRIPT:
784
+ {transcript}
785
+
786
+ {instructions}
787
+
788
+ Be precise, professional, and empathetic in your analysis. Focus on the linguistic patterns present in the sample.
789
+ """
790
+
791
+ # Call the appropriate API or fallback to demo mode
792
+ if bedrock_client:
793
+ response = call_bedrock(prompt)
794
+ else:
795
+ response = generate_demo_response(prompt)
796
+
797
+ # Parse the response
798
+ results = parse_casl_response(response)
799
+
800
+ # Create visualizations
801
+ plot_image = create_casl_plots(results['speech_factors'], results['casl_data'])
802
+ radar_image = create_casl_radar_chart(results['speech_factors'])
803
+
804
+ return results, plot_image, radar_image, response
805
+
806
+ def generate_report(patient_info, analysis_results, report_type="formal"):
807
+ """Generate a professional report based on analysis results"""
808
+
809
+ patient_name = patient_info.get("name", "")
810
+ record_id = patient_info.get("record_id", "")
811
+ age = patient_info.get("age", "")
812
+ gender = patient_info.get("gender", "")
813
+ assessment_date = patient_info.get("assessment_date", datetime.now().strftime('%m/%d/%Y'))
814
+ clinician = patient_info.get("clinician", "")
815
+
816
+ prompt = f"""
817
+ You are a professional Speech-Language Pathologist creating a {report_type} report based on an assessment.
818
+
819
+ PATIENT INFORMATION:
820
+ Name: {patient_name}
821
+ Record ID: {record_id}
822
+ Age: {age}
823
+ Gender: {gender}
824
+ Assessment Date: {assessment_date}
825
+ Clinician: {clinician}
826
+
827
+ ASSESSMENT RESULTS:
828
+ {analysis_results}
829
+
830
+ Please create a professional {report_type} report that includes:
831
+ 1. Patient information and assessment details
832
+ 2. Summary of findings (strengths and areas of concern)
833
+ 3. Detailed analysis of language domains
834
+ 4. Specific recommendations for therapy
835
+ 5. Recommendation for frequency and duration of services
836
+
837
+ Use clear, professional language appropriate for {'educational professionals' if report_type == 'formal' else 'parents and caregivers'}.
838
+ Format the report with proper headings and sections.
839
+ """
840
+
841
+ # Call the API or use demo mode
842
+ if bedrock_client:
843
+ report = call_bedrock(prompt, max_tokens=6000)
844
+ else:
845
+ # For demo, create a simulated report
846
+ if report_type == 'formal':
847
+ report = f"""
848
+ # FORMAL LANGUAGE ASSESSMENT REPORT
849
+
850
+ **Date of Assessment:** {assessment_date}
851
+ **Clinician:** {clinician}
852
+
853
+ ## PATIENT INFORMATION
854
+ **Name:** {patient_name}
855
+ **Record ID:** {record_id}
856
+ **Age:** {age}
857
+ **Gender:** {gender}
858
+
859
+ ## ASSESSMENT SUMMARY
860
+
861
+ The patient was assessed using the Comprehensive Assessment of Spoken Language, Second Edition (CASL-2) to evaluate language skills across multiple domains. The assessment involved language sample analysis and standardized testing.
862
+
863
+ ## KEY FINDINGS
864
+
865
+ **Areas of Strength:**
866
+ - Ability to maintain conversational topics
867
+ - Good vocabulary for everyday topics
868
+ - Strong nonverbal communication skills
869
+
870
+ **Areas of Challenge:**
871
+ - Word-finding difficulties during conversation
872
+ - Grammatical errors in complex sentences
873
+ - Difficulty with abstract language concepts
874
+
875
+ ## DETAILED ANALYSIS
876
+
877
+ **Lexical/Semantic Skills:** Standard Score 91 (27th percentile) - Low Average Range
878
+ The student demonstrates adequate vocabulary but struggles with retrieving specific words during conversation. Word-finding pauses were noted throughout the language sample.
879
+
880
+ **Syntactic Skills:** Standard Score 85 (16th percentile) - Low Average Range
881
+ The student shows difficulty with complex grammatical structures, particularly verb tense consistency and complex sentence formation.
882
+
883
+ **Supralinguistic Skills:** Standard Score 83 (13th percentile) - Below Average Range
884
+ The student struggles with understanding figurative language, making inferences, and comprehending abstract concepts.
885
+
886
+ ## RECOMMENDATIONS
887
+
888
+ 1. Speech-Language Therapy focused on:
889
+ - Word-finding strategies using semantic feature analysis
890
+ - Structured grammatical exercises to improve sentence complexity
891
+ - Explicit instruction in figurative language comprehension
892
+ - Narrative language development using visual supports
893
+
894
+ 2. Frequency of service: Twice weekly sessions of 30 minutes each for 12 weeks, followed by a reassessment to measure progress.
895
+
896
+ 3. Classroom accommodations including:
897
+ - Extended time for verbal responses
898
+ - Visual supports for complex instructions
899
+ - Pre-teaching of vocabulary for academic units
900
+
901
+ ## PROGNOSIS
902
+
903
+ The prognosis for improvement is good with consistent therapeutic intervention and support. Regular reassessment is recommended to monitor progress.
904
+
905
+ Respectfully submitted,
906
+
907
+ {clinician}
908
+ Speech-Language Pathologist
909
+ """
910
+ else:
911
+ report = f"""
912
+ # PARENT-FRIENDLY LANGUAGE ASSESSMENT SUMMARY
913
+
914
+ **Date of Assessment:** {assessment_date}
915
+ **Clinician:** {clinician}
916
+
917
+ ## PATIENT INFORMATION
918
+ **Name:** {patient_name}
919
+ **Record ID:** {record_id}
920
+ **Age:** {age}
921
+ **Gender:** {gender}
922
+
923
+ ## ASSESSMENT SUMMARY
924
+
925
+ We completed a language assessment to better understand your child's communication strengths and challenges. This helps us create a plan to support their development.
926
+
927
+ ## KEY FINDINGS
928
+
929
+ **Areas of Strength:**
930
+ - Ability to maintain conversational topics
931
+ - Good vocabulary for everyday topics
932
+ - Strong nonverbal communication skills
933
+
934
+ **Areas of Challenge:**
935
+ - Word-finding difficulties during conversation
936
+ - Grammatical errors in complex sentences
937
+ - Difficulty with abstract language concepts
938
+
939
+ ## DETAILED ANALYSIS
940
+
941
+ **Lexical/Semantic Skills:** Standard Score 91 (27th percentile) - Low Average Range
942
+ The student demonstrates adequate vocabulary but struggles with retrieving specific words during conversation. Word-finding pauses were noted throughout the language sample.
943
+
944
+ **Syntactic Skills:** Standard Score 85 (16th percentile) - Low Average Range
945
+ The student shows difficulty with complex grammatical structures, particularly verb tense consistency and complex sentence formation.
946
+
947
+ **Supralinguistic Skills:** Standard Score 83 (13th percentile) - Below Average Range
948
+ The student struggles with understanding figurative language, making inferences, and comprehending abstract concepts.
949
+
950
+ ## RECOMMENDATIONS
951
+
952
+ We recommend:
953
+ - Word-finding strategies using semantic feature analysis
954
+ - Structured grammatical exercises to improve sentence complexity
955
+ - Explicit instruction in figurative language comprehension
956
+ - Narrative language development using visual supports
957
+
958
+ 2. We recommend therapy twice a week for 30 minutes. This consistency will help your child make better progress.
959
+
960
+ 3. In school, your child may benefit from:
961
+ - Extended time for verbal responses
962
+ - Visual supports for complex instructions
963
+ - Pre-teaching of vocabulary for academic units
964
+
965
+ ## PROGNOSIS
966
+
967
+ With regular therapy and support at home, we expect your child to make good progress in these areas.
968
+
969
+ Please reach out with any questions!
970
+
971
+ {clinician}
972
+ Speech-Language Pathologist
973
+ """
974
+
975
+ return report
976
+
977
+ def transcribe_audio(audio_path, patient_age):
978
+ """Transcribe an audio recording using CHAT format"""
979
+ # In a real implementation, this would use a speech-to-text service
980
+ # For demo purposes, we'll return a simulated transcription
981
+
982
+ if bedrock_client:
983
+ # In a real implementation, you would process the audio file and send it to a transcription service
984
+ # Here we just simulate the result
985
+ transcription = generate_demo_transcription(audio_path)
986
+ else:
987
+ transcription = generate_demo_transcription(audio_path)
988
+
989
+ return transcription
990
+
991
+ def answer_slp_question(question):
992
+ """Answer a question about SLP practice or CASL assessment"""
993
+
994
+ prompt = f"""
995
+ You are an experienced Speech-Language Pathologist answering a question from a colleague.
996
+
997
+ QUESTION:
998
+ {question}
999
+
1000
+ Please provide a clear, evidence-based answer focused specifically on the question asked.
1001
+ Reference best practices and current research where appropriate.
1002
+ Keep your answer concise but comprehensive.
1003
+ """
1004
+
1005
+ if bedrock_client:
1006
+ answer = call_bedrock(prompt)
1007
+ else:
1008
+ answer = generate_demo_qa_response(question)
1009
+
1010
+ return answer
1011
+
1012
+ # ===============================
1013
+ # Gradio Interface
1014
+ # ===============================
1015
+
1016
+ def create_interface():
1017
+ """Create the main Gradio interface"""
1018
+
1019
+ # Use a simple theme with default colors
1020
+ custom_theme = gr.themes.Soft(
1021
+ font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"]
1022
+ )
1023
+
1024
+ with gr.Blocks(theme=custom_theme, css="""
1025
+ .header {
1026
+ text-align: center;
1027
+ margin-bottom: 20px;
1028
+ }
1029
+ .header img {
1030
+ max-height: 100px;
1031
+ margin-bottom: 10px;
1032
+ }
1033
+ .container {
1034
+ border-radius: 10px;
1035
+ padding: 10px;
1036
+ margin-bottom: 20px;
1037
+ }
1038
+ .patient-info {
1039
+ background-color: #e3f2fd;
1040
+ }
1041
+ .speech-sample {
1042
+ background-color: #f0f8ff;
1043
+ }
1044
+ .results-container {
1045
+ background-color: #f9f9f9;
1046
+ }
1047
+ .viz-container {
1048
+ display: flex;
1049
+ justify-content: center;
1050
+ margin-bottom: 20px;
1051
+ }
1052
+ .footer {
1053
+ text-align: center;
1054
+ margin-top: 30px;
1055
+ padding: 10px;
1056
+ font-size: 0.8em;
1057
+ color: #78909C;
1058
+ }
1059
+ .info-box {
1060
+ background-color: #e8f5e9;
1061
+ border-left: 4px solid #4CAF50;
1062
+ padding: 10px 15px;
1063
+ margin-bottom: 15px;
1064
+ border-radius: 4px;
1065
+ }
1066
+ .warning-box {
1067
+ background-color: #fff8e1;
1068
+ border-left: 4px solid #FFC107;
1069
+ padding: 10px 15px;
1070
+ border-radius: 4px;
1071
+ }
1072
+ .markdown-text h3 {
1073
+ color: #2C7FB8;
1074
+ border-bottom: 1px solid #eaeaea;
1075
+ padding-bottom: 5px;
1076
+ }
1077
+ .evidence-table {
1078
+ border-collapse: collapse;
1079
+ width: 100%;
1080
+ }
1081
+ .evidence-table th, .evidence-table td {
1082
+ border: 1px solid #ddd;
1083
+ padding: 8px;
1084
+ text-align: left;
1085
+ }
1086
+ .evidence-table th {
1087
+ background-color: #f5f7fa;
1088
+ color: #333;
1089
+ }
1090
+ .evidence-table tr:nth-child(even) {
1091
+ background-color: #f9f9f9;
1092
+ }
1093
+ .tab-content {
1094
+ padding: 15px;
1095
+ background-color: white;
1096
+ border-radius: 0 0 8px 8px;
1097
+ box-shadow: 0 2px 5px rgba(0,0,0,0.05);
1098
+ }
1099
+ """) as app:
1100
+ # Create header with logo
1101
+ gr.HTML(
1102
+ """
1103
+ <div class="header">
1104
+ <h1>SLP Analysis Tool</h1>
1105
+ <p>A comprehensive assessment tool for Speech-Language Pathologists</p>
1106
+ </div>
1107
+ """
1108
+ )
1109
+
1110
+ # Main tabs
1111
+ with gr.Tabs() as main_tabs:
1112
+ # ===============================
1113
+ # CASL Analysis Tab
1114
+ # ===============================
1115
+ with gr.TabItem("CASL Analysis", id=0):
1116
+ with gr.Row():
1117
+ # Left column - Input section
1118
+ with gr.Column(scale=1):
1119
+ # Patient information panel
1120
+ with gr.Group(elem_classes="container patient-info"):
1121
+ gr.Markdown("### Patient Information")
1122
+
1123
+ with gr.Row():
1124
+ patient_name = gr.Textbox(label="Patient Name", placeholder="Enter patient name")
1125
+ record_id = gr.Textbox(label="Record ID", placeholder="Enter record ID")
1126
+
1127
+ with gr.Row():
1128
+ age = gr.Number(label="Age", value=8, minimum=1, maximum=120)
1129
+ gender = gr.Radio(["male", "female", "other"], label="Gender", value="male")
1130
+
1131
+ with gr.Row():
1132
+ assessment_date = gr.Textbox(
1133
+ label="Assessment Date",
1134
+ placeholder="MM/DD/YYYY",
1135
+ value=datetime.now().strftime('%m/%d/%Y')
1136
+ )
1137
+ clinician_name = gr.Textbox(
1138
+ label="Clinician",
1139
+ placeholder="Enter clinician name"
1140
+ )
1141
+
1142
+ # Speech sample panel
1143
+ with gr.Group(elem_classes="container speech-sample"):
1144
+ gr.Markdown("### Speech Sample")
1145
+
1146
+ # Sample button
1147
+ sample_btn = gr.Button("Load Sample Transcript", size="sm")
1148
+
1149
+ # Transcript input
1150
+ transcript = gr.Textbox(
1151
+ label="Transcript",
1152
+ placeholder="Paste the speech transcript here...",
1153
+ lines=10
1154
+ )
1155
+
1156
+ # Add info about transcript format
1157
+ gr.Markdown(
1158
+ """
1159
+ <div class="info-box">
1160
+ <strong>Transcript Format:</strong> Use CHAT format with *PAR: for patient lines.
1161
+ Mark word-finding with &-um, paraphasias with [*], and provide intended words with [: word].
1162
+ </div>
1163
+ """,
1164
+ elem_classes="markdown-text"
1165
+ )
1166
+
1167
+ # File upload
1168
+ file_upload = gr.File(
1169
+ label="Or upload a transcript file",
1170
+ file_types=["text", "txt", "pdf", "rtf"]
1171
+ )
1172
+
1173
+ # Analysis button
1174
+ analyze_btn = gr.Button("Analyze Speech Sample", variant="primary", size="lg")
1175
+
1176
+ # Right column - Results section
1177
+ with gr.Column(scale=1):
1178
+ with gr.Group(elem_classes="container results-container"):
1179
+ with gr.Tabs() as results_tabs:
1180
+ # Summary tab
1181
+ with gr.TabItem("Summary", id=0, elem_classes="tab-content"):
1182
+ with gr.Row():
1183
+ output_image = gr.Image(
1184
+ label="Speech Factors & CASL-2 Scores",
1185
+ show_label=True,
1186
+ elem_classes="viz-container"
1187
+ )
1188
+
1189
+ with gr.Row():
1190
+ radar_chart = gr.Image(
1191
+ label="Severity Profile",
1192
+ show_label=True,
1193
+ elem_classes="viz-container"
1194
+ )
1195
+
1196
+ with gr.Group():
1197
+ gr.Markdown("### Key Findings", elem_classes="markdown-text")
1198
+ speech_factors_table = gr.DataFrame(
1199
+ label="Speech Factors Analysis",
1200
+ headers=["Factor", "Occurrences", "Severity (Percentile)", "Example Errors"],
1201
+ interactive=False
1202
+ )
1203
+ casl_table = gr.DataFrame(
1204
+ label="CASL-2 Assessment",
1205
+ headers=["Domain", "Standard Score", "Percentile", "Performance Level", "Example"],
1206
+ interactive=False
1207
+ )
1208
+
1209
+ with gr.Accordion("Specific Error Examples", open=False):
1210
+ specific_errors_md = gr.Markdown(elem_classes="markdown-text")
1211
+
1212
+ # Treatment tab
1213
+ with gr.TabItem("Treatment Plan", id=1, elem_classes="tab-content"):
1214
+ gr.Markdown("### Recommended Treatment Approaches", elem_classes="markdown-text")
1215
+ treatment_md = gr.Markdown(elem_classes="treatment-panel")
1216
+
1217
+ gr.Markdown("### Clinical Rationale", elem_classes="markdown-text")
1218
+ explanation_md = gr.Markdown(elem_classes="panel")
1219
+
1220
+ with gr.Accordion("Supporting Evidence", open=False):
1221
+ gr.Markdown("""
1222
+ <table class="evidence-table">
1223
+ <tr>
1224
+ <th>Factor</th>
1225
+ <th>Evidence-based Approaches</th>
1226
+ <th>References</th>
1227
+ </tr>
1228
+ <tr>
1229
+ <td>Word Retrieval</td>
1230
+ <td>Semantic feature analysis, phonological cueing, word generation tasks</td>
1231
+ <td>Boyle, 2010; Kiran & Thompson, 2003</td>
1232
+ </tr>
1233
+ <tr>
1234
+ <td>Grammatical Errors</td>
1235
+ <td>Treatment of Underlying Forms (TUF), Morphosyntactic therapy</td>
1236
+ <td>Thompson et al., 2003; Ebbels, 2014</td>
1237
+ </tr>
1238
+ <tr>
1239
+ <td>Fluency/Prosody</td>
1240
+ <td>Rate control, rhythmic cueing, contrastive stress exercises</td>
1241
+ <td>Ballard et al., 2010; Tamplin & Baker, 2017</td>
1242
+ </tr>
1243
+ </table>
1244
+ """, elem_classes="markdown-text")
1245
+
1246
+ # Full report tab
1247
+ with gr.TabItem("Full Report", id=2, elem_classes="tab-content"):
1248
+ full_analysis = gr.Markdown()
1249
+
1250
+ # Add PDF export option
1251
+ export_btn = gr.Button("Export Report as PDF", variant="secondary")
1252
+ export_status = gr.Markdown("")
1253
+
1254
+ # Raw LLM Output tab
1255
+ with gr.TabItem("Raw LLM Output", id=3, elem_classes="tab-content"):
1256
+ gr.Markdown("### Complete Model Output", elem_classes="markdown-text")
1257
+ gr.Markdown("This tab shows the unprocessed output from the AI model for debugging purposes.")
1258
+ raw_llm_output = gr.Textbox(
1259
+ label="Raw AI Output",
1260
+ lines=20,
1261
+ interactive=False
1262
+ )
1263
+
1264
+ # ===============================
1265
+ # Patient Records Tab
1266
+ # ===============================
1267
+ with gr.TabItem("Patient Records", id=1):
1268
+ with gr.Row():
1269
+ with gr.Column(scale=1):
1270
+ gr.Markdown("### Patient Records")
1271
+
1272
+ # Records table
1273
+ patient_records_table = gr.Dataframe(
1274
+ headers=["ID", "Name", "Record ID", "Age", "Gender", "Assessment Date", "Clinician"],
1275
+ datatype=["str", "str", "str", "str", "str", "str", "str"],
1276
+ label="Saved Patients",
1277
+ interactive=False
1278
+ )
1279
+
1280
+ refresh_records_btn = gr.Button("Refresh Records", size="sm")
1281
+ records_status = gr.Markdown("")
1282
+
1283
+ # Record selection
1284
+ selected_record_id = gr.Textbox(label="Selected Record ID", visible=False)
1285
+ load_record_btn = gr.Button("Load Selected Record", variant="primary")
1286
+
1287
+ with gr.Column(scale=1):
1288
+ # Record details
1289
+ record_details = gr.Markdown(label="Record Details")
1290
+
1291
+ # Event handlers for records
1292
+ def refresh_patient_records():
1293
+ """Refresh the patient records table"""
1294
+ records = get_all_patient_records()
1295
+ data = []
1296
+ for r in records:
1297
+ data.append([
1298
+ r["id"], r["name"], r["record_id"],
1299
+ r["age"], r["gender"], r["assessment_date"], r["clinician"]
1300
+ ])
1301
+ df = pd.DataFrame(data)
1302
+ status_msg = f"Found {len(data)} patient records."
1303
+ return df, status_msg
1304
+
1305
+ refresh_records_btn.click(
1306
+ refresh_patient_records,
1307
+ outputs=[patient_records_table, records_status]
1308
+ )
1309
+
1310
+ # Automatically load records when tab is selected
1311
+ main_tabs.select(
1312
+ lambda tab_id: refresh_patient_records() if tab_id == 1 else (pd.DataFrame(), ""),
1313
+ inputs=[main_tabs],
1314
+ outputs=[patient_records_table, records_status]
1315
+ )
1316
+
1317
+ # Load record when a row is selected
1318
+ def handle_record_selection(evt: gr.SelectData, records):
1319
+ if records is None or len(records) == 0:
1320
+ return "", "No record selected."
1321
+
1322
+ selected_row = evt.index[0]
1323
+ if selected_row < len(records):
1324
+ record_id = records.iloc[selected_row, 0]
1325
+
1326
+ # Load the record to show details
1327
+ record_data = load_patient_record(record_id)
1328
+ if record_data:
1329
+ patient_info = record_data.get("patient_info", {})
1330
+
1331
+ # Format record details as markdown
1332
+ details = f"""
1333
+ ## Selected Patient Record
1334
+
1335
+ **Name:** {patient_info.get('name', 'N/A')}
1336
+ **Record ID:** {patient_info.get('record_id', 'N/A')}
1337
+ **Age:** {patient_info.get('age', 'N/A')}
1338
+ **Gender:** {patient_info.get('gender', 'N/A')}
1339
+ **Assessment Date:** {patient_info.get('assessment_date', 'N/A')}
1340
+ **Clinician:** {patient_info.get('clinician', 'N/A')}
1341
+ **Analyzed:** {record_data.get('timestamp', 'Unknown')}
1342
+
1343
+ ### Preview
1344
+
1345
+ This record contains:
1346
+ - Speech transcript analysis
1347
+ - CASL assessment results
1348
+ - Treatment recommendations
1349
+
1350
+ Click "Load Selected Record" to view the full analysis.
1351
+ """
1352
+
1353
+ return record_id, details
1354
+
1355
+ return record_id, f"Selected record: {record_id}"
1356
+
1357
+ return "", "Invalid selection."
1358
+
1359
+ patient_records_table.select(
1360
+ handle_record_selection,
1361
+ inputs=[patient_records_table],
1362
+ outputs=[selected_record_id, record_details]
1363
+ )
1364
+
1365
+ # Load record into analysis tab
1366
+ def load_patient_record_to_analysis(record_id):
1367
+ if not record_id:
1368
+ return gr.update(selected=1), {}, "", "", "", "", "", ""
1369
+
1370
+ record_data = load_patient_record(record_id)
1371
+ if not record_data:
1372
+ return gr.update(selected=1), "", "", "", "male", "", "", ""
1373
+
1374
+ # Extract data
1375
+ patient_info = record_data.get("patient_info", {})
1376
+ transcript_text = record_data.get("transcript", "")
1377
+ analysis_results = record_data.get("analysis_results", {})
1378
+
1379
+ # Create status message for the record loading
1380
+ status_msg = f"✅ Record loaded successfully: {patient_info.get('name', 'Unknown')} ({record_id})"
1381
+
1382
+ # Now we should also load the analysis results
1383
+ # In a future version, we would need to update all analysis outputs here as well
1384
+
1385
+ return (
1386
+ gr.update(selected=0), # Switch to analysis tab
1387
+ patient_info.get("name", ""),
1388
+ patient_info.get("record_id", ""),
1389
+ patient_info.get("age", ""),
1390
+ patient_info.get("gender", "male"),
1391
+ patient_info.get("assessment_date", ""),
1392
+ patient_info.get("clinician", ""),
1393
+ transcript_text,
1394
+ status_msg
1395
+ )
1396
+
1397
+ load_record_btn.click(
1398
+ load_patient_record_to_analysis,
1399
+ inputs=[selected_record_id],
1400
+ outputs=[
1401
+ main_tabs,
1402
+ patient_name, record_id, age, gender,
1403
+ assessment_date, clinician_name, transcript,
1404
+ records_status
1405
+ ]
1406
+ )
1407
+
1408
+ # ===============================
1409
+ # Report Generator Tab
1410
+ # ===============================
1411
+ with gr.TabItem("Report Generator", id=2):
1412
+ with gr.Row():
1413
+ with gr.Column(scale=1):
1414
+ gr.Markdown("### Generate Professional Reports")
1415
+
1416
+ # Patient info
1417
+ with gr.Group(elem_classes="container patient-info"):
1418
+ gr.Markdown("#### Patient Information")
1419
+ report_patient_name = gr.Textbox(label="Patient Name", placeholder="Enter patient name")
1420
+ report_record_id = gr.Textbox(label="Record ID", placeholder="Enter record ID")
1421
+ report_age = gr.Number(label="Age", value=8, minimum=1, maximum=120)
1422
+ report_gender = gr.Radio(["male", "female", "other"], label="Gender", value="male")
1423
+ report_date = gr.Textbox(
1424
+ label="Assessment Date",
1425
+ placeholder="MM/DD/YYYY",
1426
+ value=datetime.now().strftime('%m/%d/%Y')
1427
+ )
1428
+ report_clinician = gr.Textbox(label="Clinician", placeholder="Enter clinician name")
1429
+
1430
+ with gr.Group():
1431
+ gr.Markdown("#### Assessment Results")
1432
+ report_results = gr.Textbox(
1433
+ label="Paste assessment results or notes here",
1434
+ placeholder="Include key findings, test scores, and observations...",
1435
+ lines=10
1436
+ )
1437
+
1438
+ report_type = gr.Radio(
1439
+ ["Formal (for professionals)", "Parent-friendly"],
1440
+ label="Report Type",
1441
+ value="Formal (for professionals)"
1442
+ )
1443
+
1444
+ generate_report_btn = gr.Button("Generate Report", variant="primary")
1445
+
1446
+ with gr.Column(scale=1):
1447
+ report_output = gr.Markdown()
1448
+ report_download_btn = gr.Button("Download Report as PDF", variant="secondary")
1449
+ report_download_status = gr.Markdown("")
1450
+
1451
+ # ===============================
1452
+ # Transcription Tool Tab
1453
+ # ===============================
1454
+ with gr.TabItem("Transcription Tool", id=3):
1455
+ with gr.Row():
1456
+ with gr.Column(scale=1):
1457
+ gr.Markdown("### Audio Transcription Tool")
1458
+ gr.Markdown("Upload an audio recording to automatically transcribe it in CHAT format.")
1459
+
1460
+ audio_input = gr.Audio(type="filepath", label="Upload Audio Recording")
1461
+
1462
+ with gr.Row():
1463
+ transcription_age = gr.Number(label="Patient Age", value=8, minimum=1, maximum=120)
1464
+ transcribe_btn = gr.Button("Transcribe Audio", variant="primary")
1465
+
1466
+ with gr.Column(scale=1):
1467
+ transcription_output = gr.Textbox(
1468
+ label="Transcription Result",
1469
+ placeholder="Transcription will appear here...",
1470
+ lines=12
1471
+ )
1472
+
1473
+ with gr.Row():
1474
+ copy_to_analysis_btn = gr.Button("Use for Analysis", variant="secondary")
1475
+ edit_transcription_btn = gr.Button("Edit Transcription", variant="secondary")
1476
+
1477
+ # ===============================
1478
+ # SLP Assistant Tab
1479
+ # ===============================
1480
+ with gr.TabItem("SLP Assistant", id=4):
1481
+ with gr.Row():
1482
+ with gr.Column(scale=1):
1483
+ gr.Markdown("### SLP Knowledge Assistant")
1484
+ gr.Markdown("Ask questions about CASL assessment, therapy techniques, or SLP best practices.")
1485
+
1486
+ question_input = gr.Textbox(
1487
+ label="Your Question",
1488
+ placeholder="e.g., What activities help improve word-finding skills?",
1489
+ lines=3
1490
+ )
1491
+
1492
+ ask_question_btn = gr.Button("Ask Question", variant="primary")
1493
+
1494
+ # Quick question buttons
1495
+ gr.Markdown("#### Common Questions")
1496
+ with gr.Row():
1497
+ q1_btn = gr.Button("What is CASL?")
1498
+ q2_btn = gr.Button("How do I interpret scores?")
1499
+
1500
+ with gr.Row():
1501
+ q3_btn = gr.Button("Activities for word finding")
1502
+ q4_btn = gr.Button("When to reassess")
1503
+
1504
+ with gr.Column(scale=1):
1505
+ answer_output = gr.Markdown()
1506
+
1507
+ with gr.Accordion("References", open=False):
1508
+ gr.Markdown("""
1509
+ - American Speech-Language-Hearing Association (ASHA)
1510
+ - Comprehensive Assessment of Spoken Language (CASL-2) Manual
1511
+ - Evidence-Based Practice in Speech-Language Pathology
1512
+ - Current research in pediatric language intervention
1513
+ """)
1514
+
1515
+ # ===============================
1516
+ # Event Handlers
1517
+ # ===============================
1518
+
1519
+ # Load sample transcript button
1520
+ def load_sample():
1521
+ return SAMPLE_TRANSCRIPT
1522
+
1523
+ sample_btn.click(load_sample, outputs=[transcript])
1524
+
1525
+ # File upload handler
1526
+ file_upload.upload(process_upload, file_upload, transcript)
1527
+
1528
+ # Analysis button handler
1529
+ def on_analyze_click(transcript_text, age_val, gender_val, patient_name_val, record_id_val, clinician_val, assessment_date_val):
1530
+ if not transcript_text or len(transcript_text.strip()) < 50:
1531
+ return (
1532
+ pd.DataFrame(),
1533
+ pd.DataFrame(),
1534
+ None,
1535
+ None,
1536
+ "Error: Please provide a longer transcript for analysis.",
1537
+ "The transcript is too short for meaningful analysis.",
1538
+ "Please provide a speech sample with at least 50 characters.",
1539
+ "",
1540
+ "",
1541
+ ""
1542
+ )
1543
+
1544
+ try:
1545
+ results, plot_img, radar_img, full_text = analyze_transcript(transcript_text, age_val, gender_val)
1546
+
1547
+ # Format treatment suggestions as markdown
1548
+ treatment_text = ""
1549
+ for i, suggestion in enumerate(results['treatment_suggestions']):
1550
+ treatment_text += f"- {suggestion}\n"
1551
+
1552
+ # Format specific error examples
1553
+ specific_errors_text = "## Speech Error Examples\n\n"
1554
+ if 'specific_errors' in results:
1555
+ for factor, example in results['specific_errors'].items():
1556
+ if example:
1557
+ specific_errors_text += f"**{factor}:** \"{example}\"\n\n"
1558
+ else:
1559
+ specific_errors_text += f"**{factor}:** No specific example found\n\n"
1560
+ else:
1561
+ specific_errors_text += "No specific error examples were identified."
1562
+
1563
+ # Save the record to storage
1564
+ patient_info = {
1565
+ "name": patient_name_val,
1566
+ "record_id": record_id_val,
1567
+ "age": age_val,
1568
+ "gender": gender_val,
1569
+ "assessment_date": assessment_date_val,
1570
+ "clinician": clinician_val
1571
+ }
1572
+
1573
+ saved_id = save_patient_record(patient_info, results, transcript_text)
1574
+
1575
+ save_message = ""
1576
+ if saved_id:
1577
+ save_message = f"""
1578
+ ✅ Patient record saved successfully.
1579
+
1580
+ **System ID:** {saved_id}
1581
+ **Patient:** {patient_name_val or "Unnamed"}
1582
+ **Record ID:** {record_id_val or "Not provided"}
1583
+
1584
+ You can access this record later in the Patient Records tab.
1585
+ """
1586
+ else:
1587
+ save_message = "⚠️ Failed to save patient record. Please check data directory permissions."
1588
+
1589
+ # Format to include patient metadata in the full report
1590
+ patient_info_text = ""
1591
+ if patient_name_val:
1592
+ patient_info_text += f"**Patient:** {patient_name_val}\n"
1593
+ if record_id_val:
1594
+ patient_info_text += f"**Record ID:** {record_id_val}\n"
1595
+ if age_val:
1596
+ patient_info_text += f"**Age:** {age_val} years\n"
1597
+ if gender_val:
1598
+ patient_info_text += f"**Gender:** {gender_val}\n"
1599
+ if assessment_date_val:
1600
+ patient_info_text += f"**Assessment Date:** {assessment_date_val}\n"
1601
+ if clinician_val:
1602
+ patient_info_text += f"**Clinician:** {clinician_val}\n"
1603
+ if saved_id:
1604
+ patient_info_text += f"**System ID:** {saved_id}\n"
1605
+
1606
+ if patient_info_text:
1607
+ full_report = f"## Patient Information\n\n{patient_info_text}\n\n## Analysis Report\n\n{full_text}"
1608
+ else:
1609
+ full_report = f"## Complete Analysis Report\n\n{full_text}"
1610
+
1611
+ # Get the raw LLM response
1612
+ raw_output = results.get('raw_response', full_text)
1613
+
1614
+ # Convert image buffers to PIL images
1615
+ plot_img_pil = Image.open(plot_img)
1616
+ radar_img_pil = Image.open(radar_img)
1617
+
1618
+ return (
1619
+ results['speech_factors'],
1620
+ results['casl_data'],
1621
+ plot_img_pil,
1622
+ radar_img_pil,
1623
+ treatment_text,
1624
+ results['explanation'],
1625
+ full_report,
1626
+ save_message,
1627
+ specific_errors_text,
1628
+ raw_output
1629
+ )
1630
+ except Exception as e:
1631
+ logger.exception("Error during analysis")
1632
+ error_message = f"Error during analysis: {str(e)}"
1633
+ return (
1634
+ pd.DataFrame(),
1635
+ pd.DataFrame(),
1636
+ None,
1637
+ None,
1638
+ f"Error during analysis: {str(e)}",
1639
+ "An error occurred while processing the transcript.",
1640
+ f"Error details: {str(e)}",
1641
+ "",
1642
+ "",
1643
+ f"Analysis failed with error: {error_message}\n\nPlease check your transcript format and try again."
1644
+ )
1645
+
1646
+ analyze_btn.click(
1647
+ on_analyze_click,
1648
+ inputs=[
1649
+ transcript, age, gender,
1650
+ patient_name, record_id, clinician_name, assessment_date
1651
+ ],
1652
+ outputs=[
1653
+ speech_factors_table,
1654
+ casl_table,
1655
+ output_image,
1656
+ radar_chart,
1657
+ treatment_md,
1658
+ explanation_md,
1659
+ full_analysis,
1660
+ export_status,
1661
+ specific_errors_md,
1662
+ raw_llm_output
1663
+ ]
1664
+ )
1665
+
1666
+ # Export report functionality
1667
+ def export_pdf(report_text, patient_name="Patient", record_id=""):
1668
+ try:
1669
+ from reportlab.lib.pagesizes import letter
1670
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
1671
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
1672
+ import tempfile
1673
+ import webbrowser
1674
+ import os
1675
+
1676
+ # Generate a safe filename
1677
+ if patient_name and record_id:
1678
+ safe_name = f"{patient_name.replace(' ', '_')}_{record_id}"
1679
+ elif patient_name:
1680
+ safe_name = patient_name.replace(' ', '_')
1681
+ else:
1682
+ safe_name = f"speech_analysis_{datetime.now().strftime('%Y%m%d%H%M%S')}"
1683
+
1684
+ # Create a temporary file for the PDF
1685
+ temp_dir = tempfile.gettempdir()
1686
+ pdf_path = os.path.join(temp_dir, f"{safe_name}.pdf")
1687
+
1688
+ # Create the PDF document
1689
+ doc = SimpleDocTemplate(pdf_path, pagesize=letter)
1690
+ styles = getSampleStyleSheet()
1691
+
1692
+ # Create custom styles
1693
+ styles.add(ParagraphStyle(
1694
+ name='Heading1',
1695
+ parent=styles['Heading1'],
1696
+ fontSize=16,
1697
+ spaceAfter=12
1698
+ ))
1699
+
1700
+ styles.add(ParagraphStyle(
1701
+ name='Heading2',
1702
+ parent=styles['Heading2'],
1703
+ fontSize=14,
1704
+ spaceAfter=10,
1705
+ spaceBefore=10
1706
+ ))
1707
+
1708
+ styles.add(ParagraphStyle(
1709
+ name='BodyText',
1710
+ parent=styles['BodyText'],
1711
+ fontSize=12,
1712
+ spaceAfter=8
1713
+ ))
1714
+
1715
+ # Convert markdown to PDF elements
1716
+ # Very basic conversion - in a real app, use a proper markdown to PDF library
1717
+ story = []
1718
+
1719
+ # Add title
1720
+ story.append(Paragraph("Speech Language Assessment Report", styles['Title']))
1721
+ story.append(Spacer(1, 12))
1722
+
1723
+ # Process the markdown content line by line
1724
+ current_style = styles['BodyText']
1725
+
1726
+ for line in report_text.split('\n'):
1727
+ # Skip empty lines
1728
+ if not line.strip():
1729
+ story.append(Spacer(1, 6))
1730
+ continue
1731
+
1732
+ # Check for headings
1733
+ if line.startswith('# '):
1734
+ story.append(Paragraph(line[2:], styles['Heading1']))
1735
+ elif line.startswith('## '):
1736
+ story.append(Paragraph(line[3:], styles['Heading2']))
1737
+ elif line.startswith('- '):
1738
+ # Bullet points
1739
+ story.append(Paragraph('• ' + line[2:], styles['BodyText']))
1740
+ elif line.startswith('**') and line.endswith('**'):
1741
+ # Bold text - assuming it's a short line like a heading
1742
+ text = line.replace('**', '')
1743
+ story.append(Paragraph(f"<b>{text}</b>", styles['BodyText']))
1744
+ else:
1745
+ # Regular text
1746
+ story.append(Paragraph(line, styles['BodyText']))
1747
+
1748
+ # Build the PDF
1749
+ doc.build(story)
1750
+
1751
+ # Open the PDF (in a real web app, you'd provide a download link)
1752
+ # This will work in a desktop environment
1753
+ return f"Report saved as PDF: {pdf_path}"
1754
+
1755
+ except Exception as e:
1756
+ logger.exception("Error creating PDF")
1757
+ return f"Error creating PDF: {str(e)}\n\nIn a production environment, we would generate a proper PDF for download."
1758
+
1759
+ # Simplified simulation for HuggingFace Spaces environment
1760
+ def export_pdf_simulation(report_text):
1761
+ return "Report export initiated. In a production environment, a PDF would be generated and downloaded."
1762
+
1763
+ # Use the actual function in a desktop environment, simulation in web environment
1764
+ if os.getenv("SPACE_ID"): # Check if running on HuggingFace Spaces
1765
+ export_btn.click(
1766
+ lambda x: export_pdf_simulation(x),
1767
+ inputs=[full_analysis],
1768
+ outputs=[export_status]
1769
+ )
1770
+ report_download_btn.click(
1771
+ lambda x: export_pdf_simulation(x),
1772
+ inputs=[report_output],
1773
+ outputs=[report_download_status]
1774
+ )
1775
+ else:
1776
+ # Running locally, use actual PDF generation
1777
+ export_btn.click(
1778
+ lambda x, y, z: export_pdf(x, y, z),
1779
+ inputs=[full_analysis, patient_name, record_id],
1780
+ outputs=[export_status]
1781
+ )
1782
+ report_download_btn.click(
1783
+ lambda x, y, z: export_pdf(x, y, z),
1784
+ inputs=[report_output, report_patient_name, report_record_id],
1785
+ outputs=[report_download_status]
1786
+ )
1787
+
1788
+ # Report generator button
1789
+ def on_generate_report(name, record_id, age, gender, date, clinician, results, report_type):
1790
+ patient_info = {
1791
+ "name": name,
1792
+ "record_id": record_id,
1793
+ "age": age,
1794
+ "gender": gender,
1795
+ "assessment_date": date,
1796
+ "clinician": clinician
1797
+ }
1798
+
1799
+ report_type_val = "formal" if "Formal" in report_type else "parent-friendly"
1800
+
1801
+ try:
1802
+ report = generate_report(patient_info, results, report_type_val)
1803
+ return report
1804
+ except Exception as e:
1805
+ logger.exception("Error generating report")
1806
+ return f"Error generating report: {str(e)}"
1807
+
1808
+ generate_report_btn.click(
1809
+ on_generate_report,
1810
+ inputs=[
1811
+ report_patient_name, report_record_id, report_age,
1812
+ report_gender, report_date, report_clinician,
1813
+ report_results, report_type
1814
+ ],
1815
+ outputs=[report_output]
1816
+ )
1817
+
1818
+ # Transcription button
1819
+ def on_transcribe_audio(audio_path, age):
1820
+ try:
1821
+ if not audio_path:
1822
+ return "Please upload an audio file to transcribe."
1823
+
1824
+ transcription = transcribe_audio(audio_path, age)
1825
+ return transcription
1826
+ except Exception as e:
1827
+ logger.exception("Error transcribing audio")
1828
+ return f"Error transcribing audio: {str(e)}"
1829
+
1830
+ transcribe_btn.click(
1831
+ on_transcribe_audio,
1832
+ inputs=[audio_input, transcription_age],
1833
+ outputs=[transcription_output]
1834
+ )
1835
+
1836
+ # Copy transcription to analysis
1837
+ def copy_to_analysis(transcription):
1838
+ return transcription, gr.update(selected=0) # Switches to the Analysis tab
1839
+
1840
+ copy_to_analysis_btn.click(
1841
+ copy_to_analysis,
1842
+ inputs=[transcription_output],
1843
+ outputs=[transcript, main_tabs]
1844
+ )
1845
+
1846
+ # SLP Assistant question handling
1847
+ def on_ask_question(question):
1848
+ try:
1849
+ answer = answer_slp_question(question)
1850
+ return answer
1851
+ except Exception as e:
1852
+ logger.exception("Error getting answer")
1853
+ return f"Error: {str(e)}"
1854
+
1855
+ ask_question_btn.click(
1856
+ on_ask_question,
1857
+ inputs=[question_input],
1858
+ outputs=[answer_output]
1859
+ )
1860
+
1861
+ # Quick question buttons
1862
+ q1_btn.click(lambda: "What is CASL?", outputs=[question_input])
1863
+ q2_btn.click(lambda: "How do I interpret CASL scores?", outputs=[question_input])
1864
+ q3_btn.click(lambda: "What activities help with word finding difficulties?", outputs=[question_input])
1865
+ q4_btn.click(lambda: "When should I reassess a patient?", outputs=[question_input])
1866
+
1867
+ return app
1868
+
1869
+ # ===============================
1870
+ # Main Application
1871
+ # ===============================
1872
+
1873
+ # Create requirements.txt file for HuggingFace Spaces
1874
+ def create_requirements_file():
1875
+ requirements = [
1876
+ "gradio>=4.0.0",
1877
+ "pandas",
1878
+ "matplotlib",
1879
+ "numpy",
1880
+ "Pillow",
1881
+ "PyPDF2",
1882
+ "boto3",
1883
+ "reportlab",
1884
+ "uuid"
1885
+ ]
1886
+
1887
+ with open("requirements.txt", "w") as f:
1888
+ for req in requirements:
1889
+ f.write(f"{req}\n")
1890
+
1891
+ # Create and launch the interface
1892
+ if __name__ == "__main__":
1893
+ # Create requirements.txt for HuggingFace Spaces
1894
+ create_requirements_file()
1895
+
1896
+ # Check for AWS credentials
1897
+ if not AWS_ACCESS_KEY or not AWS_SECRET_KEY:
1898
+ print("NOTE: AWS credentials not found. The app will run in demo mode with simulated responses.")
1899
+ print("To enable full functionality, set AWS_ACCESS_KEY and AWS_SECRET_KEY environment variables.")
1900
+
1901
+ # Launch the Gradio app
1902
+ app = create_interface()
1903
+ app.launch()