File size: 8,770 Bytes
3c6b86c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22046bd
 
 
3c6b86c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import logging
import os
import uvicorn
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from pydantic import BaseModel
from simple_salesforce import Salesforce
from contextlib import asynccontextmanager
import requests
from typing import Optional

# Set up logging early
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.info("Starting application initialization")

# Load environment variables
SF_USERNAME = os.getenv('SF_USERNAME')
SF_PASSWORD = os.getenv('SF_PASSWORD')
SF_SECURITY_TOKEN = os.getenv('SF_SECURITY_TOKEN')

# Validate environment variables
required_vars = {
    'SF_USERNAME': SF_USERNAME,
    'SF_PASSWORD': SF_PASSWORD,
    'SF_SECURITY_TOKEN': SF_SECURITY_TOKEN
}
missing_vars = [var for var, value in required_vars.items() if not value]
if missing_vars:
    logger.error(f"Missing required environment variables: {', '.join(missing_vars)}")
    raise ValueError(f"Missing required environment variables: {', '.join(missing_vars)}")

# Global Salesforce connection
sf = None

@asynccontextmanager
async def lifespan(app: FastAPI):
    """Manage application lifecycle."""
    global sf
    logger.info("Starting application lifecycle")
    
    # Initialize Salesforce connection
    try:
        sf = Salesforce(
            username=SF_USERNAME,
            password=SF_PASSWORD,
            security_token=SF_SECURITY_TOKEN,
            instance_url='https://aicoachforsitesupervisors-dev-ed.develop.my.salesforce.com',
            version='63.0'
        )
        logger.info("Successfully connected to Salesforce")
    except Exception as e:
        logger.error(f"Failed to connect to Salesforce: {str(e)}")
        sf = None
    
    logger.info("Application initialized successfully")
    yield
    logger.info("Shutting down application")

# Initialize FastAPI app with lifespan
app = FastAPI(lifespan=lifespan)

# OpenWeatherMap API key (hardcoded as it works and doesn't cause issues)
WEATHER_API_KEY = "60c00e1b8293d3c0482f8d6ca1a37003"

# Pydantic model for request body
class ProjectRequest(BaseModel):
    projectId: str
    projectName: str
    milestones: Optional[str] = None
    weatherLogs: Optional[str] = None
    safetyLogs: Optional[str] = None
    role: str

def fetch_weather_data(location: str) -> str:
    """Fetch weather data for a given location."""
    try:
        url = f"http://api.openweathermap.org/data/2.5/weather?q={location}&appid={WEATHER_API_KEY}&units=metric"
        response = requests.get(url)
        response.raise_for_status()
        data = response.json()
        weather = data['weather'][0]['description']
        temp = data['main']['temp']
        return f"{weather}, {temp}°C"
    except Exception as e:
        logger.error(f"Failed to fetch weather data: {str(e)}")
        return "Weather data unavailable"

def generate_coaching_data(project: dict, role: str) -> dict:
    """Mock AI model to generate checklist, tips, and engagement score."""
    logger.info(f"Generating coaching data for project {project.get('Name')}")
    checklist = f"1. Review safety protocols for {project.get('Project_Name__c', 'project')}\n2. Check {role} tasks\n3. Update milestones"
    tips = f"1. Prioritize safety due to {project.get('Weather_Logs__c', 'conditions')}\n2. Focus on {role} responsibilities\n3. Communicate progress"
    engagement_score = 85.0  # Mock score; replace with real AI model
    return {
        "checklist": checklist,
        "tips": tips,
        "engagementScore": engagement_score
    }

