lokesh341 commited on
Commit
76604ee
·
verified ·
1 Parent(s): 2af0ca0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +108 -338
app.py CHANGED
@@ -1,16 +1,14 @@
1
- from flask import Flask, request, Response, jsonify, render_template
2
- from pydantic import BaseModel
3
  from reportlab.lib.pagesizes import letter
4
  from reportlab.pdfgen import canvas
5
  import base64
6
  import os
7
  import logging
8
- import traceback
9
  import requests
10
- from datetime import datetime
11
- from simple_salesforce import Salesforce
12
 
13
- # Set up logging with more detail
14
  logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
15
  logger = logging.getLogger(__name__)
16
 
@@ -22,10 +20,6 @@ SF_PASSWORD = "Internal@1"
22
  SF_SECURITY_TOKEN = "NbUKcTx45azba5HEdntE9YAh"
23
  SF_DOMAIN = "login"
24
 
25
- # Hugging Face API details (mocked for now)
26
- HUGGING_FACE_API_URL = "https://api-inference.huggingface.co/models/your-model-name"
27
- HUGGING_FACE_API_TOKEN = "your-hugging-face-api-token"
28
-
29
  # Initialize Salesforce connection
30
  try:
31
  sf = Salesforce(
@@ -35,378 +29,154 @@ try:
35
  domain=SF_DOMAIN
36
  )
37
  logger.info("Successfully connected to Salesforce")
38
-
39
- # Debug: Describe the Subcontractor_Performance_Score__c object to log its fields
40
- object_description = sf.Subcontractor_Performance_Score__c.describe()
41
- logger.info("Fields on Subcontractor_Performance_Score__c:")
42
- for field in object_description['fields']:
43
- logger.info(f"Field Name: {field['name']}")
44
  except Exception as e:
45
  logger.error(f"Failed to connect to Salesforce: {str(e)}")
46
  raise ValueError("Failed to connect to Salesforce")
47
 
48
- # VendorLog model to match Salesforce data
49
- class VendorLog(BaseModel):
50
- vendorLogId: str
51
- vendorId: str
52
- workDetails: str
53
- qualityReport: str
54
- incidentLog: str
55
- workCompletionDate: str
56
- actualCompletionDate: str
57
- vendorLogName: str
58
- delayDays: int
59
- project: str
60
-
61
- # Store vendor logs for display
62
  vendor_logs = []
 
63
 
64
- def fetch_vendor_logs_from_salesforce():
65
- """Fetch vendor logs from Salesforce to send to Hugging Face."""
66
  try:
