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)}")
|