File size: 10,410 Bytes
64cc33d
 
5c224a0
f7d1072
64cc33d
0c8edc8
64cc33d
8136580
f75809a
0c8edc8
997d665
0c8edc8
f75809a
0c8edc8
5c224a0
0d3b455
f75809a
 
 
 
 
 
64cc33d
f75809a
 
 
 
64cc33d
f75809a
 
 
64cc33d
 
 
0d3b455
0c8edc8
 
f75809a
0c8edc8
 
f75809a
0c8edc8
f7d1072
 
 
 
0c8edc8
f75809a
0c8edc8
f75809a
997d665
0c8edc8
 
f75809a
0c8edc8
 
f75809a
0c8edc8
997d665
49c0f36
f75809a
49c0f36
997d665
 
 
49c0f36
997d665
7119982
ced5f86
997d665
 
49c0f36
 
 
 
 
 
f75809a
 
 
 
 
 
fe79a2b
49c0f36
f75809a
49c0f36
f75809a
 
49c0f36
64cc33d
 
f75809a
64cc33d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0c8edc8
f75809a
64cc33d
 
0d3b455
 
f75809a
64cc33d
 
 
 
 
 
f75809a
64cc33d
 
f75809a
fe79a2b
64cc33d
 
fe79a2b
64cc33d
 
fe79a2b
0d3b455
f75809a
 
0d3b455
997d665
64cc33d
f75809a
64cc33d
 
f75809a
fe79a2b
0d3b455
997d665
 
 
64cc33d
997d665
f75809a
ced5f86
 
f75809a
 
 
64cc33d
 
f75809a
 
fe79a2b
0d3b455
f75809a
 
64cc33d
997d665
 
64cc33d
f75809a
64cc33d
997d665
 
f75809a
997d665
 
 
f75809a
997d665
f75809a
997d665
 
f75809a
997d665
 
 
f75809a
997d665
 
f75809a
 
997d665
 
 
 
 
 
 
 
 
f75809a
997d665
f75809a
997d665
 
f75809a
997d665
 
f75809a
997d665
 
f75809a
64cc33d
ced5f86
64cc33d
 
f75809a
64cc33d
49c0f36
 
f75809a
7119982
f75809a
fe79a2b
f75809a
7119982
997d665
 
 
 
 
 
 
 
 
 
 
f75809a
 
997d665
 
f75809a
 
 
997d665
 
f75809a
997d665
f75809a
 
49c0f36
f75809a
0d3b455
997d665
f75809a
997d665
f75809a
997d665
 
 
1667a68
f75809a
64cc33d
f75809a
64cc33d
 
 
 
f75809a
 
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
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)