67
- # Validate Salesforce session
68
- if not sf.session_id:
69
- logger.error("Salesforce session is invalid")
70
- raise ValueError("Salesforce session is invalid")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
- # Use a more flexible query
73
- query = """
74
- SELECT Id, Vendor_Log_Id__c, VendorId__c, Work_Details__c, Quality_Report__c,
75
- Incident_Log__c, Work_Completion_Date__c, Actual_Completion_Date__c,
76
- Vendor_Log_Name__c, Delay_Days__c, Project__c
77
- FROM Vendor_Log__c
78
- WHERE CreatedDate >= 2025-05-01T00:00:00Z AND CreatedDate <= 2025-05-31T23:59:59Z
79
- """
80
- result = sf.query(query)
81
- logs = []
82
- for record in result['records']:
83
- # Handle missing fields with defaults
84
- log = VendorLog(
85
- vendorLogId=record.get('Vendor_Log_Id__c', 'Unknown'),
86
- vendorId=record.get('VendorId__c', 'Unknown'),
87
- workDetails=record.get('Work_Details__c', '0% completed'),
88
- qualityReport=record.get('Quality_Report__c', '0% quality'),
89
- incidentLog=record.get('Incident_Log__c', 'None'),
90
- workCompletionDate=record.get('Work_Completion_Date__c', '2025-05-01'),
91
- actualCompletionDate=record.get('Actual_Completion_Date__c', '2025-05-01'),
92
- vendorLogName=record.get('Vendor_Log_Name__c', 'Unknown Vendor'),
93
- delayDays=record.get('Delay_Days__c', 0),
94
- project=record.get('Project__c', 'Unknown Project')
95
- )
96
- logs.append(log)
97
- logger.info(f"Fetched {len(logs)} vendor logs from Salesforce")
98
- return logs
99
- except Exception as e:
100
- logger.error(f"Error fetching vendor logs from Salesforce: {str(e)}")
101
- return []
102
-
103
- def calculate_scores_with_hugging_face(log: VendorLog):
104
- """Send data to Hugging Face and get scores (mocked for now)."""
105
- try:
106
- # Prepare data for Hugging Face
107
- payload = {
108
- "workDetails": log.workDetails,
109
- "qualityReport": log.qualityReport,
110
- "incidentLog": log.incidentLog,
111
- "delayDays": log.delayDays
112
- }
113
-
114
- # Mocked Hugging Face API call (replace with actual API call)
115
- # headers = {"Authorization": f"Bearer {HUGGING_FACE_API_TOKEN}"}
116
- # response = requests.post(HUGGING_FACE_API_URL, json=payload, headers=headers)
117
- # if response.status_code != 200:
118
- # logger.error(f"Hugging Face API error: {response.text}")
119
- # raise ValueError("Failed to get scores from Hugging Face")
120
- # scores = response.json()
121
-
122
- # Mocked response for now
123
  scores = {
124
- 'qualityScore': float(log.qualityReport.replace('% quality', '')),
125
- 'timelinessScore': 100.0 if log.delayDays <= 0 else 80.0 if log.delayDays <= 3 else 60.0 if log.delayDays <= 7 else 40.0,
126
- 'safetyScore': {'None': 100.0, 'Low': 80.0, 'Minor': 80.0, 'Medium': 50.0, 'High': 20.0}.get(log.incidentLog, 100.0),
127
- 'communicationScore': 0.0,
128
- 'finalScore': 0.0
129
  }
130
- scores['communicationScore'] = (scores['qualityScore'] * 0.33 + scores['timelinessScore'] * 0.33 + scores['safetyScore'] * 0.33)
131
- scores['finalScore'] = (scores['qualityScore'] + scores['timelinessScore'] + scores['safetyScore'] + scores['communicationScore']) / 4
132
-
133
- # Round scores
134
- for key in scores:
135
- scores[key] = round(scores[key], 2)
136
-
137
- logger.debug(f"Calculated scores for vendor {log.vendorId}: {scores}")
138
  return scores
139
  except Exception as e:
140
- logger.error(f"Error calculating scores with Hugging Face: {str(e)}")
141
  raise
142
 
143
- def get_feedback(score: float, metric: str) -> str:
144
- try:
145
- if score >= 90:
146
- return "Excellent: Maintain this standard"
147
- elif score >= 70:
148
- return "Good: Keep up the good work"
149
- elif score >= 50:
150
- if metric == 'Timeliness':
151
- return "Needs Improvement: Maintain schedules to complete tasks on time"
152
- elif metric == 'Safety':
153
- return "Needs Improvement: Implement stricter safety protocols"
154
- elif metric == 'Quality':
155
- return "Needs Improvement: Focus on improving work quality"
156
- else:
157
- return "Needs Improvement: Enhance coordination with project teams"
158
- else:
159
- if metric == 'Timeliness':
160
- return "Poor: Significant delays detected"
161
- elif metric == 'Safety':
162
- return "Poor: Critical safety issues identified"
163
- elif metric == 'Quality':
164
- return "Poor: Quality standards not met"
165
- else:
166
- return "Poor: Communication issues detected"
167
- except Exception as e:
168
- logger.error(f"Error generating feedback: {str(e)}")
169
- return "Feedback unavailable"
170
-
171
- def generate_pdf(vendor_id: str, vendor_log_name: str, scores: dict):
172
  try:
 
 
173
  filename = f'report_{vendor_id}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.pdf'
174
  c = canvas.Canvas(filename, pagesize=letter)
