telco-churn-app / src /serving /validator.py
ziadkassem's picture
feat: Dockerize app, fix XGBoost dependencies, and add Gradio UI
061507f
"""
API-specific validation for ChurnRequest
"""
from typing import List, Tuple
from src.serving.schema import ChurnRequest
def validate_churn_request(request: ChurnRequest) -> Tuple[bool, List[str]]:
"""
Validate a ChurnRequest object against business rules.
Returns:
Tuple[bool, List[str]]: (is_valid, list_of_errors)
"""
errors = []
allowed_gender = ["Male", "Female"]
if request.gender not in allowed_gender:
errors.append(f"Invalid gender: '{request.gender}'. Must be one of: {allowed_gender}")
if request.SeniorCitizen not in [0, 1]:
errors.append(f"Invalid SeniorCitizen: {request.SeniorCitizen}. Must be 0 or 1")
if not (0 <= request.tenure <= 72):
errors.append(f"Invalid tenure: {request.tenure}. Must be between 0 and 72")
binary_fields = {
"Partner": request.Partner,
"Dependents": request.Dependents,
"PhoneService": request.PhoneService,
"PaperlessBilling": request.PaperlessBilling
}
for field_name, field_value in binary_fields.items():
if field_value not in ["Yes", "No"]:
errors.append(f"Invalid {field_name}: '{field_value}'. Must be 'Yes' or 'No'")
optional_binary_fields = {
"MultipleLines": request.MultipleLines,
"OnlineSecurity": request.OnlineSecurity,
"OnlineBackup": request.OnlineBackup,
"DeviceProtection": request.DeviceProtection,
"TechSupport": request.TechSupport,
"StreamingTV": request.StreamingTV,
"StreamingMovies": request.StreamingMovies
}
for field_name, field_value in optional_binary_fields.items():
if field_value is not None and field_value not in ["Yes", "No", "No phone service", "No internet service"]:
errors.append(
f"Invalid {field_name}: '{field_value}'. "
f"Must be 'Yes', 'No', 'No phone service', or 'No internet service'"
)
allowed_internet = ["DSL", "Fiber optic", "No"]
if request.InternetService not in allowed_internet:
errors.append(
f"Invalid InternetService: '{request.InternetService}'. "
f"Must be one of: {allowed_internet}"
)
allowed_contract = ["Month-to-month", "One year", "Two year"]
if request.Contract not in allowed_contract:
errors.append(
f"Invalid Contract: '{request.Contract}'. "
f"Must be one of: {allowed_contract}"
)
allowed_payment = [
"Electronic check",
"Mailed check",
"Bank transfer (automatic)",
"Credit card (automatic)"
]
if request.PaymentMethod not in allowed_payment:
errors.append(
f"Invalid PaymentMethod: '{request.PaymentMethod}'. "
f"Must be one of: {allowed_payment}"
)
if request.MonthlyCharges < 0:
errors.append(f"MonthlyCharges cannot be negative: {request.MonthlyCharges}")
if request.TotalCharges < 0:
errors.append(f"TotalCharges cannot be negative: {request.TotalCharges}")
if request.TotalCharges < request.MonthlyCharges:
errors.append(
f"TotalCharges ({request.TotalCharges}) must be >= MonthlyCharges ({request.MonthlyCharges})"
)
if request.InternetService == "No":
internet_dependent_fields = {
"OnlineSecurity": request.OnlineSecurity,
"OnlineBackup": request.OnlineBackup,
"DeviceProtection": request.DeviceProtection,
"TechSupport": request.TechSupport,
"StreamingTV": request.StreamingTV,
"StreamingMovies": request.StreamingMovies
}
for field_name, field_value in internet_dependent_fields.items():
if field_value not in [None, "No internet service"]:
errors.append(
f"Cannot have InternetService='No' and {field_name}='{field_value}'. "
f"{field_name} must be None or 'No internet service'"
)
if request.PhoneService == "No" and request.MultipleLines not in [None, "No phone service"]:
errors.append(
f"Cannot have PhoneService='No' and MultipleLines='{request.MultipleLines}'. "
f"MultipleLines must be None or 'No phone service'"
)
return len(errors) == 0, errors