@app.get("/api/latest-project")
async def get_latest_project():
    """Fetch the latest Project__c record based on CreatedDate."""
    if sf is None:
        logger.error("Salesforce connection not initialized")
        raise HTTPException(status_code=500, detail="Salesforce connection not initialized")
    
    try:
        query = """
            SELECT Id, Name, Project_Name__c, Milestones__c, Weather_Logs__c, Safety_Logs__c, Location__c, Supervisor_ID__c
            FROM Project__c
            ORDER BY CreatedDate DESC
            LIMIT 1
        """
        logger.info(f"Executing SOQL query for latest project: {query}")
        result = sf.query(query)
        
        if result['totalSize'] == 0:
            logger.warning("No Project__c records found")
            raise HTTPException(status_code=404, detail="No projects found")
        
        project = result['records'][0]
        logger.info(f"Fetched latest project: {project['Name']}")
        
        # Prepare response with extracted fields
        response = {
            "projectId": project['Name'],
            "projectName": project['Project_Name__c'],
            "milestones": project.get('Milestones__c', ''),
            "weatherLogs": project.get('Weather_Logs__c', ''),
            "safetyLogs": project.get('Safety_Logs__c', ''),
            "role": "Site Manager"  # Default role; can be updated based on Supervisor_ID__c if needed
        }
        
        return response
    except Exception as e:
        logger.error(f"Error fetching latest project: {str(e)}")
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/generate")
async def generate_coaching(request: ProjectRequest):
    if sf is None:
        logger.error("Salesforce connection not initialized")
        raise HTTPException(status_code=500, detail="Salesforce connection not initialized")
    
    try:
        # Query Project__c by projectId to get the full record
        query = f"SELECT Id, Name, Project_Name__c, Milestones__c, Weather_Logs__c, Safety_Logs__c, Location__c, Supervisor_ID__c FROM Project__c WHERE Name = '{request.projectId}' LIMIT 1"
        logger.info(f"Executing SOQL query: {query}")
        result = sf.query(query)
        
        if result['totalSize'] == 0:
            logger.warning(f"Project {request.projectId} not found")
            raise HTTPException(status_code=404, detail="Project not found")
        
        project = result['records'][0]
        logger.info(f"Retrieved project: {project['Name']}")
        
        # Update weather logs if location available
        if project.get('Location__c'):
            project['Weather_Logs__c'] = fetch_weather_data(project['Location__c'])
        
        # Generate coaching data
        coaching_data = generate_coaching_data(project, request.role)
        
        # Prepare response with extracted fields and generated results
        response = {
            "projectId": project['Name'],
            "projectName": project['Project_Name__c'],
            "milestones": project.get('Milestones__c', ''),
            "weatherLogs": project.get('Weather_Logs__c', ''),
            "safetyLogs": project.get('Safety_Logs__c', ''),
            "role": request.role,
            "checklist": coaching_data['checklist'],
            "tips": coaching_data['tips'],
            "engagementScore": coaching_data['engagementScore']
        }
        
        # Insert into Supervisor_AI_Coaching__c
        try:
            coaching_record = {
                'Project_ID__c': project['Id'],  # Updated to correct API name for Lookup field
                'Checklist__c': coaching_data['checklist'],
                'Tips__c': coaching_data['tips'],
                'Engagement_Score__c': coaching_data['engagementScore'],
                'Role__c': request.role,
                'Project_Name__c': project['Project_Name__c'],
                'Milestones__c': project.get('Milestones__c', ''),
                'Weather_Logs__c': project.get('Weather_Logs__c', ''),
                'Safety_Logs__c': project.get('Safety_Logs__c', '')
            }
            sf.Supervisor_AI_Coaching__c.create(coaching_record)
            logger.info(f"Inserted coaching data into Supervisor_AI_Coaching__c for project {project['Name']}")
        except Exception as e:
            logger.error(f"Failed to insert into Supervisor_AI_Coaching__c: {str(e)}")
            raise HTTPException(status_code=500, detail=f"Failed to save coaching data: {str(e)}")
        
        logger.info(f"Generated coaching response: {response}")
        return response
    except Exception as e:
        logger.error(f"Error generating coaching data: {str(e)}")
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/")
async def root():
    logger.info("Serving index.html from root directory")
    return FileResponse("index.html")

@app.get("/styles.css")
async def serve_styles():
    logger.info("Serving styles.css from root directory")
    return FileResponse("styles.css", media_type="text/css")

# Start Uvicorn server for Hugging Face Spaces
logger.info("Starting Uvicorn server for Hugging Face Spaces")
uvicorn.run(app, host="0.0.0.0", port=7860)