175
  c.setFont('Helvetica', 12)
176
  c.drawString(100, 750, 'Subcontractor Performance Report')
177
  c.drawString(100, 730, f'Vendor ID: {vendor_id}')
178
- c.drawString(100, 710, f'Vendor Log Name: {vendor_log_name}')
179
- c.drawString(100, 690, f'Quality Score: {scores["qualityScore"]}% ({get_feedback(scores["qualityScore"], "Quality")})')
180
- c.drawString(100, 670, f'Timeliness Score: {scores["timelinessScore"]}% ({get_feedback(scores["timelinessScore"], "Timeliness")})')
181
- c.drawString(100, 650, f'Safety Score: {scores["safetyScore"]}% ({get_feedback(scores["safetyScore"], "Safety")})')
182
- c.drawString(100, 630, f'Communication Score: {scores["communicationScore"]}% ({get_feedback(scores["communicationScore"], "Communication")})')
183
- c.drawString(100, 610, f'Final Score: {scores["finalScore"]}%')
184
  c.save()
185
 
186
  with open(filename, 'rb') as f:
187
  pdf_content = f.read()
 
188
 
189
- # Clean up the file
190
  try:
191
  os.remove(filename)
192
  except Exception as e:
193
  logger.warning(f"Could not delete temporary PDF file {filename}: {str(e)}")
194
 
195
- return pdf_content
196
  except Exception as e:
197
- logger.error(f"Error generating PDF: {str(e)}")
198
  raise
199
 
200
- def determine_alert_flag(final_score: float, all_logs: list):
201
- try:
202
- if not all_logs:
203
- return False
204
- if final_score < 50:
205
- return True
206
- lowest_score = min([log['scores']['finalScore'] for log in all_logs])
207
- return final_score == lowest_score
208
- except Exception as e:
209
- logger.error(f"Error determining alert flag: {str(e)}")
210
- return False
211
 
212
- @app.route('/score', methods=['POST'])
213
- def score_vendor():
214
  try:
