Swathi6 commited on
Commit
5cd0c37
·
verified ·
1 Parent(s): 5caf1d0

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +459 -0
app.py ADDED
@@ -0,0 +1,459 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException, Header
2
+ from pydantic import BaseModel
3
+ from reportlab.lib.pagesizes import letter
4
+ from reportlab.pdfgen import canvas
5
+ import base64
6
+ import os
7
+ import logging
8
+ from datetime import datetime
9
+ from fastapi.responses import HTMLResponse
10
+ from simple_salesforce import Salesforce
11
+ import json
12
+
13
+ # Set up logging to capture errors and debug information
14
+ logging.basicConfig(level=logging.INFO)
15
+ logger = logging.getLogger(__name__)
16
+
17
+ app = FastAPI()
18
+
19
+ # Salesforce credentials
20
+ SF_USERNAME = os.getenv("SF_USERNAME", "[email protected]")
21
+ SF_PASSWORD = os.getenv("SF_PASSWORD", "Internal@1")
22
+ SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN", "NbUKcTx45azba5HEdntE9YAh")
23
+ SF_DOMAIN = os.getenv("SF_DOMAIN", "login")
24
+
25
+ # Verify API key is set
26
+ API_KEY = os.getenv("HUGGINGFACE_API_KEY")
27
+ if not API_KEY:
28
+ logger.error("HUGGINGFACE_API_KEY environment variable not set")
29
+ raise ValueError("HUGGINGFACE_API_KEY environment variable not set")
30
+
31
+ # Connect to Salesforce
32
+ try:
33
+ sf = Salesforce(
34
+ username=SF_USERNAME,
35
+ password=SF_PASSWORD,
36
+ security_token=SF_SECURITY_TOKEN,
37
+ domain=SF_DOMAIN
38
+ )
39
+ logger.info("Successfully connected to Salesforce")
40
+ except Exception as e:
41
+ logger.error(f"Failed to connect to Salesforce: {str(e)}")
42
+ raise
43
+
44
+ # VendorLog model to match Salesforce data
45
+ class VendorLog(BaseModel):
46
+ vendorLogId: str
47
+ vendorId: str
48
+ vendorRecordId: str
49
+ workDetails: str
50
+ qualityReport: str
51
+ incidentLog: str
52
+ workCompletionDate: str
53
+ actualCompletionDate: str
54
+ vendorLogName: str
55
+ delayDays: int
56
+ project: str
57
+
58
+ # Store vendor logs for display
59
+ vendor_logs = []
60
+
61
+ def fetch_vendor_logs_from_salesforce():
62
+ try:
63
+ query = """
64
+ SELECT Id, Name, Vendor__c, Work_Completion_Percentage__c, Quality_Percentage__c, Incident_Severity__c,
65
+ Work_Completion_Date__c, Actual_Completion_Date__c, Delay_Days__c,Project__c
66
+
67
+ FROM Vendor_Log__c
68
+ """
69
+ result = sf.query_all(query)
70
+ logs = []
71
+ for record in result['records']:
72
+ if not record['Vendor__c']:
73
+ logger.warning(f"Skipping Vendor_Log__c record with ID {record['Id']} due to missing Vendor__c")
74
+ continue
75
+ log = VendorLog(
76
+ vendorLogId=record['Id'] or "Unknown",
77
+ vendorId=record['Name'] or "Unknown",
78
+ vendorRecordId=record['Vendor__c'] or "Unknown",
79
+ workDetails=str(record['Work_Completion_Percentage__c'] or "0.0"),
80
+ qualityReport=str(record['Quality_Percentage__c'] or "0.0"),
81
+ incidentLog=record['Incident_Severity__c'] or "None",
82
+ workCompletionDate=record['Work_Completion_Date__c'] or "N/A",
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
+ project=record['Project__c'] or "Unknown"
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:
96
+ work_completion_percentage = float(log.workDetails)
97
+ quality_percentage = float(log.qualityReport)
98
+
99
+ # Quality Score: Directly use the quality percentage
100
+ quality_score = quality_percentage
101
+
102
+ # Timeliness Score: Based on delay days
103
+ timeliness_score = 100.0 if log.delayDays <= 0 else 80.0 if log.delayDays <= 3 else 60.0 if log.delayDays <= 7 else 40.0
104
+
105
+ # Safety Score: Based on incident severity
106
+ severity_map = {'None': 100.0, 'Low': 80.0, 'Minor': 80.0, 'Medium': 50.0, 'High': 20.0}
107
+ safety_score = severity_map.get(log.incidentLog, 100.0)
108
+
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),
116
+ 'safetyScore': round(safety_score, 2),
117
+ 'communicationScore': round(communication_score, 2)
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:
125
+ if score >= 90:
126
+ return "Excellent: Maintain this standard"
127
+ elif score >= 70:
128
+ return "Good: Keep up the good work"
129
+ elif score >= 50:
130
+ if metric == 'Timeliness':
131
+ return "Needs Improvement: Maintain schedules to complete tasks on time"
132
+ elif metric == 'Safety':
133
+ return "Needs Improvement: Implement stricter safety protocols"
134
+ elif metric == 'Quality':
135
+ return "Needs Improvement: Focus on improving work quality"
136
+ else:
137
+ return "Needs Improvement: Enhance coordination with project teams"
138
+ else:
139
+ if metric == 'Timeliness':
140
+ return "Poor: Significant delays detected"
141
+ elif metric == 'Safety':
142
+ return "Poor: Critical safety issues identified"
143
+ elif metric == 'Quality':
144
+ return "Poor: Quality standards not met"
145
+ else:
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:
153
+ filename = f'report_{vendor_id}.pdf'
154
+ c = canvas.Canvas(filename, pagesize=letter)
155
+ c.setFont('Helvetica', 12)
156
+ c.drawString(100, 750, 'Subcontractor Performance Report')
157
+ c.drawString(100, 730, f'Vendor ID: {vendor_id}')
158
+ c.drawString(100, 710, f'Vendor Log Name: {vendor_log_name}')
159
+ c.drawString(100, 690, f'Quality Score: {scores["qualityScore"]}% ({get_feedback(scores["qualityScore"], "Quality")})')
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:
167
+ pdf_content = f.read()
168
+ os.remove(filename)
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
+ # Since finalScore is a Formula field, we'll need to fetch it from Salesforce or adjust logic
179
+ # For now, we'll base the alert on the average of other scores
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'] + log['scores']['safetyScore'] + log['scores']['communicationScore']) / 4 for log in all_logs])
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
+ # Step 1: Create the Subcontractor_Performance_Score__c record without Final_Score__c
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"Successfully created Subcontractor_Performance_Score__c record with ID: {score_record_id}")
204
+
205
+ # Step 2: Upload the PDF as a ContentVersion
206
+ pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
207
+ content_version = sf.ContentVersion.create({
208
+ 'Title': f'Performance_Report_{log.vendorId}',
209
+ 'PathOnClient': f'report_{log.vendorId}.pdf',
210
+ 'VersionData': pdf_base64,
211
+ 'FirstPublishLocationId': score_record_id
212
+ })
213
+ logger.info(f"Successfully uploaded PDF as ContentVersion for Vendor Log ID: {log.vendorLogId}")
214
+
215
+ # Step 3: Get the ContentDocumentId and construct a URL to the file
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
+ # Step 4: Update the Subcontractor_Performance_Score__c record with the PDF URL
224
+ sf.Subcontractor_Performance_Score__c.update(score_record_id, {
225
+ 'PDF_Link__c': pdf_url
226
+ })
227
+ logger.info(f"Successfully updated Subcontractor_Performance_Score__c record with PDF URL: {pdf_url}")
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
+ if authorization != f'Bearer {API_KEY}':
238
+ raise HTTPException(status_code=401, detail='Invalid API key')
239
+
240
+ scores = calculate_scores(log)
241
+ pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
242
+ pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
243
+ alert_flag = determine_alert_flag(scores, vendor_logs)
244
+ store_scores_in_salesforce(log, scores, pdf_content, alert_flag)
245
+
246
+ vendor_logs.append({
247
+ 'vendorLogId': log.vendorLogId,
248
+ 'vendorId': log.vendorId,
249
+ 'vendorLogName': log.vendorLogName,
250
+ 'workDetails': log.workDetails,
251
+ 'qualityReport': log.qualityReport,
252
+ 'incidentLog': log.incidentLog,
253
+ 'workCompletionDate': log.workCompletionDate,
254
+ 'actualCompletionDate': log.actualCompletionDate,
255
+ 'delayDays': log.delayDays,
256
+ 'project': log.project,
257
+ 'scores': scores,
258
+ 'extracted': True
259
+ })
260
+
261
+ return {
262
+ 'vendorLogId': log.vendorLogId,
263
+ 'vendorId': log.vendorId,
264
+ 'vendorLogName': log.vendorLogName,
265
+ 'qualityScore': scores['qualityScore'],
266
+ 'timelinessScore': scores['timelinessScore'],
267
+ 'safetyScore': scores['safetyScore'],
268
+ 'communicationScore': scores['communicationScore'],
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)}")
275
+
276
+ @app.get('/', response_class=HTMLResponse)
277
+ async def get_dashboard():
278
+ try:
279
+ global vendor_logs
280
+ fetched_logs = fetch_vendor_logs_from_salesforce()
281
+ for log in fetched_logs:
282
+ if not any(existing_log['vendorLogId'] == log.vendorLogId for existing_log in vendor_logs):
283
+ scores = calculate_scores(log)
284
+ pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
285
+ pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
286
+ alert_flag = determine_alert_flag(scores, vendor_logs)
287
+ store_scores_in_salesforce(log, scores, pdf_content, alert_flag)
288
+ vendor_logs.append({
289
+ 'vendorLogId': log.vendorLogId,
290
+ 'vendorId': log.vendorId,
291
+ 'vendorLogName': log.vendorLogName,
292
+ 'workDetails': log.workDetails,
293
+ 'qualityReport': log.qualityReport,
294
+ 'incidentLog': log.incidentLog,
295
+ 'workCompletionDate': log.workCompletionDate,
296
+ 'actualCompletionDate': log.actualCompletionDate,
297
+ 'delayDays': log.delayDays,
298
+ 'project': log.project,
299
+ 'scores': scores,
300
+ 'extracted': True
301
+ })
302
+
303
+ html_content = """
304
+ <html>
305
+ <head>
306
+ <title>Subcontractor Performance Score App</title>
307
+ <style>
308
+ body { font-family: Arial, sans-serif; margin: 20px; }
309
+ table { width: 100%; border-collapse: collapse; margin-top: 20px; }
310
+ th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
311
+ th { background-color: #f2f2f2; }
312
+ h1, h2 { text-align: center; }
313
+ .generate-btn {
314
+ display: block;
315
+ margin: 20px auto;
316
+ padding: 10px 20px;
317
+ background-color: #4CAF50;
318
+ color: white;
319
+ border: none;
320
+ border-radius: 5px;
321
+ cursor: pointer;
322
+ font-size: 16px;
323
+ }
324
+ .generate-btn:hover { background-color: #45a049; }
325
+ </style>
326
+ <script>
327
+ async function generateScores() {
328
+ const response = await fetch('/generate', { method: 'POST' });
329
+ if (response.ok) {
330
+ window.location.reload();
331
+ } else {
332
+ alert('Error generating scores');
333
+ }
334
+ }
335
+ </script>
336
+ </head>
337
+ <body>
338
+ <h1>SUBCONTRACTOR PERFORMANCE SCORE APP GENERATOR</h1>
339
+ <h2>VENDOR LOGS SUBMISSION</h2>
340
+ <table>
341
+ <tr>
342
+ <th>Vendor ID</th>
343
+ <th>Vendor Log Name</th>
344
+ <th>Project</th>
345
+ <th>Work Completion Percentage</th>
346
+ <th>Quality Percentage</th>
347
+ <th>Incident Severity</th>
348
+ <th>Work Completion Date</th>
349
+ <th>Actual Completion Date</th>
350
+ <th>Delay Days</th>
351
+ </tr>
352
+ """
353
+
354
+ if not vendor_logs:
355
+ html_content += """
356
+ <tr>
357
+ <td colspan="9" style="text-align: center;">No vendor logs available</td>
358
+ </tr>
359
+ """
360
+ else:
361
+ for log in vendor_logs:
362
+ html_content += f"""
363
+ <tr>
364
+ <td>{log['vendorId']}</td>
365
+ <td>{log['vendorLogName']}</td>
366
+ <td>{log['project']}</td>
367
+ <td>{log['workDetails']}</td>
368
+ <td>{log['qualityReport']}</td>
369
+ <td>{log['incidentLog']}</td>
370
+ <td>{log['workCompletionDate']}</td>
371
+ <td>{log['actualCompletionDate']}</td>
372
+ <td>{log['delayDays']}</td>
373
+ </tr>
374
+ """
375
+
376
+ html_content += """
377
+ </table>
378
+ <button class="generate-btn" onclick="generateScores()">Generate</button>
379
+ <h2>SUBCONTRACTOR PERFORMANCE SCORES</h2>
380
+ <table>
381
+ <tr>
382
+ <th>Vendor ID</th>
383
+ <th>Vendor Log Name</th>
384
+ <th>Project</th>
385
+ <th>Quality Score</th>
386
+ <th>Timeliness Score</th>
387
+ <th>Safety Score</th>
388
+ <th>Communication Score</th>
389
+ <th>Alert Flag</th>
390
+ </tr>
391
+ """
392
+
393
+ if not vendor_logs:
394
+ html_content += """
395
+ <tr>
396
+ <td colspan="8" style="text-align: center;">No scores available</td>
397
+ </tr>
398
+ """
399
+ else:
400
+ for log in vendor_logs:
401
+ scores = log['scores']
402
+ alert_flag = determine_alert_flag(scores, vendor_logs)
403
+ html_content += f"""
404
+ <tr>
405
+ <td>{log['vendorId']}</td>
406
+ <td>{log['vendorLogName']}</td>
407
+ <td>{log['project']}</td>
408
+ <td>{scores['qualityScore']}%</td>
409
+ <td>{scores['timelinessScore']}%</td>
410
+ <td>{scores['safetyScore']}%</td>
411
+ <td>{scores['communicationScore']}%</td>
412
+ <td>{'Checked' if alert_flag else 'Unchecked'}</td>
413
+ </tr>
414
+ """
415
+
416
+ html_content += """
417
+ </table>
418
+ </body>
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)}")
425
+
426
+ @app.post('/generate')
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)
435
+ pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
436
+ alert_flag = determine_alert_flag(scores, vendor_logs)
437
+ store_scores_in_salesforce(log, scores, pdf_content, alert_flag)
438
+ vendor_logs.append({
439
+ 'vendorLogId': log.vendorLogId,
440
+ 'vendorId': log.vendorId,
441
+ 'vendorLogName': log.vendorLogName,
442
+ 'workDetails': log.workDetails,
443
+ 'qualityReport': log.qualityReport,
444
+ 'incidentLog': log.incidentLog,
445
+ 'workCompletionDate': log.workCompletionDate,
446
+ 'actualCompletionDate': log.actualCompletionDate,
447
+ 'delayDays': log.delayDays,
448
+ 'project': log.project,
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)}")
456
+
457
+ if __name__ == "__main__":
458
+ import uvicorn
459
+ uvicorn.run(app, host="0.0.0.0", port=7860)