Swathi6 commited on
Commit
aa68531
·
verified ·
1 Parent(s): 7e08e15

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +129 -198
app.py CHANGED
@@ -15,12 +15,12 @@ from dotenv import load_dotenv
15
  load_dotenv()
16
 
17
  # Set up logging
18
- logging.basicConfig(level=logging.INFO)
19
  logger = logging.getLogger(__name__)
20
 
21
  app = FastAPI()
22
 
23
- # Environment variables for Salesforce
24
  SF_USERNAME = os.getenv("SF_USERNAME")
25
  SF_PASSWORD = os.getenv("SF_PASSWORD")
26
  SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN")
@@ -35,14 +35,12 @@ for var in required_env_vars:
35
  logger.error(f"Environment variable {var} is not set")
36
  raise ValueError(f"Environment variable {var} is not set")
37
 
38
- # Optional Hugging Face validation
39
- USE_HUGGINGFACE = HUGGINGFACE_API_KEY and HUGGINGFACE_API_URL
40
- if USE_HUGGINGFACE:
41
- logger.info("Hugging Face integration enabled")
42
- else:
43
- logger.info("Hugging Face integration disabled; using local scoring")
44
 
45
- # Connect to Salesforce
 
46
  try:
47
  sf = Salesforce(
48
  username=SF_USERNAME,
@@ -53,9 +51,9 @@ try:
53
  logger.info("Successfully connected to Salesforce")
54
  except Exception as e:
55
  logger.error(f"Failed to connect to Salesforce: {str(e)}")
56
- raise
57
 
58
- # VendorLog model to match Salesforce data
59
  class VendorLog(BaseModel):
60
  vendorLogId: str
61
  vendorId: str
@@ -69,42 +67,38 @@ class VendorLog(BaseModel):
69
  delayDays: int
70
  project: str
71
 
72
- # Store vendor logs for display
73
  vendor_logs = []
74
 
75
  def validate_salesforce_fields():
76
- """Verify required fields exist in Salesforce"""
77
  try:
78
- # Check Vendor_Log__c fields
79
- vendor_log_desc = sf.Vendor_Log__c.describe()
80
  required_fields = [
81
  'Vendor__c', 'Work_Completion_Percentage__c', 'Quality_Percentage__c',
82
  'Incident_Severity__c', 'Work_Completion_Date__c', 'Actual_Completion_Date__c',
83
  'Delay_Days__c', 'Project__c'
84
  ]
85
- available_fields = [field['name'] for field in vendor_log_desc['fields']]
86
  for field in required_fields:
87
- if field not in available_fields:
88
  logger.error(f"Field {field} not found in Vendor_Log__c")
89
  raise ValueError(f"Field {field} not found in Vendor_Log__c")
90
 
91
- # Check Subcontractor_Performance_Score__c fields
92
- score_desc = sf.Subcontractor_Performance_Score__c.describe()
93
  required_score_fields = [
94
  'Vendor__c', 'Month__c', 'Quality_Score__c', 'Timeliness_Score__c',
95
  'Safety_Score__c', 'Communication_Score__c', 'Alert_Flag__c', 'Certification_URL__c'
96
  ]
97
- available_score_fields = [field['name'] for field in score_desc['fields']]
98
  for field in required_score_fields:
99
- if field not in available_score_fields:
100
  logger.error(f"Field {field} not found in Subcontractor_Performance_Score__c")
101
  raise ValueError(f"Field {field} not found in Subcontractor_Performance_Score__c")
102
- logger.info("All required Salesforce fields validated successfully")
103
  except Exception as e:
104
  logger.error(f"Error validating Salesforce fields: {str(e)}")
105
  raise
106
 
107
- # Validate fields on startup
108
  validate_salesforce_fields()
109
 
110
  def fetch_vendor_logs_from_salesforce():
@@ -119,42 +113,38 @@ def fetch_vendor_logs_from_salesforce():
119
  result = sf.query_all(query)
120
  logs = []
121
  for record in result['records']:
122
- log = VendorLog(
123
- vendorLogId=record['Id'] or "Unknown",
124
- vendorId=record['Name'] or "Unknown",
125
- vendorRecordId=record['Vendor__c'] or "Unknown",
126
- workDetails=str(record['Work_Completion_Percentage__c'] or "0.0"),
127
- qualityReport=str(record['Quality_Percentage__c'] or "0.0"),
128
- incidentLog=record['Incident_Severity__c'] or "None",
129
- workCompletionDate=record['Work_Completion_Date__c'] or "N/A",
130
- actualCompletionDate=record['Actual_Completion_Date__c'] or "N/A",
131
- vendorLogName=record['Name'] or "Unknown",
132
- delayDays=int(record['Delay_Days__c'] or 0),
133
- project=record['Project__c'] or "Unknown"
134
- )
135
- logs.append(log)
136
- logger.info(f"Fetched {len(logs)} vendor logs from Salesforce")
 
 
 
137
  return logs
138
  except Exception as e:
139
- logger.error(f"Error fetching vendor logs from Salesforce: {str(e)}")
140
- raise HTTPException(status_code=500, detail=f"Error fetching vendor logs: {str(e)}")
141
 
142
  def calculate_scores_local(log: VendorLog):
143
  try:
144
- work_completion_percentage = float(log.workDetails)
145
- quality_percentage = float(log.qualityReport)
146
 
147
- # Quality Score: Directly use the quality percentage
148
  quality_score = quality_percentage
149
-
150
- # Timeliness Score: Based on delay days
151
  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
152
-
153
- # Safety Score: Based on incident severity
154
  severity_map = {'None': 100.0, 'Low': 80.0, 'Minor': 80.0, 'Medium': 50.0, 'High': 20.0}
155
  safety_score = severity_map.get(log.incidentLog, 100.0)
156
-
157
- # Communication Score: Weighted average of other scores
158
  communication_score = (quality_score * 0.33 + timeliness_score * 0.33 + safety_score * 0.33)
159
 
160
  return {
@@ -165,39 +155,34 @@ def calculate_scores_local(log: VendorLog):
165
  }
166
  except Exception as e:
167
  logger.error(f"Error calculating local scores: {str(e)}")
168
- raise HTTPException(status_code=500, detail=f"Error calculating scores: {str(e)}")
169
 
170
  def calculate_scores_huggingface(log: VendorLog):
171
  try:
172
  payload = {
173
  'vendor_id': log.vendorId,
174
  'delay_days': log.delayDays,
175
- 'work_completion_percentage': float(log.workDetails),
176
- 'quality_percentage': float(log.qualityReport),
177
  'incident_severity': log.incidentLog,
178
- 'communication_frequency': 5 # Placeholder; adjust as needed
179
  }
180
- headers = {
181
- 'Authorization': f'Bearer {HUGGINGFACE_API_KEY}',
182
- 'Content-Type': 'application/json'
183
- }
184
- response = requests.post(HUGGINGFACE_API_URL, json=payload, headers=headers, timeout=30)
185
  response.raise_for_status()
186
  result = response.json()
187
  return {
188
- 'qualityScore': round(result.get('quality_score', 0), 2),
189
- 'timelinessScore': round(result.get('timeliness_score', 0), 2),
190
- 'safetyScore': round(result.get('safety_score', 0), 2),
191
- 'communicationScore': round(result.get('communication_score', 0), 2)
192
  }
193
  except Exception as e:
194
- logger.error(f"Error calculating Hugging Face scores: {str(e)}")
195
- raise HTTPException(status_code=500, detail=f"Error with Hugging Face API: {str(e)}")
196
 
197
  def calculate_scores(log: VendorLog):
198
- if USE_HUGGINGFACE:
199
- return calculate_scores_huggingface(log)
200
- return calculate_scores_local(log)
201
 
202
  def get_feedback(score: float, metric: str) -> str:
203
  try:
@@ -206,26 +191,11 @@ def get_feedback(score: float, metric: str) -> str:
206
  elif score >= 70:
207
  return "Good: Keep up the good work"
208
  elif score >= 50:
209
- if metric == 'Timeliness':
210
- return "Needs Improvement: Maintain schedules to complete tasks on time"
211
- elif metric == 'Safety':
212
- return "Needs Improvement: Implement stricter safety protocols"
213
- elif metric == 'Quality':
214
- return "Needs Improvement: Focus on improving work quality"
215
- else:
216
- return "Needs Improvement: Enhance coordination with project teams"
217
  else:
218
- if metric == 'Timeliness':
219
- return "Poor: Significant delays detected"
220
- elif metric == 'Safety':
221
- return "Poor: Critical safety issues identified"
222
- elif metric == 'Quality':
223
- return "Poor: Quality standards not met"
224
- else:
225
- return "Poor: Communication issues detected"
226
- except Exception as e:
227
- logger.error(f"Error generating feedback: {str(e)}")
228
- raise HTTPException(status_code=500, detail=f"Error generating feedback: {str(e)}")
229
 
230
  def generate_pdf(vendor_id: str, vendor_log_name: str, scores: dict):
231
  try:
@@ -240,34 +210,29 @@ def generate_pdf(vendor_id: str, vendor_log_name: str, scores: dict):
240
  c.drawString(100, 650, f'Safety Score: {scores["safetyScore"]}% ({get_feedback(scores["safetyScore"], "Safety")})')
241
  c.drawString(100, 630, f'Communication Score: {scores["communicationScore"]}% ({get_feedback(scores["communicationScore"], "Communication")})')
242
  c.save()
243
-
244
  with open(filename, 'rb') as f:
245
  pdf_content = f.read()
246
  os.remove(filename)
247
  return pdf_content
248
  except Exception as e:
249
  logger.error(f"Error generating PDF: {str(e)}")
250
- raise HTTPException(status_code=500, detail=f"Error generating PDF: {str(e)}")
251
 
252
  def determine_alert_flag(scores: dict, all_logs: list):
253
  try:
254
  if not all_logs:
255
  return False
256
- avg_score = (scores['qualityScore'] + scores['timelinessScore'] +
257
- scores['safetyScore'] + scores['communicationScore']) / 4
258
  if avg_score < 50:
259
  return True
260
- lowest_avg = min([(log['scores']['qualityScore'] + log['scores']['timelinessScore'] +
261
- log['scores']['safetyScore'] + log['scores']['communicationScore']) / 4
262
- for log in all_logs], default=avg_score)
263
  return avg_score == lowest_avg
264
  except Exception as e:
265
  logger.error(f"Error determining alert flag: {str(e)}")
266
- raise HTTPException(status_code=500, detail=f"Error determining alert flag: {str(e)}")
267
 
268
  def store_scores_in_salesforce(log: VendorLog, scores: dict, pdf_content: bytes, alert_flag: bool):
269
  try:
270
- # Create Subcontractor_Performance_Score__c record
271
  score_record = sf.Subcontractor_Performance_Score__c.create({
272
  'Vendor__c': log.vendorRecordId,
273
  'Month__c': datetime.today().replace(day=1).strftime('%Y-%m-%d'),
@@ -278,9 +243,8 @@ def store_scores_in_salesforce(log: VendorLog, scores: dict, pdf_content: bytes,
278
  'Alert_Flag__c': alert_flag
279
  })
280
  score_record_id = score_record['id']
281
- logger.info(f"Created Subcontractor_Performance_Score__c record with ID: {score_record_id}")
282
 
283
- # Upload PDF as ContentVersion
284
  pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
285
  content_version = sf.ContentVersion.create({
286
  'Title': f'Performance_Report_{log.vendorId}',
@@ -288,30 +252,23 @@ def store_scores_in_salesforce(log: VendorLog, scores: dict, pdf_content: bytes,
288
  'VersionData': pdf_base64,
289
  'FirstPublishLocationId': score_record_id
290
  })
291
- logger.info(f"Uploaded PDF as ContentVersion for Vendor Log ID: {log.vendorLogId}")
292
-
293
- # Get ContentDocumentId and construct URL
294
  content_version_id = content_version['id']
295
  content_version_record = sf.query(f"SELECT ContentDocumentId FROM ContentVersion WHERE Id = '{content_version_id}'")
296
  if content_version_record['totalSize'] == 0:
297
- logger.error(f"No ContentVersion found for ID: {content_version_id}")
298
- raise HTTPException(status_code=500, detail="Failed to retrieve ContentDocumentId")
299
  content_document_id = content_version_record['records'][0]['ContentDocumentId']
300
  pdf_url = f"https://{sf.sf_instance}/sfc/servlet.shepherd/document/download/{content_document_id}"
301
 
302
- # Update Subcontractor_Performance_Score__c with PDF URL
303
- sf.Subcontractor_Performance_Score__c.update(score_record_id, {
304
- 'Certification_URL__c': pdf_url
305
- })
306
- logger.info(f"Updated Subcontractor_Performance_Score__c with Certification_URL__c: {pdf_url}")
307
  except Exception as e:
308
  logger.error(f"Error storing scores in Salesforce: {str(e)}")
309
- raise HTTPException(status_code=500, detail=f"Error storing scores: {str(e)}")
310
 
311
  @app.post('/score')
312
- async def score_vendor(log: VendorLog, authorization: str = Header(None)):
313
  try:
314
- logger.info(f"Received Vendor Log: {log}")
315
  scores = calculate_scores(log)
316
  pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
317
  pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
@@ -347,7 +304,7 @@ async def score_vendor(log: VendorLog, authorization: str = Header(None)):
347
  except HTTPException as e:
348
  raise e
349
  except Exception as e:
350
- logger.error(f"Error in /score endpoint: {str(e)}")
351
  raise HTTPException(status_code=500, detail=f"Error processing vendor log: {str(e)}")
352
 
353
  @app.get('/', response_class=HTMLResponse)
@@ -359,7 +316,6 @@ async def get_dashboard():
359
  if not any(existing_log['vendorLogId'] == log.vendorLogId for existing_log in vendor_logs):
360
  scores = calculate_scores(log)
361
  pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
362
- pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
363
  alert_flag = determine_alert_flag(scores, vendor_logs)
364
  store_scores_in_salesforce(log, scores, pdf_content, alert_flag)
365
  vendor_logs.append({
@@ -383,19 +339,16 @@ async def get_dashboard():
383
  <title>Subcontractor Performance Score App</title>
384
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
385
  <style>
386
- body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f4f9; }
387
- .container { max-width: 1200px; }
388
- table { width: 100%; border-collapse: collapse; margin-top: 20px; }
389
- th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
390
- th { background-color: #007bff; color: white; }
391
  h1, h2 { text-align: center; color: #333; }
 
392
  .generate-btn {
393
  display: block; margin: 20px auto; padding: 10px 20px;
394
  background-color: #4CAF50; color: white; border: none;
395
- border-radius: 5px; cursor: pointer; font-size: 16px;
396
  }
397
  .generate-btn:hover { background-color: #45a049; }
398
- .table-striped tbody tr:nth-of-type(odd) { background-color: #f9f9f9; }
399
  </style>
400
  <script>
401
  async function generateScores() {
@@ -407,15 +360,15 @@ async def get_dashboard():
407
  alert('Error generating scores');
408
  }
409
  } catch (error) {
410
- alert('Error generating scores: ' + error.message);
411
  }
412
  }
413
  </script>
414
  </head>
415
  <body>
416
  <div class="container">
417
- <h1>SUBCONTRACTOR PERFORMANCE SCORE APP</h1>
418
- <h2>VENDOR LOGS SUBMISSION</h2>
419
  <table class="table table-striped">
420
  <thead>
421
  <tr>
@@ -431,35 +384,27 @@ async def get_dashboard():
431
  </tr>
432
  </thead>
433
  <tbody>
434
- """
435
-
436
- if not vendor_logs:
437
- html_content += """
438
- <tr>
439
- <td colspan="9" style="text-align: center;">No vendor logs available</td>
440
- </tr>
441
- """
442
- else:
443
- for log in vendor_logs:
444
- html_content += f"""
445
- <tr>
446
- <td>{log['vendorId']}</td>
447
- <td>{log['vendorLogName']}</td>
448
- <td>{log['project']}</td>
449
- <td>{log['workDetails']}</td>
450
- <td>{log['qualityReport']}</td>
451
- <td>{log['incidentLog']}</td>
452
- <td>{log['workCompletionDate']}</td>
453
- <td>{log['actualCompletionDate']}</td>
454
- <td>{log['delayDays']}</td>
455
- </tr>
456
- """
457
-
458
- html_content += """
459
  </tbody>
460
  </table>
461
  <button class="generate-btn" onclick="generateScores()">Generate Scores</button>
462
- <h2>SUBCONTRACTOR PERFORMANCE SCORES</h2>
463
  <table class="table table-striped">
464
  <thead>
465
  <tr>
@@ -474,44 +419,35 @@ async def get_dashboard():
474
  </tr>
475
  </thead>
476
  <tbody>
477
- """
478
-
479
- if not vendor_logs:
480
- html_content += """
481
- <tr>
482
- <td colspan="8" style="text-align: center;">No scores available</td>
483
- </tr>
484
- """
485
- else:
486
- for log in vendor_logs:
487
- scores = log['scores']
488
- alert_flag = determine_alert_flag(scores, vendor_logs)
489
- html_content += f"""
490
- <tr>
491
- <td>{log['vendorId']}</td>
492
- <td>{log['vendorLogName']}</td>
493
- <td>{log['project']}</td>
494
- <td>{scores['qualityScore']}%</td>
495
- <td>{scores['timelinessScore']}%</td>
496
- <td>{scores['safetyScore']}%</td>
497
- <td>{scores['communicationScore']}%</td>
498
- <td>{'Checked' if alert_flag else 'Unchecked'}</td>
499
- </tr>
500
- """
501
-
502
- html_content += """
503
  </tbody>
504
  </table>
505
  </div>
506
  </body>
507
  </html>
508
- """
 
 
 
509
  return HTMLResponse(content=html_content)
510
- except HTTPException as e:
511
- raise e
512
  except Exception as e:
513
- logger.error(f"Error in / endpoint: {str(e)}")
514
- raise HTTPException(status_code=500, detail=f"Error generating dashboard: {str(e)}")
515
 
516
  @app.post('/generate')
517
  async def generate_scores():
@@ -522,7 +458,6 @@ async def generate_scores():
522
  for log in fetched_logs:
523
  scores = calculate_scores(log)
524
  pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
525
- pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
526
  alert_flag = determine_alert_flag(scores, vendor_logs)
527
  store_scores_in_salesforce(log, scores, pdf_content, alert_flag)
528
  vendor_logs.append({
@@ -539,32 +474,28 @@ async def generate_scores():
539
  'scores': scores,
540
  'extracted': True
541
  })
542
- logger.info(f"Generated scores for {len(vendor_logs)} vendor logs")
543
  return {"status": "success"}
544
- except HTTPException as e:
545
- raise e
546
  except Exception as e:
547
- logger.error(f"Error in /generate endpoint: {str(e)}")
548
- raise HTTPException(status_code=500, detail=f"Error generating scores: {str(e)}")
549
 
550
- @app.get('/health')
551
- async def health_check():
552
  try:
553
- # Test Salesforce connection
554
- result = sf.query("SELECT Id FROM Vendor_Log__c LIMIT 1")
 
555
  return {
556
- "status": "healthy",
557
- "salesforce_connection": "success",
558
- "vendor_log_count": result['totalSize'],
 
559
  "huggingface_enabled": USE_HUGGINGFACE
560
  }
561
  except Exception as e:
562
- logger.error(f"Health check failed: {str(e)}")
563
- return {
564
- "status": "unhealthy",
565
- "salesforce_connection": f"failed: {str(e)}",
566
- "huggingface_enabled": USE_HUGGINGFACE
567
- }
568
 
569
  if __name__ == "__main__":
570
  import uvicorn
 
15
  load_dotenv()
16
 
17
  # Set up logging
18
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
19
  logger = logging.getLogger(__name__)
20
 
21
  app = FastAPI()
22
 
23
+ # Environment variables
24
  SF_USERNAME = os.getenv("SF_USERNAME")
25
  SF_PASSWORD = os.getenv("SF_PASSWORD")
26
  SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN")
 
35
  logger.error(f"Environment variable {var} is not set")
36
  raise ValueError(f"Environment variable {var} is not set")
37
 
38
+ # Hugging Face configuration
39
+ USE_HUGGINGFACE = bool(HUGGINGFACE_API_KEY and HUGGINGFACE_API_URL)
40
+ logger.info(f"Hugging Face integration {'enabled' if USE_HUGGINGFACE else 'disabled'}")
 
 
 
41
 
42
+ # Salesforce connection
43
+ sf = None
44
  try:
45
  sf = Salesforce(
46
  username=SF_USERNAME,
 
51
  logger.info("Successfully connected to Salesforce")
52
  except Exception as e:
53
  logger.error(f"Failed to connect to Salesforce: {str(e)}")
54
+ raise RuntimeError(f"Cannot connect to Salesforce: {str(e)}")
55
 
56
+ # VendorLog model
57
  class VendorLog(BaseModel):
58
  vendorLogId: str
59
  vendorId: str
 
67
  delayDays: int
68
  project: str
69
 
70
+ # Store vendor logs
71
  vendor_logs = []
72
 
73
  def validate_salesforce_fields():
74
+ """Validate required Salesforce fields"""
75
  try:
76
+ vendor_log_fields = [f['name'] for f in sf.Vendor_Log__c.describe()['fields']]
 
77
  required_fields = [
78
  'Vendor__c', 'Work_Completion_Percentage__c', 'Quality_Percentage__c',
79
  'Incident_Severity__c', 'Work_Completion_Date__c', 'Actual_Completion_Date__c',
80
  'Delay_Days__c', 'Project__c'
81
  ]
 
82
  for field in required_fields:
83
+ if field not in vendor_log_fields:
84
  logger.error(f"Field {field} not found in Vendor_Log__c")
85
  raise ValueError(f"Field {field} not found in Vendor_Log__c")
86
 
87
+ score_fields = [f['name'] for f in sf.Subcontractor_Performance_Score__c.describe()['fields']]
 
88
  required_score_fields = [
89
  'Vendor__c', 'Month__c', 'Quality_Score__c', 'Timeliness_Score__c',
90
  'Safety_Score__c', 'Communication_Score__c', 'Alert_Flag__c', 'Certification_URL__c'
91
  ]
 
92
  for field in required_score_fields:
93
+ if field not in score_fields:
94
  logger.error(f"Field {field} not found in Subcontractor_Performance_Score__c")
95
  raise ValueError(f"Field {field} not found in Subcontractor_Performance_Score__c")
96
+ logger.info("Salesforce fields validated successfully")
97
  except Exception as e:
98
  logger.error(f"Error validating Salesforce fields: {str(e)}")
99
  raise
100
 
101
+ # Validate schema on startup
102
  validate_salesforce_fields()
103
 
104
  def fetch_vendor_logs_from_salesforce():
 
113
  result = sf.query_all(query)
114
  logs = []
115
  for record in result['records']:
116
+ try:
117
+ log = VendorLog(
118
+ vendorLogId=record.get('Id', 'Unknown'),
119
+ vendorId=record.get('Name', 'Unknown'),
120
+ vendorRecordId=record.get('Vendor__c', 'Unknown'),
121
+ workDetails=str(record.get('Work_Completion_Percentage__c', 0.0)),
122
+ qualityReport=str(record.get('Quality_Percentage__c', 0.0)),
123
+ incidentLog=record.get('Incident_Severity__c', 'None'),
124
+ workCompletionDate=record.get('Work_Completion_Date__c', 'N/A'),
125
+ actualCompletionDate=record.get('Actual_Completion_Date__c', 'N/A'),
126
+ vendorLogName=record.get('Name', 'Unknown'),
127
+ delayDays=int(record.get('Delay_Days__c', 0)),
128
+ project=record.get('Project__c', 'Unknown')
129
+ )
130
+ logs.append(log)
131
+ except Exception as e:
132
+ logger.warning(f"Skipping invalid Vendor_Log__c record {record.get('Id')}: {str(e)}")
133
+ logger.info(f"Fetched {len(logs)} vendor logs")
134
  return logs
135
  except Exception as e:
136
+ logger.error(f"Error fetching vendor logs: {str(e)}")
137
+ return []
138
 
139
  def calculate_scores_local(log: VendorLog):
140
  try:
141
+ work_completion_percentage = float(log.workDetails or 0.0)
142
+ quality_percentage = float(log.qualityReport or 0.0)
143
 
 
144
  quality_score = quality_percentage
 
 
145
  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
 
 
146
  severity_map = {'None': 100.0, 'Low': 80.0, 'Minor': 80.0, 'Medium': 50.0, 'High': 20.0}
147
  safety_score = severity_map.get(log.incidentLog, 100.0)
 
 
148
  communication_score = (quality_score * 0.33 + timeliness_score * 0.33 + safety_score * 0.33)
149
 
150
  return {
 
155
  }
156
  except Exception as e:
157
  logger.error(f"Error calculating local scores: {str(e)}")
158
+ return {'qualityScore': 0.0, 'timelinessScore': 0.0, 'safetyScore': 0.0, 'communicationScore': 0.0}
159
 
160
  def calculate_scores_huggingface(log: VendorLog):
161
  try:
162
  payload = {
163
  'vendor_id': log.vendorId,
164
  'delay_days': log.delayDays,
165
+ 'work_completion_percentage': float(log.workDetails or 0.0),
166
+ 'quality_percentage': float(log.qualityReport or 0.0),
167
  'incident_severity': log.incidentLog,
168
+ 'communication_frequency': 5
169
  }
170
+ headers = {'Authorization': f'Bearer {HUGGINGFACE_API_KEY}', 'Content-Type': 'application/json'}
171
+ response = requests.post(HUGGINGFACE_API_URL, json=payload, headers=headers, timeout=10)
 
 
 
172
  response.raise_for_status()
173
  result = response.json()
174
  return {
175
+ 'qualityScore': round(result.get('quality_score', 0.0), 2),
176
+ 'timelinessScore': round(result.get('timeliness_score', 0.0), 2),
177
+ 'safetyScore': round(result.get('safety_score', 0.0), 2),
178
+ 'communicationScore': round(result.get('communication_score', 0.0), 2)
179
  }
180
  except Exception as e:
181
+ logger.error(f"Hugging Face API error: {str(e)}")
182
+ return calculate_scores_local(log) # Fallback to local scoring
183
 
184
  def calculate_scores(log: VendorLog):
185
+ return calculate_scores_huggingface(log) if USE_HUGGINGFACE else calculate_scores_local(log)
 
 
186
 
187
  def get_feedback(score: float, metric: str) -> str:
188
  try:
 
191
  elif score >= 70:
192
  return "Good: Keep up the good work"
193
  elif score >= 50:
194
+ return f"Needs Improvement: {'Maintain schedules' if metric == 'Timeliness' else 'Improve quality' if metric == 'Quality' else 'Enhance safety' if metric == 'Safety' else 'Better communication'}"
 
 
 
 
 
 
 
195
  else:
196
+ return f"Poor: {'Significant delays' if metric == 'Timeliness' else 'Quality issues' if metric == 'Quality' else 'Safety issues' if metric == 'Safety' else 'Communication issues'}"
197
+ except Exception:
198
+ return "Feedback unavailable"
 
 
 
 
 
 
 
 
199
 
200
  def generate_pdf(vendor_id: str, vendor_log_name: str, scores: dict):
201
  try:
 
210
  c.drawString(100, 650, f'Safety Score: {scores["safetyScore"]}% ({get_feedback(scores["safetyScore"], "Safety")})')
211
  c.drawString(100, 630, f'Communication Score: {scores["communicationScore"]}% ({get_feedback(scores["communicationScore"], "Communication")})')
212
  c.save()
 
213
  with open(filename, 'rb') as f:
214
  pdf_content = f.read()
215
  os.remove(filename)
216
  return pdf_content
217
  except Exception as e:
218
  logger.error(f"Error generating PDF: {str(e)}")
219
+ raise HTTPException(status_code=500, detail="Failed to generate PDF")
220
 
221
  def determine_alert_flag(scores: dict, all_logs: list):
222
  try:
223
  if not all_logs:
224
  return False
225
+ avg_score = sum(scores.values()) / 4
 
226
  if avg_score < 50:
227
  return True
228
+ lowest_avg = min([sum(log['scores'].values()) / 4 for log in all_logs], default=avg_score)
 
 
229
  return avg_score == lowest_avg
230
  except Exception as e:
231
  logger.error(f"Error determining alert flag: {str(e)}")
232
+ return False
233
 
234
  def store_scores_in_salesforce(log: VendorLog, scores: dict, pdf_content: bytes, alert_flag: bool):
235
  try:
 
236
  score_record = sf.Subcontractor_Performance_Score__c.create({
237
  'Vendor__c': log.vendorRecordId,
238
  'Month__c': datetime.today().replace(day=1).strftime('%Y-%m-%d'),
 
243
  'Alert_Flag__c': alert_flag
244
  })
245
  score_record_id = score_record['id']
246
+ logger.info(f"Created score record: {score_record_id}")
247
 
 
248
  pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
249
  content_version = sf.ContentVersion.create({
250
  'Title': f'Performance_Report_{log.vendorId}',
 
252
  'VersionData': pdf_base64,
253
  'FirstPublishLocationId': score_record_id
254
  })
 
 
 
255
  content_version_id = content_version['id']
256
  content_version_record = sf.query(f"SELECT ContentDocumentId FROM ContentVersion WHERE Id = '{content_version_id}'")
257
  if content_version_record['totalSize'] == 0:
258
+ logger.error(f"No ContentVersion for ID: {content_version_id}")
259
+ raise ValueError("Failed to retrieve ContentDocumentId")
260
  content_document_id = content_version_record['records'][0]['ContentDocumentId']
261
  pdf_url = f"https://{sf.sf_instance}/sfc/servlet.shepherd/document/download/{content_document_id}"
262
 
263
+ sf.Subcontractor_Performance_Score__c.update(score_record_id, {'Certification_URL__c': pdf_url})
264
+ logger.info(f"Updated score record with PDF URL: {pdf_url}")
 
 
 
265
  except Exception as e:
266
  logger.error(f"Error storing scores in Salesforce: {str(e)}")
267
+ raise HTTPException(status_code=500, detail="Failed to store scores")
268
 
269
  @app.post('/score')
270
+ async def score_vendor(log: VendorLog):
271
  try:
 
272
  scores = calculate_scores(log)
273
  pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
274
  pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
 
304
  except HTTPException as e:
305
  raise e
306
  except Exception as e:
307
+ logger.error(f"Error in /score: {str(e)}")
308
  raise HTTPException(status_code=500, detail=f"Error processing vendor log: {str(e)}")
309
 
310
  @app.get('/', response_class=HTMLResponse)
 
316
  if not any(existing_log['vendorLogId'] == log.vendorLogId for existing_log in vendor_logs):
317
  scores = calculate_scores(log)
318
  pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
 
319
  alert_flag = determine_alert_flag(scores, vendor_logs)
320
  store_scores_in_salesforce(log, scores, pdf_content, alert_flag)
321
  vendor_logs.append({
 
339
  <title>Subcontractor Performance Score App</title>
340
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
341
  <style>
342
+ body { font-family: Arial, sans-serif; background-color: #f4f4f9; }
343
+ .container { max-width: 1200px; margin: 20px auto; }
 
 
 
344
  h1, h2 { text-align: center; color: #333; }
345
+ .table { margin-top: 20px; }
346
  .generate-btn {
347
  display: block; margin: 20px auto; padding: 10px 20px;
348
  background-color: #4CAF50; color: white; border: none;
349
+ border-radius: 5px; cursor: pointer;
350
  }
351
  .generate-btn:hover { background-color: #45a049; }
 
352
  </style>
353
  <script>
354
  async function generateScores() {
 
360
  alert('Error generating scores');
361
  }
362
  } catch (error) {
363
+ alert('Error: ' + error.message);
364
  }
365
  }
366
  </script>
367
  </head>
368
  <body>
369
  <div class="container">
370
+ <h1>Subcontractor Performance Score App</h1>
371
+ <h2>Vendor Logs</h2>
372
  <table class="table table-striped">
373
  <thead>
374
  <tr>
 
384
  </tr>
385
  </thead>
386
  <tbody>
387
+ {% if not vendor_logs %}
388
+ <tr><td colspan="9" class="text-center">No vendor logs available</td></tr>
389
+ {% else %}
390
+ {% for log in vendor_logs %}
391
+ <tr>
392
+ <td>{{ log['vendorId'] }}</td>
393
+ <td>{{ log['vendorLogName'] }}</td>
394
+ <td>{{ log['project'] }}</td>
395
+ <td>{{ log['workDetails'] }}</td>
396
+ <td>{{ log['qualityReport'] }}</td>
397
+ <td>{{ log['incidentLog'] }}</td>
398
+ <td>{{ log['workCompletionDate'] }}</td>
399
+ <td>{{ log['actualCompletionDate'] }}</td>
400
+ <td>{{ log['delayDays'] }}</td>
401
+ </tr>
402
+ {% endfor %}
403
+ {% endif %}
 
 
 
 
 
 
 
 
404
  </tbody>
405
  </table>
406
  <button class="generate-btn" onclick="generateScores()">Generate Scores</button>
407
+ <h2>Performance Scores</h2>
408
  <table class="table table-striped">
409
  <thead>
410
  <tr>
 
419
  </tr>
420
  </thead>
421
  <tbody>
422
+ {% if not vendor_logs %}
423
+ <tr><td colspan="8" class="text-center">No scores available</td></tr>
424
+ {% else %}
425
+ {% for log in vendor_logs %}
426
+ <tr>
427
+ <td>{{ log['vendorId'] }}</td>
428
+ <td>{{ log['vendorLogName'] }}</td>
429
+ <td>{{ log['project'] }}</td>
430
+ <td>{{ log['scores']['qualityScore'] }}%</td>
431
+ <td>{{ log['scores']['timelinessScore'] }}%</td>
432
+ <td>{{ log['scores']['safetyScore'] }}%</td>
433
+ <td>{{ log['scores']['communicationScore'] }}%</td>
434
+ <td>{{ 'Checked' if determine_alert_flag(log['scores'], vendor_logs) else 'Unchecked' }}</td>
435
+ </tr>
436
+ {% endfor %}
437
+ {% endif %}
 
 
 
 
 
 
 
 
 
 
438
  </tbody>
439
  </table>
440
  </div>
441
  </body>
442
  </html>
443
+ """.replace('{% if not vendor_logs %}', '' if not vendor_logs else '').replace(
444
+ '{% else %}', '').replace('{% for log in vendor_logs %}', '' if not vendor_logs else '\n'.join(
445
+ [html_content.split('{% for log in vendor_logs %}')[1].split('{% endfor %}')[0].format(**log) for log in vendor_logs]
446
+ )).replace('{% endif %}', '')
447
  return HTMLResponse(content=html_content)
 
 
448
  except Exception as e:
449
+ logger.error(f"Error in /: {str(e)}")
450
+ return HTMLResponse(content="<h1>Error</h1><p>Failed to load dashboard. Check logs for details.</p>", status_code=500)
451
 
452
  @app.post('/generate')
453
  async def generate_scores():
 
458
  for log in fetched_logs:
459
  scores = calculate_scores(log)
460
  pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
 
461
  alert_flag = determine_alert_flag(scores, vendor_logs)
462
  store_scores_in_salesforce(log, scores, pdf_content, alert_flag)
463
  vendor_logs.append({
 
474
  'scores': scores,
475
  'extracted': True
476
  })
477
+ logger.info(f"Generated scores for {len(vendor_logs)} logs")
478
  return {"status": "success"}
 
 
479
  except Exception as e:
480
+ logger.error(f"Error in /generate: {str(e)}")
481
+ raise HTTPException(status_code=500, detail="Failed to generate scores")
482
 
483
+ @app.get('/debug')
484
+ async def debug_info():
485
  try:
486
+ log_count = sf.query("SELECT COUNT() FROM Vendor_Log__c")['totalSize']
487
+ fields = [f['name'] for f in sf.Vendor_Log__c.describe()['fields']]
488
+ score_fields = [f['name'] for f in sf.Subcontractor_Performance_Score__c.describe()['fields']]
489
  return {
490
+ "salesforce_connected": True,
491
+ "vendor_log_count": log_count,
492
+ "vendor_log_fields": fields,
493
+ "score_fields": score_fields,
494
  "huggingface_enabled": USE_HUGGINGFACE
495
  }
496
  except Exception as e:
497
+ logger.error(f"Debug error: {str(e)}")
498
+ return {"salesforce_connected": False, "error": str(e)}
 
 
 
 
499
 
500
  if __name__ == "__main__":
501
  import uvicorn