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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +211 -254
app.py CHANGED
@@ -8,39 +8,27 @@ import logging
8
  from datetime import datetime
9
  from fastapi.responses import HTMLResponse
10
  from simple_salesforce import Salesforce
11
- import requests
12
- from dotenv import load_dotenv
13
 
14
- # Load environment variables
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")
27
  SF_DOMAIN = os.getenv("SF_DOMAIN", "login")
28
- HUGGINGFACE_API_KEY = os.getenv("HUGGINGFACE_API_KEY")
29
- HUGGINGFACE_API_URL = os.getenv("HUGGINGFACE_API_URL")
30
-
31
- # Validate environment variables
32
- required_env_vars = ["SF_USERNAME", "SF_PASSWORD", "SF_SECURITY_TOKEN"]
33
- for var in required_env_vars:
34
- if not os.getenv(var):
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,9 +39,9 @@ try:
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,86 +55,61 @@ class VendorLog(BaseModel):
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():
105
  try:
106
  query = """
107
- SELECT Id, Name, Vendor__c, Work_Completion_Percentage__c, Quality_Percentage__c,
108
- Incident_Severity__c, Work_Completion_Date__c, Actual_Completion_Date__c,
109
- Delay_Days__c, Project__c
110
  FROM Vendor_Log__c
111
- WHERE Vendor__c != null
112
  """
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 {
151
  'qualityScore': round(quality_score, 2),
152
  'timelinessScore': round(timeliness_score, 2),
@@ -154,35 +117,8 @@ def calculate_scores_local(log: VendorLog):
154
  'communicationScore': round(communication_score, 2)
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,15 +127,30 @@ def get_feedback(score: float, metric: str) -> str:
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:
202
- filename = f'report_{vendor_id}_{datetime.now().strftime("%Y%m%d%H%M%S")}.pdf'
203
  c = canvas.Canvas(filename, pagesize=letter)
204
  c.setFont('Helvetica', 12)
205
  c.drawString(100, 750, 'Subcontractor Performance Report')
@@ -209,42 +160,49 @@ def generate_pdf(vendor_id: str, vendor_log_name: str, scores: dict):
209
  c.drawString(100, 670, f'Timeliness Score: {scores["timelinessScore"]}% ({get_feedback(scores["timelinessScore"], "Timeliness")})')
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'),
239
  'Quality_Score__c': scores['qualityScore'],
240
  'Timeliness_Score__c': scores['timelinessScore'],
241
  'Safety_Score__c': scores['safetyScore'],
242
  'Communication_Score__c': scores['communicationScore'],
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,23 +210,33 @@ def store_scores_in_salesforce(log: VendorLog, scores: dict, pdf_content: bytes,
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')
@@ -301,10 +269,8 @@ async def score_vendor(log: VendorLog):
301
  'pdfContent': pdf_base64,
302
  'alert': alert_flag
303
  }
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,6 +282,7 @@ async def get_dashboard():
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({
@@ -337,127 +304,135 @@ async def get_dashboard():
337
  <html>
338
  <head>
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() {
355
- try {
356
- const response = await fetch('/generate', { method: 'POST' });
357
- if (response.ok) {
358
- window.location.reload();
359
- } else {
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>
375
- <th>Vendor ID</th>
376
- <th>Vendor Log Name</th>
377
- <th>Project</th>
378
- <th>Work Completion %</th>
379
- <th>Quality %</th>
380
- <th>Incident Severity</th>
381
- <th>Work Completion Date</th>
382
- <th>Actual Completion Date</th>
383
- <th>Delay Days</th>
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>
411
- <th>Vendor ID</th>
412
- <th>Vendor Log Name</th>
413
- <th>Project</th>
414
- <th>Quality Score</th>
415
- <th>Timeliness Score</th>
416
- <th>Safety Score</th>
417
- <th>Communication Score</th>
418
- <th>Alert Flag</th>
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():
454
  try:
455
  global vendor_logs
456
- vendor_logs = []
457
  fetched_logs = fetch_vendor_logs_from_salesforce()
 
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,28 +449,10 @@ async def generate_scores():
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
 
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
  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
  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
  '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
  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
  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
  '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
  '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
  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
  <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
  '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