Spaces:
Sleeping
Sleeping
Upload simple_casl_app.py
Browse files- simple_casl_app.py +192 -23
simple_casl_app.py
CHANGED
|
@@ -107,7 +107,7 @@ else:
|
|
| 107 |
logger.warning("Claude API key not found - using demo mode")
|
| 108 |
|
| 109 |
def validate_analysis_completeness(response_text):
|
| 110 |
-
"""Validate that all 12 sections are present in the analysis"""
|
| 111 |
required_sections = [
|
| 112 |
"1. SPEECH FACTORS",
|
| 113 |
"2. LANGUAGE SKILLS ASSESSMENT",
|
|
@@ -124,25 +124,129 @@ def validate_analysis_completeness(response_text):
|
|
| 124 |
]
|
| 125 |
|
| 126 |
missing_sections = []
|
|
|
|
|
|
|
|
|
|
| 127 |
for section in required_sections:
|
| 128 |
-
|
|
|
|
|
|
|
| 129 |
missing_sections.append(section)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
if missing_sections:
|
| 132 |
print(f"\n⚠️ MISSING SECTIONS: {missing_sections}")
|
| 133 |
-
return False
|
|
|
|
|
|
|
|
|
|
| 134 |
else:
|
| 135 |
-
print(f"\n✅ ALL 12 SECTIONS PRESENT")
|
| 136 |
-
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
def call_claude_api_with_continuation(prompt, max_continuations=3):
|
| 139 |
"""Call Claude API with continuation prompting to ensure complete responses"""
|
| 140 |
if not ANTHROPIC_API_KEY:
|
| 141 |
return "❌ Claude API key not configured. Please set ANTHROPIC_API_KEY environment variable."
|
| 142 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
try:
|
| 144 |
-
|
| 145 |
continuation_count = 0
|
|
|
|
| 146 |
|
| 147 |
# Add continuation instruction to original prompt
|
| 148 |
initial_prompt = prompt + "\n\nIMPORTANT: If your response is cut off or incomplete, end with <CONTINUE> to indicate more content is needed. Ensure you complete all sections of the analysis."
|
|
@@ -190,29 +294,28 @@ def call_claude_api_with_continuation(prompt, max_continuations=3):
|
|
| 190 |
print(f"Last 200 chars: {response_text[-200:]}...")
|
| 191 |
print("=" * 50)
|
| 192 |
|
| 193 |
-
#
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
|
|
|
| 199 |
|
| 200 |
# Check if response indicates continuation is needed
|
| 201 |
needs_continuation = "<CONTINUE>" in response_text
|
| 202 |
|
| 203 |
print(f"Needs continuation: {needs_continuation}")
|
| 204 |
print(f"Continuation count: {continuation_count}/{max_continuations}")
|
|
|
|
|
|
|
| 205 |
|
| 206 |
# Continue if <CONTINUE> is present and we haven't reached max
|
| 207 |
if needs_continuation and continuation_count < max_continuations:
|
| 208 |
-
# Remove the CONTINUE marker
|
| 209 |
-
full_response = full_response.replace("<CONTINUE>", "")
|
| 210 |
continuation_count += 1
|
| 211 |
logger.info(f"Continuing analysis (attempt {continuation_count}/{max_continuations})")
|
| 212 |
continue
|
| 213 |
else:
|
| 214 |
-
# Clean up any remaining continuation markers
|
| 215 |
-
full_response = full_response.replace("<CONTINUE>", "")
|
| 216 |
break
|
| 217 |
else:
|
| 218 |
logger.error(f"Claude API error: {response.status_code} - {response.text}")
|
|
@@ -222,16 +325,36 @@ def call_claude_api_with_continuation(prompt, max_continuations=3):
|
|
| 222 |
logger.error(f"Error calling Claude API: {str(e)}")
|
| 223 |
return f"❌ Error: {str(e)}"
|
| 224 |
|
| 225 |
-
#
|
| 226 |
-
|
| 227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
print("=" * 50)
|
| 234 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
# Print the entire final response for debugging
|
| 236 |
print(f"\n=== ENTIRE FINAL RESPONSE ===")
|
| 237 |
print(full_response)
|
|
@@ -750,6 +873,30 @@ def analyze_transcript_content(transcript_content, age, gender, slp_notes):
|
|
| 750 |
|
| 751 |
# Get analysis from Claude API
|
| 752 |
result = call_claude_api_with_continuation(prompt, max_continuations=5)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 753 |
return result
|
| 754 |
|
| 755 |
def analyze_transcript(file, age, gender, slp_notes):
|
|
@@ -874,6 +1021,14 @@ def targeted_analysis(transcript, custom_question, age, gender, slp_notes):
|
|
| 874 |
|
| 875 |
# Get targeted analysis from Claude API
|
| 876 |
result = call_claude_api_with_continuation(prompt, max_continuations=3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 877 |
return result
|
| 878 |
|
| 879 |
# Create enhanced interface with tabs
|
|
@@ -1312,6 +1467,20 @@ with gr.Blocks(title="Enhanced CASL Analysis", theme=gr.themes.Soft()) as app:
|
|
| 1312 |
"""
|
| 1313 |
|
| 1314 |
result = call_claude_api_with_continuation(prompt, max_continuations=2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1315 |
progress_msg = "✅ Quick analysis completed" if "[Analysis completed in" in result else "🔄 Quick analysis in progress..."
|
| 1316 |
return result, progress_msg
|
| 1317 |
|
|
|
|
| 107 |
logger.warning("Claude API key not found - using demo mode")
|
| 108 |
|
| 109 |
def validate_analysis_completeness(response_text):
|
| 110 |
+
"""Validate that all 12 sections are present in the analysis exactly once"""
|
| 111 |
required_sections = [
|
| 112 |
"1. SPEECH FACTORS",
|
| 113 |
"2. LANGUAGE SKILLS ASSESSMENT",
|
|
|
|
| 124 |
]
|
| 125 |
|
| 126 |
missing_sections = []
|
| 127 |
+
duplicate_sections = []
|
| 128 |
+
section_counts = {}
|
| 129 |
+
|
| 130 |
for section in required_sections:
|
| 131 |
+
count = response_text.count(section)
|
| 132 |
+
section_counts[section] = count
|
| 133 |
+
if count == 0:
|
| 134 |
missing_sections.append(section)
|
| 135 |
+
elif count > 1:
|
| 136 |
+
duplicate_sections.append(section)
|
| 137 |
+
|
| 138 |
+
# Log detailed validation results
|
| 139 |
+
print(f"\n=== COMPREHENSIVE VALIDATION ===")
|
| 140 |
+
print(f"Total response length: {len(response_text)} characters")
|
| 141 |
+
print(f"Missing sections: {missing_sections}")
|
| 142 |
+
print(f"Duplicate sections: {duplicate_sections}")
|
| 143 |
+
print(f"Section counts: {section_counts}")
|
| 144 |
|
| 145 |
if missing_sections:
|
| 146 |
print(f"\n⚠️ MISSING SECTIONS: {missing_sections}")
|
| 147 |
+
return False, missing_sections, duplicate_sections, section_counts
|
| 148 |
+
elif duplicate_sections:
|
| 149 |
+
print(f"\n⚠️ DUPLICATE SECTIONS: {duplicate_sections}")
|
| 150 |
+
return False, missing_sections, duplicate_sections, section_counts
|
| 151 |
else:
|
| 152 |
+
print(f"\n✅ ALL 12 SECTIONS PRESENT EXACTLY ONCE")
|
| 153 |
+
return True, missing_sections, duplicate_sections, section_counts
|
| 154 |
+
|
| 155 |
+
def fix_incomplete_analysis(response_text, missing_sections):
|
| 156 |
+
"""Attempt to fix incomplete analysis by requesting missing sections"""
|
| 157 |
+
if not missing_sections:
|
| 158 |
+
return response_text
|
| 159 |
+
|
| 160 |
+
if not ANTHROPIC_API_KEY:
|
| 161 |
+
return response_text + "\n\n❌ Cannot fix incomplete analysis - API key not configured"
|
| 162 |
+
|
| 163 |
+
try:
|
| 164 |
+
# Create a focused prompt for missing sections
|
| 165 |
+
missing_sections_text = "\n".join([f"- {section}" for section in missing_sections])
|
| 166 |
+
|
| 167 |
+
fix_prompt = f"""
|
| 168 |
+
The following sections are missing from the CASL analysis. Please provide ONLY these missing sections:
|
| 169 |
+
|
| 170 |
+
{missing_sections_text}
|
| 171 |
+
|
| 172 |
+
IMPORTANT:
|
| 173 |
+
- Provide ONLY the missing sections listed above
|
| 174 |
+
- Do not repeat any sections that are already present
|
| 175 |
+
- Use the exact section headers as shown above
|
| 176 |
+
- Make each section comprehensive and detailed
|
| 177 |
+
- Ensure clinical accuracy and appropriate depth for SLP assessment
|
| 178 |
+
"""
|
| 179 |
+
|
| 180 |
+
headers = {
|
| 181 |
+
"Content-Type": "application/json",
|
| 182 |
+
"x-api-key": ANTHROPIC_API_KEY,
|
| 183 |
+
"anthropic-version": "2023-06-01"
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
data = {
|
| 187 |
+
"model": "claude-3-5-sonnet-20241022",
|
| 188 |
+
"max_tokens": 4096,
|
| 189 |
+
"messages": [
|
| 190 |
+
{
|
| 191 |
+
"role": "user",
|
| 192 |
+
"content": fix_prompt
|
| 193 |
+
}
|
| 194 |
+
]
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
response = requests.post(
|
| 198 |
+
"https://api.anthropic.com/v1/messages",
|
| 199 |
+
headers=headers,
|
| 200 |
+
json=data,
|
| 201 |
+
timeout=90
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
if response.status_code == 200:
|
| 205 |
+
response_json = response.json()
|
| 206 |
+
fix_text = response_json['content'][0]['text']
|
| 207 |
+
|
| 208 |
+
# Combine original response with fix
|
| 209 |
+
complete_response = response_text + "\n\n" + fix_text
|
| 210 |
+
|
| 211 |
+
print(f"\n=== FIXED INCOMPLETE ANALYSIS ===")
|
| 212 |
+
print(f"Added missing sections: {missing_sections}")
|
| 213 |
+
print(f"Fix text length: {len(fix_text)} characters")
|
| 214 |
+
print("=" * 50)
|
| 215 |
+
|
| 216 |
+
return complete_response
|
| 217 |
+
else:
|
| 218 |
+
logger.error(f"Error fixing incomplete analysis: {response.status_code}")
|
| 219 |
+
return response_text + f"\n\n❌ Error fixing incomplete analysis: {response.status_code}"
|
| 220 |
+
|
| 221 |
+
except Exception as e:
|
| 222 |
+
logger.error(f"Error in fix_incomplete_analysis: {str(e)}")
|
| 223 |
+
return response_text + f"\n\n❌ Error fixing incomplete analysis: {str(e)}"
|
| 224 |
|
| 225 |
def call_claude_api_with_continuation(prompt, max_continuations=3):
|
| 226 |
"""Call Claude API with continuation prompting to ensure complete responses"""
|
| 227 |
if not ANTHROPIC_API_KEY:
|
| 228 |
return "❌ Claude API key not configured. Please set ANTHROPIC_API_KEY environment variable."
|
| 229 |
|
| 230 |
+
# Define all required sections
|
| 231 |
+
required_sections = [
|
| 232 |
+
"1. SPEECH FACTORS",
|
| 233 |
+
"2. LANGUAGE SKILLS ASSESSMENT",
|
| 234 |
+
"3. COMPLEX SENTENCE ANALYSIS",
|
| 235 |
+
"4. FIGURATIVE LANGUAGE ANALYSIS",
|
| 236 |
+
"5. PRAGMATIC LANGUAGE ASSESSMENT",
|
| 237 |
+
"6. VOCABULARY AND SEMANTIC ANALYSIS",
|
| 238 |
+
"7. MORPHOLOGICAL AND PHONOLOGICAL ANALYSIS",
|
| 239 |
+
"8. COGNITIVE-LINGUISTIC FACTORS",
|
| 240 |
+
"9. FLUENCY AND RHYTHM ANALYSIS",
|
| 241 |
+
"10. QUANTITATIVE METRICS",
|
| 242 |
+
"11. CLINICAL IMPLICATIONS",
|
| 243 |
+
"12. PROGNOSIS AND SUMMARY"
|
| 244 |
+
]
|
| 245 |
+
|
| 246 |
try:
|
| 247 |
+
response_parts = [] # Store each part as a separate item
|
| 248 |
continuation_count = 0
|
| 249 |
+
completed_sections = set() # Track which sections have been completed
|
| 250 |
|
| 251 |
# Add continuation instruction to original prompt
|
| 252 |
initial_prompt = prompt + "\n\nIMPORTANT: If your response is cut off or incomplete, end with <CONTINUE> to indicate more content is needed. Ensure you complete all sections of the analysis."
|
|
|
|
| 294 |
print(f"Last 200 chars: {response_text[-200:]}...")
|
| 295 |
print("=" * 50)
|
| 296 |
|
| 297 |
+
# Store this part
|
| 298 |
+
response_parts.append(response_text)
|
| 299 |
+
|
| 300 |
+
# Check which sections are present in this part
|
| 301 |
+
for section in required_sections:
|
| 302 |
+
if section in response_text:
|
| 303 |
+
completed_sections.add(section)
|
| 304 |
|
| 305 |
# Check if response indicates continuation is needed
|
| 306 |
needs_continuation = "<CONTINUE>" in response_text
|
| 307 |
|
| 308 |
print(f"Needs continuation: {needs_continuation}")
|
| 309 |
print(f"Continuation count: {continuation_count}/{max_continuations}")
|
| 310 |
+
print(f"Completed sections: {len(completed_sections)}/12")
|
| 311 |
+
print(f"Missing sections: {[s for s in required_sections if s not in completed_sections]}")
|
| 312 |
|
| 313 |
# Continue if <CONTINUE> is present and we haven't reached max
|
| 314 |
if needs_continuation and continuation_count < max_continuations:
|
|
|
|
|
|
|
| 315 |
continuation_count += 1
|
| 316 |
logger.info(f"Continuing analysis (attempt {continuation_count}/{max_continuations})")
|
| 317 |
continue
|
| 318 |
else:
|
|
|
|
|
|
|
| 319 |
break
|
| 320 |
else:
|
| 321 |
logger.error(f"Claude API error: {response.status_code} - {response.text}")
|
|
|
|
| 325 |
logger.error(f"Error calling Claude API: {str(e)}")
|
| 326 |
return f"❌ Error: {str(e)}"
|
| 327 |
|
| 328 |
+
# Combine all parts and clean up
|
| 329 |
+
full_response = "\n\n".join(response_parts)
|
| 330 |
+
full_response = full_response.replace("<CONTINUE>", "")
|
| 331 |
+
|
| 332 |
+
# Validate completeness
|
| 333 |
+
missing_sections = []
|
| 334 |
+
duplicate_sections = []
|
| 335 |
+
section_counts = {}
|
| 336 |
|
| 337 |
+
for section in required_sections:
|
| 338 |
+
count = full_response.count(section)
|
| 339 |
+
section_counts[section] = count
|
| 340 |
+
if count == 0:
|
| 341 |
+
missing_sections.append(section)
|
| 342 |
+
elif count > 1:
|
| 343 |
+
duplicate_sections.append(section)
|
| 344 |
+
|
| 345 |
+
# Log validation results
|
| 346 |
+
print(f"\n=== VALIDATION RESULTS ===")
|
| 347 |
+
print(f"Total response length: {len(full_response)} characters")
|
| 348 |
+
print(f"Number of parts: {len(response_parts)}")
|
| 349 |
+
print(f"Missing sections: {missing_sections}")
|
| 350 |
+
print(f"Duplicate sections: {duplicate_sections}")
|
| 351 |
+
print(f"Section counts: {section_counts}")
|
| 352 |
print("=" * 50)
|
| 353 |
|
| 354 |
+
# Add completion indicator
|
| 355 |
+
if len(response_parts) > 1:
|
| 356 |
+
full_response += f"\n\n[Analysis completed in {len(response_parts)} parts]"
|
| 357 |
+
|
| 358 |
# Print the entire final response for debugging
|
| 359 |
print(f"\n=== ENTIRE FINAL RESPONSE ===")
|
| 360 |
print(full_response)
|
|
|
|
| 873 |
|
| 874 |
# Get analysis from Claude API
|
| 875 |
result = call_claude_api_with_continuation(prompt, max_continuations=5)
|
| 876 |
+
|
| 877 |
+
# Validate completeness and fix if needed
|
| 878 |
+
is_complete, missing_sections, duplicate_sections, section_counts = validate_analysis_completeness(result)
|
| 879 |
+
|
| 880 |
+
if not is_complete:
|
| 881 |
+
print(f"\n🔧 ATTEMPTING TO FIX INCOMPLETE ANALYSIS...")
|
| 882 |
+
print(f"Missing sections: {missing_sections}")
|
| 883 |
+
print(f"Duplicate sections: {duplicate_sections}")
|
| 884 |
+
|
| 885 |
+
# Try to fix missing sections
|
| 886 |
+
if missing_sections:
|
| 887 |
+
result = fix_incomplete_analysis(result, missing_sections)
|
| 888 |
+
|
| 889 |
+
# Re-validate after fix
|
| 890 |
+
is_complete_after_fix, missing_after_fix, duplicate_after_fix, counts_after_fix = validate_analysis_completeness(result)
|
| 891 |
+
|
| 892 |
+
if not is_complete_after_fix:
|
| 893 |
+
print(f"\n⚠️ ANALYSIS STILL INCOMPLETE AFTER FIX ATTEMPT")
|
| 894 |
+
print(f"Still missing: {missing_after_fix}")
|
| 895 |
+
print(f"Still duplicate: {duplicate_after_fix}")
|
| 896 |
+
result += f"\n\n⚠️ WARNING: Analysis may be incomplete. Missing sections: {missing_after_fix}"
|
| 897 |
+
else:
|
| 898 |
+
print(f"\n✅ ANALYSIS FIXED SUCCESSFULLY")
|
| 899 |
+
|
| 900 |
return result
|
| 901 |
|
| 902 |
def analyze_transcript(file, age, gender, slp_notes):
|
|
|
|
| 1021 |
|
| 1022 |
# Get targeted analysis from Claude API
|
| 1023 |
result = call_claude_api_with_continuation(prompt, max_continuations=3)
|
| 1024 |
+
|
| 1025 |
+
# For targeted analysis, we don't need the full 12-section validation
|
| 1026 |
+
# but we can still validate that the response is complete and well-structured
|
| 1027 |
+
if len(result.strip()) < 500: # Basic length check
|
| 1028 |
+
print(f"\n⚠️ TARGETED ANALYSIS MAY BE INCOMPLETE")
|
| 1029 |
+
print(f"Response length: {len(result)} characters")
|
| 1030 |
+
result += f"\n\n⚠️ WARNING: This targeted analysis may be incomplete. Please review the results carefully."
|
| 1031 |
+
|
| 1032 |
return result
|
| 1033 |
|
| 1034 |
# Create enhanced interface with tabs
|
|
|
|
| 1467 |
"""
|
| 1468 |
|
| 1469 |
result = call_claude_api_with_continuation(prompt, max_continuations=2)
|
| 1470 |
+
|
| 1471 |
+
# For quick analysis, validate that all selected questions were addressed
|
| 1472 |
+
if questions and len(questions) > 0:
|
| 1473 |
+
missing_questions = []
|
| 1474 |
+
for question in questions:
|
| 1475 |
+
# Check if the question was addressed (basic check)
|
| 1476 |
+
if question.lower() not in result.lower():
|
| 1477 |
+
missing_questions.append(question)
|
| 1478 |
+
|
| 1479 |
+
if missing_questions:
|
| 1480 |
+
print(f"\n⚠️ QUICK ANALYSIS MAY BE INCOMPLETE")
|
| 1481 |
+
print(f"Missing questions: {missing_questions}")
|
| 1482 |
+
result += f"\n\n⚠️ WARNING: Some selected questions may not have been fully addressed: {missing_questions}"
|
| 1483 |
+
|
| 1484 |
progress_msg = "✅ Quick analysis completed" if "[Analysis completed in" in result else "🔄 Quick analysis in progress..."
|
| 1485 |
return result, progress_msg
|
| 1486 |
|