AI-RESEARCHER-2024 commited on
Commit
dc910d3
Β·
verified Β·
1 Parent(s): 52011aa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +328 -215
app.py CHANGED
@@ -1,22 +1,22 @@
 
1
  # ============================================
2
- # CICE 2.0 Healthcare Team Assessment Tool - HF Gradio Version
3
- # app.py for deployment on Hugging Face Spaces
4
  # ============================================
5
 
6
  import os
7
  import time
8
- from datetime import datetime
9
  import re
10
- import numpy as np
11
- from PIL import Image
12
- import cv2
13
  import tempfile
14
  import mimetypes
15
- import shutil
 
 
16
  import gradio as gr
17
  from google import genai
18
  from google.genai import types
19
  from gtts import gTTS
 
20
  from reportlab.lib.pagesizes import letter
21
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
22
  from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
@@ -24,206 +24,276 @@ from reportlab.lib.units import inch
24
  from reportlab.lib.enums import TA_JUSTIFY, TA_CENTER
25
  from reportlab.lib.colors import HexColor
26
 
27
- # Set up the API key β€” Replace with a hardcoded key for demo or read from env on HF
28
- GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") or "YOUR_GOOGLE_API_KEY"
 
 
 
 
29
 
30
- client = genai.Client(api_key=GOOGLE_API_KEY)
31
- model_name = "gemini-2.0-flash-exp"
 
 
 
32
 
 
 
 
 
 
 
33
 
34
- def analyze_video(video_path):
35
- """Analyze video using the 18-point CICE 2.0 assessment with specific behavioral cues"""
36
- try:
37
- mime_type, _ = mimetypes.guess_type(video_path)
38
- if mime_type is None:
39
- mime_type = 'video/mp4'
40
 
41
- print(f"πŸ“€ Uploading video to Gemini AI (type: {mime_type})...")
42
- with open(video_path, 'rb') as f:
43
- video_file = client.files.upload(file=f, config={'mime_type': mime_type})
 
 
 
 
44
 
45
- print("⏳ Processing video...")
46
- max_wait = 300
47
- wait_time = 0
48
- while video_file.state == "PROCESSING" and wait_time < max_wait:
49
- time.sleep(3)
50
- wait_time += 3
51
- video_file = client.files.get(name=video_file.name)
52
 
53
- if video_file.state == "FAILED":
54
- raise Exception("Video processing failed")
55
 
56
- print("πŸ” Analyzing team interactions...")
57
 
58
- prompt = """Analyze this healthcare team interaction video and provide a comprehensive...
 
59
 
60
- [Same prompt as before β€” trimmed for brevity here but paste in full from original]
61
-
62
- STRUCTURE YOUR RESPONSE AS FOLLOWS:
63
 
64
- ## OVERALL ASSESSMENT
65
- ...
66
 
67
- ## DETAILED COMPETENCY EVALUATION
68
- ...
69
 
70
- ## STRENGTHS
71
- ...
72
 
73
- ## AREAS FOR IMPROVEMENT
74
- ...
75
 
76
- ## AUDIO SUMMARY
77
- ...
78
 
79
- ## FINAL SCORE
80
- ..."""
81
-
82
- response = client.models.generate_content(
83
- model=model_name,
84
- contents=[
85
- types.Part.from_uri(file_uri=video_file.uri, mime_type=video_file.mime_type),
86
- prompt
87
- ]
88
- )
89
- print("βœ… Analysis complete!")
90
- return response.text
91
 
92
- except Exception as e:
93
- return f"Error during analysis: {str(e)}"
94
 
 
 
95
 
96
- def generate_audio_feedback(text):
97
- """Generate audio summary from text"""
98
- audio_summary_match = re.search(r'## AUDIO SUMMARY\s*(.*?)(?=##|\Z)', text, re.DOTALL)
99
- if audio_summary_match:
100
- summary_text = audio_summary_match.group(1).strip()
101
- else:
102
- summary_text = create_brief_summary(text)
103
 
104
- clean_text = re.sub(r'[#*_\[\]()]', ' ', summary_text)
105
- clean_text = re.sub(r'\s+', ' ', clean_text).strip()
106
 
107
- audio_script = f"""CICE Healthcare Team Assessment Summary.\n\n{clean_text}\n\nPlease refer to the detailed report for full insights.\nEnd of summary."""
 
108
 
