File size: 15,904 Bytes
2e47d2a
9c1061e
c21dfef
 
c435100
88e3d9a
465a073
2e47d2a
9c1061e
 
465a073
2e47d2a
2af0ca0
465a073
c0d489a
8d1d1a4
c435100
b3909e1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9c1061e
2e47d2a
 
 
 
 
 
 
 
 
b3909e1
 
2e47d2a
c435100
2e47d2a
9c1061e
 
 
 
 
 
 
 
 
 
 
 
 
f673d62
c435100
9c1061e
2e47d2a
 
 
 
2af0ca0
2e47d2a
9c1061e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2e47d2a
 
 
 
 
 
 
9c1061e
 
 
2e47d2a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5fe4733
9c1061e
 
 
 
 
5fe4733
9c1061e
 
 
 
 
5fe4733
646567b
2e47d2a
 
 
 
 
 
 
 
c435100
9c1061e
 
 
 
 
 
 
2e47d2a
9c1061e
2e47d2a
9c1061e
 
 
 
 
2e47d2a
646567b
2e47d2a
 
646567b
 
 
9c1061e
 
 
 
 
 
2e47d2a
646567b
2e47d2a
 
ca02ee3
9c1061e
646567b
c435100
9c1061e
2e47d2a
9c1061e
 
 
 
 
 
 
 
 
 
465a073
9c1061e
 
2e47d2a
646567b
9c1061e
 
 
 
 
2e47d2a
9c1061e
2e47d2a
 
9c1061e
 
 
 
 
 
 
 
 
 
 
 
2e47d2a
9c1061e
2e47d2a
9c1061e
b3909e1
 
 
 
9c1061e
 
 
 
2e47d2a
9c1061e
 
 
 
 
2e47d2a
9c1061e
 
 
 
 
2e47d2a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9c1061e
 
 
2e47d2a
9c1061e
2e47d2a
 
 
9c1061e
2e47d2a
9c1061e
2e47d2a
9c1061e
2e47d2a
 
 
 
9c1061e
 
 
 
 
 
 
 
 
2e47d2a
 
9c1061e
 
 
 
 
 
 
 
 
 
 
 
 
3d4b58a
9c1061e
 
 
 
3d4b58a
9c1061e
 
 
 
 
 
 
 
 
 
 
 
 
2e47d2a
 
 
9c1061e
 
 
646567b
2e47d2a
9c1061e
 
 
 
 
 
 
 
 
 
 
 
2e47d2a
 
 
9c1061e
 
f541eee
9c1061e
 
2e47d2a
9c1061e
 
 
2e47d2a
 
5fe4733
465a073
8d1d1a4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
from flask import Flask, request, Response, jsonify, render_template, send_file
from pydantic import BaseModel
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
import base64
import os
import logging
import io
from datetime import datetime
from simple_salesforce import Salesforce

# Set up logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

app = Flask(__name__)

# Salesforce credentials
SF_USERNAME = "scores@app.com"
SF_PASSWORD = "Internal@1"
SF_SECURITY_TOKEN = "NbUKcTx45azba5HEdntE9YAh"
SF_DOMAIN = "login"

# Initialize Salesforce connection
try:
    sf = Salesforce(
        username=SF_USERNAME,
        password=SF_PASSWORD,
        security_token=SF_SECURITY_TOKEN,
        domain=SF_DOMAIN
    )
    logger.info("Successfully connected to Salesforce")

    # Debug: Describe objects for field verification
    for obj in ['Subcontractor_Performance_Score__c', 'Vendor_Log__c']:
        try:
            description = getattr(sf, obj).describe()
            logger.info(f"Fields on {obj}:")
            for field in description['fields']:
                logger.info(f"Field Name: {field['name']}")
        except Exception as e:
            logger.error(f"Error describing {obj}: {str(e)}")
except Exception as e:
    logger.error(f"Failed to connect to Salesforce: {str(e)}")
    sf = None

# VendorLog model
class VendorLog(BaseModel):
    vendorLogId: str
    vendorId: str
    workDetails: str
    qualityReport: str
    incidentLog: str
    workCompletionDate: str
    actualCompletionDate: str
    vendorLogName: str
    delayDays: int
    project: str

# Store vendor logs for display
vendor_logs = []