215
- # Validate the Authorization header
216
- authorization = request.headers.get('Authorization')
217
- if not authorization:
218
- logger.warning("Authorization header missing in request")
219
- return jsonify({'error': 'Authorization header missing'}), 401
220
-
221
- # Parse the request data
222
- data = request.get_json()
223
- if not data:
224
- logger.warning("Invalid or missing JSON data in request")
225
- return jsonify({'error': 'Invalid request data'}), 400
226
-
227
- # Validate and create VendorLog instance
228
- log = VendorLog(**data)
229
- logger.info(f"Received Vendor Log: {log}")
230
-
231
- # Validate Salesforce session
232
- if not sf.session_id:
233
- logger.error("Salesforce session invalid")
234
- return jsonify({'error': 'Salesforce session invalid'}), 401
235
-
236
- # Calculate scores using Hugging Face
237
- scores = calculate_scores_with_hugging_face(log)
238
-
239
- # Generate PDF
240
- pdf_content = generate_pdf(log.vendorId, log.vendorLogName, scores)
241
- pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
242
-
243
- # Determine alert flag
244
- alert_flag = determine_alert_flag(scores['finalScore'], vendor_logs)
245
-
246
- # Store the log and scores in memory
247
- vendor_logs.append({
248
- 'vendorLogId': log.vendorLogId,
249
- 'vendorId': log.vendorId,
250
- 'vendorLogName': log.vendorLogName,
251
- 'workDetails': log.workDetails,
252
- 'qualityReport': log.qualityReport,
253
- 'incidentLog': log.incidentLog,
254
- 'workCompletionDate': log.workCompletionDate,
255
- 'actualCompletionDate': log.actualCompletionDate,
256
- 'delayDays': log.delayDays,
257
- 'project': log.project,
258
- 'scores': scores,
259
- 'extracted': True
260
- })
261
-
262
- # Save scores and PDF to Salesforce
263
- try:
264
- sf.Subcontractor_Performance_Score__c.create({
265
- 'VendorId__c': log.vendorId, # Updated field name
266
- 'Month__c': datetime.now().strftime('%Y-%m-%d'),
267
  'Quality_Score__c': scores['qualityScore'],
268
  'Timeliness_Score__c': scores['timelinessScore'],
269
  'Safety_Score__c': scores['safetyScore'],
270
  'Communication_Score__c': scores['communicationScore'],
271
- 'Final_Score__c': scores['finalScore'],
272
- 'Certification_URL__c': pdf_base64,
273
- 'Alert_Flag__c': alert_flag
274
- })
275
- logger.info(f"Successfully saved scores to Salesforce for Vendor Log: {log.vendorLogId}")
276
- except Exception as e:
277
- logger.error(f"Error saving to Salesforce: {str(e)}")
278
- # Continue even if Salesforce save fails
279
-
280
- # Return the response
281
- return jsonify({
282
- 'vendorLogId': log.vendorLogId,
283
- 'vendorId': log.vendorId,
284
- 'vendorLogName': log.vendorLogName,
285
- 'qualityScore': scores['qualityScore'],
286
- 'timelinessScore': scores['timelinessScore'],
287
- 'safetyScore': scores['safetyScore'],
288
- 'communicationScore': scores['communicationScore'],
289
- 'finalScore': scores['finalScore'],
290
- 'pdfContent': pdf_base64,
291
- 'alert': alert_flag
292
- }), 200
293
- except Exception as e:
294
- logger.error(f"Error in /score endpoint: {str(e)}")
295
- return jsonify({'error': f"Error processing vendor log: {str(e)}"}), 500
296
-
297
- @app.route('/', methods=['GET'])
298
- def get_dashboard():
299
- try:
300
- # Fetch data from Salesforce with updated field name
301
- query = """
302
- SELECT VendorId__c, Month__c, Quality_Score__c, Timeliness_Score__c,
303
- Safety_Score__c, Communication_Score__c, Final_Score__c, Certification_URL__c,
304
- Alert_Flag__c
305
- FROM Subcontractor_Performance_Score__c
306
- WHERE Month__c = '2025-05-01'
307
- """
308
- try:
309
- result = sf.query(query)
310
- except Exception as e:
311
- logger.error(f"Error querying Salesforce: {str(e)}")
312
- # Fallback to empty data to render the dashboard
313
- result = {'records': []}
314
-
315
- vendor_logs.clear()
316
- for record in result['records']:
317
- # Handle missing or null scores
318
- scores = {
319
- 'qualityScore': record.get('Quality_Score__c', 0.0) or 0.0,
320
- 'timelinessScore': record.get('Timeliness_Score__c', 0.0) or 0.0,
321
- 'safetyScore': record.get('Safety_Score__c', 0.0) or 0.0,
322
- 'communicationScore': record.get('Communication_Score__c', 0.0) or 0.0,
323
- 'finalScore': record.get('Final_Score__c', 0.0) or 0.0
324
  }