109
- try:
110
- tts = gTTS(text=audio_script, lang='en', slow=False, tld='com')
111
- temp_audio_path = "/tmp/feedback.mp3"
112
- tts.save(temp_audio_path)
113
- return temp_audio_path
114
- except Exception as e:
115
- print(f"⚠️ Audio generation failed: {str(e)}")
116
- return None
117
 
 
 
118
 
119
- def create_brief_summary(text):
120
- observed_count = text.lower().count("observed") - text.lower().count("not observed")
121
- total = 18
122
- percentage = (observed_count / total) * 100
123
-
124
- if percentage >= 85:
125
- level = "Exemplary"
126
- elif percentage >= 70:
127
- level = "Proficient"
128
- elif percentage >= 50:
129
- level = "Developing"
130
- else:
131
- level = "Needs Improvement"
132
-
133
- return f"""The team demonstrated {level} performance with {observed_count} out of {total} competencies observed,
134
- achieving {percentage:.0f} percent overall.
135
- Key strengths included strong team communication and role clarity.
136
- Areas for improvement include enhancing active listening and conflict resolution skills.
137
- The team should focus on pre-briefing protocols and post-scenario debriefing.
138
- Emphasis should be placed on clear role assignment and closed-loop communication."""
139
-
140
-
141
- def parse_assessment_scores(assessment_text):
142
- observed_count = assessment_text.lower().count("observed") - assessment_text.lower().count("not observed")
143
- total_competencies = 18
144
- percentage = (observed_count / total_competencies) * 100
145
-
146
- if percentage >= 85:
147
- level = "Exemplary"
148
- color = "#0F766E"
149
- elif percentage >= 70:
150
- level = "Proficient"
151
- color = "#1E40AF"
152
- elif percentage >= 50:
153
- level = "Developing"
154
- color = "#EA580C"
155
- else:
156
- level = "Needs Improvement"
157
- color = "#B91C1C"
158
-
159
- return observed_count, total_competencies, percentage, level, color
160
-
161
-
162
- def generate_pdf_report(assessment_text):
163
- temp_pdf_path = "/tmp/report.pdf"
164
- try:
165
- doc = SimpleDocTemplate(temp_pdf_path, pagesize=letter)
166
- styles = getSampleStyleSheet()
167
- title_style = ParagraphStyle('CustomTitle', parent=styles['Heading1'], fontSize=20, textColor=HexColor('#111827'), spaceAfter=30, alignment=TA_CENTER)
168
- heading_style = ParagraphStyle('CustomHeading', parent=styles['Heading2'], fontSize=14, textColor=HexColor('#1E40AF'), spaceAfter=12, bold=True)
169
- body_style = ParagraphStyle('CustomBody', parent=styles['BodyText'], fontSize=11, alignment=TA_JUSTIFY, spaceAfter=6)
170
-
171
- elements = [Paragraph("CICE 2.0 Healthcare Team Assessment Report", title_style)]
172
-
173
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
174
- elements.append(Paragraph(f"<b>Assessment Date:</b> {timestamp}", body_style))
175
- elements.append(Spacer(1, 12))
176
-
177
- lines = assessment_text.split('\n')
178
- for line in lines:
179
- line = line.strip()
180
- if not line:
181
- elements.append(Spacer(1, 6))
182
- elif line.startswith('##'):
183
- heading = line.replace('##', '').strip()
184
- elements.append(Paragraph(heading, heading_style))
185
- elif line.startswith('Competency'):
186
- elements.append(Paragraph(f"<b>{line}</b>", body_style))
187
- elif line.startswith('Status:') or line.startswith('Evidence:'):
188
- elements.append(Paragraph(line, body_style))
189
- else:
190
- escaped_line = line.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
191
- elements.append(Paragraph(escaped_line, body_style))
192
 
