File size: 14,606 Bytes
eba0723
 
5a6aa4a
02615cd
eba0723
 
 
d85f039
02615cd
 
 
eba0723
 
 
 
 
 
 
 
 
 
 
a65ab2b
1799b50
eba0723
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a65ab2b
eba0723
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
02615cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5a6aa4a
 
02615cd
 
 
 
 
 
 
 
 
 
5a6aa4a
02615cd
 
 
 
 
 
 
 
 
 
 
5a6aa4a
02615cd
 
 
 
 
 
 
 
5a6aa4a
 
 
02615cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5a6aa4a
 
02615cd
 
 
 
 
 
5a6aa4a
 
02615cd
 
d85f039
02615cd
 
 
 
 
 
 
 
 
 
 
 
eba0723
5a6aa4a
02615cd
 
 
 
 
5a6aa4a
02615cd
 
 
 
 
 
 
 
 
 
 
5a6aa4a
02615cd
5a6aa4a
02615cd
5a6aa4a
a65ab2b
02615cd
 
 
 
 
 
 
eba0723
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
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)