from fastapi import FastAPI, HTTPException, Header from pydantic import BaseModel from typing import Optional, List from datetime import datetime import logging import uvicorn import os # Set up logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) # Initialize FastAPI app app = FastAPI(title="Vendor Performance Scoring API") # Define input data model class VendorInput(BaseModel): Vendor_ID__c: str Vendor_Name__c: str Issue_Count__c: int Feedback_Rating__c: float Timeliness_Score__c: float Evaluation_Date__c: str # ISO format, e.g., "2025-05-14" Email__c: Optional[str] = None # Define output data model class VendorOutput(BaseModel): Vendor_ID__c: str Score__c: float Rationale__c: str Alert_Flag__c: bool # API token from environment variable EXPECTED_API_TOKEN = os.getenv("API_TOKEN", "hf_default_token_for_testing") # Scoring function def calculate_vendor_score(data: VendorInput) -> VendorOutput: try: # Validate inputs if data.Issue_Count__c < 0: raise ValueError("Issue_Count__c cannot be negative") if not (0 <= data.Feedback_Rating__c <= 10): raise ValueError("Feedback_Rating__c must be between 0 and 10") if not (0 <= data.Timeliness_Score__c <= 10): raise ValueError("Timeliness_Score__c must be between 0 and 10") # Parse and validate evaluation date try: eval_date = datetime.fromisoformat(data.Evaluation_Date__c.replace("Z", "+00:00")) current_date = datetime.now().astimezone() if eval_date > current_date: raise ValueError("Evaluation_Date__c cannot be in the future") except ValueError as e: raise ValueError(f"Invalid Evaluation_Date__c format or value: {str(e)}") # Calculate score base_score = 100 issue_deduction = data.Issue_Count__c * 5 feedback_bonus = data.Feedback_Rating__c * 2 timeliness_bonus = data.Timeliness_Score__c * 3 score = base_score - issue_deduction + feedback_bonus + timeliness_bonus score = max(0, min(100, score)) # Generate rationale rationale_parts = [] if issue_deduction > 0: rationale_parts.append(f"Deducted {issue_deduction} points for {data.Issue_Count__c} issues") rationale_parts.append(f"Added {feedback_bonus:.1f} points for feedback rating {data.Feedback_Rating__c}") rationale_parts.append(f"Added {timeliness_bonus:.1f} points for timeliness score {data.Timeliness_Score__c}") rationale = "; ".join(rationale_parts) + f"; Final score: {score:.1f}" # Set alert flag alert_flag = score < 50 or data.Issue_Count__c > 5 logger.info(f"Scored Vendor {data.Vendor_ID__c}: Score={score:.1f}, Alert={alert_flag}") return VendorOutput( Vendor_ID__c=data.Vendor_ID__c, Score__c=score, Rationale__c=rationale, Alert_Flag__c=alert_flag ) except Exception as e: logger.error(f"Error scoring vendor {data.Vendor_ID__c}: {str(e)}") raise HTTPException(status_code=400, detail=f"Scoring error: {str(e)}") # Single vendor scoring endpoint @app.post("/score-vendor", response_model=VendorOutput) async def score_vendor(data: VendorInput, authorization: Optional[str] = Header(None)): if not authorization or authorization != f"Bearer {EXPECTED_API_TOKEN}": logger.warning("Invalid or missing API token") raise HTTPException(status_code=401, detail="Invalid or missing API token") return calculate_vendor_score(data) # Batch vendor scoring endpoint @app.post("/score-vendors", response_model=List[VendorOutput]) async def score_vendors(data: List[VendorInput], authorization: Optional[str] = Header(None)): if not authorization or authorization != f"Bearer {EXPECTED_API_TOKEN}": logger.warning("Invalid or missing API token") raise HTTPException(status_code=401, detail="Invalid or missing API token") results = [] for vendor in data: try: result = calculate_vendor_score(vendor) results.append(result) except Exception as e: logger.error(f"Skipping vendor {vendor.Vendor_ID__c}: {str(e)}") results.append(VendorOutput( Vendor_ID__c=vendor.Vendor_ID__c, Score__c=0, Rationale__c=f"Error: {str(e)}", Alert_Flag__c=True )) return results # Health check endpoint @app.get("/") async def root(): logger.info("Root endpoint accessed") return {"message": "Vendor Scoring API is running"} # Start Uvicorn server if __name__ == "__main__": port = int(os.getenv("PORT", 7860)) logger.info(f"Starting server on port {port}") uvicorn.run(app, host="0.0.0.0", port=port, log_level="info")