Spaces:
Running
Running
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) |