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()