CICE-Mobile-App / app.py
stevafernandes's picture
Update app.py
2553c38 verified
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()