325
- vendor_logs.append({
326
- 'vendorId': record.get('VendorId__c', 'Unknown'),
327
- 'vendorLogName': f"Vendor {record.get('VendorId__c', 'Unknown')}",
328
- 'scores': scores,
329
- 'extracted': True
330
- })
331
-
332
- # Calculate summary metrics
333
- total_vendors = len(vendor_logs)
334
- performance_alerts = sum(1 for log in vendor_logs if determine_alert_flag(log['scores']['finalScore'], vendor_logs))
335
- top_performers = sum(1 for log in vendor_logs if log['scores']['finalScore'] >= 90)
336
- improving_vendors = sum(1 for log in vendor_logs if log['scores']['finalScore'] >= 70)
337
-
338
- # Sort vendor logs by final score for leaderboard
339
- sorted_logs = sorted(vendor_logs, key=lambda x: x['scores']['finalScore'], reverse=True)
340
- top_logs_data = sorted_logs[:5] # Top 5 for leaderboard
341
- top_performing_logs = sorted_logs[:4] # Top 4 for "Top Performing Vendors" section
342
- alert_logs = [log for log in vendor_logs if determine_alert_flag(log['scores']['finalScore'], vendor_logs)][:3]
343
-
344
- # Prepare data for the leaderboard
345
- top_logs = []
346
- for idx, log in enumerate(top_logs_data, 1):
347
- scores = log['scores']
348
- alert_flag = determine_alert_flag(scores['finalScore'], vendor_logs)
349
- trend = "trend-up" if scores['finalScore'] >= 90 else "trend-down" if scores['finalScore'] < 70 else "trend-flat"
350
- trend_symbol = "↗" if trend == "trend-up" else "↘" if trend == "trend-down" else "—"
351
- status_class = "status-good" if not alert_flag else "status-alert"
352
- status_text = "Good" if not alert_flag else "Alert"
353
- top_logs.append({
354
- 'idx': idx,
355
- 'vendorLogName': log['vendorLogName'],
356
  'scores': scores,
357
- 'trend': trend,
358
- 'trend_symbol': trend_symbol,
359
- 'status_class': status_class,
360
- 'status_text': status_text
361
  })
362
-
363
- # Ensure all variables are defined for the template
364
- template_data = {
365
- 'total_vendors': total_vendors,
366
- 'performance_alerts': performance_alerts,
367
- 'percent_alerts': round(performance_alerts/total_vendors*100, 1) if total_vendors else 0,
368
- 'top_performers': top_performers,
369
- 'percent_top': round(top_performers/total_vendors*100, 1) if total_vendors else 0,
370
- 'improving_vendors': improving_vendors,
371
- 'percent_improving': round(improving_vendors/total_vendors*100, 1) if total_vendors else 0,
372
- 'top_logs': top_logs,
373
- 'alert_logs': alert_logs,
374
- 'top_performing_logs': top_performing_logs,
375
- 'vendor_logs': vendor_logs,
376
- 'sorted_logs': sorted_logs
377
- }
378
-
379
- logger.info(f"Rendering dashboard with data: {template_data}")
380
- return render_template('dashboard.html', **template_data)
381
  except Exception as e:
382
- error_trace = traceback.format_exc()
383
- logger.error(f"Error in / endpoint: {str(e)}\nStack trace:\n{error_trace}")
384
- # Fallback to render the dashboard with empty data
385
- template_data = {
386
- 'total_vendors': 0,
387
- 'performance_alerts': 0,
388
- 'percent_alerts': 0,
389
- 'top_performers': 0,
390
- 'percent_top': 0,
391
- 'improving_vendors': 0,
392
- 'percent_improving': 0,
393
- 'top_logs': [],
394
- 'alert_logs': [],
395
- 'top_performing_logs': [],
396
- 'vendor_logs': [],
397
- 'sorted_logs': []
398
- }
399
- logger.info("Rendering dashboard with fallback data due to error")
400
- return render_template('dashboard.html', **template_data)
401
 
402
- @app.route('/document', methods=['GET'])
403
- def get_document():
404
- try:
405
- return render_template('document.html')
406
- except Exception as e:
407
- error_trace = traceback.format_exc()
408
- logger.error(f"Error in /document endpoint: {str(e)}\nStack trace:\n{error_trace}")
409
- return jsonify({'error': f"Error generating document: {str(e)}"}), 500
410
 
411
  if __name__ == "__main__":
412
  app.run(host="0.0.0.0", port=7860, debug=True)
 
1
+ from flask import Flask, render_template
2
+ from simple_salesforce import Salesforce
3
  from reportlab.lib.pagesizes import letter
4
  from reportlab.pdfgen import canvas
5
  import base64
6
  import os
7
  import logging
8
+ from datetime import datetime, date
9
  import requests
 
 
10
 
11
+ # Set up logging
12
  logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
13
  logger = logging.getLogger(__name__)
14
 
 
20
  SF_SECURITY_TOKEN = "NbUKcTx45azba5HEdntE9YAh"
21
  SF_DOMAIN = "login"
22
 
 
 
 
 
