| | import gradio as gr |
| | import tempfile |
| | import pandas as pd |
| | import numpy as np |
| | import plotly.express as px |
| | import plotly.graph_objects as go |
| | from plotly.subplots import make_subplots |
| |
|
| | from langgraph.graph import StateGraph, END |
| | from typing import TypedDict, List, Dict, Any, Annotated |
| | import json |
| | from langchain_google_genai import ChatGoogleGenerativeAI |
| | from langchain.prompts import ChatPromptTemplate |
| | import html |
| | import markdown |
| |
|
| |
|
| | class EvaluationState(TypedDict): |
| | raw_data: pd.DataFrame |
| | processed_data: Dict[str, Any] |
| | visualizations: List[Dict[str, Any]] |
| | insights: str |
| | report_html: str |
| |
|
| | |
| | def preprocess_data(state: EvaluationState) -> EvaluationState: |
| | """Process the raw CSV/Excel data and extract key metrics.""" |
| | df = state["raw_data"] |
| | |
| | |
| | |
| | |
| | if 'feedback_rating' in df.columns: |
| | df['feedback_rating'] = pd.to_numeric(df['feedback_rating'], errors='coerce') |
| | if 'instructor_rating' in df.columns: |
| | df['instructor_rating'] = pd.to_numeric(df['instructor_rating'], errors='coerce') |
| | |
| | |
| | processed_data = {} |
| | |
| | |
| | subjects = df['subject'].unique() |
| | |
| | processed_data['subjects'] = [] |
| | for subject in subjects: |
| | subject_data = df[df['subject'] == subject] |
| | |
| | feedback_distribution = {} |
| | instructor_distribution = {} |
| | |
| | |
| | for i in range(1, 6): |
| | feedback_distribution[i] = int(subject_data['feedback_rating'].eq(i).sum()) |
| | instructor_distribution[i] = int(subject_data['instructor_rating'].eq(i).sum()) |
| | |
| | |
| | avg_feedback = subject_data['feedback_rating'].mean() |
| | avg_instructor = subject_data['instructor_rating'].mean() |
| | |
| | |
| | correlation = subject_data['feedback_rating'].corr(subject_data['instructor_rating']) |
| | |
| | processed_data['subjects'].append({ |
| | 'name': subject, |
| | 'feedback_distribution': feedback_distribution, |
| | 'instructor_distribution': instructor_distribution, |
| | 'avg_feedback': avg_feedback, |
| | 'avg_instructor': avg_instructor, |
| | 'correlation': correlation if pd.notna(correlation) else 0.0, |
| | 'sample_size': len(subject_data) |
| | }) |
| | |
| | |
| | processed_data['overall'] = { |
| | 'avg_feedback': df['feedback_rating'].mean(), |
| | 'avg_instructor': df['instructor_rating'].mean(), |
| | 'total_responses': len(df), |
| | 'correlation': df['feedback_rating'].corr(df['instructor_rating']) if pd.notna(df['feedback_rating'].corr(df['instructor_rating'])) else 0.0 |
| | } |
| | |
| | state["processed_data"] = processed_data |
| | return state |
| |
|
| | |
| | def create_visualizations(state: EvaluationState) -> EvaluationState: |
| | """Create plotly visualizations based on the processed data.""" |
| | processed_data = state["processed_data"] |
| | visualizations = [] |
| | |
| | |
| | feedback_data = [] |
| | for subject_data in processed_data['subjects']: |
| | for rating, count in subject_data['feedback_distribution'].items(): |
| | feedback_data.append({ |
| | 'Subject': subject_data['name'], |
| | 'Rating': rating, |
| | 'Count': count, |
| | 'Type': 'Feedback' |
| | }) |
| | |
| | if feedback_data: |
| | feedback_df = pd.DataFrame(feedback_data) |
| | fig_feedback = px.bar( |
| | feedback_df, |
| | x='Rating', |
| | y='Count', |
| | color='Subject', |
| | title='Distribution of Feedback Ratings by Subject', |
| | labels={'Rating': 'Rating (1-5)', 'Count': 'Number of Responses'}, |
| | barmode='group' |
| | ) |
| | visualizations.append({ |
| | 'title': 'feedback_distribution', |
| | 'fig': fig_feedback, |
| | 'description': 'Distribution of feedback ratings across different subjects' |
| | }) |
| | |
| | |
| | instructor_data = [] |
| | for subject_data in processed_data['subjects']: |
| | for rating, count in subject_data['instructor_distribution'].items(): |
| | instructor_data.append({ |
| | 'Subject': subject_data['name'], |
| | 'Rating': rating, |
| | 'Count': count, |
| | 'Type': 'Instructor' |
| | }) |
| | |
| | if instructor_data: |
| | instructor_df = pd.DataFrame(instructor_data) |
| | fig_instructor = px.bar( |
| | instructor_df, |
| | x='Rating', |
| | y='Count', |
| | color='Subject', |
| | title='Distribution of Instructor Ratings by Subject', |
| | labels={'Rating': 'Rating (1-5)', 'Count': 'Number of Responses'}, |
| | barmode='group' |
| | ) |
| | visualizations.append({ |
| | 'title': 'instructor_distribution', |
| | 'fig': fig_instructor, |
| | 'description': 'Distribution of instructor ratings across different subjects' |
| | }) |
| | |
| | |
| | categories = [subject['name'] for subject in processed_data['subjects']] |
| | if categories: |
| | fig_radar = go.Figure() |
| | |
| | fig_radar.add_trace(go.Scatterpolar( |
| | r=[subject['avg_feedback'] for subject in processed_data['subjects']], |
| | theta=categories, |
| | fill='toself', |
| | name='Avg. Feedback Rating' |
| | )) |
| | |
| | fig_radar.add_trace(go.Scatterpolar( |
| | r=[subject['avg_instructor'] for subject in processed_data['subjects']], |
| | theta=categories, |
| | fill='toself', |
| | name='Avg. Instructor Rating' |
| | )) |
| | |
| | fig_radar.update_layout( |
| | polar=dict( |
| | radialaxis=dict( |
| | visible=True, |
| | range=[0, 5] |
| | ) |
| | ), |
| | title_text="Average Ratings Comparison by Subject" |
| | ) |
| | visualizations.append({ |
| | 'title': 'radar_comparison', |
| | 'fig': fig_radar, |
| | 'description': 'Comparison of average feedback and instructor ratings by subject' |
| | }) |
| | |
| | |
| | correlation_data = [] |
| | for subject_data in processed_data['subjects']: |
| | correlation_data.append({ |
| | 'Subject': subject_data['name'], |
| | 'Correlation': subject_data['correlation'], |
| | 'Sample Size': subject_data['sample_size'] |
| | }) |
| | |
| | if correlation_data: |
| | correlation_df = pd.DataFrame(correlation_data) |
| | fig_corr = px.scatter( |
| | correlation_df, |
| | x='Subject', |
| | y='Correlation', |
| | size='Sample Size', |
| | title='Correlation between Feedback and Instructor Ratings', |
| | labels={'Correlation': 'Pearson Correlation Coefficient'}, |
| | color='Correlation', |
| | color_continuous_scale=px.colors.diverging.RdBu, |
| | color_continuous_midpoint=0, |
| | |
| | hover_name='Subject', |
| | hover_data={'Subject': False, 'Correlation': ':.2f', 'Sample Size': True} |
| | ) |
| | visualizations.append({ |
| | 'title': 'correlation_analysis', |
| | 'fig': fig_corr, |
| | 'description': 'Correlation between feedback and instructor ratings by subject' |
| | }) |
| | |
| | state["visualizations"] = visualizations |
| | return state |
| |
|
| | |
| | def generate_insights(state: EvaluationState) -> EvaluationState: |
| | """Generate narrative insights based on the data analysis using GenAI.""" |
| | processed_data = state["processed_data"] |
| | |
| | |
| | |
| | |
| | google_api_key = os.getenv("GOOGLE_API_KEY") |
| |
|
| | try: |
| | if not google_api_key: |
| | raise ValueError("GOOGLE_API_KEY not found in environment.") |
| |
|
| | |
| | subjects_info = [] |
| | for subject in processed_data['subjects']: |
| | subject_info = f"Subject: {subject['name']}\n" |
| | subject_info += f" Average Feedback Rating: {subject['avg_feedback']:.2f}/5\n" |
| | subject_info += f" Average Instructor Rating: {subject['avg_instructor']:.2f}/5\n" |
| | subject_info += f" Correlation between Feedback and Instructor Rating: {subject['correlation']:.2f}\n" |
| | subject_info += f" Sample Size: {subject['sample_size']} responses\n" |
| | subject_info += " Feedback Rating Distribution (Rating: Count): " |
| | subject_info += ", ".join([f"{k}: {v}" for k, v in subject['feedback_distribution'].items()]) |
| | subject_info += "\n Instructor Rating Distribution (Rating: Count): " |
| | subject_info += ", ".join([f"{k}: {v}" for k, v in subject['instructor_distribution'].items()]) |
| | subjects_info.append(subject_info) |
| | |
| | overall_info = f"Overall Average Feedback Rating: {processed_data['overall']['avg_feedback']:.2f}/5\n" |
| | overall_info += f"Overall Average Instructor Rating: {processed_data['overall']['avg_instructor']:.2f}/5\n" |
| | overall_info += f"Overall Correlation: {processed_data['overall']['correlation']:.2f}\n" |
| | overall_info += f"Total Responses: {processed_data['overall']['total_responses']}" |
| | |
| | prompt = ChatPromptTemplate.from_messages([ |
| | ("system", """You are an expert data analyst . Your task is to analyze employees evaluation data and provide comprehensive, actionable insights. |
| | Format your response in Markdown. |
| | |
| | Your analysis should cover: |
| | 1. **Executive Summary**: A brief overview of key findings. |
| | 2. **Overall Performance Patterns**: Discuss general trends, average ratings, and overall sentiment. |
| | 3. **Subject-Specific Analysis**: |
| | * Highlight subjects with notably high or low ratings (both feedback and instructor). |
| | * Discuss variations between subjects. |
| | * Analyze the correlation between feedback and instructor ratings for each subject. |
| | 4. **Key Observations & Potential Issues**: Identify any outliers, significant discrepancies (e.g., large gap between feedback and instructor rating for a subject), or patterns that warrant attention. |
| | 5. **Actionable Recommendations**: Based on the data, provide specific, data-driven recommendations for improvement (e.g., for specific subjects, for instructor development, for curriculum adjustments). |
| | |
| | Be specific, use the data provided, and ensure your insights are clear and well-structured. |
| | """), |
| | ("user", """Please analyze the following student evaluation data: |
| | |
| | **Individual Subject Information:** |
| | {subjects_info} |
| | |
| | **Overall Statistics:** |
| | {overall_info} |
| | |
| | Provide your analysis in Markdown format. |
| | """) |
| | ]) |
| | |
| |
|
| | llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash-latest", api_key=google_api_key) |
| | chain = prompt | llm |
| | response = chain.invoke({ |
| | "subjects_info": "\n\n".join(subjects_info), |
| | "overall_info": overall_info |
| | }) |
| | |
| | state["insights"] = response.content |
| | except Exception as e: |
| | import traceback |
| | error_message = f"Error in generate_insights: {e}\n{traceback.format_exc()}" |
| | print(error_message) |
| | |
| | |
| | fallback_insights = f""" |
| | # AI-Generated Insights (Fallback) |
| | |
| | Due to an issue connecting to the AI service, a basic analysis is provided below. |
| | |
| | ## Executive Summary |
| | |
| | The overall average feedback rating is **{processed_data['overall']['avg_feedback']:.2f}/5**, and the overall average instructor rating is **{processed_data['overall']['avg_instructor']:.2f}/5**. |
| | The correlation between feedback and instructor ratings across all subjects is **{processed_data['overall']['correlation']:.2f}**. |
| | A total of **{processed_data['overall']['total_responses']}** responses were analyzed. |
| | |
| | ## Overall Performance Patterns |
| | |
| | The data indicates a general performance level around the average ratings mentioned. Further subject-specific details are below. |
| | |
| | ## Subject-Specific Analysis |
| | """ |
| | |
| | for subject in processed_data['subjects']: |
| | fallback_insights += f""" |
| | ### {subject['name']} |
| | - **Average Feedback Rating**: {subject['avg_feedback']:.2f}/5 |
| | - **Average Instructor Rating**: {subject['avg_instructor']:.2f}/5 |
| | - **Correlation (Feedback vs. Instructor)**: {subject['correlation']:.2f} |
| | - **Sample Size**: {subject['sample_size']} responses |
| | - **Feedback Distribution** (Rating: Count): {', '.join([f'{k}: {v}' for k,v in subject['feedback_distribution'].items()])} |
| | - **Instructor Distribution** (Rating: Count): {', '.join([f'{k}: {v}' for k,v in subject['instructor_distribution'].items()])} |
| | """ |
| | |
| | fallback_insights += """ |
| | ## Recommendations (Generic) |
| | |
| | 1. **Investigate Low-Performing Subjects**: Focus on subjects with ratings significantly below average to identify areas for improvement. |
| | 2. **Analyze Rating Correlations**: Understand why correlations between student feedback and instructor ratings vary across subjects. |
| | 3. **Gather Qualitative Feedback**: For subjects with polarized or unexpectedly low ratings, consider gathering more detailed qualitative feedback. |
| | 4. **Share Best Practices**: Identify practices from highly-rated courses/instructors and explore ways to share them. |
| | |
| | **Note**: This is a fallback analysis. For detailed, AI-powered insights, please ensure the Google Generative AI integration is correctly configured with a valid API key and model name. |
| | """ |
| | state["insights"] = fallback_insights |
| | |
| | return state |
| |
|
| | |
| | def compile_report(state: EvaluationState) -> EvaluationState: |
| | """Compile all analysis and visualizations into an HTML report.""" |
| | visualizations = state["visualizations"] |
| | insights = state["insights"] |
| | processed_data = state["processed_data"] |
| | |
| | from datetime import datetime |
| | date_generated = datetime.now().strftime("%B %d, %Y") |
| | |
| | subject_rows = "" |
| | for subject in processed_data['subjects']: |
| | subject_rows += f""" |
| | <tr> |
| | <td>{html.escape(subject['name'])}</td> |
| | <td>{subject['avg_feedback']:.2f}</td> |
| | <td>{subject['avg_instructor']:.2f}</td> |
| | <td>{subject['correlation']:.2f}</td> |
| | <td>{subject['sample_size']}</td> |
| | </tr> |
| | """ |
| | |
| | plotly_js_calls = [] |
| | visualizations_html_divs = [] |
| | |
| | for i, viz_data in enumerate(visualizations): |
| | div_id = f"viz-{i}" |
| | visualizations_html_divs.append(f""" |
| | <div class="visualization"> |
| | <h3>{html.escape(viz_data['description'])}</h3> |
| | <div id="{div_id}" style="width: 100%; height: 500px; min-height: 400px;"></div> |
| | </div> |
| | """) |
| | if viz_data['fig'] is not None: |
| | try: |
| | fig_json = viz_data['fig'].to_json() |
| | plotly_js_calls.append(f""" |
| | try {{ |
| | var figure_data_{i} = {fig_json}; |
| | Plotly.newPlot('{div_id}', figure_data_{i}.data, figure_data_{i}.layout, {{responsive: true}}); |
| | }} catch (plotError) {{ |
| | console.error('Error rendering plot {div_id}:', plotError); |
| | document.getElementById('{div_id}').innerHTML = '<p style=\\"color:red; padding:10px;\\">Error rendering this chart: ' + plotError.message + '</p>'; |
| | }} |
| | """) |
| | except Exception as e: |
| | print(f"Error converting figure {viz_data['title']} to JSON: {e}") |
| | plotly_js_calls.append(f""" |
| | document.getElementById('{div_id}').innerHTML = '<p style=\\"color:red; padding:10px;\\">Error preparing chart data for {html.escape(viz_data['description'])}.</p>'; |
| | """) |
| | else: |
| | plotly_js_calls.append(f""" |
| | document.getElementById('{div_id}').innerHTML = '<p style=\\"color:orange; padding:10px;\\">No data to display for {html.escape(viz_data['description'])}.</p>'; |
| | """) |
| |
|
| | |
| | plotly_script_content = "\n".join(plotly_js_calls) |
| | |
| | |
| | |
| | |
| | insights_html_content = markdown.markdown(insights, extensions=['fenced_code', 'tables', 'nl2br', 'extra']) |
| |
|
| | report_html = f""" |
| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>Akwel Evaluation Analysis Report</title> |
| | <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> |
| | <style> |
| | body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; max-width: 1200px; margin: 0 auto; padding: 20px; background-color: #f4f7f6; }} |
| | h1, h2, h3 {{ color: #2c3e50; margin-top: 1.5em; margin-bottom: 0.5em; }} |
| | h1 {{ font-size: 2.2em; border-bottom: 2px solid #3498db; padding-bottom: 0.3em; }} |
| | h2 {{ font-size: 1.8em; border-bottom: 1px solid #b0bec5; padding-bottom: 0.2em; }} |
| | h3 {{ font-size: 1.4em; }} |
| | .visualization {{ margin: 30px 0; border: 1px solid #ddd; border-radius: 8px; padding: 20px; background-color: #fff; box-shadow: 0 2px 10px rgba(0,0,0,0.05); }} |
| | .stats-container {{ display: flex; flex-wrap: wrap; justify-content: space-around; margin-bottom: 30px; gap: 20px; }} |
| | .stat-card {{ background-color: #ffffff; border-radius: 10px; padding: 25px; margin-bottom: 20px; flex: 1; min-width: 280px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); border-left: 5px solid #3498db; }} |
| | .stat-card h3 {{ margin-top: 0; color: #3498db; }} |
| | .stat-card p {{ font-size: 1.1em; }} |
| | .insights {{ background-color: #eaf5ff; padding: 25px; border-radius: 10px; margin: 30px 0; border-left: 5px solid #1abc9c; }} |
| | .insights h2 {{ color: #16a085; }} |
| | .insights p, .insights li {{ font-size: 1.05em; }} |
| | .insights ul, .insights ol {{ padding-left: 20px; }} |
| | .insights strong {{ color: #2c3e50; }} |
| | table {{ width: 100%; border-collapse: collapse; margin: 25px 0; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }} |
| | th, td {{ border: 1px solid #ddd; padding: 12px; text-align: left; }} |
| | th {{ background-color: #3498db; color: white; font-weight: bold; }} |
| | tr:nth-child(even) {{ background-color: #f8f9fa; }} |
| | tr:hover {{ background-color: #e9ecef; }} |
| | footer {{ text-align: center; margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 0.9em; color: #777; }} |
| | </style> |
| | </head> |
| | <body> |
| | <h1>Employee Evaluation Analysis Report</h1> |
| | |
| | <div class="stats-container"> |
| | <div class="stat-card"> |
| | <h3>Overall Feedback Rating</h3> |
| | <p style="font-size: 28px; font-weight: bold;">{processed_data['overall']['avg_feedback']:.2f}/5</p> |
| | <p>Based on {processed_data['overall']['total_responses']} total responses</p> |
| | </div> |
| | <div class="stat-card"> |
| | <h3>Overall Instructor Rating</h3> |
| | <p style="font-size: 28px; font-weight: bold;">{processed_data['overall']['avg_instructor']:.2f}/5</p> |
| | <p>Correlation with feedback: {processed_data['overall']['correlation']:.2f}</p> |
| | </div> |
| | </div> |
| | |
| | <h2>Performance by Subject</h2> |
| | <table> |
| | <thead><tr> |
| | <th>Subject</th> |
| | <th>Avg. Feedback</th> |
| | <th>Avg. Instructor</th> |
| | <th>Correlation</th> |
| | <th>Sample Size</th> |
| | </tr></thead> |
| | <tbody> |
| | {subject_rows} |
| | </tbody> |
| | </table> |
| | |
| | <h2>Visualizations</h2> |
| | {''.join(visualizations_html_divs)} |
| | |
| | <div class="insights"> |
| | <h2>AI-Generated Insights</h2> |
| | {insights_html_content} |
| | </div> |
| | |
| | <footer> |
| | <p>Report generated on {date_generated}</p> |
| | </footer> |
| | |
| | <script> |
| | // Wait for the DOM to be fully loaded before trying to render plots |
| | document.addEventListener('DOMContentLoaded', function() {{ |
| | if (typeof Plotly !== 'undefined') {{ |
| | console.log("Plotly object found. Attempting to render charts."); |
| | // This is where the generated JS for all plots goes |
| | {plotly_script_content} |
| | console.log("Plotly chart rendering JS execution sequence initiated."); |
| | }} else {{ |
| | console.error("Plotly.js is not loaded! Charts cannot be displayed."); |
| | const vizDivs = document.querySelectorAll('.visualization div[id^="viz-"]'); |
| | vizDivs.forEach(div => {{ |
| | div.innerHTML = "<p style='color:red; padding:10px;'>Error: Chart library (Plotly.js) did not load. Charts cannot be displayed. Check internet connection or CDN link.</p>"; |
| | }}); |
| | }} |
| | }}); |
| | </script> |
| | </body> |
| | </html> |
| | """ |
| | |
| | state["report_html"] = report_html |
| | return state |
| |
|
| |
|
| | |
| | def create_evaluation_graph(): |
| | """Create the LangGraph workflow for the evaluation analytics system.""" |
| | graph = StateGraph(EvaluationState) |
| | |
| | graph.add_node("preprocess_data", preprocess_data) |
| | graph.add_node("create_visualizations", create_visualizations) |
| | graph.add_node("generate_insights", generate_insights) |
| | graph.add_node("compile_report", compile_report) |
| | |
| | graph.add_edge("preprocess_data", "create_visualizations") |
| | graph.add_edge("create_visualizations", "generate_insights") |
| | graph.add_edge("generate_insights", "compile_report") |
| | graph.add_edge("compile_report", END) |
| | |
| | graph.set_entry_point("preprocess_data") |
| | |
| | return graph.compile() |
| |
|
| | evaluation_app = create_evaluation_graph() |
| |
|
| | def generate_report_gradio(file_obj): |
| | if file_obj is None: |
| | return "<p style='color:red; text-align:center; padding-top: 20px;'>Please upload a CSV or Excel file.</p>", None, gr.update(visible=False) |
| | |
| | try: |
| | file_path = file_obj.name |
| | if file_path.lower().endswith('.csv'): |
| | df = pd.read_csv(file_path) |
| | elif file_path.lower().endswith(('.xls', '.xlsx')): |
| | df = pd.read_excel(file_path) |
| | else: |
| | return "<p style='color:red; text-align:center; padding-top: 20px;'>Unsupported file type. Please upload a CSV or Excel file.</p>", None, gr.update(visible=False) |
| |
|
| | required_columns = ['subject', 'feedback_rating', 'instructor_rating'] |
| | if not all(col in df.columns for col in required_columns): |
| | missing = [col for col in required_columns if col not in df.columns] |
| | return f"<p style='color:red; text-align:center; padding-top: 20px;'>Missing required columns: {', '.join(missing)}.</p>", None, gr.update(visible=False) |
| |
|
| | except Exception as e: |
| | return f"<p style='color:red; text-align:center; padding-top: 20px;'>Error reading file: {html.escape(str(e))}</p>", None, gr.update(visible=False) |
| |
|
| | initial_state = {"raw_data": df, "processed_data": {}, "visualizations": [], "insights": "", "report_html": ""} |
| | |
| | try: |
| | final_state = evaluation_app.invoke(initial_state) |
| | report_html_content = final_state["report_html"] |
| | except Exception as e: |
| | import traceback |
| | tb_str = traceback.format_exc() |
| | error_msg = f"An error occurred during report generation: {html.escape(str(e))}" |
| | print(f"{error_msg}\n{tb_str}") |
| | return f"<p style='color:red; text-align:center; padding-top: 20px;'>{error_msg}</p>", None, gr.update(visible=False) |
| |
|
| | try: |
| | |
| | base_name = os.path.splitext(os.path.basename(file_path))[0] |
| | report_file_name = f"{base_name}_evaluation_report.html" |
| |
|
| | with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".html", prefix=f"{base_name}_report_", encoding="utf-8") as tmp_file: |
| | tmp_file.write(report_html_content) |
| | temp_file_path = tmp_file.name |
| | |
| | |
| | return report_html_content, gr.update(value=temp_file_path, label=f"Download: {report_file_name}", visible=True), gr.update(visible=True) |
| | except Exception as e: |
| | return f"<p style='color:red; text-align:center; padding-top: 20px;'>Error creating download file: {html.escape(str(e))}</p>", None, gr.update(visible=False) |
| |
|
| | |
| |
|
| | with gr.Blocks( |
| | title="Evaluation Analytics System", |
| | theme=gr.themes.Soft(primary_hue=gr.themes.colors.blue, secondary_hue=gr.themes.colors.sky) |
| | ) as app: |
| | gr.Markdown( |
| | """ |
| | # 📊 Formation Evaluation Analytics Dashboard |
| | Upload evaluation data (CSV or Excel format) to generate an interactive report. |
| | Required columns: `subject`, `feedback_rating` (1-5), `instructor_rating` (1-5). |
| | |
| | """ |
| | ) |
| |
|
| | with gr.Row(): |
| | with gr.Column(scale=1, min_width=300): |
| | file_input = gr.File( |
| | label="Upload Evaluation Data", |
| | file_types=['.csv', '.xls', '.xlsx'], |
| | |
| | ) |
| | generate_button = gr.Button("Generate Report", variant="primary") |
| | |
| | |
| | download_file_output = gr.File(label="Download Full Report", visible=False, interactive=False) |
| |
|
| | with gr.Column(scale=3): |
| | report_output_html = gr.HTML( |
| | label="Analysis Report Preview", |
| | value="<p style='text-align:center; color:grey; padding-top:50px;'>Upload a file and click 'Generate Report' to see the analysis.</p>" |
| | ) |
| | |
| | |
| | generate_button.click( |
| | fn=generate_report_gradio, |
| | inputs=[file_input], |
| | outputs=[report_output_html, download_file_output, download_file_output] |
| | ) |
| | |
| | app.launch(ssr_mode=False) |
| |
|
| |
|