from flask import Flask, request, jsonify, session, redirect, url_for, render_template from simple_salesforce import Salesforce, SalesforceAuthenticationFailed, SalesforceError import os from dotenv import load_dotenv import logging import bcrypt from urllib.parse import quote # Load environment variables load_dotenv() # Configure logging logging.basicConfig( level=logging.DEBUG, # Set to DEBUG for detailed logs 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', 'tTPLQduw8wDpxdKOMJ3d9dM3o') # Salesforce mock data for guest users MOCK_DATA = { "supervisor_id": "GUEST", "project_id": "PROJ_001", "last_login": "Guest Mode" } def get_salesforce_connection(): """Establish a Salesforce connection with detailed error handling.""" try: # Ensure you are passing the correct environment variables sf = Salesforce( username=os.getenv('SALESFORCE_USERNAME'), password=os.getenv('SALESFORCE_PASSWORD'), security_token=os.getenv('SALESFORCE_SECURITY_TOKEN'), domain=os.getenv('SALESFORCE_DOMAIN', 'login'), # 'test' is for sandbox, 'login' is for production version='60.0' # Specify Salesforce API version ) logger.info("Successfully connected to Salesforce") # Test connection sf.query("SELECT Id FROM Supervisor__c LIMIT 1") logger.debug("Salesforce connection test query successful") return sf except SalesforceAuthenticationFailed as e: logger.error(f"Salesforce authentication failed: {str(e)}") raise Exception(f"Salesforce authentication failed: {str(e)}. Check your credentials.") except SalesforceError as e: logger.error(f"Salesforce error during connection: {str(e)}") raise Exception(f"Salesforce error: {str(e)}. Check object permissions and API access.") except Exception as e: logger.error(f"Unexpected error connecting to Salesforce: {str(e)}") raise Exception(f"Unable to connect to Salesforce: {str(e)}. Please check your configuration.") def hash_password(password): """Hash a password using bcrypt.""" try: return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') except Exception as e: logger.error(f"Password hashing failed: {str(e)}") raise def verify_password(password, hashed_password): """Verify a password against its hash.""" try: return bcrypt.checkpw(password.encode('utf-8'), hashed_password.encode('utf-8')) except Exception as e: logger.error(f"Password verification failed: {str(e)}") return False @app.route('/') def index(): if 'supervisor_id' 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') from flask import redirect, url_for from flask import Flask, request, session, redirect, render_template, url_for, jsonify from urllib.parse import quote @app.route('/login', methods=['POST']) def login(): data = request.get_json() supervisor_id = data.get('supervisor_id') password = data.get('password') if not supervisor_id or not password: logger.warning("Login failed: Supervisor ID and password are required") return jsonify({"status": "error", "message": "Supervisor ID and password are required"}), 400 if supervisor_id == 'GUEST': session['supervisor_id'] = 'GUEST' logger.info("Guest login successful") return jsonify({"status": "success", "message": "Logged in as guest", "redirect": "/dashboard"}) try: sf = get_salesforce_connection() logger.debug(f"Querying Salesforce for Supervisor_ID__c: {supervisor_id}") supervisor_id_escaped = quote(supervisor_id, safe='') query = f"SELECT Id, Name, Password__c FROM Supervisor__c WHERE Name = '{supervisor_id_escaped}' LIMIT 1" result = sf.query(query) logger.debug(f"Salesforce query result: {result}") if not result['records']: logger.warning(f"Invalid Supervisor ID: {supervisor_id}") return jsonify({"status": "error", "message": "Invalid Supervisor ID"}), 401 record = result['records'][0] stored_password = record['Password__c'] if not stored_password: logger.warning(f"No password set for Supervisor ID: {supervisor_id}") return jsonify({"status": "error", "message": "No password set for this Supervisor ID"}), 401 if not verify_password(password, stored_password): logger.warning(f"Invalid password for Supervisor ID: {supervisor_id}") return jsonify({"status": "error", "message": "Invalid password"}), 401 session['supervisor_id'] = supervisor_id logger.info(f"Login successful for {supervisor_id}") return jsonify({"status": "success", "message": "Logged in as guest", "redirect": "/dashboard"}) except Exception as e: logger.error(f"Login error: {str(e)}") return jsonify({"status": "error", "message": str(e)}), 500 @app.route('/dashboard', methods=['GET']) def dashboard(): """ Serve the dashboard page after successful login. """ return render_template('dashboard.html') @app.route('/home') def home(): return render_template('home.html') @app.route('/signup', methods=['POST']) def signup(): data = request.get_json() supervisor_id = data.get('supervisor_id') password = data.get('password') if not supervisor_id or not password: logger.warning("Signup failed: Supervisor ID and password are required") return jsonify({"status": "error", "message": "Supervisor ID and password are required"}), 400 try: sf = get_salesforce_connection() logger.debug(f"Checking if Supervisor_ID__c {supervisor_id} already exists") supervisor_id_escaped = quote(supervisor_id, safe='') query = f"SELECT Id FROM Supervisor__c WHERE Name = '{supervisor_id_escaped}' LIMIT 1" result = sf.query(query) if result['records']: logger.warning(f"Signup failed: Supervisor ID {supervisor_id} already exists") return jsonify({"status": "error", "message": "Supervisor ID already exists"}), 400 hashed_password = hash_password(password) logger.debug(f"Creating new Supervisor__c record for {supervisor_id}") new_record = { 'Name': supervisor_id, 'Password__c': hashed_password } response = sf.Supervisor__c.create(new_record) logger.debug(f"Salesforce create response: {response}") if not response.get('success'): logger.error(f"Failed to create Supervisor record: {response.get('errors')}") return jsonify({"status": "error", "message": f"Failed to create record in Salesforce: {response.get('errors')}"}), 500 session['supervisor_id'] = supervisor_id logger.info(f"Signup successful for {supervisor_id}") return jsonify({"status": "success", "message": "Signup successful, you are now logged in"}) except Exception as e: logger.error(f"Signup error: {str(e)}") return jsonify({"status": "error", "message": str(e)}), 500 def generate_coaching_output(data): """ Generate daily checklist and tips using Hugging Face LLM. """ logger.info("Generating coaching output for supervisor %s", data['supervisor_id']) milestones_json = json.dumps(data['milestones'], indent=2) prompt = f""" You are an AI Coach for construction site supervisors. Based on the following data, generate a daily checklist, three focus tips, and a motivational quote. Ensure outputs are concise, actionable, and tailored to the supervisor's role, project status, and reflection log. Supervisor Role: {data['role']} Project Milestones: {milestones_json} Reflection Log: {data['reflection_log']} Weather: {data['weather']} Format the response as JSON: {{ "checklist": ["item1", "item2", ...], "tips": ["tip1", "tip2", "tip3"], "quote": "motivational quote" }} """ headers = { "Authorization": f"Bearer {HUGGING_FACE_API_TOKEN}", "Content-Type": "application/json" } payload = { "inputs": prompt, "parameters": { "max_length": 200, "temperature": 0.7, "top_p": 0.9 } } try: response = requests.post(HUGGING_FACE_API_URL, headers=headers, json=payload, timeout=5) response.raise_for_status() result = response.json() generated_text = result[0]["generated_text"] if isinstance(result, list) else result["generated_text"] start_idx = generated_text.find('{') end_idx = generated_text.rfind('}') + 1 if start_idx == -1 or end_idx == 0: logger.error("No valid JSON found in LLM output") raise ValueError("No valid JSON found in LLM output") json_str = generated_text[start_idx:end_idx] output = json.loads(json_str) logger.info("Successfully generated coaching output") return output except requests.exceptions.HTTPError as e: logger.error("Hugging Face API HTTP error: %s", e) return None except (json.JSONDecodeError, ValueError) as e: logger.error("Error parsing LLM output: %s", e) return None except Exception as e: logger.error("Unexpected error in Hugging Face API call: %s", e) return None def save_to_salesforce(output, supervisor_id, project_id): """ Save coaching output to Salesforce Supervisor_AI_Coaching__c object. """ if not output: logger.error("No coaching output to save") return False try: sf = Salesforce( username=SALESFORCE_USERNAME, password=SALESFORCE_PASSWORD, security_token=SALESFORCE_SECURITY_TOKEN, domain=SALESFORCE_DOMAIN ) logger.info("Connected to Salesforce") coaching_record = { "Supervisor_ID__c": supervisor_id, "Project_ID__c": project_id, "Daily_Checklist__c": "\n".join(output["checklist"]), "Suggested_Tips__c": "\n".join(output["tips"]), "Quote__c": output["quote"], "Generated_Date__c": datetime.now().strftime("%Y-%m-%d") } sf.Supervisor_AI_Coaching__c.upsert( f"Supervisor_ID__c/{supervisor_id}_{datetime.now().strftime('%Y-%m-%d')}", coaching_record ) logger.info("Successfully saved coaching record to Salesforce for supervisor %s", supervisor_id) return True except Exception as e: logger.error("Salesforce error: %s", e) return False @app.route('/', methods=['GET']) def redirect_to_ui(): """ Redirect root URL to the UI. """ return redirect(url_for('ui')) @app.route('/ui', methods=['GET']) def ui(): """ Serve the HTML user interface. """ return render_template('home.html') @app.route('/generate', methods=['POST']) def generate_endpoint(): """ Endpoint to generate coaching output based on supervisor data. """ try: data = request.get_json() if not data or not all(key in data for key in ['supervisor_id', 'role', 'project_id', 'milestones', 'reflection_log', 'weather']): return jsonify({"status": "error", "message": "Invalid or missing supervisor data"}), 400 coaching_output = generate_coaching_output(data) if coaching_output: success = save_to_salesforce(coaching_output, data["supervisor_id"], data["project_id"]) if success: return jsonify({"status": "success", "output": coaching_output}), 200 else: return jsonify({"status": "error", "message": "Failed to save to Salesforce"}), 500 else: return jsonify({"status": "error", "message": "Failed to generate coaching output"}), 500 except Exception as e: logger.error("Error in generate endpoint: %s", e) return jsonify({"status": "error", "message": str(e)}), 500 @app.route('/health', methods=['GET']) def health_check(): """ Health check endpoint. """ return jsonify({"status": "healthy", "message": "Application is running"}), 200 @app.route('/logout', methods=['POST']) def logout(): supervisor_id = session.get('supervisor_id', 'Unknown') session.pop('supervisor_id', None) logger.info(f"User {supervisor_id} logged out") return jsonify({"status": "success", "message": "Logged out successfully"}) @app.route('/get_supervisor_data') def get_supervisor_data(): supervisor_id = session.get('supervisor_id', 'GUEST') if supervisor_id == 'GUEST': logger.info("Returning mock data for guest user") return jsonify({"status": "success", "data": MOCK_DATA}) try: sf = get_salesforce_connection() supervisor_id_escaped = quote(supervisor_id, safe='') query = f""" SELECT Supervisor_ID__c, Project_ID__c FROM Supervisor__c WHERE Supervisor_ID__c = '{supervisor_id_escaped}' LIMIT 1 """ result = sf.query(query) if result['records']: record = result['records'][0] data = { "supervisor_id": record['Supervisor_ID__c'], "project_id": record['Project_ID__c'], "last_login": str(datetime.now()) } logger.info(f"Fetched data for supervisor {supervisor_id}") return jsonify({"status": "success", "data": data}) else: logger.warning(f"No data found for supervisor {supervisor_id}") return jsonify({"status": "error", "message": "No data found for this supervisor"}), 404 except Exception as e: logger.error(f"Error fetching supervisor data: {str(e)}") return jsonify({"status": "error", "message": str(e)}), 500 if __name__ == '__main__': port = int(os.getenv('PORT', 5000)) app.run(host='0.0.0.0', port=port, debug=True)