23
  # Initialize Salesforce connection
24
  try:
25
  sf = Salesforce(
 
29
  domain=SF_DOMAIN
30
  )
31
  logger.info("Successfully connected to Salesforce")
 
 
 
 
 
 
32
  except Exception as e:
33
  logger.error(f"Failed to connect to Salesforce: {str(e)}")
34
  raise ValueError("Failed to connect to Salesforce")
35
 
36
+ # Store processed logs and scores
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  vendor_logs = []
38
+ subcontractor_scores = []
39
 
40
+ def calculate_scores(vendor_log):
41
+ """Calculate scores based on Vendor Log data."""
42
  try:
43
+ # Timeliness Score: Based on Delay Days
44
+ delay_days = vendor_log['Delay_Days__c']
45
+ timeliness_score = 100.0 if delay_days <= 0 else 80.0 if delay_days <= 3 else 60.0 if delay_days <= 7 else 40.0
46
+
47
+ # Quality Score: Direct mapping from Quality Percentage
48
+ quality_score = float(vendor_log['Quality_Percentage__c'])
49
+
50
+ # Safety Score: Based on Incident Severity
51
+ incident_severity = vendor_log['Incident_Severity__c']
52
+ safety_score = {
53
+ 'None': 100.0,
54
+ 'Low': 80.0,
55
+ 'Minor': 80.0,
56
+ 'Medium': 50.0,
57
+ 'High': 20.0
58
+ }.get(incident_severity, 100.0)
59
+
60
+ # Communication Score: Based on Communication Count (scale 0-100)
61
+ comm_count = vendor_log['Communication_Count__c']
62
+ communication_score = min(100.0, comm_count * 10.0) # 10 communications = 100%
63
 
64
+ # Round scores
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  scores = {
66
+ 'qualityScore': round(quality_score, 2),
67
+ 'timelinessScore': round(timeliness_score, 2),
68
+ 'safetyScore': round(safety_score, 2),
69
+ 'communicationScore': round(communication_score, 2)
 
70
  }
71
+ # Final Score is calculated in Salesforce via Formula field
 
 
 
 
 
 
 
72
  return scores
73
  except Exception as e:
74
+ logger.error(f"Error calculating scores for Vendor Log {vendor_log['Vendor_Log_ID__c']}: {str(e)}")
75
  raise
76
 
