Swathi6 commited on
Commit
cb7ec0c
·
verified ·
1 Parent(s): e62d790

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +214 -191
app.py CHANGED
@@ -1,4 +1,4 @@
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
@@ -8,27 +8,37 @@ 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,
@@ -39,9 +49,9 @@ try:
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
@@ -55,61 +65,86 @@ class VendorLog(BaseModel):
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),
@@ -117,8 +152,12 @@ def calculate_scores(log: VendorLog):
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:
@@ -127,30 +166,15 @@ def get_feedback(score: float, metric: str) -> str:
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')
@@ -160,49 +184,42 @@ 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:
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}',
@@ -210,33 +227,23 @@ 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"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')
@@ -269,8 +276,10 @@ 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)}")
275
 
276
  @app.get('/', response_class=HTMLResponse)
@@ -282,7 +291,6 @@ async def get_dashboard():
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({
@@ -304,135 +312,132 @@ async def get_dashboard():
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({
@@ -449,10 +454,28 @@ 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)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
 
457
  if __name__ == "__main__":
458
  import uvicorn
 
1
+ from fastapi import FastAPI, HTTPException
2
  from pydantic import BaseModel
3
  from reportlab.lib.pagesizes import letter
4
  from reportlab.pdfgen import canvas
 
8
  from datetime import datetime
9
  from fastapi.responses import HTMLResponse
10
  from simple_salesforce import Salesforce
11
+ from dotenv import load_dotenv
12
 
13
+ # Load environment variables
14
+ load_dotenv()
15
+
16
+ # Set up logging
17
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
18
  logger = logging.getLogger(__name__)
19
 
20
  app = FastAPI()
21
 
22
+ # Environment variables from .env
23
+ SF_USERNAME = os.getenv("SF_USERNAME")
24
+ SF_PASSWORD = os.getenv("SF_PASSWORD")
25
+ SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN")
26
  SF_DOMAIN = os.getenv("SF_DOMAIN", "login")
27
+ HUGGINGFACE_API_KEY = os.getenv("HUGGINGFACE_API_KEY")
28
+
29
+ # Validate environment variables
30
+ required_env_vars = ["SF_USERNAME", "SF_PASSWORD", "SF_SECURITY_TOKEN"]
31
+ for var in required_env_vars:
32
+ if not os.getenv(var):
33
+ logger.error(f"Environment variable {var} is not set")
34
+ raise ValueError(f"Environment variable {var} is not set")
35
 
36
+ # Check Hugging Face configuration
37
+ USE_HUGGINGFACE = bool(HUGGINGFACE_API_KEY and HUGGINGFACE_API_KEY != "your_huggingface_api_key_here")
38
+ logger.info(f"Hugging Face integration {'enabled' if USE_HUGGINGFACE else 'disabled'}")
 
 
39
 
40
+ # Salesforce connection
41
+ sf = None
42
  try:
43
  sf = Salesforce(
44
  username=SF_USERNAME,
 
49
  logger.info("Successfully connected to Salesforce")
50
  except Exception as e:
51
  logger.error(f"Failed to connect to Salesforce: {str(e)}")
52
+ raise RuntimeError(f"Cannot connect to Salesforce: {str(e)}")
53
 
54
+ # VendorLog model
55
  class VendorLog(BaseModel):
56
  vendorLogId: str
57
  vendorId: str
 
65
  delayDays: int
66
  project: str
67
 
68
+ # Store vendor logs
69
  vendor_logs = []
70
 
71
+ def validate_salesforce_fields():
72
+ """Validate required Salesforce fields"""
73
+ try:
74
+ vendor_log_fields = [f['name'] for f in sf.Vendor_Log__c.describe()['fields']]
75
+ required_fields = [
76
+ 'Vendor__c', 'Work_Completion_Percentage__c', 'Quality_Percentage__c',
77
+ 'Incident_Severity__c', 'Work_Completion_Date__c', 'Actual_Completion_Date__c',
78
+ 'Delay_Days__c', 'Project__c'
79
+ ]
80
+ for field in required_fields:
81
+ if field not in vendor_log_fields:
82
+ logger.error(f"Field {field} not found in Vendor_Log__c")
83
+ raise ValueError(f"Field {field} not found in Vendor_Log__c")
84
+
85
+ score_fields = [f['name'] for f in sf.Subcontractor_Performance_Score__c.describe()['fields']]
86
+ required_score_fields = [
87
+ 'Vendor__c', 'Month__c', 'Quality_Score__c', 'Timeliness_Score__c',
88
+ 'Safety_Score__c', 'Communication_Score__c', 'Alert_Flag__c', 'Certification_URL__c'
89
+ ]
90
+ for field in required_score_fields:
91
+ if field not in score_fields:
92
+ logger.error(f"Field {field} not found in Subcontractor_Performance_Score__c")
93
+ raise ValueError(f"Field {field} not found in Subcontractor_Performance_Score__c")
94
+ logger.info("Salesforce fields validated successfully")
95
+ except Exception as e:
96
+ logger.error(f"Error validating Salesforce fields: {str(e)}")
97
+ raise
98
+
99
+ # Validate schema on startup
100
+ validate_salesforce_fields()
101
+
102
  def fetch_vendor_logs_from_salesforce():
103
  try:
104
  query = """
105
+ SELECT Id, Name, Vendor__c, Work_Completion_Percentage__c, Quality_Percentage__c,
106
+ Incident_Severity__c, Work_Completion_Date__c, Actual_Completion_Date__c,
107
+ Delay_Days__c, Project__c
108
  FROM Vendor_Log__c
109
+ WHERE Vendor__c != null
110
  """
111
  result = sf.query_all(query)
112
  logs = []
113
  for record in result['records']:
114
+ try:
115
+ log = VendorLog(
116
+ vendorLogId=record.get('Id', 'Unknown'),
117
+ vendorId=record.get('Name', 'Unknown'),
118
+ vendorRecordId=record.get('Vendor__c', 'Unknown'),
119
+ workDetails=str(record.get('Work_Completion_Percentage__c', 0.0)),
120
+ qualityReport=str(record.get('Quality_Percentage__c', 0.0)),
121
+ incidentLog=record.get('Incident_Severity__c', 'None'),
122
+ workCompletionDate=record.get('Work_Completion_Date__c', 'N/A'),
123
+ actualCompletionDate=record.get('Actual_Completion_Date__c', 'N/A'),
124
+ vendorLogName=record.get('Name', 'Unknown'),
125
+ delayDays=int(record.get('Delay_Days__c', 0)),
126
+ project=record.get('Project__c', 'Unknown')
127
+ )
128
+ logs.append(log)
129
+ except Exception as e:
130
+ logger.warning(f"Skipping invalid Vendor_Log__c record {record.get('Id')}: {str(e)}")
131
+ logger.info(f"Fetched {len(logs)} vendor logs")
132
  return logs
133
  except Exception as e:
134
+ logger.error(f"Error fetching vendor logs: {str(e)}")
135
+ return []
136
 
137
+ def calculate_scores_local(log: VendorLog):
138
  try:
139
+ work_completion_percentage = float(log.workDetails or 0.0)
140
+ quality_percentage = float(log.qualityReport or 0.0)
141
 
 
142
  quality_score = quality_percentage
 
 
143
  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
 
 
144
  severity_map = {'None': 100.0, 'Low': 80.0, 'Minor': 80.0, 'Medium': 50.0, 'High': 20.0}
145
  safety_score = severity_map.get(log.incidentLog, 100.0)
 
 
146
  communication_score = (quality_score * 0.33 + timeliness_score * 0.33 + safety_score * 0.33)
147
 
 
148
  return {
149
  'qualityScore': round(quality_score, 2),
150
  'timelinessScore': round(timeliness_score, 2),
 
152
  'communicationScore': round(communication_score, 2)
153
  }
154
  except Exception as e:
155
+ logger.error(f"Error calculating local scores: {str(e)}")
156
+ return {'qualityScore': 0.0, 'timelinessScore': 0.0, 'safetyScore': 0.0, 'communicationScore': 0.0}
157
+
158
+ def calculate_scores(log: VendorLog):
159
+ # Hugging Face disabled since API key is placeholder
160
+ return calculate_scores_local(log)
161
 
162
  def get_feedback(score: float, metric: str) -> str:
163
  try:
 
166
  elif score >= 70:
167
  return "Good: Keep up the good work"
168
  elif score >= 50:
169
+ return f"Needs Improvement: {'Maintain schedules' if metric == 'Timeliness' else 'Improve quality' if metric == 'Quality' else 'Enhance safety' if metric == 'Safety' else 'Better communication'}"
 
 
 
 
 
 
 
170
  else:
171
+ return f"Poor: {'Significant delays' if metric == 'Timeliness' else 'Quality issues' if metric == 'Quality' else 'Safety issues' if metric == 'Safety' else 'Communication issues'}"
172
+ except Exception:
173
+ return "Feedback unavailable"
 
 
 
 
 
 
 
 
174
 
175
  def generate_pdf(vendor_id: str, vendor_log_name: str, scores: dict):
176
  try:
177
+ filename = f'report_{vendor_id}_{datetime.now().strftime("%Y%m%d%H%M%S")}.pdf'
178
  c = canvas.Canvas(filename, pagesize=letter)
179
  c.setFont('Helvetica', 12)
180
  c.drawString(100, 750, 'Subcontractor Performance Report')
 
184
  c.drawString(100, 670, f'Timeliness Score: {scores["timelinessScore"]}% ({get_feedback(scores["timelinessScore"], "Timeliness")})')
185
  c.drawString(100, 650, f'Safety Score: {scores["safetyScore"]}% ({get_feedback(scores["safetyScore"], "Safety")})')
186
  c.drawString(100, 630, f'Communication Score: {scores["communicationScore"]}% ({get_feedback(scores["communicationScore"], "Communication")})')
 
187
  c.save()
 
188
  with open(filename, 'rb') as f:
189
  pdf_content = f.read()
190
  os.remove(filename)
191
  return pdf_content
192
  except Exception as e:
193
  logger.error(f"Error generating PDF: {str(e)}")
194
+ raise HTTPException(status_code=500, detail="Failed to generate PDF")
195
 
196
  def determine_alert_flag(scores: dict, all_logs: list):
197
  try:
198
  if not all_logs:
199
  return False
200
+ avg_score = sum(scores.values()) / 4
 
 
201
  if avg_score < 50:
202
  return True
203
+ lowest_avg = min([sum(log['scores'].values()) / 4 for log in all_logs], default=avg_score)
204
  return avg_score == lowest_avg
205
  except Exception as e:
206
  logger.error(f"Error determining alert flag: {str(e)}")
207
+ return False
208
 
209
  def store_scores_in_salesforce(log: VendorLog, scores: dict, pdf_content: bytes, alert_flag: bool):
210
  try:
 
211
  score_record = sf.Subcontractor_Performance_Score__c.create({
 
212
  'Vendor__c': log.vendorRecordId,
213
+ 'Month__c': datetime.today().replace(day=1).strftime('%Y-%m-%d'),
214
  'Quality_Score__c': scores['qualityScore'],
215
  'Timeliness_Score__c': scores['timelinessScore'],
216
  'Safety_Score__c': scores['safetyScore'],
217
  'Communication_Score__c': scores['communicationScore'],
218
  'Alert_Flag__c': alert_flag
 
219
  })
220
  score_record_id = score_record['id']
221
+ logger.info(f"Created score record: {score_record_id}")
222
 
 
223
  pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
224
  content_version = sf.ContentVersion.create({
225
  'Title': f'Performance_Report_{log.vendorId}',
 
227
  'VersionData': pdf_base64,
228
  'FirstPublishLocationId': score_record_id
229
  })
 
 
 
230
  content_version_id = content_version['id']
231
  content_version_record = sf.query(f"SELECT ContentDocumentId FROM ContentVersion WHERE Id = '{content_version_id}'")
232
+ if content_version_record['totalSize'] == 0:
233
+ logger.error(f"No ContentVersion for ID: {content_version_id}")
234
+ raise ValueError("Failed to retrieve ContentDocumentId")
235
  content_document_id = content_version_record['records'][0]['ContentDocumentId']
 
 
236
  pdf_url = f"https://{sf.sf_instance}/sfc/servlet.shepherd/document/download/{content_document_id}"
237
 
238
+ sf.Subcontractor_Performance_Score__c.update(score_record_id, {'Certification_URL__c': pdf_url})
239
+ logger.info(f"Updated score record with PDF URL: {pdf_url}")
 
 
 
 
240
  except Exception as e:
241
  logger.error(f"Error storing scores in Salesforce: {str(e)}")
242
+ raise HTTPException(status_code=500, detail="Failed to store scores")
243
 
244
  @app.post('/score')
245
+ async def score_vendor(log: VendorLog):
246
  try:
 
 
 
 
247
  scores = calculate_scores(log)
248
  pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
249
  pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
 
276
  'pdfContent': pdf_base64,
277
  'alert': alert_flag
278
  }
279
+ except HTTPException as e:
280
+ raise e
281
  except Exception as e:
282
+ logger.error(f"Error in /score: {str(e)}")
283
  raise HTTPException(status_code=500, detail=f"Error processing vendor log: {str(e)}")
284
 
285
  @app.get('/', response_class=HTMLResponse)
 
291
  if not any(existing_log['vendorLogId'] == log.vendorLogId for existing_log in vendor_logs):
292
  scores = calculate_scores(log)
293
  pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
 
294
  alert_flag = determine_alert_flag(scores, vendor_logs)
295
  store_scores_in_salesforce(log, scores, pdf_content, alert_flag)
296
  vendor_logs.append({
 
312
  <html>
313
  <head>
314
  <title>Subcontractor Performance Score App</title>
315
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
316
  <style>
317
+ body { font-family: Arial, sans-serif; background-color: #f4f4f9; }
318
+ .container { max-width: 1200px; margin: 20px auto; }
319
+ h1, h2 { text-align: center; color: #333; }
320
+ .table { margin-top: 20px; }
 
321
  .generate-btn {
322
+ display: block; margin: 20px auto; padding: 10px 20px;
323
+ background-color: #4CAF50; color: white; border: none;
324
+ border-radius: 5px; cursor: pointer;
 
 
 
 
 
 
325
  }
326
  .generate-btn:hover { background-color: #45a049; }
327
  </style>
328
  <script>
329
  async function generateScores() {
330
+ try {
331
+ const response = await fetch('/generate', { method: 'POST' });
332
+ if (response.ok) {
333
+ window.location.reload();
334
+ } else {
335
+ alert('Error generating scores');
336
+ }
337
+ } catch (error) {
338
+ alert('Error: ' + error.message);
339
  }
340
  }
341
  </script>
342
  </head>
343
  <body>
344
+ <div class="container">
345
+ <h1>Subcontractor Performance Score App</h1>
346
+ <h2>Vendor Logs</h2>
347
+ <table class="table table-striped">
348
+ <thead>
349
+ <tr>
350
+ <th>Vendor ID</th>
351
+ <th>Vendor Log Name</th>
352
+ <th>Project</th>
353
+ <th>Work Completion %</th>
354
+ <th>Quality %</th>
355
+ <th>Incident Severity</th>
356
+ <th>Work Completion Date</th>
357
+ <th>Actual Completion Date</th>
358
+ <th>Delay Days</th>
359
+ </tr>
360
+ </thead>
361
+ <tbody>
362
  """
 
363
  if not vendor_logs:
364
+ html_content += '<tr><td colspan="9" class="text-center">No vendor logs available</td></tr>'
 
 
 
 
365
  else:
366
  for log in vendor_logs:
367
  html_content += f"""
368
+ <tr>
369
+ <td>{log['vendorId']}</td>
370
+ <td>{log['vendorLogName']}</td>
371
+ <td>{log['project']}</td>
372
+ <td>{log['workDetails']}</td>
373
+ <td>{log['qualityReport']}</td>
374
+ <td>{log['incidentLog']}</td>
375
+ <td>{log['workCompletionDate']}</td>
376
+ <td>{log['actualCompletionDate']}</td>
377
+ <td>{log['delayDays']}</td>
378
+ </tr>
379
  """
380
 
381
  html_content += """
382
+ </tbody>
383
+ </table>
384
+ <button class="generate-btn" onclick="generateScores()">Generate Scores</button>
385
+ <h2>Performance Scores</h2>
386
+ <table class="table table-striped">
387
+ <thead>
388
+ <tr>
389
+ <th>Vendor ID</th>
390
+ <th>Vendor Log Name</th>
391
+ <th>Project</th>
392
+ <th>Quality Score</th>
393
+ <th>Timeliness Score</th>
394
+ <th>Safety Score</th>
395
+ <th>Communication Score</th>
396
+ <th>Alert Flag</th>
397
+ </tr>
398
+ </thead>
399
+ <tbody>
400
  """
 
401
  if not vendor_logs:
402
+ html_content += '<tr><td colspan="8" class="text-center">No scores available</td></tr>'
 
 
 
 
403
  else:
404
  for log in vendor_logs:
405
  scores = log['scores']
406
  alert_flag = determine_alert_flag(scores, vendor_logs)
407
  html_content += f"""
408
+ <tr>
409
+ <td>{log['vendorId']}</td>
410
+ <td>{log['vendorLogName']}</td>
411
+ <td>{log['project']}</td>
412
+ <td>{scores['qualityScore']}%</td>
413
+ <td>{scores['timelinessScore']}%</td>
414
+ <td>{scores['safetyScore']}%</td>
415
+ <td>{scores['communicationScore']}%</td>
416
+ <td>{'Checked' if alert_flag else 'Unchecked'}</td>
417
+ </tr>
418
  """
419
 
420
  html_content += """
421
+ </tbody>
422
+ </table>
423
+ </div>
424
  </body>
425
  </html>
426
  """
427
  return HTMLResponse(content=html_content)
428
  except Exception as e:
429
+ logger.error(f"Error in /: {str(e)}")
430
+ return HTMLResponse(content="<h1>Error</h1><p>Failed to load dashboard. Check logs for details.</p>", status_code=500)
431
 
432
  @app.post('/generate')
433
  async def generate_scores():
434
  try:
435
  global vendor_logs
 
436
  vendor_logs = []
437
+ fetched_logs = fetch_vendor_logs_from_salesforce()
438
  for log in fetched_logs:
439
  scores = calculate_scores(log)
440
  pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
 
441
  alert_flag = determine_alert_flag(scores, vendor_logs)
442
  store_scores_in_salesforce(log, scores, pdf_content, alert_flag)
443
  vendor_logs.append({
 
454
  'scores': scores,
455
  'extracted': True
456
  })
457
+ logger.info(f"Generated scores for {len(vendor_logs)} logs")
458
  return {"status": "success"}
459
  except Exception as e:
460
+ logger.error(f"Error in /generate: {str(e)}")
461
+ raise HTTPException(status_code=500, detail="Failed to generate scores")
462
+
463
+ @app.get('/debug')
464
+ async def debug_info():
465
+ try:
466
+ log_count = sf.query("SELECT COUNT() FROM Vendor_Log__c")['totalSize']
467
+ fields = [f['name'] for f in sf.Vendor_Log__c.describe()['fields']]
468
+ score_fields = [f['name'] for f in sf.Subcontractor_Performance_Score__c.describe()['fields']]
469
+ return {
470
+ "salesforce_connected": True,
471
+ "vendor_log_count": log_count,
472
+ "vendor_log_fields": fields,
473
+ "score_fields": score_fields,
474
+ "huggingface_enabled": USE_HUGGINGFACE
475
+ }
476
+ except Exception as e:
477
+ logger.error(f"Debug error: {str(e)}")
478
+ return {"salesforce_connected": False, "error": str(e)}
479
 
480
  if __name__ == "__main__":
481
  import uvicorn