def fetch_vendor_logs_from_salesforce():
    """Fetch vendor logs from Salesforce."""
    if not sf:
        logger.error("Salesforce connection not initialized")
        return []

    try:
        query = """
        SELECT Id, Vendor_Log_Id__c, VendorId__c, Work_Details__c, Quality_Report__c, 
        Incident_Log__c, Work_Completion_Date__c, Actual_Completion_Date__c, 
        Vendor_Log_Name__c, Delay_Days__c, Project__c 
        FROM Vendor_Log__c 
        WHERE CreatedDate >= 2025-05-01T00:00:00Z AND CreatedDate <= 2025-05-31T23:59:59Z
        """
        result = sf.query(query)
        logs = []
        for record in result['records']:
            log = VendorLog(
                vendorLogId=record.get('Vendor_Log_Id__c', 'Unknown'),
                vendorId=record.get('VendorId__c', 'Unknown'),
                workDetails=record.get('Work_Details__c', '0% completed'),
                qualityReport=record.get('Quality_Report__c', '0% quality'),
                incidentLog=record.get('Incident_Log__c', 'None'),
                workCompletionDate=record.get('Work_Completion_Date__c', '2025-05-01'),
                actualCompletionDate=record.get('Actual_Completion_Date__c', '2025-05-01'),
                vendorLogName=record.get('Vendor_Log_Name__c', 'Unknown Vendor'),
                delayDays=record.get('Delay_Days__c', 0),
                project=record.get('Project__c', 'Unknown Project')
            )
            logs.append(log)
        logger.info(f"Fetched {len(logs)} vendor logs from Salesforce")
        return logs
    except Exception as e:
        logger.error(f"Error fetching vendor logs: {str(e)}")
        return []

def fetch_performance_scores():
    """Fetch performance scores from Salesforce."""
    if not sf:
        logger.error("Salesforce connection not initialized")
        return []

    try:
        query = """
        SELECT VendorId__c, Month__c, Quality_Score__c, Timeliness_Score__c, 
        Safety_Score__c, Communication_Score__c, Final_Score__c, Certification_URL__c, 
        Alert_Flag__c 
        FROM Subcontractor_Performance_Score__c 
        WHERE Month__c = '2025-05-01'
        """
        result = sf.query(query)
        scores = []
        for record in result['records']:
            scores.append({
                'vendorId': record.get('VendorId__c', 'Unknown'),
                'vendorLogName': f"Vendor {record.get('VendorId__c', 'Unknown')}",
                'scores': {
                    'qualityScore': record.get('Quality_Score__c', 0.0) or 0.0,
                    'timelinessScore': record.get('Timeliness_Score__c', 0.0) or 0.0,
                    'safetyScore': record.get('Safety_Score__c', 0.0) or 0.0,
                    'communicationScore': record.get('Communication_Score__c', 0.0) or 0.0,
                    'finalScore': record.get('Final_Score__c', 0.0) or 0.0
                },
                'certification_url': record.get('Certification_URL__c', ''),
                'alert_flag': record.get('Alert_Flag__c', False)
            })
        logger.info(f"Fetched {len(scores)} performance scores from Salesforce")
        return scores
    except Exception as e:
        logger.error(f"Error fetching performance scores: {str(e)}")
        return []

def calculate_scores(log: VendorLog):
    """Calculate scores for a vendor log."""
    try:
        scores = {
            'qualityScore': float(log.qualityReport.replace('% quality', '')),
            'timelinessScore': 100.0 if log.delayDays <= 0 else 80.0 if log.delayDays <= 3 else 60.0 if log.delayDays <= 7 else 40.0,
            'safetyScore': {'None': 100.0, 'Low': 80.0, 'Minor': 80.0, 'Medium': 50.0, 'High': 20.0}.get(log.incidentLog, 100.0),
            'communicationScore': 0.0,
            'finalScore': 0.0
        }
        scores['communicationScore'] = (scores['qualityScore'] * 0.33 + scores['timelinessScore'] * 0.33 + scores['safetyScore'] * 0.33)
        scores['finalScore'] = (scores['qualityScore'] + scores['timelinessScore'] + scores['safetyScore'] + scores['communicationScore']) / 4

        for key in scores:
            scores[key] = round(scores[key], 2)
        return scores
    except Exception as e:
        logger.error(f"Error calculating scores: {str(e)}")
        return {
            'qualityScore': 0.0,
            'timelinessScore': 0.0,
            'safetyScore': 0.0,
            'communicationScore': 0.0,
            'finalScore': 0.0
        }

