import requests import json import logging import os from datetime import datetime from simple_salesforce import Salesforce, SalesforceAuthenticationFailed from flask import Flask, jsonify, request, render_template, redirect, url_for # Configure logging logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[logging.StreamHandler()] ) logger = logging.getLogger(__name__) # Load environment variables (set these in your environment) SF_USERNAME = os.getenv("SF_USERNAME") # e.g., Ai@Coach.com SF_PASSWORD = os.getenv("SF_PASSWORD") # e.g., Teja90325@ SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN") # e.g., clceSdBgQ30Rx9BSC66gAcRx SF_DOMAIN = os.getenv("SF_DOMAIN", "login") # default to "login" HUGGINGFACE_API_KEY = os.getenv("HUGGINGFACE_API_KEY") # your Hugging Face API token # Validate environment variables if not all([SF_USERNAME, SF_PASSWORD, SF_SECURITY_TOKEN, HUGGINGFACE_API_KEY]): logger.error("One or more environment variables are missing.") raise ValueError("Please set SF_USERNAME, SF_PASSWORD, SF_SECURITY_TOKEN, and HUGGINGFACE_API_KEY environment variables.") # Constants HUGGING_FACE_API_URL = "https://api-inference.huggingface.co/models/distilgpt2" HUGGING_FACE_API_TOKEN = HUGGINGFACE_API_KEY # Initialize Flask app app = Flask(__name__) def test_salesforce_connection(): """ Test the Salesforce connection. """ try: logger.debug("Connecting to Salesforce with username: %s", SF_USERNAME) sf = Salesforce( username=SF_USERNAME, password=SF_PASSWORD, security_token=SF_SECURITY_TOKEN, domain=SF_DOMAIN ) # Verify connection sf.query("SELECT Id FROM Account LIMIT 1") logger.info("Connected to Salesforce successfully.") return True, None, sf except SalesforceAuthenticationFailed as e: logger.error("Salesforce authentication failed: %s", e) return False, f"Authentication failed: {str(e)}", None except Exception as e: logger.error("Salesforce connection error: %s", e) return False, f"Connection error: {str(e)}", None def fetch_project_data(project_id): """ Fetch Project__c record data. """ success, error, sf = test_salesforce_connection() if not success: return None, error try: query = f""" SELECT Supervisor_ID__c, Role__c, Project_ID__c, Weather__c, Milestones__c, Reflection_Log__c FROM Project__c WHERE Id = '{project_id}' LIMIT 1 """ result = sf.query(query) if result['totalSize'] > 0: record = result['records'][0] return { 'supervisor_id': record.get('Supervisor_ID__c', ''), 'role': record.get('Role__c', ''), 'project_id': record.get('Project_ID__c', ''), 'weather': record.get('Weather__c', ''), 'milestones': record.get('Milestones__c', ''), 'reflection': record.get('Reflection_Log__c', '') }, None else: return None, "No Project__c record found." except Exception as e: logger.error("Error fetching project data: %s", e) return None, f"Error fetching project data: {str(e)}" def generate_coaching_output(data): """ Generate coaching output using Hugging Face API. """ 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, "debug": True } } try: response = requests.post(HUGGING_FACE_API_URL, headers=headers, json=payload, timeout=10) 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: raise ValueError("No JSON found in LLM output") json_str = generated_text[start_idx:end_idx] output = json.loads(json_str) logger.info("Generated coaching output successfully.") return output, None except requests.exceptions.HTTPError as e: logger.error("Hugging Face API HTTP error: %s", e) return None, f"Hugging Face API HTTP error: {str(e)}" except (json.JSONDecodeError, ValueError) as e: logger.error("Error parsing LLM output: %s", e) return None, f"Error parsing LLM output: {str(e)}" except Exception as e: logger.error("Unexpected error during API call: %s", e) return None, f"API call error: {str(e)}" def save_to_coaching_log(output, supervisor_id, project_id): """ Save coaching output to Salesforce Coaching_Log__c. """ if not output: logger.error("No coaching output to save.") return False, "No coaching output to save." success, error, sf = test_salesforce_connection() if not success: return False, error try: record = { "Supervisor_ID__c": supervisor_id, "Project_ID__c": project_id, "Daily_Checklist__c": "\n".join(output.get("checklist", [])), "Suggested_Tips__c": "\n".join(output.get("tips", [])), "Quote__c": output.get("quote", ""), "Generated_Date__c": datetime.now().strftime("%Y-%m-%d") } sf.Coaching_Log__c.create(record) logger.info("Coaching log saved successfully.") return True, None except Exception as e: logger.error("Error saving coaching log: %s", e) return False, f"Error saving coaching log: {str(e)}" @app.route('/process_new_project', methods=['POST']) def process_new_project(): """ Endpoint to process new Project__c creation. """ data = request.get_json() if not data or 'projectId' not in data: logger.error("Invalid request: missing projectId.") return jsonify({"status": "error", "message": "projectId not provided"}), 400 project_id = data['projectId'] logger.info("Processing project ID: %s", project_id) # Fetch project data from Salesforce project_data, fetch_error = fetch_project_data(project_id) if fetch_error: logger.error("Error fetching project data: %s", fetch_error) return jsonify({"status": "error", "message": fetch_error}), 500 if not project_data: logger.error("No data found for project ID: %s", project_id) return jsonify({"status": "error", "message": "No project data found"}), 404 # Prepare data for AI data_for_ai = { 'supervisor_id': project_data['supervisor_id'], 'role': project_data['role'], 'project_id': project_data['project_id'], 'milestones': [m.strip() for m in project_data['milestones'].split(',') if m.strip()], 'reflection_log': project_data['reflection'], 'weather': project_data['weather'] } # Generate coaching output coaching_output, gen_error = generate_coaching_output(data_for_ai) if gen_error: logger.error("Error generating coaching output: %s", gen_error) return jsonify({"status": "error", "message": gen_error}), 500 # Save to Salesforce success, save_error = save_to_coaching_log(coaching_output, project_data['supervisor_id'], project_data['project_id']) if not success: logger.error("Error saving coaching log: %s", save_error) return jsonify({"status": "error", "message": save_error}), 500 return jsonify({"status": "success", "message": "Coaching log generated and saved."}), 200 @app.route('/ui', methods=['GET']) def ui(): """ Serve UI with latest coaching log. """ form_data = {} output = {} error_msg = "" success, error_msg, sf = test_salesforce_connection() if not success: return render_template('index.html', form_data={}, output={}, error=error_msg) try: query = """ SELECT Supervisor_ID__c, Project_ID__c, Daily_Checklist__c, Suggested_Tips__c, Quote__c FROM Coaching_Log__c ORDER BY Generated_Date__c DESC LIMIT 1 """ result = sf.query(query) if result['totalSize'] > 0: record = result['records'][0] form_data = { 'supervisor_id': record.get('Supervisor_ID__c', ''), 'project_id': record.get('Project_ID__c', '') } output = { 'checklist': record.get('Daily_Checklist__c', '').split('\n'), 'tips': record.get('Suggested_Tips__c', '').split('\n'), 'quote': record.get('Quote__c', '') } else: error_msg = "No coaching logs found." except Exception as e: logger.error("Error fetching coaching logs: %s", e) error_msg = f"Error fetching coaching logs: {str(e)}" return render_template('index.html', form_data=form_data, output=output, error=error_msg) @app.route('/', methods=['GET']) def root(): """ Redirect root to /ui. """ return redirect(url_for('ui')) @app.route('/health', methods=['GET']) def health(): """ Basic health check. """ return jsonify({"status": "healthy", "message": "Application is running"}), 200 if __name__ == "__main__": # Run Flask app app.run(host='0.0.0.0', port=7860)