Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import csv | |
| import io | |
| from datetime import datetime | |
| # C-ICE 2.0 Instrument Data - Based on actual instrument | |
| CICE_DOMAINS = { | |
| "Values and Ethics": { | |
| "description": "Values and Ethics for Interprofessional Practice", | |
| "items": [ | |
| { | |
| "id": "1", | |
| "label": "Involves patient/client as a member of the health care team including goal development", | |
| "full_text": "Involves patient/client* as a member of the health care team including goal development (solicits information and listens to the patient, N/A if patient not present)", | |
| "behaviors": [ | |
| "Solicits information from patient/client", | |
| "Actively listens to patient/client concerns", | |
| "Includes patient in goal development discussions", | |
| "Acknowledges patient as team member", | |
| "Responds to patient input appropriately" | |
| ] | |
| }, | |
| { | |
| "id": "2", | |
| "label": "Validates patients' right to make their own health care decisions", | |
| "full_text": "Validate patients' right to make their own health care decisions (references patient's perspective and autonomy)", | |
| "behaviors": [ | |
| "References patient's perspective in discussions", | |
| "Respects patient autonomy", | |
| "Supports informed decision-making", | |
| "Acknowledges patient's right to refuse treatment", | |
| "Presents options without coercion" | |
| ] | |
| }, | |
| { | |
| "id": "3", | |
| "label": "Identifies factors influencing the health status of the patient", | |
| "full_text": "Identifies factors influencing the health status of the patient (verbalizes factors)", | |
| "behaviors": [ | |
| "Verbalizes social determinants of health", | |
| "Identifies medical factors affecting health", | |
| "Recognizes environmental influences", | |
| "Acknowledges lifestyle factors", | |
| "Considers psychological factors" | |
| ] | |
| }, | |
| { | |
| "id": "4", | |
| "label": "Addresses health equity of patient specific circumstances into care planning", | |
| "full_text": "Addresses health equity of patient specific circumstances into care planning (i.e., transportation, medication costs, religious or cultural practices)", | |
| "behaviors": [ | |
| "Considers transportation barriers", | |
| "Addresses medication cost concerns", | |
| "Respects religious practices in care planning", | |
| "Incorporates cultural practices", | |
| "Identifies resource limitations" | |
| ] | |
| }, | |
| { | |
| "id": "5", | |
| "label": "Identifies team goals for the patient", | |
| "full_text": "Identifies team goals for the patient", | |
| "behaviors": [ | |
| "Articulates clear patient care goals", | |
| "Ensures team alignment on goals", | |
| "Documents shared goals", | |
| "Communicates goals to all team members", | |
| "Reviews goals with patient" | |
| ] | |
| }, | |
| { | |
| "id": "6", | |
| "label": "Prioritizes goals focused on improving health outcomes", | |
| "full_text": "Prioritizes goals focused on improving health outcomes (N/A if only one goal is established)", | |
| "behaviors": [ | |
| "Ranks goals by importance", | |
| "Focuses on outcome-driven priorities", | |
| "Considers urgency in prioritization", | |
| "Balances short and long-term goals", | |
| "Adjusts priorities based on patient status" | |
| ] | |
| } | |
| ] | |
| }, | |
| "Roles and Responsibilities": { | |
| "description": "Roles and Responsibilities for Interprofessional Practice", | |
| "items": [ | |
| { | |
| "id": "7", | |
| "label": "Verbalizes discipline-specific role to team and/or patient", | |
| "full_text": "Verbalizes discipline-specific role to team and/or patient (introduces self/role)", | |
| "behaviors": [ | |
| "Introduces self with name and role", | |
| "Explains scope of practice", | |
| "Clarifies responsibilities to team", | |
| "Communicates role to patient", | |
| "Distinguishes role from other disciplines" | |
| ] | |
| }, | |
| { | |
| "id": "8", | |
| "label": "Offers to seek guidance from colleague when uncertain", | |
| "full_text": "Offers to seek guidance from colleague of the same discipline when uncertain about own knowledge, skills, and/or abilities", | |
| "behaviors": [ | |
| "Recognizes limits of own knowledge", | |
| "Verbalizes need for consultation", | |
| "Seeks appropriate colleague guidance", | |
| "Acknowledges uncertainty professionally", | |
| "Follows through on seeking guidance" | |
| ] | |
| }, | |
| { | |
| "id": "9", | |
| "label": "Communicates with team about cost-effective and/or timely care", | |
| "full_text": "Communicates with team members about cost-effective and/or timely care (i.e., generic medications, diagnostic utility)", | |
| "behaviors": [ | |
| "Suggests generic medication alternatives", | |
| "Considers diagnostic utility", | |
| "Discusses cost implications", | |
| "Proposes timely care options", | |
| "Balances quality with efficiency" | |
| ] | |
| }, | |
| { | |
| "id": "10", | |
| "label": "Directs questions to other health professionals based on expertise", | |
| "full_text": "Directs questions to other health professionals based on team member's expertise", | |
| "behaviors": [ | |
| "Identifies appropriate team member for questions", | |
| "Recognizes others' expertise areas", | |
| "Routes questions appropriately", | |
| "Respects disciplinary boundaries", | |
| "Leverages team knowledge effectively" | |
| ] | |
| } | |
| ] | |
| }, | |
| "Communication": { | |
| "description": "Interprofessional Communication", | |
| "items": [ | |
| { | |
| "id": "11", | |
| "label": "Avoids discipline-specific terminology when possible", | |
| "full_text": "Avoids discipline-specific terminology when possible (i.e., acronyms, abbreviations)", | |
| "behaviors": [ | |
| "Uses plain language", | |
| "Avoids unnecessary acronyms", | |
| "Minimizes jargon usage", | |
| "Speaks in accessible terms", | |
| "Checks for understanding" | |
| ] | |
| }, | |
| { | |
| "id": "12", | |
| "label": "Explains discipline-specific terminology when necessary", | |
| "full_text": "Explains discipline-specific terminology when necessary (i.e., responds to requests for clarification in a professional manner)", | |
| "behaviors": [ | |
| "Responds to clarification requests", | |
| "Explains terms professionally", | |
| "Provides context for terminology", | |
| "Ensures understanding after explanation", | |
| "Remains patient when clarifying" | |
| ] | |
| }, | |
| { | |
| "id": "13", | |
| "label": "Communicates one's roles and responsibilities clearly", | |
| "full_text": "Communicates one's roles and responsibilities clearly", | |
| "behaviors": [ | |
| "Articulates own responsibilities", | |
| "Clarifies role boundaries", | |
| "Communicates availability", | |
| "States capabilities clearly", | |
| "Defines scope of involvement" | |
| ] | |
| }, | |
| { | |
| "id": "14", | |
| "label": "Engages in active listening to team members", | |
| "full_text": "Engages in active listening to team members (verbal/nonverbal communication, engaging in conversation)", | |
| "behaviors": [ | |
| "Maintains appropriate eye contact", | |
| "Uses affirming nonverbal cues", | |
| "Paraphrases to confirm understanding", | |
| "Asks follow-up questions", | |
| "Avoids interrupting speakers" | |
| ] | |
| }, | |
| { | |
| "id": "15", | |
| "label": "Solicits and acknowledges perspectives of other team members", | |
| "full_text": "Solicits and acknowledges perspectives of other team members", | |
| "behaviors": [ | |
| "Asks for others' input", | |
| "Acknowledges contributions verbally", | |
| "Values diverse perspectives", | |
| "Incorporates others' ideas", | |
| "Thanks team members for input" | |
| ] | |
| }, | |
| { | |
| "id": "16", | |
| "label": "Recognizes when team members provide appropriate contribution", | |
| "full_text": "Recognizes verbally and/or nonverbally when team members provide an appropriate contribution to patient care", | |
| "behaviors": [ | |
| "Verbally acknowledges good contributions", | |
| "Uses positive nonverbal feedback", | |
| "Reinforces helpful behaviors", | |
| "Expresses appreciation", | |
| "Highlights effective teamwork" | |
| ] | |
| }, | |
| { | |
| "id": "17", | |
| "label": "Respectful of other team members", | |
| "full_text": "Respectful of other team members (i.e., maintains professionalism)", | |
| "behaviors": [ | |
| "Maintains professional demeanor", | |
| "Uses respectful language", | |
| "Demonstrates courtesy", | |
| "Avoids dismissive behaviors", | |
| "Treats all team members equitably" | |
| ] | |
| } | |
| ] | |
| }, | |
| "Teams and Teamwork": { | |
| "description": "Teams and Teamwork for Interprofessional Practice", | |
| "items": [ | |
| { | |
| "id": "18", | |
| "label": "Reaches consensus on care coordination for optimal health outcomes", | |
| "full_text": "Reaches consensus on care coordination for optimal health outcomes (i.e., integrates ideas and opinions of other team members, may be N/A in certain situations such as an emergency)", | |
| "behaviors": [ | |
| "Integrates ideas from team members", | |
| "Works toward group agreement", | |
| "Considers all opinions", | |
| "Facilitates consensus building", | |
| "Compromises when appropriate" | |
| ] | |
| }, | |
| { | |
| "id": "19", | |
| "label": "Collaboratively works through interprofessional conflicts", | |
| "full_text": "Collaboratively works through interprofessional conflicts (i.e., team members diffuse confrontations, N/A if no conflict)", | |
| "behaviors": [ | |
| "Addresses conflicts constructively", | |
| "Helps diffuse confrontations", | |
| "Seeks resolution collaboratively", | |
| "Remains calm during disagreements", | |
| "Focuses on patient-centered solutions" | |
| ] | |
| }, | |
| { | |
| "id": "20", | |
| "label": "Reflects on strengths of team interactions", | |
| "full_text": "Reflects on strengths of team interactions", | |
| "behaviors": [ | |
| "Identifies positive team dynamics", | |
| "Acknowledges effective collaboration", | |
| "Recognizes successful communication", | |
| "Notes areas of team excellence", | |
| "Verbalizes team strengths" | |
| ] | |
| }, | |
| { | |
| "id": "21", | |
| "label": "Reflects on challenges of team interactions", | |
| "full_text": "Reflects on challenges of team interactions", | |
| "behaviors": [ | |
| "Identifies areas needing improvement", | |
| "Acknowledges communication barriers", | |
| "Recognizes coordination challenges", | |
| "Notes interpersonal difficulties", | |
| "Verbalizes team challenges constructively" | |
| ] | |
| }, | |
| { | |
| "id": "22", | |
| "label": "Identifies how to improve team effectiveness", | |
| "full_text": "Identifies how to improve team effectiveness", | |
| "behaviors": [ | |
| "Proposes specific improvements", | |
| "Suggests process changes", | |
| "Offers actionable recommendations", | |
| "Identifies learning opportunities", | |
| "Plans for future improvement" | |
| ] | |
| } | |
| ] | |
| } | |
| } | |
| EVALUATION_CONTEXTS = ["Simulation", "Clinical Care", "Training Session", "Education", "Other"] | |
| PROFESSIONS = [ | |
| "Physician", "Nurse", "Pharmacist", "Physical Therapist", | |
| "Occupational Therapist", "Social Worker", "Dietitian", | |
| "Speech-Language Pathologist", "Psychologist", "Physician Assistant", | |
| "Nurse Practitioner", "Medical Student", "Nursing Student", | |
| "Pharmacy Student", "Other Healthcare Professional", "Other (specify)" | |
| ] | |
| # Global state for evaluation history | |
| evaluation_history = [] | |
| custom_css = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap'); | |
| * { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important; | |
| } | |
| .gradio-container { | |
| max-width: 900px !important; | |
| margin: 0 auto !important; | |
| background: #fff !important; | |
| } | |
| h1, h2, h3, h4 { | |
| color: #000 !important; | |
| font-weight: 600 !important; | |
| } | |
| .primary-btn { | |
| background: #000 !important; | |
| color: #fff !important; | |
| border: none !important; | |
| font-weight: 500 !important; | |
| padding: 12px 24px !important; | |
| } | |
| .primary-btn:hover { | |
| background: #333 !important; | |
| } | |
| .secondary-btn { | |
| background: #fff !important; | |
| color: #000 !important; | |
| border: 1px solid #000 !important; | |
| font-weight: 500 !important; | |
| padding: 12px 24px !important; | |
| } | |
| .secondary-btn:hover { | |
| background: #f5f5f5 !important; | |
| } | |
| .nav-btn { | |
| background: #fff !important; | |
| color: #000 !important; | |
| border: 1px solid #ccc !important; | |
| font-weight: 400 !important; | |
| } | |
| .nav-btn:hover { | |
| background: #f9f9f9 !important; | |
| border-color: #000 !important; | |
| } | |
| label { | |
| color: #000 !important; | |
| font-weight: 500 !important; | |
| } | |
| .error-text { | |
| color: #c00 !important; | |
| } | |
| """ | |
| def create_evaluation_summary( | |
| context, activity_title, activity_date, location, | |
| team_members, evaluator_name, evaluator_role, | |
| scores, behaviors, notes, domain_comments, overrides | |
| ): | |
| """Generate complete evaluation summary with all required fields.""" | |
| lines = [] | |
| lines.append("=" * 70) | |
| lines.append("C-ICE 2.0 EVALUATION SUMMARY") | |
| lines.append("=" * 70) | |
| lines.append("") | |
| # Header section | |
| lines.append("HEADER INFORMATION") | |
| lines.append("-" * 40) | |
| lines.append(f"Evaluation Context: {context}") | |
| lines.append(f"Activity Title: {activity_title}") | |
| lines.append(f"Date/Time: {activity_date}") | |
| lines.append(f"Location: {location or 'Not specified'}") | |
| lines.append("") | |
| lines.append("Team Identifier:") | |
| for member in team_members: | |
| if member.get("name"): | |
| lines.append(f" - {member['name']} ({member.get('profession', 'Not specified')})") | |
| lines.append("") | |
| lines.append(f"Evaluator Name: {evaluator_name}") | |
| lines.append(f"Evaluator Role: {evaluator_role}") | |
| lines.append("") | |
| lines.append("Scoring Key:") | |
| lines.append(" 0 = Below acceptable threshold of competency") | |
| lines.append(" 1 = Meets minimum acceptable threshold of competency") | |
| lines.append(" N/A = Not applicable to scenario") | |
| lines.append("") | |
| lines.append("=" * 70) | |
| lines.append("DOMAIN BREAKDOWN") | |
| lines.append("=" * 70) | |
| total_score = 0 | |
| total_possible = 0 | |
| for domain_name, domain_data in CICE_DOMAINS.items(): | |
| lines.append(f"\n{domain_name.upper()}") | |
| lines.append("-" * 40) | |
| domain_score = 0 | |
| domain_possible = 0 | |
| for item in domain_data["items"]: | |
| item_id = item["id"] | |
| score = scores.get(item_id, "N/A") | |
| behavior_list = behaviors.get(item_id, []) | |
| behaviors_met = len(behavior_list) if behavior_list else 0 | |
| total_behaviors = len(item["behaviors"]) | |
| note = notes.get(item_id, "") | |
| override = overrides.get(item_id, {}) | |
| lines.append(f"\n Item {item_id}: {item['label']}") | |
| lines.append(f" Final Score: {score}") | |
| lines.append(f" Behaviors Met: {behaviors_met}/{total_behaviors}") | |
| lines.append(f" Rule Applied: 0-1 behaviors -> 0; 2-5 behaviors -> 1") | |
| if override.get("flagged"): | |
| lines.append(f" Override Flag: YES") | |
| lines.append(f" Override Note: {override.get('note', 'No note provided')}") | |
| if note: | |
| lines.append(f" Item Note: {note}") | |
| if score == 1: | |
| domain_score += 1 | |
| total_score += 1 | |
| if score != "N/A": | |
| domain_possible += 1 | |
| total_possible += 1 | |
| lines.append(f"\n DOMAIN TOTAL: {domain_score}/{domain_possible} (sum of 1's, excluding N/A)") | |
| domain_comment = domain_comments.get(domain_name, "") | |
| if domain_comment: | |
| lines.append(f" Domain Comments: {domain_comment}") | |
| lines.append("") | |
| lines.append("=" * 70) | |
| lines.append("TOTALS") | |
| lines.append("=" * 70) | |
| lines.append(f"Total Earned Points: {total_score}") | |
| lines.append(f"Total Possible Points: {total_possible}") | |
| if total_possible > 0: | |
| percent = round((total_score / total_possible) * 100, 1) | |
| lines.append(f"Percent Earned: {percent}%") | |
| lines.append("=" * 70) | |
| return "\n".join(lines) | |
| def create_csv_export( | |
| context, activity_title, activity_date, location, | |
| team_members, evaluator_name, evaluator_role, | |
| scores, behaviors, notes, domain_comments, overrides | |
| ): | |
| """Generate structured CSV export.""" | |
| output = io.StringIO() | |
| writer = csv.writer(output) | |
| # Header | |
| writer.writerow(["C-ICE 2.0 EVALUATION EXPORT"]) | |
| writer.writerow([]) | |
| writer.writerow(["HEADER INFORMATION"]) | |
| writer.writerow(["Evaluation Context", context]) | |
| writer.writerow(["Activity Title", activity_title]) | |
| writer.writerow(["Date/Time", activity_date]) | |
| writer.writerow(["Location", location or "Not specified"]) | |
| writer.writerow(["Evaluator Name", evaluator_name]) | |
| writer.writerow(["Evaluator Role", evaluator_role]) | |
| writer.writerow([]) | |
| # Team members | |
| writer.writerow(["TEAM IDENTIFIER"]) | |
| writer.writerow(["Name", "Profession"]) | |
| for member in team_members: | |
| if member.get("name"): | |
| writer.writerow([member["name"], member.get("profession", "")]) | |
| writer.writerow([]) | |
| # Item scores | |
| writer.writerow(["ITEM SCORES"]) | |
| writer.writerow(["Domain", "Item ID", "Item Label", "Final Score", "Behaviors Met", | |
| "Total Behaviors", "Rule Applied", "Override Flag", "Override Note", "Item Note"]) | |
| for domain_name, domain_data in CICE_DOMAINS.items(): | |
| for item in domain_data["items"]: | |
| item_id = item["id"] | |
| score = scores.get(item_id, "N/A") | |
| behavior_list = behaviors.get(item_id, []) | |
| behaviors_met = len(behavior_list) if behavior_list else 0 | |
| total_behaviors = len(item["behaviors"]) | |
| override = overrides.get(item_id, {}) | |
| note = notes.get(item_id, "") | |
| writer.writerow([ | |
| domain_name, | |
| item_id, | |
| item["label"], | |
| score, | |
| behaviors_met, | |
| total_behaviors, | |
| "0-1->0; 2-5->1", | |
| "Yes" if override.get("flagged") else "No", | |
| override.get("note", ""), | |
| note | |
| ]) | |
| writer.writerow([]) | |
| # Domain comments | |
| writer.writerow(["DOMAIN COMMENTS"]) | |
| writer.writerow(["Domain", "Comments"]) | |
| for domain_name in CICE_DOMAINS.keys(): | |
| comment = domain_comments.get(domain_name, "") | |
| writer.writerow([domain_name, comment]) | |
| return output.getvalue() | |
| with gr.Blocks(title="C-ICE 2.0 Evaluation", css=custom_css) as app: | |
| member_count = gr.State(1) | |
| current_eval_data = gr.State({}) | |
| selected_history_idx = gr.State(-1) | |
| gr.Markdown("# C-ICE 2.0") | |
| gr.Markdown("*Creighton Interprofessional Collaborative Evaluation*") | |
| gr.Markdown("---") | |
| # Home Page | |
| with gr.Column(visible=True) as home_page: | |
| gr.Markdown("## Welcome") | |
| gr.Markdown("This application supports faculty, healthcare workers, and interprofessional team members in completing evaluations using the C-ICE 2.0 instrument.") | |
| with gr.Column(): | |
| welcome_btn = gr.Button("Welcome Video", size="lg", elem_classes=["secondary-btn"]) | |
| intro_btn = gr.Button("Introductory Video", size="lg", elem_classes=["secondary-btn"]) | |
| training_btn = gr.Button("Training Documents", size="lg", elem_classes=["secondary-btn"]) | |
| start_eval_btn = gr.Button("Start Evaluation", size="lg", elem_classes=["primary-btn"]) | |
| help_btn = gr.Button("Help and Tutorials", size="lg", elem_classes=["secondary-btn"]) | |
| profile_btn = gr.Button("Profile / Settings", size="lg", elem_classes=["secondary-btn"]) | |
| history_btn = gr.Button("View History", size="lg", elem_classes=["secondary-btn"]) | |
| # Training Documents Page | |
| with gr.Column(visible=False) as training_page: | |
| gr.Markdown("## Training Documents") | |
| back_from_training = gr.Button("Back", elem_classes=["nav-btn"]) | |
| with gr.Accordion("C-ICE 2.0 Instrument", open=False): | |
| gr.Markdown(""" | |
| The C-ICE 2.0 instrument measures interprofessional collaborative competencies across four domains: | |
| 1. **Values and Ethics** (6 items) | |
| 2. **Roles and Responsibilities** (4 items) | |
| 3. **Communication** (7 items) | |
| 4. **Teams and Teamwork** (5 items) | |
| Total: 22 items | |
| """) | |
| with gr.Accordion("Scoring Guide", open=False): | |
| gr.Markdown(""" | |
| **Score Values:** | |
| - **0** = Below acceptable threshold of competency | |
| - **1** = Meets minimum acceptable threshold of competency | |
| - **N/A** = Not applicable to scenario or not asked to do this item | |
| **Behavior Threshold Rule:** | |
| - 0-1 behaviors observed: Score 0 | |
| - 2-5 behaviors observed: Score 1 | |
| """) | |
| with gr.Accordion("Worksheet Guide", open=False): | |
| gr.Markdown("Each evaluation item includes a behavior checklist. Check observed behaviors to help determine the appropriate score.") | |
| with gr.Accordion("FAQs", open=False): | |
| gr.Markdown(""" | |
| **Q: Who should use C-ICE 2.0?** | |
| A: Faculty, healthcare workers, and interprofessional team members. | |
| **Q: How long does an evaluation take?** | |
| A: Typically 10-20 minutes depending on detail level. | |
| **Q: Can scores be overridden?** | |
| A: Yes. If you override the suggested score, you must provide justification. | |
| """) | |
| # Help Page | |
| with gr.Column(visible=False) as help_page: | |
| gr.Markdown("## Help and Tutorials") | |
| back_from_help = gr.Button("Back", elem_classes=["nav-btn"]) | |
| with gr.Accordion("Step-by-Step Guide", open=True): | |
| gr.Markdown(""" | |
| 1. **Start Evaluation**: Click the Start Evaluation button on the home screen | |
| 2. **Set Context**: Select the evaluation context (Simulation, Clinical Care, etc.) | |
| 3. **Add Activity Details**: Enter the activity title, date, and location | |
| 4. **Add Team Members**: Enter each team member's name and profession | |
| 5. **Complete Instrument**: Score each item in each domain | |
| 6. **Review and Submit**: Review the summary and export results | |
| """) | |
| with gr.Accordion("Searchable FAQ", open=False): | |
| faq_search = gr.Textbox(label="Search FAQs", placeholder="Enter search term...") | |
| gr.Markdown("Common questions will appear here based on your search.") | |
| with gr.Accordion("Contact Support", open=False): | |
| support_name = gr.Textbox(label="Your Name") | |
| support_email = gr.Textbox(label="Email") | |
| support_message = gr.Textbox(label="Message", lines=4) | |
| gr.Button("Submit Request", elem_classes=["secondary-btn"]) | |
| # Profile Page | |
| with gr.Column(visible=False) as profile_page: | |
| gr.Markdown("## Profile / Settings") | |
| back_from_profile = gr.Button("Back", elem_classes=["nav-btn"]) | |
| with gr.Group(): | |
| profile_name = gr.Textbox(label="Name", value="", placeholder="Enter your name") | |
| profile_role = gr.Textbox(label="Role/Title", value="", placeholder="Enter your role") | |
| profile_org = gr.Textbox(label="Organization", value="", placeholder="Enter your organization") | |
| profile_profession = gr.Dropdown(label="Default Profession", choices=PROFESSIONS, value="Physician") | |
| gr.Markdown("### Notification Preferences") | |
| notif_email = gr.Checkbox(label="Email notifications", value=True) | |
| notif_reminder = gr.Checkbox(label="Evaluation reminders", value=True) | |
| save_profile_btn = gr.Button("Save Profile", elem_classes=["primary-btn"]) | |
| profile_status = gr.Markdown("") | |
| # Step 1: Context | |
| with gr.Column(visible=False) as eval_step1: | |
| gr.Markdown("## Step 1: Evaluation Context") | |
| gr.Markdown("Select the context for this evaluation (required)") | |
| back_to_home = gr.Button("Back", elem_classes=["nav-btn"]) | |
| eval_context = gr.Radio( | |
| choices=EVALUATION_CONTEXTS, | |
| label="Evaluation Context", | |
| value=None | |
| ) | |
| next_to_step2 = gr.Button("Continue", elem_classes=["primary-btn"]) | |
| step1_error = gr.Markdown("", elem_classes=["error-text"]) | |
| # Step 2: Activity Details | |
| with gr.Column(visible=False) as eval_step2: | |
| gr.Markdown("## Step 2: Activity Details") | |
| back_to_step1 = gr.Button("Back", elem_classes=["nav-btn"]) | |
| activity_title = gr.Textbox( | |
| label="Activity Title/Description (required)", | |
| placeholder="e.g., End-of-life care simulation" | |
| ) | |
| activity_date = gr.Textbox( | |
| label="Date/Time", | |
| value=datetime.now().strftime("%Y-%m-%d %H:%M") | |
| ) | |
| activity_location = gr.Textbox( | |
| label="Location (optional)", | |
| placeholder="e.g., Simulation Center Room 201" | |
| ) | |
| next_to_step3 = gr.Button("Continue", elem_classes=["primary-btn"]) | |
| step2_error = gr.Markdown("", elem_classes=["error-text"]) | |
| # Step 3: Team Members | |
| with gr.Column(visible=False) as eval_step3: | |
| gr.Markdown("## Step 3: Team Identifier") | |
| gr.Markdown("Add all team members being evaluated (at least 1 required)") | |
| back_to_step2 = gr.Button("Back", elem_classes=["nav-btn"]) | |
| team_inputs = [] | |
| for i in range(10): | |
| with gr.Row(visible=(i == 0)) as team_row: | |
| name_input = gr.Textbox(label=f"Team Member {i+1} Name", scale=2) | |
| profession_input = gr.Dropdown( | |
| label="Profession/Discipline", | |
| choices=PROFESSIONS, | |
| scale=2 | |
| ) | |
| team_inputs.append((team_row, name_input, profession_input)) | |
| with gr.Row(): | |
| add_member_btn = gr.Button("Add Member", elem_classes=["secondary-btn"]) | |
| remove_member_btn = gr.Button("Remove Member", elem_classes=["secondary-btn"]) | |
| next_to_step4 = gr.Button("Continue", elem_classes=["primary-btn"]) | |
| step3_error = gr.Markdown("", elem_classes=["error-text"]) | |
| # Step 4: Evaluator Info | |
| with gr.Column(visible=False) as eval_step4: | |
| gr.Markdown("## Step 4: Evaluator Information") | |
| back_to_step3 = gr.Button("Back", elem_classes=["nav-btn"]) | |
| evaluator_name = gr.Textbox(label="Your Name", placeholder="Enter your name") | |
| evaluator_role = gr.Textbox(label="Your Role/Title", placeholder="Enter your role") | |
| next_to_instrument = gr.Button("Start Evaluation", elem_classes=["primary-btn"]) | |
| step4_error = gr.Markdown("", elem_classes=["error-text"]) | |
| # C-ICE 2.0 Instrument | |
| with gr.Column(visible=False) as instrument_page: | |
| gr.Markdown("## C-ICE 2.0 Instrument") | |
| back_to_step4 = gr.Button("Back", elem_classes=["nav-btn"]) | |
| with gr.Tabs() as domain_tabs: | |
| domain_components = {} | |
| for domain_name, domain_data in CICE_DOMAINS.items(): | |
| with gr.Tab(domain_name): | |
| gr.Markdown(f"*{domain_data['description']}*") | |
| domain_components[domain_name] = {"items": {}} | |
| for item in domain_data["items"]: | |
| item_id = item["id"] | |
| with gr.Group(): | |
| gr.Markdown(f"### {item_id}. {item['label']}") | |
| score_radio = gr.Radio( | |
| choices=["1 - Meets Expected", "0 - Below Expected", "N/A - Not Applicable"], | |
| label="Score", | |
| value=None | |
| ) | |
| with gr.Accordion("Behavior Worksheet", open=False): | |
| gr.Markdown(f"*{item['full_text']}*") | |
| behavior_checks = gr.CheckboxGroup( | |
| choices=item["behaviors"], | |
| label="Observable Behaviors (check all observed)", | |
| value=[] | |
| ) | |
| gr.Markdown("**Scoring Key:** 0-1 behaviors = Score 0; 2-5 behaviors = Score 1") | |
| suggested_score = gr.Markdown("Suggested score: --") | |
| override_check = gr.Checkbox(label="Override suggested score") | |
| override_note = gr.Textbox( | |
| label="Override Note (required if overriding)", | |
| visible=False, | |
| placeholder="Explain reason for override..." | |
| ) | |
| item_notes = gr.Textbox( | |
| label="Item Notes (optional)", | |
| placeholder="Add evidence or examples...", | |
| lines=2 | |
| ) | |
| domain_components[domain_name]["items"][item_id] = { | |
| "score": score_radio, | |
| "behaviors": behavior_checks, | |
| "suggested": suggested_score, | |
| "override": override_check, | |
| "override_note": override_note, | |
| "notes": item_notes | |
| } | |
| def update_suggested(behaviors, item_id=item_id): | |
| count = len(behaviors) if behaviors else 0 | |
| if count <= 1: | |
| return f"Suggested score: **0** ({count} behaviors observed)" | |
| else: | |
| return f"Suggested score: **1** ({count} behaviors observed)" | |
| behavior_checks.change( | |
| fn=update_suggested, | |
| inputs=[behavior_checks], | |
| outputs=[suggested_score] | |
| ) | |
| override_check.change( | |
| fn=lambda x: gr.update(visible=x), | |
| inputs=[override_check], | |
| outputs=[override_note] | |
| ) | |
| domain_comment = gr.Textbox( | |
| label=f"{domain_name} - Domain Comments", | |
| placeholder="Add overall comments for this domain...", | |
| lines=3 | |
| ) | |
| domain_components[domain_name]["comment"] = domain_comment | |
| submit_eval_btn = gr.Button("Submit Evaluation", elem_classes=["primary-btn"], size="lg") | |
| # Summary Page | |
| with gr.Column(visible=False) as summary_page: | |
| gr.Markdown("## Evaluation Summary") | |
| summary_display = gr.Textbox( | |
| label="Evaluation Results", | |
| lines=35, | |
| interactive=False | |
| ) | |
| gr.Markdown("### Actions") | |
| with gr.Row(): | |
| export_pdf_btn = gr.Button("Export PDF", elem_classes=["secondary-btn"]) | |
| export_csv_btn = gr.Button("Export CSV", elem_classes=["secondary-btn"]) | |
| email_results_btn = gr.Button("Email Results", elem_classes=["secondary-btn"]) | |
| save_history_btn = gr.Button("Save to History", elem_classes=["secondary-btn"]) | |
| with gr.Row(): | |
| new_eval_btn = gr.Button("New Evaluation", elem_classes=["primary-btn"]) | |
| csv_output = gr.File(label="Download CSV", visible=False) | |
| pdf_output = gr.File(label="Download PDF", visible=False) | |
| save_status = gr.Markdown("") | |
| email_status = gr.Markdown("") | |
| # History Page | |
| with gr.Column(visible=False) as history_page: | |
| gr.Markdown("## Evaluation History") | |
| back_from_history = gr.Button("Back", elem_classes=["nav-btn"]) | |
| gr.Markdown("### Filters") | |
| with gr.Row(): | |
| filter_context = gr.Dropdown( | |
| label="Filter by Context", | |
| choices=["All"] + EVALUATION_CONTEXTS, | |
| value="All" | |
| ) | |
| filter_date_from = gr.Textbox(label="From Date (YYYY-MM-DD)", placeholder="2024-01-01") | |
| filter_date_to = gr.Textbox(label="To Date (YYYY-MM-DD)", placeholder="2024-12-31") | |
| apply_filter_btn = gr.Button("Apply Filters", elem_classes=["secondary-btn"]) | |
| gr.Markdown("### Past Evaluations") | |
| history_display = gr.Dataframe( | |
| headers=["Index", "Date", "Activity Title", "Context", "Score"], | |
| label="Select an evaluation to view or export", | |
| interactive=False | |
| ) | |
| gr.Markdown("### Actions") | |
| with gr.Row(): | |
| view_eval_btn = gr.Button("View Selected", elem_classes=["secondary-btn"]) | |
| export_history_pdf_btn = gr.Button("Export PDF", elem_classes=["secondary-btn"]) | |
| export_history_csv_btn = gr.Button("Export CSV", elem_classes=["secondary-btn"]) | |
| duplicate_eval_btn = gr.Button("Duplicate (New with Same Team)", elem_classes=["secondary-btn"]) | |
| history_action_status = gr.Markdown("") | |
| history_csv_output = gr.File(label="Download", visible=False) | |
| # Navigation Functions | |
| def show_page(page_name): | |
| return { | |
| home_page: page_name == "home", | |
| training_page: page_name == "training", | |
| help_page: page_name == "help", | |
| profile_page: page_name == "profile", | |
| eval_step1: page_name == "step1", | |
| eval_step2: page_name == "step2", | |
| eval_step3: page_name == "step3", | |
| eval_step4: page_name == "step4", | |
| instrument_page: page_name == "instrument", | |
| summary_page: page_name == "summary", | |
| history_page: page_name == "history" | |
| } | |
| def go_home(): | |
| updates = show_page("home") | |
| return [gr.update(visible=v) for v in updates.values()] | |
| def go_training(): | |
| updates = show_page("training") | |
| return [gr.update(visible=v) for v in updates.values()] | |
| def go_help(): | |
| updates = show_page("help") | |
| return [gr.update(visible=v) for v in updates.values()] | |
| def go_profile(): | |
| updates = show_page("profile") | |
| return [gr.update(visible=v) for v in updates.values()] | |
| def go_step1(): | |
| updates = show_page("step1") | |
| return [gr.update(visible=v) for v in updates.values()] | |
| def go_step2(context): | |
| if not context: | |
| return [gr.update()] * 11 + ["Please select an evaluation context"] | |
| updates = show_page("step2") | |
| return [gr.update(visible=v) for v in updates.values()] + [""] | |
| def go_step3(title): | |
| if not title or not title.strip(): | |
| return [gr.update()] * 11 + ["Please enter an activity title"] | |
| updates = show_page("step3") | |
| return [gr.update(visible=v) for v in updates.values()] + [""] | |
| def go_step4(name1, prof1): | |
| if not name1 or not name1.strip(): | |
| return [gr.update()] * 11 + ["Please enter at least one team member"] | |
| updates = show_page("step4") | |
| return [gr.update(visible=v) for v in updates.values()] + [""] | |
| def go_instrument(eval_name, eval_role): | |
| if not eval_name or not eval_name.strip(): | |
| return [gr.update()] * 11 + ["Please enter your name"] | |
| if not eval_role or not eval_role.strip(): | |
| return [gr.update()] * 11 + ["Please enter your role"] | |
| updates = show_page("instrument") | |
| return [gr.update(visible=v) for v in updates.values()] + [""] | |
| def go_history(): | |
| updates = show_page("history") | |
| # Prepare history data | |
| history_data = [] | |
| for idx, entry in enumerate(evaluation_history): | |
| history_data.append([ | |
| idx, | |
| entry.get("date", ""), | |
| entry.get("activity", ""), | |
| entry.get("context", ""), | |
| entry.get("score", "") | |
| ]) | |
| return [gr.update(visible=v) for v in updates.values()] + [gr.update(value=history_data)] | |
| def go_summary(): | |
| updates = show_page("summary") | |
| return [gr.update(visible=v) for v in updates.values()] | |
| def add_team_member(count): | |
| new_count = min(count + 1, 10) | |
| updates = [] | |
| for i in range(10): | |
| updates.append(gr.update(visible=(i < new_count))) | |
| return [new_count] + updates | |
| def remove_team_member(count): | |
| new_count = max(count - 1, 1) | |
| updates = [] | |
| for i in range(10): | |
| updates.append(gr.update(visible=(i < new_count))) | |
| return [new_count] + updates | |
| def save_profile(name, role, org, profession): | |
| return "Profile saved successfully" | |
| def generate_summary(context, title, date, location, eval_name, eval_role, | |
| name1, prof1, name2, prof2, name3, prof3, name4, prof4, name5, prof5, | |
| *domain_inputs): | |
| team = [] | |
| names_profs = [(name1, prof1), (name2, prof2), (name3, prof3), (name4, prof4), (name5, prof5)] | |
| for name, prof in names_profs: | |
| if name and name.strip(): | |
| team.append({"name": name, "profession": prof or "Not specified"}) | |
| scores = {} | |
| behaviors = {} | |
| notes = {} | |
| domain_comments = {} | |
| overrides = {} | |
| idx = 0 | |
| for domain_name, domain_data in CICE_DOMAINS.items(): | |
| for item in domain_data["items"]: | |
| item_id = item["id"] | |
| score_val = domain_inputs[idx] if idx < len(domain_inputs) else None | |
| behavior_val = domain_inputs[idx + 1] if idx + 1 < len(domain_inputs) else [] | |
| override_val = domain_inputs[idx + 2] if idx + 2 < len(domain_inputs) else False | |
| override_note_val = domain_inputs[idx + 3] if idx + 3 < len(domain_inputs) else "" | |
| notes_val = domain_inputs[idx + 4] if idx + 4 < len(domain_inputs) else "" | |
| if score_val: | |
| if "1 -" in score_val: | |
| scores[item_id] = 1 | |
| elif "0 -" in score_val: | |
| scores[item_id] = 0 | |
| else: | |
| scores[item_id] = "N/A" | |
| else: | |
| scores[item_id] = "N/A" | |
| behaviors[item_id] = behavior_val if behavior_val else [] | |
| notes[item_id] = notes_val or "" | |
| overrides[item_id] = {"flagged": override_val, "note": override_note_val} | |
| idx += 5 | |
| if idx < len(domain_inputs): | |
| domain_comments[domain_name] = domain_inputs[idx] or "" | |
| idx += 1 | |
| summary = create_evaluation_summary( | |
| context or "Not specified", | |
| title or "Not specified", | |
| date or datetime.now().strftime("%Y-%m-%d %H:%M"), | |
| location or "", | |
| team, | |
| eval_name or "Not specified", | |
| eval_role or "Not specified", | |
| scores, | |
| behaviors, | |
| notes, | |
| domain_comments, | |
| overrides | |
| ) | |
| updates = show_page("summary") | |
| return [gr.update(visible=v) for v in updates.values()] + [summary] | |
| def export_csv(summary_text): | |
| if not summary_text: | |
| return gr.update(visible=False) | |
| output = io.StringIO() | |
| writer = csv.writer(output) | |
| for line in summary_text.split("\n"): | |
| writer.writerow([line]) | |
| csv_content = output.getvalue() | |
| filename = f"cice_evaluation_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" | |
| with open(filename, "w") as f: | |
| f.write(csv_content) | |
| return gr.update(value=filename, visible=True) | |
| def export_pdf(summary_text): | |
| # For HuggingFace deployment, we'll create a text file as PDF generation requires additional libraries | |
| if not summary_text: | |
| return gr.update(visible=False) | |
| filename = f"cice_evaluation_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt" | |
| with open(filename, "w") as f: | |
| f.write(summary_text) | |
| return gr.update(value=filename, visible=True) | |
| def email_results(): | |
| return "Email functionality requires server configuration. Please use Export CSV/PDF and attach to email." | |
| def save_to_history(summary_text, context, title, date): | |
| global evaluation_history | |
| # Calculate score from summary | |
| total_line = [l for l in summary_text.split("\n") if "Total Earned Points:" in l] | |
| score = total_line[0].split(":")[1].strip() if total_line else "N/A" | |
| evaluation_history.append({ | |
| "date": datetime.now().strftime("%Y-%m-%d %H:%M"), | |
| "activity": title or "Untitled", | |
| "context": context or "Not specified", | |
| "score": score, | |
| "summary": summary_text | |
| }) | |
| return "Saved to history successfully" | |
| def filter_history(filter_ctx, date_from, date_to): | |
| filtered = [] | |
| for idx, entry in enumerate(evaluation_history): | |
| # Filter by context | |
| if filter_ctx != "All" and entry.get("context") != filter_ctx: | |
| continue | |
| # Filter by date range | |
| entry_date = entry.get("date", "")[:10] | |
| if date_from and entry_date < date_from: | |
| continue | |
| if date_to and entry_date > date_to: | |
| continue | |
| filtered.append([ | |
| idx, | |
| entry.get("date", ""), | |
| entry.get("activity", ""), | |
| entry.get("context", ""), | |
| entry.get("score", "") | |
| ]) | |
| return gr.update(value=filtered) | |
| def view_history_eval(history_data): | |
| if not history_data or len(history_data) == 0: | |
| return "No evaluation selected" | |
| # Get selected row (first row if multiple selected) | |
| try: | |
| selected_idx = int(history_data[0][0]) if history_data else -1 | |
| if selected_idx >= 0 and selected_idx < len(evaluation_history): | |
| return evaluation_history[selected_idx].get("summary", "No summary available") | |
| except: | |
| pass | |
| return "No evaluation selected or invalid selection" | |
| def export_history_csv(history_data): | |
| if not history_data or len(history_data) == 0: | |
| return gr.update(visible=False) | |
| try: | |
| selected_idx = int(history_data[0][0]) | |
| if selected_idx >= 0 and selected_idx < len(evaluation_history): | |
| summary = evaluation_history[selected_idx].get("summary", "") | |
| if summary: | |
| output = io.StringIO() | |
| writer = csv.writer(output) | |
| for line in summary.split("\n"): | |
| writer.writerow([line]) | |
| filename = f"cice_history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" | |
| with open(filename, "w") as f: | |
| f.write(output.getvalue()) | |
| return gr.update(value=filename, visible=True) | |
| except: | |
| pass | |
| return gr.update(visible=False) | |
| all_pages = [home_page, training_page, help_page, profile_page, eval_step1, | |
| eval_step2, eval_step3, eval_step4, instrument_page, summary_page, history_page] | |
| training_btn.click(go_training, outputs=all_pages) | |
| start_eval_btn.click(go_step1, outputs=all_pages) | |
| help_btn.click(go_help, outputs=all_pages) | |
| profile_btn.click(go_profile, outputs=all_pages) | |
| history_btn.click(go_history, outputs=all_pages + [history_display]) | |
| back_from_training.click(go_home, outputs=all_pages) | |
| back_from_help.click(go_home, outputs=all_pages) | |
| back_from_profile.click(go_home, outputs=all_pages) | |
| back_to_home.click(go_home, outputs=all_pages) | |
| back_from_history.click(go_home, outputs=all_pages) | |
| back_to_step1.click(go_step1, outputs=all_pages) | |
| back_to_step2.click( | |
| fn=lambda: [gr.update(visible=v) for v in show_page("step2").values()], | |
| outputs=all_pages | |
| ) | |
| back_to_step3.click( | |
| fn=lambda: [gr.update(visible=v) for v in show_page("step3").values()], | |
| outputs=all_pages | |
| ) | |
| back_to_step4.click( | |
| fn=lambda: [gr.update(visible=v) for v in show_page("step4").values()], | |
| outputs=all_pages | |
| ) | |
| next_to_step2.click(go_step2, inputs=[eval_context], outputs=all_pages + [step1_error]) | |
| next_to_step3.click(go_step3, inputs=[activity_title], outputs=all_pages + [step2_error]) | |
| next_to_step4.click( | |
| go_step4, | |
| inputs=[team_inputs[0][1], team_inputs[0][2]], | |
| outputs=all_pages + [step3_error] | |
| ) | |
| next_to_instrument.click( | |
| go_instrument, | |
| inputs=[evaluator_name, evaluator_role], | |
| outputs=all_pages + [step4_error] | |
| ) | |
| team_rows = [t[0] for t in team_inputs] | |
| add_member_btn.click(add_team_member, inputs=[member_count], outputs=[member_count] + team_rows) | |
| remove_member_btn.click(remove_team_member, inputs=[member_count], outputs=[member_count] + team_rows) | |
| save_profile_btn.click( | |
| save_profile, | |
| inputs=[profile_name, profile_role, profile_org, profile_profession], | |
| outputs=[profile_status] | |
| ) | |
| instrument_inputs = [eval_context, activity_title, activity_date, activity_location, | |
| evaluator_name, evaluator_role, | |
| team_inputs[0][1], team_inputs[0][2], | |
| team_inputs[1][1], team_inputs[1][2], | |
| team_inputs[2][1], team_inputs[2][2], | |
| team_inputs[3][1], team_inputs[3][2], | |
| team_inputs[4][1], team_inputs[4][2]] | |
| for domain_name, domain_data in CICE_DOMAINS.items(): | |
| for item_id, components in domain_components[domain_name]["items"].items(): | |
| instrument_inputs.extend([ | |
| components["score"], | |
| components["behaviors"], | |
| components["override"], | |
| components["override_note"], | |
| components["notes"] | |
| ]) | |
| instrument_inputs.append(domain_components[domain_name]["comment"]) | |
| submit_eval_btn.click( | |
| generate_summary, | |
| inputs=instrument_inputs, | |
| outputs=all_pages + [summary_display] | |
| ) | |
| # Summary page actions | |
| export_csv_btn.click(export_csv, inputs=[summary_display], outputs=[csv_output]) | |
| export_pdf_btn.click(export_pdf, inputs=[summary_display], outputs=[pdf_output]) | |
| email_results_btn.click(email_results, outputs=[email_status]) | |
| save_history_btn.click( | |
| save_to_history, | |
| inputs=[summary_display, eval_context, activity_title, activity_date], | |
| outputs=[save_status] | |
| ) | |
| new_eval_btn.click(go_step1, outputs=all_pages) | |
| # History page actions | |
| apply_filter_btn.click( | |
| filter_history, | |
| inputs=[filter_context, filter_date_from, filter_date_to], | |
| outputs=[history_display] | |
| ) | |
| view_eval_btn.click(view_history_eval, inputs=[history_display], outputs=[history_action_status]) | |
| export_history_csv_btn.click(export_history_csv, inputs=[history_display], outputs=[history_csv_output]) | |
| export_history_pdf_btn.click(export_history_csv, inputs=[history_display], outputs=[history_csv_output]) | |
| def duplicate_evaluation(history_data): | |
| """Duplicate evaluation - starts new session with same team info displayed.""" | |
| if not history_data or len(history_data) == 0: | |
| return [gr.update()] * 11 + ["No evaluation selected to duplicate"] | |
| try: | |
| selected_idx = int(history_data[0][0]) | |
| if selected_idx >= 0 and selected_idx < len(evaluation_history): | |
| entry = evaluation_history[selected_idx] | |
| # Show the team info in the status so user can re-enter | |
| summary = entry.get("summary", "") | |
| # Extract team info from summary | |
| team_lines = [] | |
| in_team = False | |
| for line in summary.split("\n"): | |
| if "Team Identifier:" in line: | |
| in_team = True | |
| continue | |
| if in_team: | |
| if line.strip().startswith("-"): | |
| team_lines.append(line.strip()) | |
| elif line.strip() == "": | |
| break | |
| team_info = "\n".join(team_lines) if team_lines else "No team info found" | |
| updates = show_page("step1") | |
| return [gr.update(visible=v) for v in updates.values()] + [f"Duplicating evaluation. Previous team:\n{team_info}\n\nPlease re-enter team members for the new evaluation."] | |
| except Exception as e: | |
| pass | |
| updates = show_page("step1") | |
| return [gr.update(visible=v) for v in updates.values()] + ["Starting new evaluation (could not load previous team)"] | |
| duplicate_eval_btn.click( | |
| duplicate_evaluation, | |
| inputs=[history_display], | |
| outputs=all_pages + [history_action_status] | |
| ) | |
| def show_video_message(): | |
| return gr.Info("Video content would be embedded here in production.") | |
| welcome_btn.click(show_video_message) | |
| intro_btn.click(show_video_message) | |
| if __name__ == "__main__": | |
| app.launch() |