| import json |
| import logging |
| from datetime import datetime |
| from typing import Dict, List, Optional |
|
|
| |
| ALERT_THRESHOLD = 0.75 |
| QUALITY_WEIGHT = 0.4 |
| TIMELINESS_WEIGHT = 0.3 |
| SAFETY_WEIGHT = 0.15 |
| COMMUNICATION_WEIGHT = 0.15 |
| |
| def calculate_quality_score(details: str) -> float: |
| """ |
| Calculates a quality score based on work completion details. |
| |
| Args: |
| details: Text description of the work completed. |
| |
| Returns: |
| A score between 0 and 1, where 1 is the highest quality. |
| """ |
| |
| |
| |
| keywords = ["excellent", "high quality", "flawless", "best practice", "exceeded expectations"] |
| score = 0.5 |
| for keyword in keywords: |
| if keyword in details.lower(): |
| score = 1.0 |
| break |
| return score |
| def calculate_timeliness_score(delay_reports: str) -> float: |
| """ |
| Calculates a timeliness score based on delay reports. |
| |
| Args: |
| delay_reports: Text describing any delays. |
| |
| Returns: |
| A score between 0 and 1, where 1 indicates no delays. |
| """ |
| |
| |
| if not delay_reports: |
| return 1.0 |
| else: |
| |
| num_delays = len(delay_reports.split(',')) |
| return max(0, 1 - (num_delays * 0.1)) |
| return 0.7 |
| def calculate_safety_score(incident_logs: str) -> float: |
| """ |
| Calculates a safety score based on incident logs. |
| |
| Args: |
| incident_logs: Text describing any safety incidents. |
| |
| Returns: |
| A score between 0 and 1, where 1 indicates no incidents. |
| """ |
| |
| |
| if not incident_logs: |
| return 1.0 |
| else: |
| return 0.8 |
| def calculate_communication_score() -> float: |
| """ |
| Calculates a communication score. Since the input data doesn't directly |
| contain communication data, this is a placeholder. You'll need to |
| determine how to derive this score (e.g., from separate feedback logs, |
| response times, etc.). |
| |
| Returns: |
| A score between 0 and 1. |
| """ |
| return 0.9 |
| def calculate_final_score(quality: float, timeliness: float, safety: float, communication: float) -> float: |
| """ |
| Calculates the final performance score using weighted average. |
| |
| Args: |
| quality: Quality score. |
| timeliness: Timeliness score. |
| safety: Safety score. |
| communication: Communication score. |
| |
| Returns: |
| The final performance score (between 0 and 1). |
| """ |
| return ( |
| QUALITY_WEIGHT * quality + |
| TIMELINESS_WEIGHT * timeliness + |
| SAFETY_WEIGHT * safety + |
| COMMUNICATION_WEIGHT * communication |
| ) |
| def analyze_trends(vendor_id: str, scores: List[Dict]) -> float: |
| """ |
| Analyzes performance trends for a vendor. This is a placeholder, as |
| you'll need to store historical data to implement this properly. |
| |
| Args: |
| vendor_id: The ID of the vendor. |
| scores: A list of historical scores for the vendor. In a real |
| implementation, this would come from a database. |
| |
| Returns: |
| A trend deviation value (e.g., 0 for stable, >0 for improving, <0 for declining). |
| """ |
| |
| |
| if not scores: |
| return 0.0 |
| latest_score = scores[-1]['final_score'] |
| if len(scores) > 1: |
| previous_score = scores[-2]['final_score'] |
| return latest_score - previous_score |
| return 0.0 |
| def get_historical_scores(vendor_id: str) -> List[Dict]: |
| """ |
| Retrieves historical scores for a vendor from a data source. |
| THIS IS A PLACEHOLDER. You will need to replace this with |
| actual database or data retrieval logic. This example |
| returns an empty list. |
| |
| Args: |
| vendor_id: The ID of the vendor. |
| |
| Returns: |
| A list of dictionaries, where each dictionary represents a historical score record. |
| Example: |
| [ |
| {'month': '2024-01', 'final_score': 0.85}, |
| {'month': '2024-02', 'final_score': 0.90}, |
| {'month': '2024-03', 'final_score': 0.88}, |
| ] |
| """ |
| |
| return [] |
| def generate_pdf_certificate(vendor_name: str, month: str, quality: float, timeliness: float, safety: float, communication: float, final_score: float) -> str: |
| """ |
| Generates a PDF certificate (filename) for a vendor's performance. |
| |
| Args: |
| vendor_name: Name of the vendor. |
| month: The month the score is for. |
| quality: Quality score. |
| timeliness: Timeliness score. |
| safety: Safety score. |
| communication: Communication score. |
| final_score: Final performance score. |
| |
| Returns: |
| The filename of the generated PDF certificate. |
| """ |
| |
| |
| |
| filename = f"{vendor_name}_Performance_Certificate_{month}.pdf" |
| try: |
| with open(filename, "w") as f: |
| f.write(f"Vendor: {vendor_name}\n") |
| f.write(f"Month: {month}\n") |
| f.write(f"Quality: {quality:.2f}\n") |
| f.write(f"Timeliness: {timeliness:.2f}\n") |
| f.write(f"Safety: {safety:.2f}\n") |
| f.write(f"Communication: {communication:.2f}\n") |
| f.write(f"Final Score: {final_score:.2f}\n") |
| f.write("--- Certificate of Performance ---\n") |
| logging.info(f"Generated PDF certificate: {filename}") |
| except Exception as e: |
| logging.error(f"Error generating PDF: {e}") |
| return None |
| return filename |
| def send_score_to_salesforce(vendor_id: str, month: str, quality: float, timeliness: float, safety: float, communication: float, final_score: float, certificate_url: str) -> bool: |
| """ |
| Sends the vendor's performance score to Salesforce. |
| THIS IS A PLACEHOLDER. You MUST replace this with your actual |
| Salesforce API integration code (e.g., using the Salesforce REST API). |
| |
| Args: |
| vendor_id: The ID of the vendor. |
| month: The month the score is for (YYYY-MM format). |
| quality: Quality score. |
| timeliness: Timeliness score. |
| safety: Safety score. |
| communication: Communication score. |
| final_score: Final performance score. |
| certificate_url: URL where the PDF certificate can be downloaded. |
| |
| Returns: |
| True if the data was sent successfully, False otherwise. |
| """ |
| |
| |
| logging.info(f"Sending score to Salesforce for Vendor ID: {vendor_id}, Month: {month}") |
| logging.info(f"Quality: {quality}, Timeliness: {timeliness}, Safety: {safety}, Communication: {communication}, Final Score: {final_score}, Certificate URL: {certificate_url}") |
| |
| return True |
| def process_vendor_logs(vendor_logs: List[Dict]) -> List[Dict]: |
| """ |
| Processes vendor logs, calculates scores, and prepares data for Salesforce and PDF. |
| |
| Args: |
| vendor_logs: A list of dictionaries, where each dictionary represents a vendor log. |
| Example: |
| [ |
| { |
| 'vendor_id': 'V123', |
| 'work_completion_details': 'Work completed on time and to high standards.', |
| 'delay_reports': '', |
| 'incident_logs': '', |
| 'log_date': '2024-07-28' |
| }, |
| { |
| 'vendor_id': 'V456', |
| 'work_completion_details': 'Work completed with minor issues.', |
| 'delay_reports': 'Minor delay due to weather.', |
| 'incident_logs': 'Minor safety violation.', |
| 'log_date': '2024-07-29' |
| }, |
| ] |
| |
| Returns: |
| A list of dictionaries, where each dictionary represents a vendor score record |
| ready for sending to Salesforce. Includes the certificate URL. |
| Example: |
| [ |
| { |
| 'vendor_id': 'V123', |
| 'month': '2024-07', |
| 'quality': 0.95, |
| 'timeliness': 1.0, |
| 'safety': 1.0, |
| 'communication': 0.9, |
| 'final_score': 0.96, |
| 'alert_flag': False, |
| 'certificate_url': 'https://example.com/certificates/V123_2024-07.pdf' |
| }, |
| { |
| 'vendor_id': 'V456', |
| 'month': '2024-07', |
| 'quality': 0.7, |
| 'timeliness': 0.8, |
| 'safety': 0.6, |
| 'communication': 0.9, |
| 'final_score': 0.75, |
| 'alert_flag': True, |
| 'certificate_url': 'https://example.com/certificates/V456_2024-07.pdf' |
| }, |
| ] |
| """ |
| scores_for_salesforce = [] |
| for log in vendor_logs: |
| vendor_id = log.get('vendor_id') |
| work_completion_details = log.get('work_completion_details', '') |
| delay_reports = log.get('delay_reports', '') |
| incident_logs = log.get('incident_logs', '') |
| log_date_str = log.get('log_date') |
| try: |
| log_date = datetime.strptime(log_date_str, '%Y-%m-%d') |
| month = log_date.strftime('%Y-%m') |
| except ValueError: |
| logging.error(f"Invalid date format: {log_date_str} for vendor {vendor_id}. Skipping log.") |
| continue |
| quality = calculate_quality_score(work_completion_details) |
| timeliness = calculate_timeliness_score(delay_reports) |
| safety = calculate_safety_score(incident_logs) |
| communication = calculate_communication_score() |
| final_score = calculate_final_score(quality, timeliness, safety, communication) |
| historical_scores = get_historical_scores(vendor_id) |
| trend_deviation = analyze_trends(vendor_id, historical_scores) |
| alert_flag = final_score < ALERT_THRESHOLD |
| |
| certificate_filename = generate_pdf_certificate( |
| vendor_id, |
| month, |
| quality, |
| timeliness, |
| safety, |
| communication, |
| final_score |
| ) |
| if certificate_filename: |
| certificate_url = f"https://your-domain.com/certificates/{certificate_filename}" |
| else: |
| certificate_url = "" |
| scores_for_salesforce.append({ |
| 'vendor_id': vendor_id, |
| 'month': month, |
| 'quality': quality, |
| 'timeliness': timeliness, |
| 'safety': safety, |
| 'communication': communication, |
| 'final_score': final_score, |
| 'alert_flag': alert_flag, |
| 'certificate_url': certificate_url, |
| 'trend_deviation': trend_deviation |
| }) |
| return scores_for_salesforce |
| def get_lowest_score(scores: List[Dict]) -> Optional[Dict]: |
| """ |
| Finds the vendor with the lowest final score. |
| |
| Args: |
| scores: A list of vendor score dictionaries. |
| |
| Returns: |
| The dictionary representing the vendor with the lowest score, or None if the |
| list is empty. |
| """ |
| if not scores: |
| return None |
| lowest_score_vendor = scores[0] |
| for vendor_score in scores: |
| if vendor_score['final_score'] < lowest_score_vendor['final_score']: |
| lowest_score_vendor = vendor_score |
| return lowest_score_vendor |
| def send_alert_message(vendor_id: str, month: str, final_score: float): |
| """ |
| Sends an alert message to the vendor with the lowest score. |
| THIS IS A PLACEHOLDER. You will need to replace this with your actual |
| email sending or notification logic (e.g., using a library like `smtplib` |
| or an API like SendGrid, or by triggering a Salesforce alert). |
| |
| Args: |
| vendor_id: The ID of the vendor. |
| month: The month of the score. |
| final_score: The vendor's final score. |
| """ |
| |
| |
| alert_message = ( |
| f"Subject: Performance Alert - Subcontractor {vendor_id} - {month}\n\n" |
| f"Your performance score for {month} is {final_score:.2f}, which is below the acceptable threshold.\n" |
| f"Please review your performance and take corrective action." |
| ) |
| logging.warning(f"Sending alert message: {alert_message}") |
| |
| |
| def process_and_send_data(vendor_logs: List[Dict]) -> bool: |
| """ |
| Processes vendor logs, sends data to Salesforce, and handles alerts. |
| |
| Args: |
| vendor_logs: A list of dictionaries, where each dictionary represents a vendor log. |
| |
| |
| Returns: |
| True if the entire process was successful, False otherwise. |
| """ |
| try: |
| scores_for_salesforce = process_vendor_logs(vendor_logs) |
| if not scores_for_salesforce: |
| logging.warning("No scores to send to Salesforce.") |
| return True |
| |
| all_salesforce_updates_successful = True |
| for score_data in scores_for_salesforce: |
| salesforce_success = send_score_to_salesforce( |
| vendor_id=score_data['vendor_id'], |
| month=score_data['month'], |
| quality=score_data['quality'], |
| timeliness=score_data['timeliness'], |
| safety=score_data['safety'], |
| communication=score_data['communication'], |
| final_score=score_data['final_score'], |
| certificate_url=score_data['certificate_url'] |
| ) |
| if not salesforce_success: |
| all_salesforce_updates_successful = False |
| logging.error(f"Failed to send score for vendor {score_data['vendor_id']} to Salesforce.") |
| if not all_salesforce_updates_successful: |
| return False |
| |
| lowest_scoring_vendor = get_lowest_score(scores_for_salesforce) |
| if lowest_scoring_vendor and lowest_scoring_vendor['alert_flag']: |
| send_alert_message( |
| vendor_id=lowest_scoring_vendor['vendor_id'], |
| month=lowest_scoring_vendor['month'], |
| final_score=lowest_scoring_vendor['final_score'] |
| ) |
| return True |
| except Exception as e: |
| logging.error(f"Error processing and sending data: {e}") |
| return False |
| if __name__ == "__main__": |
| |
| |
| sample_vendor_logs = [ |
| { |
| 'vendor_id': 'V123', |
| 'work_completion_details': 'Excellent work, completed ahead of schedule. The team demonstrated high quality and followed best practices.', |
| 'delay_reports': '', |
| 'incident_logs': '', |
| 'log_date': '2024-07-28' |
| }, |
| { |
| 'vendor_id': 'V456', |
| 'work_completion_details': 'Work completed with minor issues; some rework was required.', |
| 'delay_reports': 'Minor delay due to weather conditions.', |
| 'incident_logs': 'Minor safety violation: worker not wearing proper PPE.', |
| 'log_date': '2024-07-29' |
| }, |
| { |
| 'vendor_id': 'V789', |
| 'work_completion_details': 'Work completed on time.', |
| 'delay_reports': 'Major delay due to lack of materials', |
| 'incident_logs': '', |
| 'log_date': '2024-07-29' |
| }, |
| ] |
| |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') |
| success = process_and_send_data(sample_vendor_logs) |
| if success: |
| logging.info("Data processing and sending successful.") |
| else: |
| logging.error("Data processing and sending failed.") |
|
|
| |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') |
| success = process_and_send_data(sample_vendor_logs) |
| if success: |
| logging.info("Data processing and sending successful.") |
| else: |
| logging.error("Data processing and sending failed.") |
|
|
|
|