import os import logging import matplotlib.pyplot as plt import io from PIL import Image import pandas as pd from dotenv import load_dotenv from datetime import datetime from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas from reportlab.lib.utils import ImageReader from reportlab.lib.colors import red, black import requests from simple_salesforce import Salesforce import gradio as gr # Added to fix NameError # Set up logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) # Load environment variables load_dotenv() HF_TOKEN = os.getenv("HF_TOKEN") SF_USERNAME = os.getenv("SF_USERNAME") SF_PASSWORD = os.getenv("SF_PASSWORD") SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN") SF_INSTANCE_URL = os.getenv("SF_INSTANCE_URL", "https://budgetoverrunriskestimator-dev-ed.develop.my.salesforce.com") # Validate environment variables if not HF_TOKEN: logger.error("Hugging Face token not set. Please add HF_TOKEN to .env file or Space Secrets.") else: logger.info("Hugging Face token loaded successfully.") if not all([SF_USERNAME, SF_PASSWORD, SF_SECURITY_TOKEN]): logger.error("Salesforce credentials incomplete. Please set SF_USERNAME, SF_PASSWORD, and SF_SECURITY_TOKEN in .env.") sf = None else: # Initialize Salesforce connection try: sf = Salesforce( username=SF_USERNAME, password=SF_PASSWORD, security_token=SF_SECURITY_TOKEN, instance_url=SF_INSTANCE_URL ) logger.info("Salesforce connection established successfully.") except Exception as e: logger.error(f"Failed to connect to Salesforce: {str(e)}") sf = None # Custom function to format numbers in Indian style (e.g., 100000000 as 1,00,00,000.00) def format_indian_number(number): try: number = float(number) integer_part, decimal_part = f"{number:.2f}".split(".") integer_part = integer_part[::-1] formatted = "" for i, digit in enumerate(integer_part): if i == 3: formatted += "," elif i > 3 and (i - 3) % 2 == 0: formatted += "," formatted += digit integer_part = formatted[::-1] return f"₹{integer_part}.{decimal_part}" except (ValueError, TypeError) as e: logger.error(f"Error formatting number {number}: {str(e)}") return "₹0.00" # Function to fetch budget data from Salesforce def fetch_budget_from_salesforce(project_id): if not sf: return None, "Error: Salesforce connection not available." try: query = f""" SELECT Planned_Cost__c, Actual_Spend_To_Date__c FROM Project_Budget_Risk__c WHERE Project_Name__c = '{project_id}' """ result = sf.query(query) records = result['records'] if not records: return None, "Error: No budget data found for the given project ID." data = [] for record in records: data.append({ 'Planned_Cost': record['Planned_Cost__c'] or 0, 'Actual_Spend': record['Actual_Spend_To_Date__c'] or 0 }) df = pd.DataFrame(data) return df, None except Exception as e: logger.error(f"Error fetching data from Salesforce: {str(e)}") return None, f"Error fetching data: {str(e)}" # Function to process uploaded file for line items def process_uploaded_file(file): if file is None: return 0, 0, [] try: df = pd.read_csv(file) if len(df) > 200: raise ValueError("File exceeds 200 line items. Please upload a file with 200 or fewer line items.") planned_cost = df['Planned_Cost'].sum() actual_spend = df['Actual_Spend'].sum() line_items = df.to_dict('records') return planned_cost, actual_spend, line_items except Exception as e: logger.error(f"Error processing uploaded file: {str(e)}") return 0, 0, [] # Function to cross-check indices with 3rd-party sources def cross_check_indices(material_cost_index, labor_index): try: material_cost_index = float(material_cost_index) labor_index = float(labor_index) if not (0 <= material_cost_index <= 300 and 0 <= labor_index <= 300): return "Warning: Material Cost Index or Labor Index out of expected range (0-300)." return "Indices within expected range." except (ValueError, TypeError) as e: logger.error(f"Error validating indices: {str(e)}") return "Error: Invalid indices provided." # Function to generate a bar chart def generate_bar_plot(planned_cost_inr, actual_spend_inr, forecast_cost_inr): try: fig, ax = plt.subplots(figsize=(8, 6)) categories = ['Planned Cost', 'Actual Spend', 'Forecasted Cost'] values = [planned_cost_inr, actual_spend_inr, forecast_cost_inr] bars = ax.bar(categories, values, color=['#1f77b4', '#ff7f0e', '#2ca02c']) ax.set_title("Budget Overview", fontsize=14, pad=15) ax.set_ylabel("Amount (₹)", fontsize=12) ax.tick_params(axis='x', rotation=45) ax.grid(True, axis='y', linestyle='--', alpha=0.7) for bar in bars: height = bar.get_height() ax.text( bar.get_x() + bar.get_width() / 2, height, format_indian_number(height), ha='center', va='bottom', fontsize=10 ) buf_gradio = io.BytesIO() plt.savefig(buf_gradio, format='png', bbox_inches='tight', dpi=100) buf_gradio.seek(0) gradio_image = Image.open(buf_gradio) buf_pdf = io.BytesIO() plt.savefig(buf_pdf, format='png', bbox_inches='tight', dpi=100) buf_pdf.seek(0) plt.close() return gradio_image, buf_pdf except Exception as e: logger.error(f"Error generating bar plot: {str(e)}") return None, None # Function to generate a pie chart for risk distribution def generate_pie_chart_data(cost_deviation_factor, material_cost_factor, labor_cost_factor, scope_change_factor): try: labels = ['Cost Deviation', 'Material Cost', 'Labor Cost', 'Scope Change'] values = [ max(float(cost_deviation_factor) * 100, 0), max(float(material_cost_factor) * 100, 0), max(float(labor_cost_factor) * 100, 0), max(float(scope_change_factor) * 100, 0) ] total = sum(values) if total == 0: values = [25, 25, 25, 25] return { "type": "pie", "data": { "labels": labels, "datasets": [{ "label": "Risk Distribution", "data": values, "backgroundColor": ["#FF6384", "#36A2EB", "#FFCE56", "#4BC0C0"], "borderColor": ["#FF6384", "#36A2EB", "#FFCE56", "#4BC0C0"], "borderWidth": 1 }] }, "options": { "responsive": true, "plugins": { "legend": { "position": "top" }, "title": { "display": true, "text": "Risk Factor Distribution" } } } } except (ValueError, TypeError) as e: logger.error(f"Error generating pie chart data: {str(e)}") return { "type": "pie", "data": { "labels": ["Error"], "datasets": [{"label": "Error", "data": [100], "backgroundColor": ["#FF0000"]}] } } # Function to generate a gauge chart def generate_gauge_chart(risk_percentage, category): try: risk_percentage = float(risk_percentage) return { "type": "radar", "data": { "labels": ["Risk Level"], "datasets": [{ "label": f"Risk for {category} (%)", "data": [risk_percentage], "backgroundColor": "rgba(255, 99, 132, 0.2)", "borderColor": "rgba(255, 99, 132, 1)", "borderWidth": 1, "pointBackgroundColor": "rgba(255, 99, 132, 1)" }] }, "options": { "responsive": true, "scales": { "r": { "min": 0, "max": 100, "ticks": { "stepSize": 20 } } }, "plugins": { "legend": { "position": "top" }, "title": { "display": true, "text": f"Risk Level Dashboard for {category}" } } } } except (ValueError, TypeError) as e: logger.error(f"Error generating gauge chart: {str(e)}") return { "type": "radar", "data": { "labels": ["Error"], "datasets": [{"label": "Error", "data": [0], "backgroundColor": ["#FF0000"]}] } } # Function to generate a PDF report def generate_pdf(planned_cost_inr, actual_spend_inr, forecast_cost_inr, total_risk, risk_percentage, insights, status, top_causes, category, project_phase, material_cost_index, labor_index, scope_change_impact, alert_message, indices_validation, bar_chart_image): try: pdf_path = f"budget_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf" c = canvas.Canvas(pdf_path, pagesize=letter) width, height = letter c.setFont("Helvetica-Bold", 16) c.drawString(50, height - 50, "Budget Overrun Risk Report") c.setFont("Helvetica", 12) y_position = height - 100 text_color = red if status == "Critical" else black c.setFillColor(text_color) c.drawString(50, y_position, f"Category: {category}") y_position -= 20 c.drawString(50, y_position, f"Project Phase: {project_phase}") y_position -= 20 c.drawString(50, y_position, f"Material Cost Index: {material_cost_index}") y_position -= 20 c.drawString(50, y_position, f"Labor Index: {labor_index}") y_position -= 20 c.drawString(50, y_position, f"Indices Validation: {indices_validation}") y_position -= 20 c.drawString(50, y_position, f"Scope Change Impact: {scope_change_impact}%") y_position -= 20 c.drawString(50, y_position, f"Planned Cost: {format_indian_number(planned_cost_inr)}") y_position -= 20 c.drawString(50, y_position, f"Actual Spend: {format_indian_number(actual_spend_inr)}") y_position -= 20 c.drawString(50, y_position, f"Forecasted Cost: {format_indian_number(forecast_cost_inr)}") y_position -= 20 c.drawString(50, y_position, f"Total Risk: {total_risk}") y_position -= 20 c.drawString(50, y_position, f"Risk Percentage: {risk_percentage}%") y_position -= 20 c.drawString(50, y_position, f"Status: {status}") y_position -= 20 c.drawString(50, y_position, f"Insights: {insights}") y_position -= 20 c.drawString(50, y_position, f"Top Causes: {top_causes}") y_position -= 20 c.drawString(50, y_position, f"Alert: {alert_message}") y_position -= 40 if bar_chart_image: chart_reader = ImageReader(bar_chart_image) c.drawImage(chart_reader, 50, y_position - 300, width=500, height=300) c.showPage() c.save() return pdf_path except Exception as e: logger.error(f"Error generating PDF: {str(e)}") return None # Function to generate an Excel file def generate_excel(planned_cost_inr, actual_spend_inr, forecast_cost_inr, total_risk, risk_percentage, insights, status, top_causes, category, project_phase, material_cost_index, labor_index, scope_change_impact, alert_message, indices_validation): try: data = { "Category": [category], "Project Phase": [project_phase], "Material Cost Index": [material_cost_index], "Labor Index": [labor_index], "Indices Validation": [indices_validation], "Scope Change Impact (%)": [scope_change_impact], "Planned Cost (INR)": [planned_cost_inr], "Actual Spend (INR)": [actual_spend_inr], "Forecasted Cost (INR)": [forecast_cost_inr], "Total Risk": [total_risk], "Risk Percentage (%)": [risk_percentage], "Insights": [insights], "Status": [status], "Top Causes": [top_causes], "Alert": [alert_message] } df = pd.DataFrame(data) excel_path = f"prediction_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx" with pd.ExcelWriter(excel_path, engine='xlsxwriter') as writer: df.to_excel(writer, index=False, sheet_name='Results') workbook = writer.book worksheet = writer.sheets['Results'] number_format = workbook.add_format({'num_format': '[₹]#,##,##,##0.00'}) worksheet.set_column('G:G', None, number_format) worksheet.set_column('H:H', None, number_format) worksheet.set_column('I:I', None, number_format) return excel_path except Exception as e: logger.error(f"Error generating Excel: {str(e)}") return None # Function to store results in Salesforce def store_results_in_salesforce(project_id, planned_cost_inr, actual_spend_inr, forecast_cost_inr, risk_percentage, insights, status, top_causes, category, project_phase, pdf_path): if not sf: return "Error: Salesforce connection not available." try: record = { 'Project_Name__c': project_id, 'Budget_Category__c': category, 'Planned_Cost__c': planned_cost_inr, 'Actual_Spend_To_Date__c': actual_spend_inr, 'Forecast_Final_Cost__c': forecast_cost_inr, 'Overrun_Risk_Score__c': risk_percentage, 'AI_Insights__c': insights, 'Status__c': status, 'Top_Causes__c': top_causes, 'Project_Phase__c': project_phase } query = f"SELECT Id FROM Project_Budget_Risk__c WHERE Project_Name__c = '{project_id}'" result = sf.query(query) if result['records']: record_id = result['records'][0]['Id'] sf.Project_Budget_Risk__c.update(record_id, record) else: sf.Project_Budget_Risk__c.create(record) if pdf_path and os.path.exists(pdf_path): with open(pdf_path, 'rb') as pdf_file: sf_file = sf.ContentVersion.create({ 'Title': f"Budget Report {project_id} {datetime.now().strftime('%Y%m%d_%H%M%S')}", 'PathOnClient': pdf_path, 'VersionData': pdf_file.read().hex() }) file_id = sf_file['id'] sf.ContentDocumentLink.create({ 'ContentDocumentId': file_id, 'LinkedEntityId': record_id, 'ShareType': 'V' }) return f"Results stored in Salesforce with PDF ID: {file_id}" return "Results stored in Salesforce (no PDF uploaded)." except Exception as e: logger.error(f"Error storing results in Salesforce: {str(e)}") return f"Error storing results in Salesforce: {str(e)}" # Prediction function def predict_risk(username, file, project_id, category, material_cost_index, labor_index, scope_change_impact, project_phase): # Validate inputs if not username: logger.error("Username is empty.") return "Error: Salesforce username is required.", None, None, None, None, None, None # Validate user role via Salesforce if not sf: return "Error: Salesforce connection not available.", None, None, None, None, None, None try: user_query = f"SELECT Profile.Name FROM User WHERE Username = '{username}'" user_result = sf.query(user_query) if not user_result['records'] or user_result['records'][0]['Profile']['Name'] != 'Finance': logger.warning(f"Access denied for user {username}: Not a Finance role.") return "Access Denied: This app is restricted to finance roles only.", None, None, None, None, None, None except Exception as e: logger.error(f"Error validating user {username}: {str(e)}") return f"Error validating user: {str(e)}", None, None, None, None, None, None # Fetch data from Salesforce if no file is uploaded if file is None and project_id: df, error = fetch_budget_from_salesforce(project_id) if error: return error, None, None, None, None, None, None else: df = None # Process uploaded file or use Salesforce data try: if df is not None: planned_cost_inr = df['Planned_Cost'].sum() actual_spend_inr = df['Actual_Spend'].sum() line_items = df.to_dict('records') else: planned_cost_inr, actual_spend_inr, line_items = process_uploaded_file(file) except Exception as e: logger.error(f"Error processing data: {str(e)}") return f"Error processing data: {str(e)}", None, None, None, None, None, None # Validate numeric inputs try: material_cost_index = float(material_cost_index) if material_cost_index else 0 labor_index = float(labor_index) if labor_index else 0 scope_change_impact = float(scope_change_impact) if scope_change_impact else 0 except ValueError: logger.error("Invalid input: Material Cost Index, Labor Index, or Scope Change Impact must be numeric.") return "Error: All numeric inputs must be valid numbers.", None, None, None, None, None, None logger.debug(f"Starting prediction: planned_cost_inr={planned_cost_inr}, actual_spend_inr={actual_spend_inr}, " f"category={category}, material_cost_index={material_cost_index}, labor_index={labor_index}, " f"scope_change_impact={scope_change_impact}, project_phase={project_phase}") # Cross-check indices indices_validation = cross_check_indices(material_cost_index, labor_index) # Risk calculation with Hugging Face API if HF_TOKEN: try: api_url = "https://api.huggingface.co/models/budget-overrun-risk" headers = {"Authorization": f"Bearer {HF_TOKEN}"} payload = { "planned_cost": planned_cost_inr, "actual_spend": actual_spend_inr, "material_cost_index": material_cost_index, "labor_index": labor_index, "scope_change_impact": scope_change_impact } response = requests.post(api_url, json=payload, headers=headers) response.raise_for_status() result = response.json() risk_percentage = result['risk_percentage'] cost_deviation_factor = result.get('cost_deviation_factor', 0) material_cost_factor = result.get('material_cost_factor', 0) labor_cost_factor = result.get('labor_cost_factor', 0) scope_change_factor = result.get('scope_change_factor', 0) except Exception as e: logger.error(f"Hugging Face API call failed: {str(e)}. Falling back to heuristic formula.") cost_deviation_factor = (actual_spend_inr - planned_cost_inr) / planned_cost_inr if planned_cost_inr > 0 else 0 material_cost_factor = (material_cost_index - 100) / 100 if material_cost_index > 100 else 0 labor_cost_factor = (labor_index - 100) / 100 if labor_index > 100 else 0 scope_change_factor = scope_change_impact / 100 risk_percentage = calculate_heuristic_risk(cost_deviation_factor, material_cost_factor, labor_cost_factor, scope_change_factor) else: logger.warning("No HF_TOKEN provided. Using heuristic formula for risk calculation.") cost_deviation_factor = (actual_spend_inr - planned_cost_inr) / planned_cost_inr if planned_cost_inr > 0 else 0 material_cost_factor = (material_cost_index - 100) / 100 if material_cost_index > 100 else 0 labor_cost_factor = (labor_index - 100) / 100 if labor_index > 100 else 0 scope_change_factor = scope_change_impact / 100 risk_percentage = calculate_heuristic_risk(cost_deviation_factor, material_cost_factor, labor_cost_factor, scope_change_factor) total_risk = 1 if risk_percentage > 50 else 0 forecast_cost_inr = planned_cost_inr * (1 + risk_percentage / 100) insights = "High risk of overrun" if total_risk == 1 else "Low risk of overrun" status = "Critical" if total_risk == 1 else "Healthy" # Identify top 3 causes causes = [] if cost_deviation_factor > 0: causes.append(f"Budget Overrun (Deviation: {round(cost_deviation_factor * 100, 2)}%)") if material_cost_index > 120: causes.append(f"Material Cost Index Deviation (Index: {material_cost_index})") if labor_index > 150: causes.append(f"Labor Index Deviation (Index: {labor_index})") if scope_change_impact > 0: causes.append(f"Scope Change Impact ({scope_change_impact}%)") while len(causes) < 3: causes.append("N/A") top_causes = ", ".join(causes) # Generate alert deviation = ((forecast_cost_inr - planned_cost_inr) / planned_cost_inr * 100) if planned_cost_inr > 0 else 0 alert_message = "Alert: Forecasted cost exceeds planned cost by more than 10%. Notify finance and engineering teams." if deviation > 10 else "No alert triggered." alert_style = "background-color: #ffcccc; padding: 10px; border: 1px solid red; border-radius: 5px;" if deviation > 10 else "background-color: #ccffcc; padding: 10px; border: 1px solid green; border-radius: 5px;" # Generate visualizations bar_chart_image, bar_chart_image_pdf = generate_bar_plot(planned_cost_inr, actual_spend_inr, forecast_cost_inr) pie_chart_data = generate_pie_chart_data(cost_deviation_factor, material_cost_factor, labor_cost_factor, scope_change_factor) gauge_chart_data = generate_gauge_chart(risk_percentage, category) # Generate reports pdf_file = generate_pdf(planned_cost_inr, actual_spend_inr, forecast_cost_inr, total_risk, risk_percentage, insights, status, top_causes, category, project_phase, material_cost_index, labor_index, scope_change_impact, alert_message, indices_validation, bar_chart_image_pdf) excel_file = generate_excel(planned_cost_inr, actual_spend_inr, forecast_cost_inr, total_risk, risk_percentage, insights, status, top_causes, category, project_phase, material_cost_index, labor_index, scope_change_impact, alert_message, indices_validation) # Store results in Salesforce if project_id: sf_result = store_results_in_salesforce(project_id, planned_cost_inr, actual_spend_inr, forecast_cost_inr, risk_percentage, insights, status, top_causes, category, project_phase, pdf_file) else: sf_result = "No project ID provided; results not stored in Salesforce." # Format output risk_level = "High" if total_risk == 1 else "Low" output_text = ( f"Risk Summary\n" f"----------------------------------------\n" f"Risk Level: {risk_level}\n" f"Risk Percentage: {risk_percentage}%\n" f"Status: {status}\n" f"Insights: {insights} due to {top_causes.lower()}.\n\n" f"Project Details\n" f"----------------------------------------\n" f"Category: {category}\n" f"Project Phase: {project_phase}\n" f"Material Cost Index: {material_cost_index}\n" f"Labor Index: {labor_index}\n" f"Indices Validation: {indices_validation}\n" f"Scope Change Impact: {scope_change_impact}%\n\n" f"Forecast Chart\n" f"----------------------------------------\n" f"[Bar chart displayed below]\n\n" f"Detailed Metrics\n" f"----------------------------------------\n" f"Total Risk: {total_risk}\n" f"Planned Cost: {format_indian_number(planned_cost_inr)}\n" f"Actual Spend: {format_indian_number(actual_spend_inr)}\n" f"Forecasted Cost: {format_indian_number(forecast_cost_inr)}\n" f"Top Causes: {top_causes}\n" f"Salesforce Storage: {sf_result}\n" f"Local PDF Report: [Download link below]\n" f"Excel Report: [Download link below]" ) return output_text, bar_chart_image, pie_chart_data, gauge_chart_data, pdf_file, excel_file, f"
{alert_message}
" # Helper function for heuristic risk calculation def calculate_heuristic_risk(cost_deviation_factor, material_cost_factor, labor_cost_factor, scope_change_factor): try: weights = {'cost_deviation': 0.4, 'material_cost': 0.2, 'labor_cost': 0.2, 'scope_change': 0.2} risk_percentage = ( weights['cost_deviation'] * min(float(cost_deviation_factor) * 100, 100) + weights['material_cost'] * min(float(material_cost_factor) * 100, 100) + weights['labor_cost'] * min(float(labor_cost_factor) * 100, 100) + weights['scope_change'] * min(float(scope_change_factor) * 100, 100) ) return round(max(0, min(risk_percentage, 100)), 2) except (ValueError, TypeError) as e: logger.error(f"Error calculating heuristic risk: {str(e)}") return 0 # Function to update explanations def update_material_cost_explanation(category): material_examples = { "Civil": "cement", "Plumbing": "pipes and fittings", "Electrical": "wiring and conduits", "Mechanical": "HVAC equipment and ducting", "Finishing": "tiles and paint", "Others": "key materials" } material = material_examples.get(category, "key materials") return ( f"**Material Cost Index**: This tracks the cost trend of primary materials for {category} projects (e.g., {material}) " f"compared to a baseline (100 = average cost in a reference year). Higher values indicate rising material costs, " f"increasing project expenses. A value above 120 flags a potential risk. Example: If the index is 130, material costs " f"are 30% higher than the baseline." ) def update_labor_explanation(category): labor_examples = { "Civil": "construction workers", "Plumbing": "plumbers and pipefitters", "Electrical": "electricians", "Mechanical": "HVAC technicians", "Finishing": "painters and tilers", "Others": "specialized labor" } labor = labor_examples.get(category, "specialized labor") return ( f"**Labor Index**: This tracks the cost trend of labor for {category} projects (e.g., {labor}) compared to a baseline " f"(100 = average cost in a reference year). Higher values indicate rising labor costs, increasing project expenses. " f"A value above 150 flags a potential risk. Example: If the index is 160, labor costs are 60% higher than the baseline." ) # Custom CSS custom_css = """ #submit-button { background-color: #FFD700 !important; color: #333 !important; width: 150px !important; height: 40px !important; border: none !important; border-radius: 5px !important; font-size: 16px !important; display: flex !important; align-items: center !important; justify-content: center !important; } #custom_css:hover { background-color: #E6C200 !important; } """ # Gradio interface with gr.Blocks(title="Budget Overrun Risk Estimator", css=custom_css) as demo: gr.Markdown("# Budget Overrun Risk Estimator") gr.Markdown("Upload a CSV file or provide a Project ID to fetch budget line items from Salesforce. All numeric fields are required.") with gr.Row(): with gr.Column(): username_input = gr.Textbox(label="Salesforce Username", placeholder="Enter your Salesforce username") project_id_input = gr.Textbox(label="Project ID (Optional)", placeholder="Enter Project ID to fetch data from Salesforce") file_input = gr.File(label="Upload Budget Line Items (CSV, Optional if Project ID provided)", file_types=[".csv"]) category_input = gr.Dropdown(label="Category", choices=["Civil", "Electrical", "Plumbing", "Mechanical", "Finishing", "Others"], value="Plumbing") material_cost_input = gr.Textbox(label="Material Cost Index", placeholder="Enter material cost index (e.g., 120)") material_cost_explanation = gr.Markdown(update_material_cost_explanation("Plumbing")) labor_index_input = gr.Textbox(label="Labor Index", placeholder="Enter labor index (e.g., 130)") labor_index_explanation = gr.Markdown(update_labor_explanation("Plumbing")) scope_change_input = gr.Textbox(label="Scope Change Impact (%)", placeholder="Enter scope change impact as a percentage (e.g., 10 for 10%)") project_phase_input = gr.Dropdown(label="Project Phase", choices=["Planning", "Execution", "Closure"], value="Planning") with gr.Row(): clear_button = gr.Button("Clear") submit_button = gr.Button("Submit", elem_id="submit-button") with gr.Column(): gr.Markdown("## Dashboard") gauge_chart_output = gr.Plot(label="Risk Level Dashboard") gr.Markdown("## Prediction Results") output_text = gr.Textbox(label="Prediction Results", lines=20, max_lines=30) gr.Markdown("## Forecast Chart") bar_chart_output = gr.Image(label="Budget Overview (Bar Chart)") gr.Markdown("## Risk Distribution") pie_chart_output = gr.Plot(label="Risk Factor Distribution (Pie Chart)") gr.Markdown("## Alerts") alert_output = gr.HTML(label="Alert Notification") output_pdf = gr.File(label="Download Local PDF Report") output_excel = gr.File(label="Download Excel Report") category_input.change( fn=update_material_cost_explanation, inputs=category_input, outputs=material_cost_explanation ) category_input.change( fn=update_labor_explanation, inputs=category_input, outputs=labor_index_explanation ) clear_button.click( fn=lambda: ("", "", None, "Plumbing", "", "", "", "Planning", "", None, None, None, "", ""), outputs=[ username_input, project_id_input, file_input, category_input, material_cost_input, labor_index_input, scope_change_input, project_phase_input, output_text, bar_chart_output, pie_chart_output, gauge_chart_output, output_pdf, output_excel, alert_output ] ) submit_button.click( fn=predict_risk, inputs=[username_input, file_input, project_id_input, category_input, material_cost_input, labor_index_input, scope_change_input, project_phase_input], outputs=[output_text, bar_chart_output, pie_chart_output, gauge_chart_output, output_pdf, output_excel, alert_output] ) # Launch the app if __name__ == "__main__": try: demo.launch( server_name="0.0.0.0", server_port=7860, share=False, auth_message="Please log in with your Salesforce credentials.", allowed_paths=["/home/user/app"], ssr_mode=False ) except Exception as e: logger.error(f"Failed to launch Gradio app: {str(e)}")