193
- doc.build(elements)
194
- return temp_pdf_path
195
- except Exception as e:
196
- print(f"⚠️ PDF generation failed: {str(e)}")
197
- temp_txt_path = "/tmp/report.txt"
198
- with open(temp_txt_path, 'w') as f:
199
- f.write("CICE 2.0 Healthcare Team Assessment\n")
200
- f.write("="*60 + "\n")
201
- f.write(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
202
- f.write("="*60 + "\n\n")
203
- f.write(assessment_text)
204
- return temp_txt_path
205
-
206
-
207
- def get_video_info(video_path):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  try:
209
- cap = cv2.VideoCapture(video_path)
210
- width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
211
- height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
212
- fps = int(cap.get(cv2.CAP_PROP_FPS))
213
- frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
214
- cap.release()
215
- return width, height, fps, frame_count
216
- except:
217
- return None, None, None, None
218
 
 
219
 
 
220
  def resize_video(input_path, target_width, target_height):
 
221
  try:
222
  cap = cv2.VideoCapture(input_path)
223
  fps = int(cap.get(cv2.CAP_PROP_FPS))
224
  fourcc = cv2.VideoWriter_fourcc(*'mp4v')
225
- temp_output_path = "/tmp/resized_video.mp4"
226
- out = cv2.VideoWriter(temp_output_path, fourcc, fps, (target_width, target_height))
 
227
 
228
  print(f"πŸ“ Resizing video to {target_width}x{target_height}...")
229
  while True:
@@ -235,22 +305,27 @@ def resize_video(input_path, target_width, target_height):
235
 
236
  cap.release()
237
  out.release()
238
- return temp_output_path
 
239
  except Exception as e:
240
- print(f"⚠️ Video resizing failed: {str(e)}")
241
  return input_path
242
 
243
-
244
  def process_video(video, resize_option):
 
245
  if video is None:
246
- return "Please upload or record a video.", None, None, None
 
 
 
247
 
248
  try:
249
- orig_width, orig_height, fps, frame_count = get_video_info(video)
250
- if orig_width and orig_height:
251
- print(f"πŸ“Ή Original video: {orig_width}x{orig_height} @ {fps}fps ({frame_count} frames)")
252
 
 
253
  video_to_process = video
 
254
  if resize_option != "Original (No Resize)":
255
  if "640x480" in resize_option:
256
  target_width, target_height = 640, 480
@@ -259,69 +334,106 @@ def process_video(video, resize_option):
259
  elif "1280x720" in resize_option:
260
  target_width, target_height = 1280, 720
261
  else:
262
- target_width, target_height = orig_width, orig_height
 
 
 
263
 
264
- if orig_width != target_width or orig_height != target_height:
265
- video_to_process = resize_video(video, target_width, target_height)
 
 
 
 
 
 
 
 
266
 
267
- assessment_result = analyze_video(video_to_process)
268
  if "Error" in assessment_result:
269
  return assessment_result, None, None, None
270
 
271
- audio_path = generate_audio_feedback(assessment_result)
272
- pdf_path = generate_pdf_report(assessment_result)
273
- observed, total, percentage, level, color = parse_assessment_scores(assessment_result)
 
 
 
 
 
274
 
 
275
  summary_html = f"""
276
- <div style="padding:20px; border-radius:10px; background:white; box-shadow:0 2px 10px rgba(0,0,0,0.08);">
277
- <h2>CICE 2.0 Assessment Summary</h2>
278
- <div style="display:flex; justify-content:space-around;">
279
- <div>
280
- <div style="font-size:36px; font-weight:bold; color:{color};">{observed}/{total}</div>
281
- <div>Competencies Observed</div>
282
  </div>
283
- <div>
284
- <div style="font-size:36px; font-weight:bold; color:{color};">{percentage:.0f}%</div>
285
- <div>Overall Score</div>
286
  </div>
287
  </div>
288
- <div style="text-align:center; margin-top:15px;">
289
- <div style="padding:15px; background:#F8FAFC; border-radius:8px;">
290
- <div style="font-size:18px; font-weight:bold; color:{color};">Performance Level: {level}</div>
291
- </div>
 
 
 
 
292
  </div>
293
  </div>
294
  """
295
 
296
  return assessment_result, summary_html, audio_path, pdf_path
297
- except Exception as e:
298
- return f"Processing error: {str(e)}", None, None, None
299
 
 
 
 
 
300
 
301
- with gr.Blocks(title="CICE 2.0 Healthcare Team Assessment", theme=gr.themes.Soft()) as demo:
 
302
  gr.Markdown("""
303
  # πŸ₯ CICE 2.0 Healthcare Team Assessment Tool
304
- Analyze team interactions using AI-driven behavioral cues.
 
 
 
305
  """)
 
306
  with gr.Row():
307
  with gr.Column(scale=1):
 
308
  resize_dropdown = gr.Dropdown(
309
  choices=[
310
  "Original (No Resize)",
311
- "640x480 (Fastest - Recommended)",
312
  "800x600 (Fast - Good balance)",
313
- "1280x720 (HD - Best quality)"
314
  ],
315
  value="800x600 (Fast - Good balance)",
316
- label="🎬 Video Resolution"
 
 
 
 
 
 
 
317
  )
318
- video_input = gr.Video(sources=["upload", "webcam"], format="mp4", include_audio=True)
319
- analyze_btn = gr.Button("πŸ” Analyze Video", variant="primary")
 
320
  with gr.Column(scale=2):
321
- summary_output = gr.HTML()
322
- audio_output = gr.Audio(type="filepath")
323
- pdf_output = gr.File(file_types=[".pdf", ".txt"])
324
- assessment_output = gr.Textbox(label="Detailed Assessment", lines=15, interactive=False)
 
325
 
326
  analyze_btn.click(
327
  fn=process_video,
@@ -329,5 +441,6 @@ with gr.Blocks(title="CICE 2.0 Healthcare Team Assessment", theme=gr.themes.Soft
329
  outputs=[assessment_output, summary_output, audio_output, pdf_output]
330
  )
331
 
 
332
  if __name__ == "__main__":
333
  demo.launch()
 
1
+ # app.py
2
  # ============================================
3
+ # CICE 2.0 Healthcare Team Assessment Tool
4
+ # Hugging Face Spaces - Single File Deployment
5
  # ============================================
6
 
7
  import os
8
  import time
 
9
  import re
 
 
 
10
  import tempfile
11
  import mimetypes
12
+ from datetime import datetime
13
+ from getpass import getpass
14
+
15
  import gradio as gr
16
  from google import genai
17
  from google.genai import types
18
  from gtts import gTTS
19
+ import cv2
20
  from reportlab.lib.pagesizes import letter
21
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
22
  from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
 
24
  from reportlab.lib.enums import TA_JUSTIFY, TA_CENTER
25
  from reportlab.lib.colors import HexColor
26
 
27
+ # Initialize Google Generative AI
28
+ def init_genai_client():
29
+ api_key = os.environ.get("GOOGLE_API_KEY") or os.environ.get("GOOGLE_API_KEY_HIDDEN")
30
+ if not api_key:
31
+ raise ValueError("GOOGLE_API_KEY environment variable not set. Please add your API key in Secrets.")
32
+ return genai.Client(api_key=api_key)
33
 
34
+ # CICE Assessment Class
35
+ class CICE_Assessment:
36
+ def __init__(self, client):
37
+ self.client = client
38
+ self.model_name = "gemini-2.0-flash-exp"
39
 
40
+ def analyze_video(self, video_path):
41
+ """Analyze video using the 18-point CICE 2.0 assessment"""
42
+ try:
43
+ mime_type, _ = mimetypes.guess_type(video_path)
44
+ if mime_type is None:
45
+ mime_type = 'video/mp4'
46
 
47
+ print(f"πŸ“€ Uploading video (type: {mime_type})...")
48
+ with open(video_path, 'rb') as f:
49
+ video_file = self.client.files.upload(file=f, config={'mime_type': mime_type})
 
 
 
50
 
51
+ print("⏳ Processing video...")
52
+ max_wait = 300
53
+ wait_time = 0
54
+ while video_file.state == "PROCESSING" and wait_time < max_wait:
55
+ time.sleep(3)
56
+ wait_time += 3
57
+ video_file = self.client.files.get(name=video_file.name)
58
 
59
+ if video_file.state == "FAILED":
60
+ raise Exception("Video processing failed")
 
 
 
 
 
61
 
62
+ print("πŸ” Analyzing team interactions...")
63
+ prompt = """Analyze this healthcare team interaction video and provide a comprehensive assessment based on the CICE 2.0 instrument's 18 interprofessional competencies, looking for these SPECIFIC BEHAVIORAL CUES:
64
 
65
+ For EACH competency, clearly state whether it was "OBSERVED" or "NOT OBSERVED" based on these specific behaviors:
66
 
67
+ 1. IDENTIFIES FACTORS INFLUENCING HEALTH STATUS
68
+ LOOK FOR: Team mentions allergy bracelet, fall-related trauma, multiple injuries, or states airway/breathing/circulation concerns out loud
69
 
70
+ 2. IDENTIFIES TEAM GOALS FOR THE PATIENT
71
+ LOOK FOR: Team verbalizes goals like: stabilize airway, CPR/AED, give epinephrine, control bleeding, preserve tooth, prepare EMS handoff
 
72
 
73
+ 3. PRIORITIZES GOALS FOCUSED ON IMPROVING HEALTH OUTCOMES
74
+ LOOK FOR: CPR/AED prioritized before bleeding/dental injury, EpiPen administered before addressing secondary injuries
75
 
76
+ 4. VERBALIZES DISCIPLINE-SPECIFIC ROLE (PRE-BRIEF)
77
+ LOOK FOR: Students acknowledge interprofessional communication expectations and scene safety review before scenario begins
78
 
79
+ 5. OFFERS TO SEEK GUIDANCE FROM COLLEAGUES
80
+ LOOK FOR: Peer-to-peer checks (e.g., dental to dental: confirm tooth storage; nursing to nursing: confirm CPR quality)
81
 
82
+ 6. COMMUNICATES ABOUT COST-EFFECTIVE AND TIMELY CARE
83
+ LOOK FOR: Team chooses readily available supplies (AED, saline, tourniquet) without delay, states need for rapid EMS transfer
84
 
85
+ 7. DIRECTS QUESTIONS TO OTHER HEALTH PROFESSIONALS BASED ON EXPERTISE
86
+ LOOK FOR: Asks discipline-specific expertise (e.g., "Dentalβ€”what do we do with the tooth?"), invites pharmacy/medical input on epinephrine use
87
 
88
+ 8. AVOIDS DISCIPLINE-SPECIFIC TERMINOLOGY
89
+ LOOK FOR: Uses plain language like "no pulse" instead of "asystole"
 
 
 
 
 
 
 
 
 
 
90
 
91
+ 9. EXPLAINS DISCIPLINE-SPECIFIC TERMINOLOGY WHEN NECESSARY
92
+ LOOK FOR: Clarifies medical/dental terms for others when necessary
93
 
94
+ 10. COMMUNICATES ROLES AND RESPONSIBILITIES CLEARLY
95
+ LOOK FOR: Announces assignments out loud: "I'll do compressions," "I'll call 911," "I'll document"
96
 
97
+ 11. ENGAGES IN ACTIVE LISTENING
98
+ LOOK FOR: Repeats back instructions ("Everyone clear for shock"), pauses to hear teammates' updates
 
 
 
 
 
99
 
100
+ 12. SOLICITS AND ACKNOWLEDGES PERSPECTIVES
101
+ LOOK FOR: Leader asks "Anything else we need to address?", responds to peer input respectfully
102
 
103
+ 13. RECOGNIZES APPROPRIATE CONTRIBUTIONS
104
+ LOOK FOR: Affirms correct actions verbally ("Good catch on allergy bracelet"), non-verbal acknowledgment (nodding, thumbs up)
105
 
106
+ 14. RESPECTFUL OF OTHER TEAM MEMBERS
107
+ LOOK FOR: Listens without interrupting, values input across professions
 
 
 
 
 
 
108
 
109
+ 15. COLLABORATIVELY WORKS THROUGH INTERPROFESSIONAL CONFLICTS
110
+ LOOK FOR: Negotiates intervention priorities (airway vs. bleeding) respectfully
111
 
112
+ 16. REFLECTS ON STRENGTHS OF TEAM INTERACTIONS (POST-BRIEF)
113
+ LOOK FOR: Notes strong teamwork, communication, or role clarity after the scenario
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
+ 17. REFLECTS ON CHALLENGES OF TEAM INTERACTIONS (POST-BRIEF)
116
+ LOOK FOR: Identifies confusion, delays, or role overlap in debriefing
117
+
118
+ 18. IDENTIFIES HOW TO IMPROVE TEAM EFFECTIVENESS (POST-BRIEF)
119
+ LOOK FOR: Suggests faster role assignment, consistent closed-loop communication, earlier epi use
120
+
121
+ STRUCTURE YOUR RESPONSE AS FOLLOWS:
122
+
123
+ ## OVERALL ASSESSMENT
124
+ Brief overview of the team interaction quality.
125
+
126
+ ## DETAILED COMPETENCY EVALUATION
127
+ For each of the 18 competencies, format as:
128
+ Competency [number]: [name]
129
+ Status: [OBSERVED/NOT OBSERVED]
130
+ Evidence: [Specific behavioral cue observed or explanation of absence]
131
+
132
+ ## STRENGTHS
133
+ Top 3-5 key strengths with specific examples
134
+
135
+ ## AREAS FOR IMPROVEMENT
136
+ Top 3-5 areas needing work with specific suggestions
137
+
138
+ ## AUDIO SUMMARY
139
+ [Create a concise 60-second spoken summary focusing on: overall performance level, top 3 strengths, top 3 areas for improvement, and 2 key actionable recommendations. Write this in a natural, conversational tone suitable for text-to-speech narration.]
140
+
141
+ ## FINAL SCORE
142
+ Competencies Observed: X/18
143
+ Overall Performance Level: [Exemplary (85-100%)/Proficient (70-84%)/Developing (50-69%)/Needs Improvement (0-49%)]"""
144
+
145
+ response = self.client.models.generate_content(
146
+ model=self.model_name,
147
+ contents=[
148
+ types.Part.from_uri(file_uri=video_file.uri, mime_type=video_file.mime_type),
149
+ prompt
150
+ ]
151
+ )
152
+ print("βœ… Analysis complete!")
153
+ return response.text
154
+
155
+ except Exception as e:
156
+ return f"Error during analysis: {str(e)}"
157
+
158
+ def generate_audio_feedback(self, text):
159
+ """Generate audio summary"""
160
+ audio_summary_match = re.search(r'## AUDIO SUMMARY\s*(.*?)(?=##|\Z)', text, re.DOTALL)
161
+ if audio_summary_match:
162
+ summary_text = audio_summary_match.group(1).strip()
163
+ else:
164
+ summary_text = self.create_brief_summary(text)
165
+
166
+ clean_text = re.sub(r'[#*_\[\]()]', ' ', summary_text)
167
+ clean_text = re.sub(r'\s+', ' ', clean_text)
168
+ clean_text = re.sub(r'[-β€’Β·]\s+', '', clean_text)
169
+
170
+ audio_script = f"""CICE Healthcare Team Assessment Summary.
171
+ {clean_text}
172
+ Please refer to the detailed written report for complete competency evaluation and specific recommendations.
173
+ End of audio summary."""
174
+
175
+ try:
176
+ tts = gTTS(text=audio_script, lang='en', slow=False, tld='com')
177
+ temp_audio = tempfile.NamedTemporaryFile(delete=False, suffix='.mp3')
178
+ tts.save(temp_audio.name)
179
+ temp_audio.close()
180
+ return temp_audio.name
181
+ except Exception as e:
182
+ print(f"⚠️ Audio generation failed: {str(e)}")
183
+ return None
184
+
185
+ def create_brief_summary(self, text):
186
+ """Create fallback summary"""
187
+ observed_count = text.lower().count("observed") - text.lower().count("not observed")
188
+ total = 18
189
+ percentage = (observed_count / total) * 100
190
+
191
+ if percentage >= 85:
192
+ level = "Exemplary"
193
+ elif percentage >= 70:
194
+ level = "Proficient"
195
+ elif percentage >= 50:
196
+ level = "Developing"
197
+ else:
198
+ level = "Needs Improvement"
199
+
200
+ return f"""The team demonstrated {level} performance with {observed_count} out of {total} competencies observed,
201
+ achieving {percentage:.0f} percent overall.
202
+ Key strengths included strong team communication and role clarity.
203
+ Areas for improvement include enhancing active listening and conflict resolution skills.
204
+ The team should focus on pre-briefing protocols and post-scenario debriefing to enhance future performance.
205
+ Emphasis should be placed on clear role assignment and closed-loop communication during critical interventions."""
206
+
207
+ def parse_assessment_scores(self, assessment_text):
208
+ """Parse scores for visualization"""
209
+ observed_count = assessment_text.lower().count("observed") - assessment_text.lower().count("not observed")
210
+ total_competencies = 18
211
+ percentage = (observed_count / total_competencies) * 100
212
+
213
+ if percentage >= 85:
214
+ level = "Exemplary"
215
+ color = "#0F766E"
216
+ elif percentage >= 70:
217
+ level = "Proficient"
218
+ color = "#1E40AF"
219
+ elif percentage >= 50:
220
+ level = "Developing"
221
+ color = "#EA580C"
222
+ else:
223
+ level = "Needs Improvement"
224
+ color = "#B91C1C"
225
+
226
+ return observed_count, total_competencies, percentage, level, color
227
+
228
+ def generate_pdf_report(self, assessment_text):
229
+ """Generate PDF report"""
230
+ try:
231
+ temp_pdf = tempfile.NamedTemporaryFile(delete=False, suffix='.pdf')
232
+ doc = SimpleDocTemplate(temp_pdf.name, pagesize=letter)
233
+ elements = []
234
+ styles = getSampleStyleSheet()
235
+
236
+ title_style = ParagraphStyle('CustomTitle', parent=styles['Heading1'], fontSize=20, textColor=HexColor('#111827'), spaceAfter=30, alignment=TA_CENTER)
237
+ heading_style = ParagraphStyle('CustomHeading', parent=styles['Heading2'], fontSize=14, textColor=HexColor('#1E40AF'), spaceAfter=12, spaceBefore=12, bold=True)
238
+ body_style = ParagraphStyle('CustomBody', parent=styles['BodyText'], fontSize=11, alignment=TA_JUSTIFY, spaceAfter=12)
239
+
240
+ elements.append(Paragraph("CICE 2.0 Healthcare Team Assessment Report", title_style))
241
+ elements.append(Spacer(1, 12))
242
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
243
+ elements.append(Paragraph(f"<b>Assessment Date:</b> {timestamp}", body_style))
244
+ elements.append(Spacer(1, 20))
245
+
246
+ lines = assessment_text.split('\n')
247
+ for line in lines:
248
+ line = line.strip()
249
+ if not line:
250
+ elements.append(Spacer(1, 6))
251
+ elif line.startswith('##'):
252
+ heading_text = line.replace('##', '').strip()
253
+ elements.append(Paragraph(heading_text, heading_style))
254
+ elif line.startswith('Competency'):
255
+ elements.append(Paragraph(f"<b>{line}</b>", body_style))
256
+ elif line.startswith('Status:') or line.startswith('Evidence:'):
257
+ elements.append(Paragraph(line, body_style))
258
+ else:
259
+ line = line.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
260
+ elements.append(Paragraph(line, body_style))
261
+
262
+ doc.build(elements)
263
+ temp_pdf.close()
264
+ return temp_pdf.name
265
+ except Exception as e:
266
+ print(f"⚠️ PDF generation failed: {str(e)}")
267
+ temp_txt = tempfile.NamedTemporaryFile(delete=False, suffix='.txt', mode='w')
268
+ temp_txt.write("CICE 2.0 Healthcare Team Interaction Assessment\n")
269
+ temp_txt.write("="*60 + "\n")
270
+ temp_txt.write(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
271
+ temp_txt.write("="*60 + "\n\n")
272
+ temp_txt.write(assessment_text)
273
+ temp_txt.close()
274
+ return temp_txt.name
275
+
276
+ # Initialize assessment tool
277
+ def initialize_assessor():
278
  try:
279
+ client = init_genai_client()
280
+ return CICE_Assessment(client)
281
+ except Exception as e:
282
+ print(f"Initialization error: {e}")
283
+ return None
 
 
 
 
284
 
285
+ assessor = initialize_assessor()
286
 
287
+ # Video processing functions
288
  def resize_video(input_path, target_width, target_height):
289
+ """Resize video for faster processing"""
290
  try:
291
  cap = cv2.VideoCapture(input_path)
292
  fps = int(cap.get(cv2.CAP_PROP_FPS))
293
  fourcc = cv2.VideoWriter_fourcc(*'mp4v')
294
+ temp_output = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
295
+ temp_output.close()
296
+ out = cv2.VideoWriter(temp_output.name, fourcc, fps, (target_width, target_height))
297
 
298
  print(f"πŸ“ Resizing video to {target_width}x{target_height}...")
299
  while True:
 
305
 
306
  cap.release()
307
  out.release()
308
+ print("βœ… Video resized successfully")
309
+ return temp_output.name
310
  except Exception as e:
311
+ print(f"⚠️ Video resize failed: {str(e)}")
312
  return input_path
313
 
314
+ # Main processing function
315
  def process_video(video, resize_option):
316
+ """Process uploaded video"""
317
  if video is None:
318
+ return "Please upload a video first.", None, None, None
319
+
320
+ if assessor is None:
321
+ return "Error: API key not configured. Please check your secrets.", None, None, None
322
 
323
  try:
324
+ print(f"πŸ“Ή Processing video ({os.path.getsize(video)/(1024*1024):.1f}MB)...")
 
 
325
 
326
+ # Handle resizing
327
  video_to_process = video
328
+ temp_resized_file = None
329
  if resize_option != "Original (No Resize)":
330
  if "640x480" in resize_option:
331
  target_width, target_height = 640, 480
 
334
  elif "1280x720" in resize_option:
335
  target_width, target_height = 1280, 720
336
  else:
337
+ target_width, target_height = 800, 600
338
+
339
+ temp_resized_file = resize_video(video, target_width, target_height)
340
+ video_to_process = temp_resized_file
341
 
342
+ # Analyze video
343
+ print("πŸ₯ Starting CICE 2.0 Assessment...")
344
+ assessment_result = assessor.analyze_video(video_to_process)
345
+
346
+ # Cleanup
347
+ if temp_resized_file and temp_resized_file != video:
348
+ try:
349
+ os.unlink(temp_resized_file)
350
+ except:
351
+ pass
352
 
 
353
  if "Error" in assessment_result:
354
  return assessment_result, None, None, None
355
 
356
+ # Generate outputs
357
+ print("πŸ”Š Generating audio summary...")
358
+ audio_path = assessor.generate_audio_feedback(assessment_result)
359
+
360
+ print("πŸ“„ Generating PDF report...")
361
+ pdf_path = assessor.generate_pdf_report(assessment_result)
362
+
363
+ observed, total, percentage, level, color = assessor.parse_assessment_scores(assessment_result)
364
 
365
+ # Create HTML summary
366
  summary_html = f"""
367
+ <div style="max-width:800px; margin:20px auto; padding:30px; border-radius:15px; box-shadow:0 2px 10px rgba(0,0,0,0.08); background:white;">
368
+ <h2 style="text-align:center; color:#111827; margin-bottom:30px; font-weight:600;">CICE 2.0 Assessment Summary</h2>
369
+ <div style="display:flex; justify-content:space-around; margin:30px 0;">
370
+ <div style="text-align:center;">
371
+ <div style="font-size:48px; font-weight:bold; color:{color};">{observed}/{total}</div>
372
+ <div style="color:#4B5563; margin-top:10px; font-weight:500;">Competencies Observed</div>
373
  </div>
374
+ <div style="text-align:center;">
375
+ <div style="font-size:48px; font-weight:bold; color:{color};">{percentage:.0f}%</div>
376
+ <div style="color:#4B5563; margin-top:10px; font-weight:500;">Overall Score</div>
377
  </div>
378
  </div>
379
+ <div style="text-align:center; padding:20px; background:#F8FAFC; border-radius:10px; margin:20px 0; border:1px solid #E2E8F0;">
380
+ <div style="font-size:24px; font-weight:bold; color:{color};">Performance Level: {level}</div>
381
+ </div>
382
+ <div style="margin-top:30px; padding:20px; background:#FFF7ED; border-radius:10px; border-left:4px solid #EA580C;">
383
+ <p style="text-align:center; color:#431407; margin:0; font-weight:600;">
384
+ πŸ”Š Listen to the 1-minute audio summary<br>
385
+ πŸ“„ Download the PDF report for complete documentation
386
+ </p>
387
  </div>
388
  </div>
389
  """
390
 
391
  return assessment_result, summary_html, audio_path, pdf_path
 
 
392
 
393
+ except Exception as e:
394
+ error_msg = f"❌ Error during processing: {str(e)}"
395
+ print(error_msg)
396
+ return error_msg, None, None, None
397
 
398
+ # Gradio Interface
399
+ with gr.Blocks(title="CICE 2.0 Healthcare Assessment", theme=gr.themes.Soft()) as demo:
400
  gr.Markdown("""
401
  # πŸ₯ CICE 2.0 Healthcare Team Assessment Tool
402
+
403
+ **Analyze healthcare team interactions using the 18-point CICE 2.0 framework**
404
+
405
+ ---
406
  """)
407
+
408
  with gr.Row():
409
  with gr.Column(scale=1):
410
+ gr.Markdown("### πŸ“Ή Video Input")
411
  resize_dropdown = gr.Dropdown(
412
  choices=[
413
  "Original (No Resize)",
414
+ "640x480 (Fastest - Recommended for quick tests)",
415
  "800x600 (Fast - Good balance)",
416
+ "1280x720 (HD - Best quality, slower)"
417
  ],
418
  value="800x600 (Fast - Good balance)",
419
+ label="Video Resolution"
420
+ )
421
+
422
+ video_input = gr.Video(
423
+ label="Upload Video",
424
+ sources=["upload"],
425
+ format="mp4",
426
+ include_audio=True
427
  )
428
+
429
+ analyze_btn = gr.Button("πŸ” Analyze Video", variant="primary", size="lg")
430
+
431
  with gr.Column(scale=2):
432
+ gr.Markdown("### πŸ“Š Assessment Results")
433
+ summary_output = gr.HTML(value="<p style='text-align:center; color:#6b7280; padding:40px;'>Results will appear here after analysis...</p>")
434
+ audio_output = gr.Audio(label="πŸ”Š 1-Minute Audio Summary", type="filepath")
435
+ pdf_output = gr.File(label="πŸ“„ Download Full PDF Report")
436
+ assessment_output = gr.Textbox(label="Detailed Assessment", lines=20, max_lines=30, interactive=False)
437
 
438
  analyze_btn.click(
439
  fn=process_video,
 
441
  outputs=[assessment_output, summary_output, audio_output, pdf_output]
442
  )
443
 
444
+ # Launch configuration for Hugging Face
445
  if __name__ == "__main__":
446
  demo.launch()