from flask import Flask, request, Response, jsonify, render_template, send_file from pydantic import BaseModel from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas import base64 import os import logging import io from datetime import datetime from simple_salesforce import Salesforce # Set up logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) app = Flask(__name__) # Salesforce credentials SF_USERNAME = "scores@app.com" SF_PASSWORD = "Internal@1" SF_SECURITY_TOKEN = "NbUKcTx45azba5HEdntE9YAh" SF_DOMAIN = "login" # Initialize Salesforce connection try: sf = Salesforce( username=SF_USERNAME, password=SF_PASSWORD, security_token=SF_SECURITY_TOKEN, domain=SF_DOMAIN ) logger.info("Successfully connected to Salesforce") # Debug: Describe objects for field verification for obj in ['Subcontractor_Performance_Score__c', 'Vendor_Log__c']: try: description = getattr(sf, obj).describe() logger.info(f"Fields on {obj}:") for field in description['fields']: logger.info(f"Field Name: {field['name']}") except Exception as e: logger.error(f"Error describing {obj}: {str(e)}") except Exception as e: logger.error(f"Failed to connect to Salesforce: {str(e)}") sf = None # VendorLog model class VendorLog(BaseModel): vendorLogId: str vendorId: str workDetails: str qualityReport: str incidentLog: str workCompletionDate: str actualCompletionDate: str vendorLogName: str delayDays: int project: str # Store vendor logs for display vendor_logs = [] def fetch_vendor_logs_from_salesforce(): """Fetch vendor logs from Salesforce.""" if not sf: logger.error("Salesforce connection not initialized") return [] try: query = """ SELECT Id, Vendor_Log_Id__c, VendorId__c, Work_Details__c, Quality_Report__c, Incident_Log__c, Work_Completion_Date__c, Actual_Completion_Date__c, Vendor_Log_Name__c, Delay_Days__c, Project__c FROM Vendor_Log__c WHERE CreatedDate >= 2025-05-01T00:00:00Z AND CreatedDate <= 2025-05-31T23:59:59Z """ result = sf.query(query) logs = [] for record in result['records']: log = VendorLog( vendorLogId=record.get('Vendor_Log_Id__c', 'Unknown'), vendorId=record.get('VendorId__c', 'Unknown'), workDetails=record.get('Work_Details__c', '0% completed'), qualityReport=record.get('Quality_Report__c', '0% quality'), incidentLog=record.get('Incident_Log__c', 'None'), workCompletionDate=record.get('Work_Completion_Date__c', '2025-05-01'), actualCompletionDate=record.get('Actual_Completion_Date__c', '2025-05-01'), vendorLogName=record.get('Vendor_Log_Name__c', 'Unknown Vendor'), delayDays=record.get('Delay_Days__c', 0), project=record.get('Project__c', 'Unknown Project') ) logs.append(log) logger.info(f"Fetched {len(logs)} vendor logs from Salesforce") return logs except Exception as e: logger.error(f"Error fetching vendor logs: {str(e)}") return [] def fetch_performance_scores(): """Fetch performance scores from Salesforce.""" if not sf: logger.error("Salesforce connection not initialized") return [] try: query = """ SELECT VendorId__c, Month__c, Quality_Score__c, Timeliness_Score__c, Safety_Score__c, Communication_Score__c, Final_Score__c, Certification_URL__c, Alert_Flag__c FROM Subcontractor_Performance_Score__c WHERE Month__c = '2025-05-01' """ result = sf.query(query) scores = [] for record in result['records']: scores.append({ 'vendorId': record.get('VendorId__c', 'Unknown'), 'vendorLogName': f"Vendor {record.get('VendorId__c', 'Unknown')}", 'scores': { 'qualityScore': record.get('Quality_Score__c', 0.0) or 0.0, 'timelinessScore': record.get('Timeliness_Score__c', 0.0) or 0.0, 'safetyScore': record.get('Safety_Score__c', 0.0) or 0.0, 'communicationScore': record.get('Communication_Score__c', 0.0) or 0.0, 'finalScore': record.get('Final_Score__c', 0.0) or 0.0 }, 'certification_url': record.get('Certification_URL__c', ''), 'alert_flag': record.get('Alert_Flag__c', False) }) logger.info(f"Fetched {len(scores)} performance scores from Salesforce") return scores except Exception as e: logger.error(f"Error fetching performance scores: {str(e)}") return [] def calculate_scores(log: VendorLog): """Calculate scores for a vendor log.""" try: scores = { 'qualityScore': float(log.qualityReport.replace('% quality', '')), 'timelinessScore': 100.0 if log.delayDays <= 0 else 80.0 if log.delayDays <= 3 else 60.0 if log.delayDays <= 7 else 40.0, 'safetyScore': {'None': 100.0, 'Low': 80.0, 'Minor': 80.0, 'Medium': 50.0, 'High': 20.0}.get(log.incidentLog, 100.0), 'communicationScore': 0.0, 'finalScore': 0.0 } scores['communicationScore'] = (scores['qualityScore'] * 0.33 + scores['timelinessScore'] * 0.33 + scores['safetyScore'] * 0.33) scores['finalScore'] = (scores['qualityScore'] + scores['timelinessScore'] + scores['safetyScore'] + scores['communicationScore']) / 4 for key in scores: scores[key] = round(scores[key], 2) return scores except Exception as e: logger.error(f"Error calculating scores: {str(e)}") return { 'qualityScore': 0.0, 'timelinessScore': 0.0, 'safetyScore': 0.0, 'communicationScore': 0.0, 'finalScore': 0.0 } def get_feedback(score: float, metric: str) -> str: try: if score >= 90: return "Excellent: Maintain this standard" elif score >= 70: return "Good: Keep up the good work" elif score >= 50: return f"Needs Improvement: Enhance {metric.lower()} performance" else: return f"Poor: Critical issues in {metric.lower()}" except Exception as e: logger.error(f"Error generating feedback: {str(e)}") return "Feedback unavailable" def generate_pdf(vendor_id: str, vendor_log_name: str, scores: dict): """Generate a PDF report for a vendor.""" try: buffer = io.BytesIO() c = canvas.Canvas(buffer, pagesize=letter) c.setFont('Helvetica', 12) c.drawString(100, 750, 'Subcontractor Performance Report') c.drawString(100, 730, f'Vendor ID: {vendor_id}') c.drawString(100, 710, f'Vendor Log Name: {vendor_log_name}') c.drawString(100, 690, f'Quality Score: {scores["qualityScore"]}% ({get_feedback(scores["qualityScore"], "Quality")})') c.drawString(100, 670, f'Timeliness Score: {scores["timelinessScore"]}% ({get_feedback(scores["timelinessScore"], "Timeliness")})') c.drawString(100, 650, f'Safety Score: {scores["safetyScore"]}% ({get_feedback(scores["safetyScore"], "Safety")})') c.drawString(100, 630, f'Communication Score: {scores["communicationScore"]}% ({get_feedback(scores["communicationScore"], "Communication")})') c.drawString(100, 610, f'Final Score: {scores["finalScore"]}%') c.drawString(100, 590, f'Generated On: May 15, 2025, 12:01 PM IST') c.save() buffer.seek(0) return buffer except Exception as e: logger.error(f"Error generating PDF: {str(e)}") raise def determine_alert_flag(final_score: float, all_logs: list): """Determine if an alert flag should be set.""" try: if not all_logs: return False if final_score < 50: return True lowest_score = min([log['scores']['finalScore'] for log in all_logs]) return final_score == lowest_score except Exception as e: logger.error(f"Error determining alert flag: {str(e)}") return False @app.route('/score', methods=['POST']) def score_vendor(): """Score a vendor and save to Salesforce.""" try: data = request.get_json() if not data: return jsonify({'error': 'Invalid request data'}), 400 log = VendorLog(**data) scores = calculate_scores(log) pdf_buffer = generate_pdf(log.vendorId, log.vendorLogName, scores) pdf_content = pdf_buffer.getvalue() pdf_base64 = base64.b64encode(pdf_content).decode('utf-8') alert_flag = determine_alert_flag(scores['finalScore'], vendor_logs) vendor_logs.append({ 'vendorLogId': log.vendorLogId, 'vendorId': log.vendorId, 'vendorLogName': log.vendorLogName, 'scores': scores, 'extracted': True }) if sf: sf.Subcontractor_Performance_Score__c.create({ 'VendorId__c': log.vendorId, 'Month__c': datetime.now().strftime('%Y-%m-%d'), 'Quality_Score__c': scores['qualityScore'], 'Timeliness_Score__c': scores['timelinessScore'], 'Safety_Score__c': scores['safetyScore'], 'Communication_Score__c': scores['communicationScore'], 'Final_Score__c': scores['finalScore'], 'Certification_URL__c': pdf_base64, 'Alert_Flag__c': alert_flag }) logger.info(f"Saved scores to Salesforce for Vendor Log: {log.vendorLogId}") return jsonify({ 'vendorLogId': log.vendorLogId, 'vendorId': log.vendorId, 'vendorLogName': log.vendorLogName, 'scores': scores, 'pdfContent': pdf_base64, 'alert': alert_flag }), 200 except Exception as e: logger.error(f"Error in /score endpoint: {str(e)}") return jsonify({'error': str(e)}), 500 @app.route('/generate_report', methods=['POST']) def generate_report(): """Generate a report for a selected vendor.""" try: data = request.form vendor_name = data.get('vendor_name') report_month = data.get('report_month', 'May, 2025') # Fetch the vendor's performance score performance_scores = fetch_performance_scores() vendor_data = next((v for v in performance_scores if v['vendorLogName'] == vendor_name), None) if not vendor_data: return jsonify({'error': 'Vendor not found'}), 404 # Generate PDF pdf_buffer = generate_pdf(vendor_data['vendorId'], vendor_name, vendor_data['scores']) # Save to Salesforce pdf_content = pdf_buffer.getvalue() pdf_base64 = base64.b64encode(pdf_content).decode('utf-8') if sf: sf.Subcontractor_Performance_Score__c.update(vendor_data['vendorId'], { 'Certification_URL__c': pdf_base64 }) return send_file( pdf_buffer, attachment_filename=f'report_{vendor_data["vendorId"]}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.pdf', as_attachment=True ) except Exception as e: logger.error(f"Error in /generate_report endpoint: {str(e)}") return jsonify({'error': str(e)}), 500 @app.route('/', methods=['GET']) def get_dashboard(): """Render the dashboard with Salesforce data.""" try: # Fetch data from Salesforce performance_scores = fetch_performance_scores() vendor_logs_list = fetch_vendor_logs_from_salesforce() # Prepare data for dashboard vendor_logs.clear() for score in performance_scores: vendor_logs.append({ 'vendorLogId': score['vendorId'], 'vendorId': score['vendorId'], 'vendorLogName': score['vendorLogName'], 'scores': score['scores'], 'extracted': True }) total_vendors = len(vendor_logs) performance_alerts = sum(1 for log in vendor_logs if determine_alert_flag(log['scores']['finalScore'], vendor_logs)) top_performers = sum(1 for log in vendor_logs if log['scores']['finalScore'] >= 90) improving_vendors = sum(1 for log in vendor_logs if log['scores']['finalScore'] >= 70) sorted_logs = sorted(vendor_logs, key=lambda x: x['scores']['finalScore'], reverse=True) top_logs_data = sorted_logs[:5] top_performing_logs = sorted_logs[:4] alert_logs = [log for log in vendor_logs if determine_alert_flag(log['scores']['finalScore'], vendor_logs)][:3] top_logs = [] for idx, log in enumerate(top_logs_data, 1): scores = log['scores'] alert_flag = determine_alert_flag(scores['finalScore'], vendor_logs) trend = "trend-up" if scores['finalScore'] >= 90 else "trend-down" if scores['finalScore'] < 70 else "trend-flat" trend_symbol = "↗" if trend == "trend-up" else "↘" if trend == "trend-down" else "—" status_class = "status-good" if not alert_flag else "status-alert" status_text = "Good" if not alert_flag else "Alert" top_logs.append({ 'idx': idx, 'vendorLogName': log['vendorLogName'], 'scores': scores, 'trend': trend, 'trend_symbol': trend_symbol, 'status_class': status_class, 'status_text': status_text }) template_data = { 'total_vendors': total_vendors, 'performance_alerts': performance_alerts, 'percent_alerts': round(performance_alerts/total_vendors*100, 1) if total_vendors else 0, 'top_performers': top_performers, 'percent_top': round(top_performers/total_vendors*100, 1) if total_vendors else 0, 'improving_vendors': improving_vendors, 'percent_improving': round(improving_vendors/total_vendors*100, 1) if total_vendors else 0, 'top_logs': top_logs, 'alert_logs': alert_logs, 'top_performing_logs': top_performing_logs, 'vendor_logs': vendor_logs, 'sorted_logs': sorted_logs, 'vendor_names': [log.vendorLogName for log in vendor_logs_list], 'report_month': 'May, 2025' } return render_template('dashboard.html', **template_data) except Exception as e: logger.error(f"Error in / endpoint: {str(e)}") template_data = { 'total_vendors': 0, 'performance_alerts': 0, 'percent_alerts': 0, 'top_performers': 0, 'percent_top': 0, 'improving_vendors': 0, 'percent_improving': 0, 'top_logs': [], 'alert_logs': [], 'top_performing_logs': [], 'vendor_logs': [], 'sorted_logs': [], 'vendor_names': [], 'report_month': 'May, 2025' } return render_template('dashboard.html', **template_data) @app.route('/document', methods=['GET']) def get_document(): """Render the document page.""" try: return render_template('document.html') except Exception as e: logger.error(f"Error in /document endpoint: {str(e)}") return jsonify({'error': str(e)}), 500 if __name__ == "__main__": app.run(host="0.0.0.0", port=7860, debug=True)