Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -10,23 +10,24 @@ from fastapi.responses import HTMLResponse
|
|
| 10 |
from simple_salesforce import Salesforce
|
| 11 |
import json
|
| 12 |
|
| 13 |
-
# Set up logging
|
| 14 |
logging.basicConfig(level=logging.INFO)
|
| 15 |
logger = logging.getLogger(__name__)
|
| 16 |
|
| 17 |
app = FastAPI()
|
| 18 |
|
| 19 |
-
# Salesforce
|
| 20 |
-
SF_USERNAME = os.getenv("SF_USERNAME"
|
| 21 |
-
SF_PASSWORD = os.getenv("SF_PASSWORD"
|
| 22 |
-
SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN"
|
| 23 |
SF_DOMAIN = os.getenv("SF_DOMAIN", "login")
|
| 24 |
|
| 25 |
-
#
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
| 30 |
|
| 31 |
# Connect to Salesforce
|
| 32 |
try:
|
|
@@ -61,9 +62,9 @@ vendor_logs = []
|
|
| 61 |
def fetch_vendor_logs_from_salesforce():
|
| 62 |
try:
|
| 63 |
query = """
|
| 64 |
-
SELECT Id, Name, Vendor__c, Work_Completion_Percentage__c, Quality_Percentage__c,
|
| 65 |
-
Work_Completion_Date__c, Actual_Completion_Date__c,
|
| 66 |
-
|
| 67 |
FROM Vendor_Log__c
|
| 68 |
"""
|
| 69 |
result = sf.query_all(query)
|
|
@@ -83,13 +84,14 @@ def fetch_vendor_logs_from_salesforce():
|
|
| 83 |
actualCompletionDate=record['Actual_Completion_Date__c'] or "N/A",
|
| 84 |
vendorLogName=record['Name'] or "Unknown",
|
| 85 |
delayDays=int(record['Delay_Days__c'] or 0),
|
| 86 |
-
|
| 87 |
)
|
| 88 |
logs.append(log)
|
|
|
|
| 89 |
return logs
|
| 90 |
except Exception as e:
|
| 91 |
logger.error(f"Error fetching vendor logs from Salesforce: {str(e)}")
|
| 92 |
-
raise
|
| 93 |
|
| 94 |
def calculate_scores(log: VendorLog):
|
| 95 |
try:
|
|
@@ -109,7 +111,6 @@ def calculate_scores(log: VendorLog):
|
|
| 109 |
# Communication Score: Weighted average of other scores
|
| 110 |
communication_score = (quality_score * 0.33 + timeliness_score * 0.33 + safety_score * 0.33)
|
| 111 |
|
| 112 |
-
# Removed finalScore calculation since Final_Score__c is a Formula field
|
| 113 |
return {
|
| 114 |
'qualityScore': round(quality_score, 2),
|
| 115 |
'timelinessScore': round(timeliness_score, 2),
|
|
@@ -118,7 +119,7 @@ def calculate_scores(log: VendorLog):
|
|
| 118 |
}
|
| 119 |
except Exception as e:
|
| 120 |
logger.error(f"Error calculating scores: {str(e)}")
|
| 121 |
-
raise
|
| 122 |
|
| 123 |
def get_feedback(score: float, metric: str) -> str:
|
| 124 |
try:
|
|
@@ -146,7 +147,7 @@ def get_feedback(score: float, metric: str) -> str:
|
|
| 146 |
return "Poor: Communication issues detected"
|
| 147 |
except Exception as e:
|
| 148 |
logger.error(f"Error generating feedback: {str(e)}")
|
| 149 |
-
raise
|
| 150 |
|
| 151 |
def generate_pdf(vendor_id: str, vendor_log_name: str, scores: dict):
|
| 152 |
try:
|
|
@@ -160,7 +161,6 @@ def generate_pdf(vendor_id: str, vendor_log_name: str, scores: dict):
|
|
| 160 |
c.drawString(100, 670, f'Timeliness Score: {scores["timelinessScore"]}% ({get_feedback(scores["timelinessScore"], "Timeliness")})')
|
| 161 |
c.drawString(100, 650, f'Safety Score: {scores["safetyScore"]}% ({get_feedback(scores["safetyScore"], "Safety")})')
|
| 162 |
c.drawString(100, 630, f'Communication Score: {scores["communicationScore"]}% ({get_feedback(scores["communicationScore"], "Communication")})')
|
| 163 |
-
# Removed Final Score from PDF since it's a Formula field
|
| 164 |
c.save()
|
| 165 |
|
| 166 |
with open(filename, 'rb') as f:
|
|
@@ -169,40 +169,40 @@ def generate_pdf(vendor_id: str, vendor_log_name: str, scores: dict):
|
|
| 169 |
return pdf_content
|
| 170 |
except Exception as e:
|
| 171 |
logger.error(f"Error generating PDF: {str(e)}")
|
| 172 |
-
raise
|
| 173 |
|
| 174 |
def determine_alert_flag(scores: dict, all_logs: list):
|
| 175 |
try:
|
| 176 |
if not all_logs:
|
| 177 |
return False
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
avg_score = (scores['qualityScore'] + scores['timelinessScore'] + scores['safetyScore'] + scores['communicationScore']) / 4
|
| 181 |
if avg_score < 50:
|
| 182 |
return True
|
| 183 |
-
lowest_avg = min([(log['scores']['qualityScore'] + log['scores']['timelinessScore'] +
|
|
|
|
|
|
|
| 184 |
return avg_score == lowest_avg
|
| 185 |
except Exception as e:
|
| 186 |
logger.error(f"Error determining alert flag: {str(e)}")
|
| 187 |
-
raise
|
| 188 |
|
| 189 |
def store_scores_in_salesforce(log: VendorLog, scores: dict, pdf_content: bytes, alert_flag: bool):
|
| 190 |
try:
|
| 191 |
-
#
|
| 192 |
score_record = sf.Subcontractor_Performance_Score__c.create({
|
| 193 |
-
'Vendor_Log__c': log.vendorLogId,
|
| 194 |
'Vendor__c': log.vendorRecordId,
|
|
|
|
| 195 |
'Quality_Score__c': scores['qualityScore'],
|
| 196 |
'Timeliness_Score__c': scores['timelinessScore'],
|
| 197 |
'Safety_Score__c': scores['safetyScore'],
|
| 198 |
'Communication_Score__c': scores['communicationScore'],
|
| 199 |
'Alert_Flag__c': alert_flag
|
| 200 |
-
# Removed Final_Score__c since it's a Formula field
|
| 201 |
})
|
| 202 |
score_record_id = score_record['id']
|
| 203 |
-
logger.info(f"
|
| 204 |
|
| 205 |
-
#
|
| 206 |
pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
|
| 207 |
content_version = sf.ContentVersion.create({
|
| 208 |
'Title': f'Performance_Report_{log.vendorId}',
|
|
@@ -210,32 +210,34 @@ def store_scores_in_salesforce(log: VendorLog, scores: dict, pdf_content: bytes,
|
|
| 210 |
'VersionData': pdf_base64,
|
| 211 |
'FirstPublishLocationId': score_record_id
|
| 212 |
})
|
| 213 |
-
logger.info(f"
|
| 214 |
|
| 215 |
-
#
|
| 216 |
content_version_id = content_version['id']
|
| 217 |
content_version_record = sf.query(f"SELECT ContentDocumentId FROM ContentVersion WHERE Id = '{content_version_id}'")
|
|
|
|
|
|
|
|
|
|
| 218 |
content_document_id = content_version_record['records'][0]['ContentDocumentId']
|
| 219 |
-
|
| 220 |
-
# Construct the URL to the file
|
| 221 |
pdf_url = f"https://{sf.sf_instance}/sfc/servlet.shepherd/document/download/{content_document_id}"
|
| 222 |
|
| 223 |
-
#
|
|
|
|
| 224 |
sf.Subcontractor_Performance_Score__c.update(score_record_id, {
|
| 225 |
-
'
|
| 226 |
})
|
| 227 |
-
logger.info(f"
|
| 228 |
-
|
| 229 |
except Exception as e:
|
| 230 |
logger.error(f"Error storing scores in Salesforce: {str(e)}")
|
| 231 |
-
raise
|
| 232 |
|
| 233 |
@app.post('/score')
|
| 234 |
-
async def score_vendor(log: VendorLog, authorization: str = Header(
|
| 235 |
try:
|
| 236 |
logger.info(f"Received Vendor Log: {log}")
|
| 237 |
-
|
| 238 |
-
|
|
|
|
| 239 |
|
| 240 |
scores = calculate_scores(log)
|
| 241 |
pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
|
|
@@ -269,6 +271,8 @@ async def score_vendor(log: VendorLog, authorization: str = Header(...)):
|
|
| 269 |
'pdfContent': pdf_base64,
|
| 270 |
'alert': alert_flag
|
| 271 |
}
|
|
|
|
|
|
|
| 272 |
except Exception as e:
|
| 273 |
logger.error(f"Error in /score endpoint: {str(e)}")
|
| 274 |
raise HTTPException(status_code=500, detail=f"Error processing vendor log: {str(e)}")
|
|
@@ -325,11 +329,15 @@ async def get_dashboard():
|
|
| 325 |
</style>
|
| 326 |
<script>
|
| 327 |
async function generateScores() {
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
}
|
| 334 |
}
|
| 335 |
</script>
|
|
@@ -419,6 +427,8 @@ async def get_dashboard():
|
|
| 419 |
</html>
|
| 420 |
"""
|
| 421 |
return HTMLResponse(content=html_content)
|
|
|
|
|
|
|
| 422 |
except Exception as e:
|
| 423 |
logger.error(f"Error in / endpoint: {str(e)}")
|
| 424 |
raise HTTPException(status_code=500, detail=f"Error generating dashboard: {str(e)}")
|
|
@@ -427,8 +437,8 @@ async def get_dashboard():
|
|
| 427 |
async def generate_scores():
|
| 428 |
try:
|
| 429 |
global vendor_logs
|
| 430 |
-
fetched_logs = fetch_vendor_logs_from_salesforce()
|
| 431 |
vendor_logs = []
|
|
|
|
| 432 |
for log in fetched_logs:
|
| 433 |
scores = calculate_scores(log)
|
| 434 |
pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
|
|
@@ -449,7 +459,10 @@ async def generate_scores():
|
|
| 449 |
'scores': scores,
|
| 450 |
'extracted': True
|
| 451 |
})
|
|
|
|
| 452 |
return {"status": "success"}
|
|
|
|
|
|
|
| 453 |
except Exception as e:
|
| 454 |
logger.error(f"Error in /generate endpoint: {str(e)}")
|
| 455 |
raise HTTPException(status_code=500, detail=f"Error generating scores: {str(e)}")
|
|
|
|
| 10 |
from simple_salesforce import Salesforce
|
| 11 |
import json
|
| 12 |
|
| 13 |
+
# Set up logging
|
| 14 |
logging.basicConfig(level=logging.INFO)
|
| 15 |
logger = logging.getLogger(__name__)
|
| 16 |
|
| 17 |
app = FastAPI()
|
| 18 |
|
| 19 |
+
# Environment variables for Salesforce
|
| 20 |
+
SF_USERNAME = os.getenv("SF_USERNAME")
|
| 21 |
+
SF_PASSWORD = os.getenv("SF_PASSWORD")
|
| 22 |
+
SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN")
|
| 23 |
SF_DOMAIN = os.getenv("SF_DOMAIN", "login")
|
| 24 |
|
| 25 |
+
# Validate environment variables
|
| 26 |
+
required_env_vars = ["SF_USERNAME", "SF_PASSWORD", "SF_SECURITY_TOKEN"]
|
| 27 |
+
for var in required_env_vars:
|
| 28 |
+
if not os.getenv(var):
|
| 29 |
+
logger.error(f"Environment variable {var} is not set")
|
| 30 |
+
raise ValueError(f"Environment variable {var} is not set")
|
| 31 |
|
| 32 |
# Connect to Salesforce
|
| 33 |
try:
|
|
|
|
| 62 |
def fetch_vendor_logs_from_salesforce():
|
| 63 |
try:
|
| 64 |
query = """
|
| 65 |
+
SELECT Id, Name, Vendor__c, Work_Completion_Percentage__c, Quality_Percentage__c,
|
| 66 |
+
Incident_Severity__c, Work_Completion_Date__c, Actual_Completion_Date__c,
|
| 67 |
+
Delay_Days__c, Project__c
|
| 68 |
FROM Vendor_Log__c
|
| 69 |
"""
|
| 70 |
result = sf.query_all(query)
|
|
|
|
| 84 |
actualCompletionDate=record['Actual_Completion_Date__c'] or "N/A",
|
| 85 |
vendorLogName=record['Name'] or "Unknown",
|
| 86 |
delayDays=int(record['Delay_Days__c'] or 0),
|
| 87 |
+
project=record['Project__c'] or "Unknown"
|
| 88 |
)
|
| 89 |
logs.append(log)
|
| 90 |
+
logger.info(f"Fetched {len(logs)} vendor logs from Salesforce")
|
| 91 |
return logs
|
| 92 |
except Exception as e:
|
| 93 |
logger.error(f"Error fetching vendor logs from Salesforce: {str(e)}")
|
| 94 |
+
raise HTTPException(status_code=500, detail=f"Error fetching vendor logs: {str(e)}")
|
| 95 |
|
| 96 |
def calculate_scores(log: VendorLog):
|
| 97 |
try:
|
|
|
|
| 111 |
# Communication Score: Weighted average of other scores
|
| 112 |
communication_score = (quality_score * 0.33 + timeliness_score * 0.33 + safety_score * 0.33)
|
| 113 |
|
|
|
|
| 114 |
return {
|
| 115 |
'qualityScore': round(quality_score, 2),
|
| 116 |
'timelinessScore': round(timeliness_score, 2),
|
|
|
|
| 119 |
}
|
| 120 |
except Exception as e:
|
| 121 |
logger.error(f"Error calculating scores: {str(e)}")
|
| 122 |
+
raise HTTPException(status_code=500, detail=f"Error calculating scores: {str(e)}")
|
| 123 |
|
| 124 |
def get_feedback(score: float, metric: str) -> str:
|
| 125 |
try:
|
|
|
|
| 147 |
return "Poor: Communication issues detected"
|
| 148 |
except Exception as e:
|
| 149 |
logger.error(f"Error generating feedback: {str(e)}")
|
| 150 |
+
raise HTTPException(status_code=500, detail=f"Error generating feedback: {str(e)}")
|
| 151 |
|
| 152 |
def generate_pdf(vendor_id: str, vendor_log_name: str, scores: dict):
|
| 153 |
try:
|
|
|
|
| 161 |
c.drawString(100, 670, f'Timeliness Score: {scores["timelinessScore"]}% ({get_feedback(scores["timelinessScore"], "Timeliness")})')
|
| 162 |
c.drawString(100, 650, f'Safety Score: {scores["safetyScore"]}% ({get_feedback(scores["safetyScore"], "Safety")})')
|
| 163 |
c.drawString(100, 630, f'Communication Score: {scores["communicationScore"]}% ({get_feedback(scores["communicationScore"], "Communication")})')
|
|
|
|
| 164 |
c.save()
|
| 165 |
|
| 166 |
with open(filename, 'rb') as f:
|
|
|
|
| 169 |
return pdf_content
|
| 170 |
except Exception as e:
|
| 171 |
logger.error(f"Error generating PDF: {str(e)}")
|
| 172 |
+
raise HTTPException(status_code=500, detail=f"Error generating PDF: {str(e)}")
|
| 173 |
|
| 174 |
def determine_alert_flag(scores: dict, all_logs: list):
|
| 175 |
try:
|
| 176 |
if not all_logs:
|
| 177 |
return False
|
| 178 |
+
avg_score = (scores['qualityScore'] + scores['timelinessScore'] +
|
| 179 |
+
scores['safetyScore'] + scores['communicationScore']) / 4
|
|
|
|
| 180 |
if avg_score < 50:
|
| 181 |
return True
|
| 182 |
+
lowest_avg = min([(log['scores']['qualityScore'] + log['scores']['timelinessScore'] +
|
| 183 |
+
log['scores']['safetyScore'] + log['scores']['communicationScore']) / 4
|
| 184 |
+
for log in all_logs], default=avg_score)
|
| 185 |
return avg_score == lowest_avg
|
| 186 |
except Exception as e:
|
| 187 |
logger.error(f"Error determining alert flag: {str(e)}")
|
| 188 |
+
raise HTTPException(status_code=500, detail=f"Error determining alert flag: {str(e)}")
|
| 189 |
|
| 190 |
def store_scores_in_salesforce(log: VendorLog, scores: dict, pdf_content: bytes, alert_flag: bool):
|
| 191 |
try:
|
| 192 |
+
# Create Subcontractor_Performance_Score__c record
|
| 193 |
score_record = sf.Subcontractor_Performance_Score__c.create({
|
|
|
|
| 194 |
'Vendor__c': log.vendorRecordId,
|
| 195 |
+
'Month__c': datetime.today().replace(day=1).strftime('%Y-%m-%d'),
|
| 196 |
'Quality_Score__c': scores['qualityScore'],
|
| 197 |
'Timeliness_Score__c': scores['timelinessScore'],
|
| 198 |
'Safety_Score__c': scores['safetyScore'],
|
| 199 |
'Communication_Score__c': scores['communicationScore'],
|
| 200 |
'Alert_Flag__c': alert_flag
|
|
|
|
| 201 |
})
|
| 202 |
score_record_id = score_record['id']
|
| 203 |
+
logger.info(f"Created Subcontractor_Performance_Score__c record with ID: {score_record_id}")
|
| 204 |
|
| 205 |
+
# Upload PDF as ContentVersion
|
| 206 |
pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
|
| 207 |
content_version = sf.ContentVersion.create({
|
| 208 |
'Title': f'Performance_Report_{log.vendorId}',
|
|
|
|
| 210 |
'VersionData': pdf_base64,
|
| 211 |
'FirstPublishLocationId': score_record_id
|
| 212 |
})
|
| 213 |
+
logger.info(f"Uploaded PDF as ContentVersion for Vendor Log ID: {log.vendorLogId}")
|
| 214 |
|
| 215 |
+
# Get ContentDocumentId and construct URL
|
| 216 |
content_version_id = content_version['id']
|
| 217 |
content_version_record = sf.query(f"SELECT ContentDocumentId FROM ContentVersion WHERE Id = '{content_version_id}'")
|
| 218 |
+
if content_version_record['totalSize'] == 0:
|
| 219 |
+
logger.error(f"No ContentVersion found for ID: {content_version_id}")
|
| 220 |
+
raise HTTPException(status_code=500, detail="Failed to retrieve ContentDocumentId")
|
| 221 |
content_document_id = content_version_record['records'][0]['ContentDocumentId']
|
|
|
|
|
|
|
| 222 |
pdf_url = f"https://{sf.sf_instance}/sfc/servlet.shepherd/document/download/{content_document_id}"
|
| 223 |
|
| 224 |
+
# Update Subcontractor_Performance_Score__c with PDF URL
|
| 225 |
+
# Note: Changed PDF_Link__c to Certification_URL__c to match your previous Salesforce schema
|
| 226 |
sf.Subcontractor_Performance_Score__c.update(score_record_id, {
|
| 227 |
+
'Certification_URL__c': pdf_url
|
| 228 |
})
|
| 229 |
+
logger.info(f"Updated Subcontractor_Performance_Score__c with Certification_URL__c: {pdf_url}")
|
|
|
|
| 230 |
except Exception as e:
|
| 231 |
logger.error(f"Error storing scores in Salesforce: {str(e)}")
|
| 232 |
+
raise HTTPException(status_code=500, detail=f"Error storing scores: {str(e)}")
|
| 233 |
|
| 234 |
@app.post('/score')
|
| 235 |
+
async def score_vendor(log: VendorLog, authorization: str = Header(None)):
|
| 236 |
try:
|
| 237 |
logger.info(f"Received Vendor Log: {log}")
|
| 238 |
+
# Optional: Add API key check if needed
|
| 239 |
+
# if authorization != f'Bearer {API_KEY}':
|
| 240 |
+
# raise HTTPException(status_code=401, detail='Invalid API key')
|
| 241 |
|
| 242 |
scores = calculate_scores(log)
|
| 243 |
pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
|
|
|
|
| 271 |
'pdfContent': pdf_base64,
|
| 272 |
'alert': alert_flag
|
| 273 |
}
|
| 274 |
+
except HTTPException as e:
|
| 275 |
+
raise e
|
| 276 |
except Exception as e:
|
| 277 |
logger.error(f"Error in /score endpoint: {str(e)}")
|
| 278 |
raise HTTPException(status_code=500, detail=f"Error processing vendor log: {str(e)}")
|
|
|
|
| 329 |
</style>
|
| 330 |
<script>
|
| 331 |
async function generateScores() {
|
| 332 |
+
try {
|
| 333 |
+
const response = await fetch('/generate', { method: 'POST' });
|
| 334 |
+
if (response.ok) {
|
| 335 |
+
window.location.reload();
|
| 336 |
+
} else {
|
| 337 |
+
alert('Error generating scores');
|
| 338 |
+
}
|
| 339 |
+
} catch (error) {
|
| 340 |
+
alert('Error generating scores: ' + error.message);
|
| 341 |
}
|
| 342 |
}
|
| 343 |
</script>
|
|
|
|
| 427 |
</html>
|
| 428 |
"""
|
| 429 |
return HTMLResponse(content=html_content)
|
| 430 |
+
except HTTPException as e:
|
| 431 |
+
raise e
|
| 432 |
except Exception as e:
|
| 433 |
logger.error(f"Error in / endpoint: {str(e)}")
|
| 434 |
raise HTTPException(status_code=500, detail=f"Error generating dashboard: {str(e)}")
|
|
|
|
| 437 |
async def generate_scores():
|
| 438 |
try:
|
| 439 |
global vendor_logs
|
|
|
|
| 440 |
vendor_logs = []
|
| 441 |
+
fetched_logs = fetch_vendor_logs_from_salesforce()
|
| 442 |
for log in fetched_logs:
|
| 443 |
scores = calculate_scores(log)
|
| 444 |
pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
|
|
|
|
| 459 |
'scores': scores,
|
| 460 |
'extracted': True
|
| 461 |
})
|
| 462 |
+
logger.info(f"Generated scores for {len(vendor_logs)} vendor logs")
|
| 463 |
return {"status": "success"}
|
| 464 |
+
except HTTPException as e:
|
| 465 |
+
raise e
|
| 466 |
except Exception as e:
|
| 467 |
logger.error(f"Error in /generate endpoint: {str(e)}")
|
| 468 |
raise HTTPException(status_code=500, detail=f"Error generating scores: {str(e)}")
|