def get_feedback(score: float, metric: str) -> str:
    try:
        if score >= 90:
            return "Excellent: Maintain this standard"
        elif score >= 70:
            return "Good: Keep up the good work"
        elif score >= 50:
            return f"Needs Improvement: Enhance {metric.lower()} performance"
        else:
            return f"Poor: Critical issues in {metric.lower()}"
    except Exception as e:
        logger.error(f"Error generating feedback: {str(e)}")
        return "Feedback unavailable"

def generate_pdf(vendor_id: str, vendor_log_name: str, scores: dict):
    """Generate a PDF report for a vendor."""
    try:
        buffer = io.BytesIO()
        c = canvas.Canvas(buffer, pagesize=letter)
        c.setFont('Helvetica', 12)
        c.drawString(100, 750, 'Subcontractor Performance Report')
        c.drawString(100, 730, f'Vendor ID: {vendor_id}')
        c.drawString(100, 710, f'Vendor Log Name: {vendor_log_name}')
        c.drawString(100, 690, f'Quality Score: {scores["qualityScore"]}% ({get_feedback(scores["qualityScore"], "Quality")})')
        c.drawString(100, 670, f'Timeliness Score: {scores["timelinessScore"]}% ({get_feedback(scores["timelinessScore"], "Timeliness")})')
        c.drawString(100, 650, f'Safety Score: {scores["safetyScore"]}% ({get_feedback(scores["safetyScore"], "Safety")})')
        c.drawString(100, 630, f'Communication Score: {scores["communicationScore"]}% ({get_feedback(scores["communicationScore"], "Communication")})')
        c.drawString(100, 610, f'Final Score: {scores["finalScore"]}%')
        c.drawString(100, 590, f'Generated On: May 15, 2025, 12:01 PM IST')
        c.save()
        buffer.seek(0)
        return buffer
    except Exception as e:
        logger.error(f"Error generating PDF: {str(e)}")
        raise

def determine_alert_flag(final_score: float, all_logs: list):
    """Determine if an alert flag should be set."""
    try:
        if not all_logs:
            return False
        if final_score < 50:
            return True
        lowest_score = min([log['scores']['finalScore'] for log in all_logs])
        return final_score == lowest_score
    except Exception as e:
        logger.error(f"Error determining alert flag: {str(e)}")
        return False

@app.route('/score', methods=['POST'])
def score_vendor():
    """Score a vendor and save to Salesforce."""
    try:
        data = request.get_json()
        if not data:
            return jsonify({'error': 'Invalid request data'}), 400

        log = VendorLog(**data)
        scores = calculate_scores(log)
        
        pdf_buffer = generate_pdf(log.vendorId, log.vendorLogName, scores)
        pdf_content = pdf_buffer.getvalue()
        pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')

        alert_flag = determine_alert_flag(scores['finalScore'], vendor_logs)

        vendor_logs.append({
            'vendorLogId': log.vendorLogId,
            'vendorId': log.vendorId,
            'vendorLogName': log.vendorLogName,
            'scores': scores,
            'extracted': True
        })

        if sf:
            sf.Subcontractor_Performance_Score__c.create({
                'VendorId__c': log.vendorId,
                'Month__c': datetime.now().strftime('%Y-%m-%d'),
                'Quality_Score__c': scores['qualityScore'],
                'Timeliness_Score__c': scores['timelinessScore'],
                'Safety_Score__c': scores['safetyScore'],
                'Communication_Score__c': scores['communicationScore'],
                'Final_Score__c': scores['finalScore'],
                'Certification_URL__c': pdf_base64,
                'Alert_Flag__c': alert_flag
            })
            logger.info(f"Saved scores to Salesforce for Vendor Log: {log.vendorLogId}")

        return jsonify({
            'vendorLogId': log.vendorLogId,
            'vendorId': log.vendorId,
            'vendorLogName': log.vendorLogName,
            'scores': scores,
            'pdfContent': pdf_base64,
            'alert': alert_flag
        }), 200
    except Exception as e:
        logger.error(f"Error in /score endpoint: {str(e)}")
        return jsonify({'error': str(e)}), 500

