File size: 5,007 Bytes
16b6f84
 
e2483f2
16b6f84
 
3f5c978
 
16b6f84
 
e2483f2
16b6f84
 
 
 
 
e2483f2
16b6f84
 
 
 
 
 
e2483f2
16b6f84
 
 
 
 
 
 
 
 
e2483f2
 
16b6f84
 
 
 
 
 
 
 
 
 
 
 
e2483f2
16b6f84
 
e2483f2
 
16b6f84
 
e2483f2
16b6f84
e2483f2
16b6f84
e2483f2
 
 
16b6f84
 
e2483f2
16b6f84
 
 
 
 
 
 
 
 
 
 
 
e2483f2
 
16b6f84
 
 
 
 
 
 
 
 
 
e2483f2
16b6f84
 
 
e2483f2
16b6f84
 
 
 
e2483f2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16b6f84
 
e2483f2
3f5c978
 
e2483f2
3f5c978
e2483f2
 
 
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
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")