77
+ def generate_pdf(vendor_log, scores):
78
+ """Generate a PDF report for the vendor."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  try:
80
+ vendor_id = vendor_log['Vendor__r']['Name']
81
+ vendor_name = vendor_log['Vendor__r']['Vendor_Name__c']
82
  filename = f'report_{vendor_id}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.pdf'
83
  c = canvas.Canvas(filename, pagesize=letter)
84
  c.setFont('Helvetica', 12)
85
  c.drawString(100, 750, 'Subcontractor Performance Report')
86
  c.drawString(100, 730, f'Vendor ID: {vendor_id}')
87
+ c.drawString(100, 710, f'Vendor Name: {vendor_name}')
88
+ c.drawString(100, 690, f'Quality Score: {scores["qualityScore"]}%')
89
+ c.drawString(100, 670, f'Timeliness Score: {scores["timelinessScore"]}%')
90
+ c.drawString(100, 650, f'Safety Score: {scores["safetyScore"]}%')
91
+ c.drawString(100, 630, f'Communication Score: {scores["communicationScore"]}%')
92
+ c.drawString(100, 610, 'Final Score: Calculated in Salesforce')
93
  c.save()
94
 
95
  with open(filename, 'rb') as f:
96
  pdf_content = f.read()
97
+ pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
98
 
 
99
  try:
100
  os.remove(filename)
101
  except Exception as e:
102
  logger.warning(f"Could not delete temporary PDF file {filename}: {str(e)}")
103
 
104
+ return pdf_base64
105
  except Exception as e:
106
+ logger.error(f"Error generating PDF for Vendor Log {vendor_log['Vendor_Log_ID__c']}: {str(e)}")
107
  raise
108
 
109
+ def fetch_and_process_logs():
110
+ """Fetch Vendor Logs and calculate scores on the 15th of each month."""
111
+ global vendor_logs, subcontractor_scores
112
+ today = date.today()
113
+ if today.day != 15: # Only process on the 15th
114
+ logger.info("Today is not the 15th. Skipping processing.")
115
+ return
 
 
 
 
116
 
 
 
117
  try:
118
+ # Fetch Vendor Logs for the current month
119
+ current_month = f"{today.month}-{today.year}"
120
+ query = """
121
+ SELECT Id, Vendor_Log_ID__c, Vendor__c, Vendor__r.Name, Vendor__r.Vendor_Name__c, Vendor__r.Contact_Email__c,
122
+ Project__c, Work_Completion_Date__c, Actual_Completion_Date__c, Delay_Days__c,
123
+ Quality_Percentage__c, Incident_Severity__c, Communication_Count__c, Work_Completion_Percentage__c
124
+ FROM Vendor_Log__c
125
+ WHERE Created_Month__c = :current_month
126
+ """
127
+ result = sf.query(query.replace(':current_month', f"'{current_month}'"))
128
+ vendor_logs = result['records']
129
+
130
+ subcontractor_scores = []
131
+ for log in vendor_logs:
132
+ # Calculate scores
133
+ scores = calculate_scores(log)
134
+ # Generate PDF
135
+ pdf_base64 = generate_pdf(log, scores)
136
+ pdf_link = f"data:application/pdf;base64,{pdf_base64}"
137
+
138
+ # Fetch previous month's score for trend calculation
139
+ previous_month = today.month - 1 if today.month > 1 else 12
140
+ previous_year = today.year if today.month > 1 else today.year - 1
141
+ prev_query = """
142
+ SELECT Final_Score__c
143
+ FROM Subcontractor_Performance_Score__c
144
+ WHERE Vendor__c = :vendorId AND CALENDAR_MONTH(Month__c) = :prevMonth AND CALENDAR_YEAR(Month__c) = :prevYear
145
+ """
146
+ prev_result = sf.query(prev_query.replace(':vendorId', f"'{log['Vendor__c']}'")
147
+ .replace(':prevMonth', str(previous_month))
148
+ .replace(':prevYear', str(previous_year)))
149
+ previous_score = prev_result['records'][0]['Final_Score__c'] if prev_result['records'] else 0.0
150
+
151
+ # Save to Subcontractor Performance Score
152
+ score_record = {
153
+ 'Vendor__c': log['Vendor__c'],
154
+ 'Vendor_Log__c': log['Id'],
155
+ 'Month__c': today.strftime('%Y-%m-%d'),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  'Quality_Score__c': scores['qualityScore'],
157
  'Timeliness_Score__c': scores['timelinessScore'],
158
  'Safety_Score__c': scores['safetyScore'],
159
  'Communication_Score__c': scores['communicationScore'],
160
+ 'PDF_Link__c': pdf_link,
161
+ 'Previous_Month_Score__c': previous_score
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  }
163
+ sf.Subcontractor_Performance_Score__c.create(score_record)
164
+ subcontractor_scores.append({
165
+ 'Vendor_Log_ID__c': log['Vendor_Log_ID__c'],
166
+ 'Vendor_Name__c': log['Vendor__r']['Vendor_Name__c'],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  'scores': scores,
168
+ 'pdf_link': pdf_link
 
 
 
169
  })
170
+ logger.info(f"Processed {len(vendor_logs)} vendor logs.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  except Exception as e:
172
+ logger.error(f"Error processing vendor logs: {str(e)}")
173
+ raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
+ @app.route('/')
176
+ def index():
177
+ # Process logs if today is the 15th
178
+ fetch_and_process_logs()
179
+ return render_template('index.html', vendor_logs=vendor_logs, subcontractor_scores=subcontractor_scores)
 
 
 
180
 
181
  if __name__ == "__main__":
182
  app.run(host="0.0.0.0", port=7860, debug=True)