@app.route('/generate_report', methods=['POST'])
def generate_report():
    """Generate a report for a selected vendor."""
    try:
        data = request.form
        vendor_name = data.get('vendor_name')
        report_month = data.get('report_month', 'May, 2025')

        # Fetch the vendor's performance score
        performance_scores = fetch_performance_scores()
        vendor_data = next((v for v in performance_scores if v['vendorLogName'] == vendor_name), None)

        if not vendor_data:
            return jsonify({'error': 'Vendor not found'}), 404

        # Generate PDF
        pdf_buffer = generate_pdf(vendor_data['vendorId'], vendor_name, vendor_data['scores'])
        
        # Save to Salesforce
        pdf_content = pdf_buffer.getvalue()
        pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
        if sf:
            sf.Subcontractor_Performance_Score__c.update(vendor_data['vendorId'], {
                'Certification_URL__c': pdf_base64
            })

        return send_file(
            pdf_buffer,
            attachment_filename=f'report_{vendor_data["vendorId"]}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.pdf',
            as_attachment=True
        )
    except Exception as e:
        logger.error(f"Error in /generate_report endpoint: {str(e)}")
        return jsonify({'error': str(e)}), 500

@app.route('/', methods=['GET'])
def get_dashboard():
    """Render the dashboard with Salesforce data."""
    try:
        # Fetch data from Salesforce
        performance_scores = fetch_performance_scores()
        vendor_logs_list = fetch_vendor_logs_from_salesforce()

        # Prepare data for dashboard
        vendor_logs.clear()
        for score in performance_scores:
            vendor_logs.append({
                'vendorLogId': score['vendorId'],
                'vendorId': score['vendorId'],
                'vendorLogName': score['vendorLogName'],
                'scores': score['scores'],
                'extracted': True
            })

        total_vendors = len(vendor_logs)
        performance_alerts = sum(1 for log in vendor_logs if determine_alert_flag(log['scores']['finalScore'], vendor_logs))
        top_performers = sum(1 for log in vendor_logs if log['scores']['finalScore'] >= 90)
        improving_vendors = sum(1 for log in vendor_logs if log['scores']['finalScore'] >= 70)

        sorted_logs = sorted(vendor_logs, key=lambda x: x['scores']['finalScore'], reverse=True)
        top_logs_data = sorted_logs[:5]
        top_performing_logs = sorted_logs[:4]
        alert_logs = [log for log in vendor_logs if determine_alert_flag(log['scores']['finalScore'], vendor_logs)][:3]

        top_logs = []
        for idx, log in enumerate(top_logs_data, 1):
            scores = log['scores']
            alert_flag = determine_alert_flag(scores['finalScore'], vendor_logs)
            trend = "trend-up" if scores['finalScore'] >= 90 else "trend-down" if scores['finalScore'] < 70 else "trend-flat"
            trend_symbol = "↗" if trend == "trend-up" else "↘" if trend == "trend-down" else "—"
            status_class = "status-good" if not alert_flag else "status-alert"
            status_text = "Good" if not alert_flag else "Alert"
            top_logs.append({
                'idx': idx,
                'vendorLogName': log['vendorLogName'],
                'scores': scores,
                'trend': trend,
                'trend_symbol': trend_symbol,
                'status_class': status_class,
                'status_text': status_text
            })

        template_data = {
            'total_vendors': total_vendors,
            'performance_alerts': performance_alerts,
            'percent_alerts': round(performance_alerts/total_vendors*100, 1) if total_vendors else 0,
            'top_performers': top_performers,
            'percent_top': round(top_performers/total_vendors*100, 1) if total_vendors else 0,
            'improving_vendors': improving_vendors,
            'percent_improving': round(improving_vendors/total_vendors*100, 1) if total_vendors else 0,
            'top_logs': top_logs,
            'alert_logs': alert_logs,
            'top_performing_logs': top_performing_logs,
            'vendor_logs': vendor_logs,
            'sorted_logs': sorted_logs,
            'vendor_names': [log.vendorLogName for log in vendor_logs_list],
            'report_month': 'May, 2025'
        }

        return render_template('dashboard.html', **template_data)
    except Exception as e:
        logger.error(f"Error in / endpoint: {str(e)}")
        template_data = {
            'total_vendors': 0,
            'performance_alerts': 0,
            'percent_alerts': 0,
            'top_performers': 0,
            'percent_top': 0,
            'improving_vendors': 0,
            'percent_improving': 0,
            'top_logs': [],
            'alert_logs': [],
            'top_performing_logs': [],
            'vendor_logs': [],
            'sorted_logs': [],
            'vendor_names': [],
            'report_month': 'May, 2025'
        }
        return render_template('dashboard.html', **template_data)

@app.route('/document', methods=['GET'])
def get_document():
    """Render the document page."""
    try:
        return render_template('document.html')
    except Exception as e:
        logger.error(f"Error in /document endpoint: {str(e)}")
        return jsonify({'error': str(e)}), 500

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=7860, debug=True)