|
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_dotenv() |
|
|
|
|
|
logging.basicConfig( |
|
level=logging.DEBUG, |
|
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') |
|
|
|
|
|
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: |
|
|
|
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'), |
|
version='60.0' |
|
) |
|
logger.info("Successfully connected to Salesforce") |
|
|
|
|
|
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) |