from flask import Flask, request, jsonify, session, redirect, url_for, render_template, send_file from simple_salesforce import Salesforce, SalesforceAuthenticationFailed, SalesforceError import os from datetime import datetime from dotenv import load_dotenv import logging from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas import io import tempfile # Load environment variables load_dotenv() # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('app.log'), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) app = Flask(__name__) app.secret_key = os.getenv('FLASK_SECRET_KEY', 'your-secret-key') # Salesforce mock data for guest users MOCK_DATA = { "supervisor_name": "GUEST", "project_id": "PROJ_001", "daily_checklist": "Inspect safety equipment\nReview team assignments\nCheck project timeline", "suggested_tips": "Prioritize safety checks\nCommunicate clearly with the team\nMonitor resource usage", "reflection_log": "", "engagement_score": 85, "kpi_flag": False, "download_link": "", "last_login": str(datetime.now()) } def get_salesforce_connection(): """Establish a Salesforce connection with error handling.""" try: sf = Salesforce( username=os.getenv('SALESFORCE_USERNAME'), password=os.getenv('SALESFORCE_PASSWORD'), security_token=os.getenv('SALESFORCE_SECURITY_TOKEN'), domain=os.getenv('SALESFORCE_DOMAIN', 'test') # 'login' for production ) logger.info("Successfully connected to Salesforce") return sf except SalesforceAuthenticationFailed as e: logger.error(f"Salesforce authentication failed: {str(e)}") raise Exception("Salesforce authentication failed. Check your credentials.") except Exception as e: logger.error(f"Error connecting to Salesforce: {str(e)}") raise Exception("Unable to connect to Salesforce. Please try again later.") @app.route('/') def index(): if 'supervisor_name' not in session: logger.info("User not logged in, redirecting to login page") return redirect(url_for('login_page')) return render_template('index.html') @app.route('/login', methods=['GET']) def login_page(): return render_template('login.html') @app.route('/signup', methods=['GET']) def signup_page(): return render_template('signup.html') @app.route('/login', methods=['POST']) def login(): data = request.get_json() username = data.get('username') # Now using Name field password = data.get('password') if username == 'GUEST': session['supervisor_name'] = 'GUEST' logger.info("Guest login successful") return jsonify({"status": "success", "message": "Logged in as guest"}) try: sf = get_salesforce_connection() # Query Supervisor_AI_Coaching__c for Name and Password__c query = f"SELECT Id, Name, Password__c FROM Supervisor_AI_Coaching__c WHERE Name = '{username}' LIMIT 1" result = sf.query(query) if not result['records']: logger.warning(f"Invalid username: {username}") return jsonify({"status": "error", "message": "Invalid username"}), 401 record = result['records'][0] stored_password = record['Password__c'] if stored_password != password: logger.warning(f"Invalid password for username: {username}") return jsonify({"status": "error", "message": "Invalid password"}), 401 session['supervisor_name'] = username logger.info(f"Login successful for {username}") return jsonify({"status": "success", "message": "Login successful"}) except Exception as e: logger.error(f"Login error: {str(e)}") return jsonify({"status": "error", "message": str(e)}), 500 @app.route('/signup', methods=['POST']) def signup(): data = request.get_json() username = data.get('username') # Name field password = data.get('password') # Password__c field project_id = data.get('project_id', 'PROJ_001') # Project_ID__c engagement_score = float(data.get('engagement_score', 85)) # Engagement_Score__c kpi_flag = data.get('kpi_flag', False) # KPI_Flag__c if not username or not password: logger.warning("Signup failed: Username and password are required") return jsonify({"status": "error", "message": "Username and password are required"}), 400 try: sf = get_salesforce_connection() # Check if username already exists query = f"SELECT Id FROM Supervisor_AI_Coaching__c WHERE Name = '{username}' LIMIT 1" result = sf.query(query) if result['records']: logger.warning(f"Signup failed: Username {username} already exists") return jsonify({"status": "error", "message": "Username already exists"}), 400 # Create new Supervisor_AI_Coaching__c record new_record = { 'Name': username, 'Password__c': password, 'Project_ID__c': project_id, 'Engagement_Score__c': engagement_score, 'KPI_Flag__c': kpi_flag, 'Daily_Checklist__c': '', 'Suggested_Tips__c': '', 'Reflection_Log__c': '', 'Download_Link__c': '' } sf.Supervisor_AI_Coaching__c.create(new_record) logger.info(f"Signup successful for {username}") # Automatically log in the user after signup session['supervisor_name'] = username return jsonify({"status": "success", "message": "Signup successful, you are now logged in"}) except SalesforceError as e: logger.error(f"Salesforce API error during signup: {str(e)}") return jsonify({"status": "error", "message": "Salesforce API error. Please try again later."}), 500 except Exception as e: logger.error(f"Signup error: {str(e)}") return jsonify({"status": "error", "message": str(e)}), 500 @app.route('/logout', methods=['POST']) def logout(): supervisor_name = session.get('supervisor_name', 'Unknown') session.pop('supervisor_name', None) logger.info(f"User {supervisor_name} logged out") return jsonify({"status": "success", "message": "Logged out successfully"}) @app.route('/get_supervisor_data') def get_supervisor_data(): supervisor_name = session.get('supervisor_name', 'GUEST') if supervisor_name == 'GUEST': logger.info("Returning mock data for guest user") return jsonify({"status": "success", "data": MOCK_DATA}) try: sf = get_salesforce_connection() query = f""" SELECT Name, Project_ID__c, Daily_Checklist__c, Suggested_Tips__c, Reflection_Log__c, Engagement_Score__c, KPI_Flag__c, Download_Link__c FROM Supervisor_AI_Coaching__c WHERE Name = '{supervisor_name}' LIMIT 1 """ result = sf.query(query) if result['records']: record = result['records'][0] data = { "supervisor_name": record['Name'], "project_id": record['Project_ID__c'], "daily_checklist": record['Daily_Checklist__c'] or "", "suggested_tips": record['Suggested_Tips__c'] or "", "reflection_log": record['Reflection_Log__c'] or "", "engagement_score": record['Engagement_Score__c'] or 0, "kpi_flag": record['KPI_Flag__c'], "download_link": record['Download_Link__c'] or "", "last_login": str(datetime.now()) } logger.info(f"Fetched data for supervisor {supervisor_name}") return jsonify({"status": "success", "data": data}) else: logger.warning(f"No data found for supervisor {supervisor_name}") return jsonify({"status": "error", "message": "No data found for this supervisor"}) except SalesforceError as e: logger.error(f"Salesforce API error while fetching data: {str(e)}") return jsonify({"status": "error", "message": "Salesforce API error. Please try again later."}), 500 except Exception as e: logger.error(f"Error fetching supervisor data: {str(e)}") return jsonify({"status": "error", "message": str(e)}), 500 @app.route('/submit_reflection', methods=['POST']) def submit_reflection(): supervisor_name = session.get('supervisor_name', 'GUEST') if supervisor_name == 'GUEST': MOCK_DATA['reflection_log'] = request.get_json().get('reflection') logger.info("Reflection submitted for guest user") return jsonify({"status": "success", "message": "Reflection submitted successfully (guest mode)"}) data = request.get_json() reflection = data.get('reflection') try: sf = get_salesforce_connection() query = f"SELECT Id FROM Supervisor_AI_Coaching__c WHERE Name = '{supervisor_name}' LIMIT 1" result = sf.query(query) if not result['records']: logger.warning(f"No record found for supervisor {supervisor_name}") return jsonify({"status": "error", "message": "No record found for this supervisor"}), 404 record_id = result['records'][0]['Id'] sf.Supervisor_AI_Coaching__c.update(record_id, {'Reflection_Log__c': reflection}) logger.info(f"Reflection updated for supervisor {supervisor_name}") return jsonify({"status": "success", "message": "Reflection submitted successfully"}) except SalesforceError as e: logger.error(f"Salesforce API error while submitting reflection: {str(e)}") return jsonify({"status": "error", "message": "Salesforce API error. Please try again later."}), 500 except Exception as e: logger.error(f"Error submitting reflection: {str(e)}") return jsonify({"status": "error", "message": str(e)}), 500 @app.route('/generate', methods=['POST']) def generate(): supervisor_name = session.get('supervisor_name', 'GUEST') data = request.get_json() # Generate checklist and tips (simplified logic) checklist = [ "Inspect safety equipment", "Review team assignments", "Check project timeline" ] tips = [ "Prioritize safety checks", "Communicate clearly with the team", "Monitor resource usage" ] if supervisor_name == 'GUEST': MOCK_DATA['daily_checklist'] = '\n'.join(checklist) MOCK_DATA['suggested_tips'] = '\n'.join(tips) logger.info("Generated coaching output for guest user") return jsonify({ "status": "success", "output": { "checklist": checklist, "tips": tips } }) try: sf = get_salesforce_connection() query = f"SELECT Id FROM Supervisor_AI_Coaching__c WHERE Name = '{supervisor_name}' LIMIT 1" result = sf.query(query) if not result['records']: logger.warning(f"No record found for supervisor {supervisor_name}") return jsonify({"status": "error", "message": "No record found for this supervisor"}), 404 record_id = result['records'][0]['Id'] sf.Supervisor_AI_Coaching__c.update(record_id, { 'Daily_Checklist__c': '\n'.join(checklist), 'Suggested_Tips__c': '\n'.join(tips) }) logger.info(f"Generated and updated coaching output for supervisor {supervisor_name}") return jsonify({ "status": "success", "output": { "checklist": checklist, "tips": tips } }) except SalesforceError as e: logger.error(f"Salesforce API error while generating output: {str(e)}") return jsonify({"status": "error", "message": "Salesforce API error. Please try again later."}), 500 except Exception as e: logger.error(f"Error generating coaching output: {str(e)}") return jsonify({"status": "error", "message": str(e)}), 500 def generate_pdf_summary(data): """Generate a PDF summary and return the file path.""" try: with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf', dir='/tmp') as tmp_file: pdf_path = tmp_file.name c = canvas.Canvas(pdf_path, pagesize=letter) width, height = letter # Title c.setFont("Helvetica-Bold", 16) c.drawString(50, height - 50, "Supervisor AI Coaching Summary") # Supervisor Data c.setFont("Helvetica", 12) y_position = height - 100 c.drawString(50, y_position, f"Supervisor Name: {data.get('supervisor_name', 'N/A')}") y_position -= 20 c.drawString(50, y_position, f"Project ID: {data.get('project_id', 'N/A')}") y_position -= 20 c.drawString(50, y_position, f"Last Login: {data.get('last_login', 'N/A')}") y_position -= 40 # Daily Checklist c.setFont("Helvetica-Bold", 14) c.drawString(50, y_position, "Daily Checklist") c.setFont("Helvetica", 12) y_position -= 20 checklist = data.get('daily_checklist', '').split('\n') for item in checklist: if item.strip(): c.drawString(70, y_position, f"- {item}") y_position -= 20 if y_position < 50: c.showPage() y_position = height - 50 # Suggested Tips y_position -= 20 c.setFont("Helvetica-Bold", 14) c.drawString(50, y_position, "Suggested Tips") c.setFont("Helvetica", 12) y_position -= 20 tips = data.get('suggested_tips', '').split('\n') for tip in tips: if tip.strip(): c.drawString(70, y_position, f"- {tip}") y_position -= 20 if y_position < 50: c.showPage() y_position = height - 50 # Reflection Log y_position -= 20 c.setFont("Helvetica-Bold", 14) c.drawString(50, y_position, "Reflection Log") c.setFont("Helvetica", 12) y_position -= 20 reflection = data.get('reflection_log', 'No reflection available.') lines = reflection.split('\n') for line in lines: c.drawString(70, y_position, line) y_position -= 20 if y_position < 50: c.showPage() y_position = height - 50 # KPIs y_position -= 20 c.setFont("Helvetica-Bold", 14) c.drawString(50, y_position, "KPIs") c.setFont("Helvetica", 12) y_position -= 20 c.drawString(70, y_position, f"Engagement Score: {data.get('engagement_score', 0)}%") y_position -= 20 c.drawString(70, y_position, f"KPI Flag: {'Active' if data.get('kpi_flag', False) else 'Inactive'}") y_position -= 20 c.save() logger.info(f"Generated PDF at {pdf_path}") return pdf_path except Exception as e: logger.error(f"Error generating PDF: {str(e)}") raise @app.route('/download_pdf', methods=['GET']) def download_pdf(): supervisor_name = session.get('supervisor_name', 'GUEST') if supervisor_name == 'GUEST': logger.info("Download not available for guest user") return jsonify({"status": "success", "message": "Download not available in guest mode"}) try: sf = get_salesforce_connection() query = f""" SELECT Id, Name, Project_ID__c, Daily_Checklist__c, Suggested_Tips__c, Reflection_Log__c, Engagement_Score__c, KPI_Flag__c, Download_Link__c FROM Supervisor_AI_Coaching__c WHERE Name = '{supervisor_name}' LIMIT 1 """ result = sf.query(query) if not result['records']: logger.warning(f"No record found for supervisor {supervisor_name}") return jsonify({"status": "error", "message": "No record found for this supervisor"}), 404 record = result['records'][0] record_id = record['Id'] data = { "supervisor_name": record['Name'], "project_id": record['Project_ID__c'], "daily_checklist": record['Daily_Checklist__c'] or "", "suggested_tips": record['Suggested_Tips__c'] or "", "reflection_log": record['Reflection_Log__c'] or "", "engagement_score": record['Engagement_Score__c'] or 0, "kpi_flag": record['KPI_Flag__c'], "download_link": record['Download_Link__c'] or "", "last_login": str(datetime.now()) } # Generate PDF pdf_path = generate_pdf_summary(data) # Placeholder for Download_Link__c (requires actual storage solution in production) download_link = "https://example.com/report.pdf" sf.Supervisor_AI_Coaching__c.update(record_id, {'Download_Link__c': download_link}) logger.info(f"Updated Download_Link__c for supervisor {supervisor_name}") # Serve the PDF file return send_file( pdf_path, as_attachment=True, download_name=f"supervisor_report_{supervisor_name}.pdf", mimetype='application/pdf' ) except SalesforceError as e: logger.error(f"Salesforce API error while downloading PDF: {str(e)}") return jsonify({"status": "error", "message": "Salesforce API error. Please try again later."}), 500 except Exception as e: logger.error(f"Error downloading PDF: {str(e)}") return jsonify({"status": "error", "message": str(e)}), 500 finally: if 'pdf_path' in locals(): try: os.remove(pdf_path) logger.info(f"Cleaned up temporary PDF file: {pdf_path}") except Exception as e: logger.warning(f"Failed to clean up PDF file: {str(e)}") if __name__ == '__main__': port = int(os.getenv('PORT', 5000)) app.run(host='0.0.0.0', port=port, debug=True)