lokesh341 commited on
Commit
9c1061e
·
verified ·
1 Parent(s): 6638394

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +338 -110
app.py CHANGED
@@ -1,14 +1,16 @@
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,6 +22,10 @@ SF_PASSWORD = "Internal@1"
20
  SF_SECURITY_TOKEN = "NbUKcTx45azba5HEdntE9YAh"
21
  SF_DOMAIN = "login"
22
 
 
 
 
 
23
  # Initialize Salesforce connection
24
  try:
25
  sf = Salesforce(
@@ -29,156 +35,378 @@ try:
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'] if vendor_log['Delay_Days__c'] is not None else 0
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']) if vendor_log['Quality_Percentage__c'] is not None else 0.0
49
-
50
- # Safety Score: Based on Incident Severity
51
- incident_severity = vendor_log['Incident_Severity__c'] if vendor_log['Incident_Severity__c'] is not None else 'None'
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'] if vendor_log['Communication_Count__c'] is not None else 0
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
  return scores
72
  except Exception as e:
73
- logger.error(f"Error calculating scores for Vendor Log {vendor_log.get('Vendor_Log_ID__c', 'Unknown')}: {str(e)}")
74
  raise
75
 
76
- def generate_pdf(vendor_log, scores):
77
- """Generate a PDF report for the vendor."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  try:
79
- vendor_id = vendor_log['Vendor__r']['Name'] if vendor_log['Vendor__r'] and vendor_log['Vendor__r']['Name'] else 'Unknown'
80
- vendor_name = vendor_log['Vendor__r']['Vendor_Name__c'] if vendor_log['Vendor__r'] and vendor_log['Vendor__r']['Vendor_Name__c'] else 'Unknown'
81
  filename = f'report_{vendor_id}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.pdf'
82
  c = canvas.Canvas(filename, pagesize=letter)
83
  c.setFont('Helvetica', 12)
84
  c.drawString(100, 750, 'Subcontractor Performance Report')
85
  c.drawString(100, 730, f'Vendor ID: {vendor_id}')
86
- c.drawString(100, 710, f'Vendor Name: {vendor_name}')
87
- c.drawString(100, 690, f'Quality Score: {scores["qualityScore"]}%')
88
- c.drawString(100, 670, f'Timeliness Score: {scores["timelinessScore"]}%')
89
- c.drawString(100, 650, f'Safety Score: {scores["safetyScore"]}%')
90
- c.drawString(100, 630, f'Communication Score: {scores["communicationScore"]}%')
91
- c.drawString(100, 610, 'Final Score: Calculated in Salesforce')
92
  c.save()
93
 
94
  with open(filename, 'rb') as f:
95
  pdf_content = f.read()
96
- pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
97
 
 
98
  try:
99
  os.remove(filename)
100
  except Exception as e:
101
  logger.warning(f"Could not delete temporary PDF file {filename}: {str(e)}")
102
 
103
- return pdf_base64
104
  except Exception as e:
105
- logger.error(f"Error generating PDF for Vendor Log {vendor_log.get('Vendor_Log_ID__c', 'Unknown')}: {str(e)}")
106
  raise
107
 
108
- def fetch_and_process_logs():
109
- """Fetch Vendor Logs and calculate scores on the 15th of each month."""
110
- global vendor_logs, subcontractor_scores
111
- today = date.today()
112
- if today.day != 15: # Only process on the 15th
113
- logger.info("Today is not the 15th. Skipping processing.")
114
- return
 
 
 
 
115
 
 
 
116
  try:
117
- # Fetch Vendor Logs for the current month using CreatedDate
118
- current_month = today.month
119
- current_year = 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 CALENDAR_MONTH(CreatedDate) = :currentMonth AND CALENDAR_YEAR(CreatedDate) = :currentYear
126
- """
127
- query = query.replace(':currentMonth', str(current_month)).replace(':currentYear', str(current_year))
128
- result = sf.query(query)
129
- vendor_logs = result['records']
130
- logger.info(f"Fetched {len(vendor_logs)} Vendor Log records for {current_month}-{current_year}")
131
-
132
- subcontractor_scores = []
133
- for log in vendor_logs:
134
- # Calculate scores
135
- scores = calculate_scores(log)
136
- # Generate PDF
137
- pdf_base64 = generate_pdf(log, scores)
138
- pdf_link = f"data:application/pdf;base64,{pdf_base64}"
139
-
140
- # Fetch previous month's score for trend calculation
141
- previous_month = today.month - 1 if today.month > 1 else 12
142
- previous_year = today.year if today.month > 1 else today.year - 1
143
- prev_query = """
144
- SELECT Final_Score__c
145
- FROM Subcontractor_Performance_Score__c
146
- WHERE Vendor__c = :vendorId AND CALENDAR_MONTH(Month__c) = :prevMonth AND CALENDAR_YEAR(Month__c) = :prevYear
147
- """
148
- prev_result = sf.query(prev_query.replace(':vendorId', f"'{log['Vendor__c']}'")
149
- .replace(':prevMonth', str(previous_month))
150
- .replace(':prevYear', str(previous_year)))
151
- previous_score = prev_result['records'][0]['Final_Score__c'] if prev_result['records'] else 0.0
152
-
153
- # Save to Subcontractor Performance Score
154
- score_record = {
155
- 'Vendor__c': log['Vendor__c'],
156
- 'Vendor_Log__c': log['Id'],
157
- 'Month__c': today.strftime('%Y-%m-%d'),
 
 
 
 
 
 
 
 
 
 
 
158
  'Quality_Score__c': scores['qualityScore'],
159
  'Timeliness_Score__c': scores['timelinessScore'],
160
  'Safety_Score__c': scores['safetyScore'],
161
  'Communication_Score__c': scores['communicationScore'],
162
- 'PDF_Link__c': pdf_link,
163
- 'Previous_Month_Score__c': previous_score
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  }
165
- sf.Subcontractor_Performance_Score__c.create(score_record)
166
- subcontractor_scores.append({
167
- 'Vendor_Log_ID__c': log['Vendor_Log_ID__c'],
168
- 'Vendor_Name__c': log['Vendor__r']['Vendor_Name__c'],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  'scores': scores,
170
- 'pdf_link': pdf_link
 
 
 
171
  })
172
- logger.info(f"Processed {len(vendor_logs)} vendor logs and created {len(subcontractor_scores)} score records.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  except Exception as e:
174
- logger.error(f"Error processing vendor logs: {str(e)}")
175
- raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
- @app.route('/')
178
- def index():
179
- # Process logs if today is the 15th
180
- fetch_and_process_logs()
181
- return render_template('index.html', vendor_logs=vendor_logs, subcontractor_scores=subcontractor_scores)
 
 
 
182
 
183
  if __name__ == "__main__":
184
  app.run(host="0.0.0.0", port=7860, debug=True)
 
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
  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
  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)