Swathi6 commited on
Commit
894c3a3
·
verified ·
1 Parent(s): a94f5c9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +192 -255
app.py CHANGED
@@ -1,4 +1,4 @@
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,38 +8,27 @@ import logging
8
  from datetime import datetime
9
  from fastapi.responses import HTMLResponse
10
  from simple_salesforce import Salesforce
11
- from dotenv import load_dotenv
12
- from datasets import load_dataset # Added for Hugging Face
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 from .env
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
-
30
- # Validate environment variables
31
- required_env_vars = ["SF_USERNAME", "SF_PASSWORD", "SF_SECURITY_TOKEN"]
32
- for var in required_env_vars:
33
- if not os.getenv(var):
34
- logger.error(f"Environment variable {var} is not set")
35
- raise ValueError(f"Environment variable {var} is not set")
36
 
37
- # Check Hugging Face configuration
38
- USE_HUGGINGFACE = bool(HUGGINGFACE_API_KEY and HUGGINGFACE_API_KEY != "your_huggingface_api_key_here")
39
- logger.info(f"Hugging Face integration {'enabled' if USE_HUGGINGFACE else 'disabled'}")
 
 
40
 
41
- # Salesforce connection
42
- sf = None
43
  try:
44
  sf = Salesforce(
45
  username=SF_USERNAME,
@@ -50,9 +39,9 @@ try:
50
  logger.info("Successfully connected to Salesforce")
51
  except Exception as e:
52
  logger.error(f"Failed to connect to Salesforce: {str(e)}")
53
- raise RuntimeError(f"Cannot connect to Salesforce: {str(e)}")
54
 
55
- # VendorLog model
56
  class VendorLog(BaseModel):
57
  vendorLogId: str
58
  vendorId: str
@@ -66,105 +55,61 @@ class VendorLog(BaseModel):
66
  delayDays: int
67
  project: str
68
 
69
- # Store vendor logs
70
  vendor_logs = []
71
 
72
- # New function to fetch records from Hugging Face
73
- def fetch_huggingface_records(dataset_name: str = "imdb"):
74
- """Fetch a dataset from Hugging Face and return its records."""
75
- if not USE_HUGGINGFACE:
76
- logger.warning("Hugging Face integration is disabled. Cannot fetch records.")
77
- return []
78
- try:
79
- # Set Hugging Face token for authentication
80
- os.environ["HUGGINGFACE_TOKEN"] = HUGGINGFACE_API_KEY
81
- dataset = load_dataset(dataset_name)
82
- logger.info(f"Successfully fetched dataset: {dataset_name}")
83
- # Example: Convert dataset to a list of records (adjust based on dataset structure)
84
- records = [record for record in dataset['train']] # Assuming 'train' split
85
- return records[:10] # Limit to 10 records for demonstration
86
- except Exception as e:
87
- logger.error(f"Error fetching Hugging Face dataset {dataset_name}: {str(e)}")
88
- return []
89
-
90
- def validate_salesforce_fields():
91
- """Validate required Salesforce fields"""
92
- try:
93
- vendor_log_fields = [f['name'] for f in sf.Vendor_Log__c.describe()['fields']]
94
- required_fields = [
95
- 'Vendor__c', 'Work_Completion_Percentage__c', 'Quality_Percentage__c',
96
- 'Incident_Severity__c', 'Work_Completion_Date__c', 'Actual_Completion_Date__c',
97
- 'Delay_Days__c', 'Project__c'
98
- ]
99
- for field in required_fields:
100
- if field not in vendor_log_fields:
101
- logger.error(f"Field {field} not found in Vendor_Log__c")
102
- raise ValueError(f"Field {field} not found in Vendor_Log__c")
103
-
104
- score_fields = [f['name'] for f in sf.Subcontractor_Performance_Score__c.describe()['fields']]
105
- required_score_fields = [
106
- 'Vendor__c', 'Month__c', 'Quality_Score__c', 'Timeliness_Score__c',
107
- 'Safety_Score__c', 'Communication_Score__c', 'Alert_Flag__c'
108
- # Removed 'Certification_URL__c' to avoid error
109
- ]
110
- for field in required_score_fields:
111
- if field not in score_fields:
112
- logger.error(f"Field {field} not found in Subcontractor_Performance_Score__c")
113
- raise ValueError(f"Field {field} not found in Subcontractor_Performance_Score__c")
114
- logger.info("Salesforce fields validated successfully")
115
- except Exception as e:
116
- logger.error(f"Error validating Salesforce fields: {str(e)}")
117
- raise
118
-
119
- # Validate schema on startup
120
- validate_salesforce_fields()
121
-
122
  def fetch_vendor_logs_from_salesforce():
123
  try:
124
  query = """
125
- SELECT Id, Name, Vendor__c, Work_Completion_Percentage__c, Quality_Percentage__c,
126
- Incident_Severity__c, Work_Completion_Date__c, Actual_Completion_Date__c,
127
- Delay_Days__c, Project__c
128
  FROM Vendor_Log__c
129
- WHERE Vendor__c != null
130
  """
131
  result = sf.query_all(query)
132
  logs = []
133
  for record in result['records']:
134
- try:
135
- log = VendorLog(
136
- vendorLogId=record.get('Id', 'Unknown'),
137
- vendorId=record.get('Name', 'Unknown'),
138
- vendorRecordId=record.get('Vendor__c', 'Unknown'),
139
- workDetails=str(record.get('Work_Completion_Percentage__c', 0.0)),
140
- qualityReport=str(record.get('Quality_Percentage__c', 0.0)),
141
- incidentLog=record.get('Incident_Severity__c', 'None'),
142
- workCompletionDate=record.get('Work_Completion_Date__c', 'N/A'),
143
- actualCompletionDate=record.get('Actual_Completion_Date__c', 'N/A'),
144
- vendorLogName=record.get('Name', 'Unknown'),
145
- delayDays=int(record.get('Delay_Days__c', 0)),
146
- project=record.get('Project__c', 'Unknown')
147
- )
148
- logs.append(log)
149
- except Exception as e:
150
- logger.warning(f"Skipping invalid Vendor_Log__c record {record.get('Id')}: {str(e)}")
151
- logger.info(f"Fetched {len(logs)} vendor logs")
152
  return logs
153
  except Exception as e:
154
- logger.error(f"Error fetching vendor logs: {str(e)}")
155
- return []
156
 
157
- def calculate_scores_local(log: VendorLog):
158
  try:
159
- work_completion_percentage = float(log.workDetails or 0.0)
160
- quality_percentage = float(log.qualityReport or 0.0)
161
 
 
162
  quality_score = quality_percentage
 
 
163
  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
 
 
164
  severity_map = {'None': 100.0, 'Low': 80.0, 'Minor': 80.0, 'Medium': 50.0, 'High': 20.0}
165
  safety_score = severity_map.get(log.incidentLog, 100.0)
 
 
166
  communication_score = (quality_score * 0.33 + timeliness_score * 0.33 + safety_score * 0.33)
167
 
 
168
  return {
169
  'qualityScore': round(quality_score, 2),
170
  'timelinessScore': round(timeliness_score, 2),
@@ -172,16 +117,8 @@ def calculate_scores_local(log: VendorLog):
172
  'communicationScore': round(communication_score, 2)
173
  }
174
  except Exception as e:
175
- logger.error(f"Error calculating local scores: {str(e)}")
176
- return {'qualityScore': 0.0, 'timelinessScore': 0.0, 'safetyScore': 0.0, 'communicationScore': 0.0}
177
-
178
- def calculate_scores(log: VendorLog):
179
- if USE_HUGGINGFACE:
180
- # Example: Use Hugging Face model for score enhancement (placeholder)
181
- logger.info("Using Hugging Face for score calculation (placeholder)")
182
- return calculate_scores_local(log) # Replace with actual Hugging Face logic if needed
183
- else:
184
- return calculate_scores_local(log)
185
 
186
  def get_feedback(score: float, metric: str) -> str:
187
  try:
@@ -190,15 +127,30 @@ def get_feedback(score: float, metric: str) -> str:
190
  elif score >= 70:
191
  return "Good: Keep up the good work"
192
  elif score >= 50:
193
- return f"Needs Improvement: {'Maintain schedules' if metric == 'Timeliness' else 'Improve quality' if metric == 'Quality' else 'Enhance safety' if metric == 'Safety' else 'Better communication'}"
 
 
 
 
 
 
 
194
  else:
195
- return f"Poor: {'Significant delays' if metric == 'Timeliness' else 'Quality issues' if metric == 'Quality' else 'Safety issues' if metric == 'Safety' else 'Communication issues'}"
196
- except Exception:
197
- return "Feedback unavailable"
 
 
 
 
 
 
 
 
198
 
199
  def generate_pdf(vendor_id: str, vendor_log_name: str, scores: dict):
200
  try:
201
- filename = f'report_{vendor_id}_{datetime.now().strftime("%Y%m%d%H%M%S")}.pdf'
202
  c = canvas.Canvas(filename, pagesize=letter)
203
  c.setFont('Helvetica', 12)
204
  c.drawString(100, 750, 'Subcontractor Performance Report')
@@ -208,42 +160,49 @@ def generate_pdf(vendor_id: str, vendor_log_name: str, scores: dict):
208
  c.drawString(100, 670, f'Timeliness Score: {scores["timelinessScore"]}% ({get_feedback(scores["timelinessScore"], "Timeliness")})')
209
  c.drawString(100, 650, f'Safety Score: {scores["safetyScore"]}% ({get_feedback(scores["safetyScore"], "Safety")})')
210
  c.drawString(100, 630, f'Communication Score: {scores["communicationScore"]}% ({get_feedback(scores["communicationScore"], "Communication")})')
 
211
  c.save()
 
212
  with open(filename, 'rb') as f:
213
  pdf_content = f.read()
214
  os.remove(filename)
215
  return pdf_content
216
  except Exception as e:
217
  logger.error(f"Error generating PDF: {str(e)}")
218
- raise HTTPException(status_code=500, detail="Failed to generate PDF")
219
 
220
  def determine_alert_flag(scores: dict, all_logs: list):
221
  try:
222
  if not all_logs:
223
  return False
224
- avg_score = sum(scores.values()) / 4
 
 
225
  if avg_score < 50:
226
  return True
227
- lowest_avg = min([sum(log['scores'].values()) / 4 for log in all_logs], default=avg_score)
228
  return avg_score == lowest_avg
229
  except Exception as e:
230
  logger.error(f"Error determining alert flag: {str(e)}")
231
- return False
232
 
233
  def store_scores_in_salesforce(log: VendorLog, scores: dict, pdf_content: bytes, alert_flag: bool):
234
  try:
 
235
  score_record = sf.Subcontractor_Performance_Score__c.create({
 
236
  'Vendor__c': log.vendorRecordId,
237
- 'Month__c': datetime.today().replace(day=1).strftime('%Y-%m-%d'),
238
  'Quality_Score__c': scores['qualityScore'],
239
  'Timeliness_Score__c': scores['timelinessScore'],
240
  'Safety_Score__c': scores['safetyScore'],
241
  'Communication_Score__c': scores['communicationScore'],
242
  'Alert_Flag__c': alert_flag
 
243
  })
244
  score_record_id = score_record['id']
245
- logger.info(f"Created score record: {score_record_id}")
246
 
 
247
  pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
248
  content_version = sf.ContentVersion.create({
249
  'Title': f'Performance_Report_{log.vendorId}',
@@ -251,24 +210,33 @@ def store_scores_in_salesforce(log: VendorLog, scores: dict, pdf_content: bytes,
251
  'VersionData': pdf_base64,
252
  'FirstPublishLocationId': score_record_id
253
  })
 
 
 
254
  content_version_id = content_version['id']
255
  content_version_record = sf.query(f"SELECT ContentDocumentId FROM ContentVersion WHERE Id = '{content_version_id}'")
256
- if content_version_record['totalSize'] == 0:
257
- logger.error(f"No ContentVersion for ID: {content_version_id}")
258
- raise ValueError("Failed to retrieve ContentDocumentId")
259
  content_document_id = content_version_record['records'][0]['ContentDocumentId']
 
 
260
  pdf_url = f"https://{sf.sf_instance}/sfc/servlet.shepherd/document/download/{content_document_id}"
261
 
262
- # Comment out Certification_URL__c update to avoid error
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 not stored due to missing field)")
 
 
 
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,132 +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
  """
 
388
  if not vendor_logs:
389
- html_content += '<tr><td colspan="9" class="text-center">No vendor logs available</td></tr>'
 
 
 
 
390
  else:
391
  for log in vendor_logs:
392
  html_content += f"""
393
- <tr>
394
- <td>{log['vendorId']}</td>
395
- <td>{log['vendorLogName']}</td>
396
- <td>{log['project']}</td>
397
- <td>{log['workDetails']}</td>
398
- <td>{log['qualityReport']}</td>
399
- <td>{log['incidentLog']}</td>
400
- <td>{log['workCompletionDate']}</td>
401
- <td>{log['actualCompletionDate']}</td>
402
- <td>{log['delayDays']}</td>
403
- </tr>
404
  """
405
 
406
  html_content += """
407
- </tbody>
408
- </table>
409
- <button class="generate-btn" onclick="generateScores()">Generate Scores</button>
410
- <h2>Performance Scores</h2>
411
- <table class="table table-striped">
412
- <thead>
413
- <tr>
414
- <th>Vendor ID</th>
415
- <th>Vendor Log Name</th>
416
- <th>Project</th>
417
- <th>Quality Score</th>
418
- <th>Timeliness Score</th>
419
- <th>Safety Score</th>
420
- <th>Communication Score</th>
421
- <th>Alert Flag</th>
422
- </tr>
423
- </thead>
424
- <tbody>
425
  """
 
426
  if not vendor_logs:
427
- html_content += '<tr><td colspan="8" class="text-center">No scores available</td></tr>'
 
 
 
 
428
  else:
429
  for log in vendor_logs:
430
  scores = log['scores']
431
  alert_flag = determine_alert_flag(scores, vendor_logs)
432
  html_content += f"""
433
- <tr>
434
- <td>{log['vendorId']}</td>
435
- <td>{log['vendorLogName']}</td>
436
- <td>{log['project']}</td>
437
- <td>{scores['qualityScore']}%</td>
438
- <td>{scores['timelinessScore']}%</td>
439
- <td>{scores['safetyScore']}%</td>
440
- <td>{scores['communicationScore']}%</td>
441
- <td>{'Checked' if alert_flag else 'Unchecked'}</td>
442
- </tr>
443
  """
444
 
445
  html_content += """
446
- </tbody>
447
- </table>
448
- </div>
449
  </body>
450
  </html>
451
  """
452
  return HTMLResponse(content=html_content)
453
  except Exception as e:
454
- logger.error(f"Error in /: {str(e)}")
455
- return HTMLResponse(content="<h1>Error</h1><p>Failed to load dashboard. Check logs for details.</p>", status_code=500)
456
 
457
  @app.post('/generate')
458
  async def generate_scores():
459
  try:
460
  global vendor_logs
461
- vendor_logs = []
462
  fetched_logs = fetch_vendor_logs_from_salesforce()
 
463
  for log in fetched_logs:
464
  scores = calculate_scores(log)
465
  pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
 
466
  alert_flag = determine_alert_flag(scores, vendor_logs)
467
  store_scores_in_salesforce(log, scores, pdf_content, alert_flag)
468
  vendor_logs.append({
@@ -479,44 +449,11 @@ async def generate_scores():
479
  'scores': scores,
480
  'extracted': True
481
  })
482
- logger.info(f"Generated scores for {len(vendor_logs)} logs")
483
  return {"status": "success"}
484
  except Exception as e:
485
- logger.error(f"Error in /generate: {str(e)}")
486
- raise HTTPException(status_code=500, detail="Failed to generate scores")
487
-
488
- @app.get('/debug')
489
- async def debug_info():
490
- try:
491
- log_count = sf.query("SELECT COUNT() FROM Vendor_Log__c")['totalSize']
492
- fields = [f['name'] for f in sf.Vendor_Log__c.describe()['fields']]
493
- score_fields = [f['name'] for f in sf.Subcontractor_Performance_Score__c.describe()['fields']]
494
- # Fetch sample Hugging Face records for debugging
495
- hf_records = fetch_huggingface_records() if USE_HUGGINGFACE else []
496
- return {
497
- "salesforce_connected": True,
498
- "vendor_log_count": log_count,
499
- "vendor_log_fields": fields,
500
- "score_fields": score_fields,
501
- "huggingface_enabled": USE_HUGGINGFACE,
502
- "huggingface_records_sample": hf_records
503
- }
504
- except Exception as e:
505
- logger.error(f"Debug error: {str(e)}")
506
- return {"salesforce_connected": False, "error": str(e)}
507
-
508
- @app.get('/huggingface-records')
509
- async def get_huggingface_records():
510
- """New endpoint to fetch and return Hugging Face records."""
511
- try:
512
- records = fetch_huggingface_records()
513
- if not records:
514
- raise HTTPException(status_code=404, detail="No records fetched from Hugging Face")
515
- return {"records": records}
516
- except Exception as e:
517
- logger.error(f"Error fetching Hugging Face records: {str(e)}")
518
- raise HTTPException(status_code=500, detail=f"Failed to fetch Hugging Face records: {str(e)}")
519
 
520
  if __name__ == "__main__":
521
  import uvicorn
522
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
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
  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
459
+ uvicorn.run(app, host="0.0.0.0", port=7860)