product-return-api / function.py
Silvio0's picture
Upload 3 files
e9ad1e1 verified
Raw
History Blame Contribute Delete
7.61 kB
# ==============================================================================
# 1. IMPORT NECESSARY LIBRARIES
# ==============================================================================
import pandas as pd
import scipy
import lime
import lime.lime_tabular
import numpy as np
from datetime import date
# ==============================================================================
# 2. GLOBAL CONFIGURATION
# ==============================================================================
RANDOM_SEED = 42
TARGET_LABELS = ["Not Returned (0)", "Returned (1)"]
# ==============================================================================
# 3. DATA PREPARATION FUNCTIONS (FEATURE ENGINEERING)
# ==============================================================================
def prepare_data_pre(
customer_age: int,
product_category: str,
payment_method: str,
order_value_usd: float,
order_date: date
) -> pd.DataFrame:
"""
Transforms raw pre-delivery input parameters into a structured Pandas DataFrame.
Extracts temporal features from the order date.
"""
print("โณ [FEATURE ENG] Structuring raw Pre-Delivery inputs...")
# Wrap scalars in lists to correctly construct a single-row DataFrame
df_testing = pd.DataFrame({
"customer_age": [customer_age],
"product_category": [product_category],
"payment_method": [payment_method],
"order_value_usd": [order_value_usd],
"order_date": [order_date]
})
print("๐Ÿ“… [FEATURE ENG] Extracting temporal features from 'order_date'...")
df_testing["order_date"] = pd.to_datetime(df_testing["order_date"])
df_testing["day"] = df_testing["order_date"].dt.day
df_testing["month"] = df_testing["order_date"].dt.month
df_testing["week"] = df_testing["order_date"].dt.isocalendar().week.astype(int)
# Boolean flag: True if Monday-Friday, False if Saturday/Sunday
df_testing["working_day"] = df_testing["order_date"].dt.day_of_week < 5
# Drop the original datetime column to prevent model errors
df_testing.drop("order_date", axis=1, inplace=True)
print(f"โœ… [FEATURE ENG] Pre-Delivery DataFrame ready. Shape: {df_testing.shape}")
return df_testing
def prepare_data_post(
customer_age: int,
product_category: str,
payment_method: str,
order_value_usd: float,
delivery_time_days: int,
customer_rating: float,
order_date: date
) -> pd.DataFrame:
"""
Transforms raw post-delivery input parameters into a structured Pandas DataFrame.
Includes additional post-delivery metrics (delivery time and rating).
"""
print("โณ [FEATURE ENG] Structuring raw Post-Delivery inputs...")
df_testing = pd.DataFrame({
"customer_age": [customer_age],
"product_category": [product_category],
"payment_method": [payment_method],
"order_value_usd": [order_value_usd],
"delivery_time_days": [delivery_time_days],
"customer_rating": [customer_rating],
"order_date": [order_date]
})
print("๐Ÿ“… [FEATURE ENG] Extracting temporal features from 'order_date'...")
df_testing["order_date"] = pd.to_datetime(df_testing["order_date"])
df_testing["day"] = df_testing["order_date"].dt.day
df_testing["month"] = df_testing["order_date"].dt.month
df_testing["week"] = df_testing["order_date"].dt.isocalendar().week.astype(int)
df_testing["working_day"] = df_testing["order_date"].dt.day_of_week < 5
df_testing.drop("order_date", axis=1, inplace=True)
print(f"โœ… [FEATURE ENG] Post-Delivery DataFrame ready. Shape: {df_testing.shape}")
return df_testing
# ==============================================================================
# 4. INFERENCE FUNCTION (PREDICTION)
# ==============================================================================
def predict(best_model, df_testing: pd.DataFrame, target_labels: list = TARGET_LABELS) -> tuple:
"""
Executes model inference on the prepared DataFrame.
Extracts the predicted class, return probability, and overall confidence for a single instance.
"""
print("๐Ÿง  [INFERENCE] Executing model prediction...")
# Generate predictions and probabilities using the loaded model
y_pred = best_model.predict(df_testing)
y_pred_proba = best_model.predict_proba(df_testing)
# Map the numeric prediction to the corresponding string label
result = [target_labels[pred] for pred in y_pred]
# Extract the specific values for the single transaction (index 0)
prediction = result[0]
returned_proba = ((y_pred_proba[:, 1] * 100).round(2).astype(str) + '%')[0]
prediction_conf = ((y_pred_proba.max(axis=1) * 100).round(2).astype(str) + '%')[0]
# Log the extracted metrics to the terminal for observability
print(f"๐ŸŽฏ [INFERENCE] Result: {prediction} | Prob: {returned_proba} | Conf: {prediction_conf}")
return prediction, returned_proba, prediction_conf
# ==============================================================================
# 5. EXPLAINABILITY FUNCTION (LIME)
# ==============================================================================
def explain(best_model, X_train_processed: np.ndarray, df_testing: pd.DataFrame, target_label: list = TARGET_LABELS) -> str:
"""
Generates a LIME (Local Interpretable Model-agnostic Explanations) HTML output.
Bypasses background data transformation as X_train_processed is already encoded.
"""
print("๐Ÿ” [XAI] Initializing LIME Explainer...")
# Extract the preprocessing step and the machine learning model from the pipeline
try:
preprocessor = best_model.named_steps["Preprocessor"]
ml_model = best_model.named_steps["Model"]
except KeyError as e:
raise KeyError(f"๐Ÿšจ [PIPELINE ERROR] Expected steps 'Preprocessor' and 'Model' not found in pipeline. Details: {e}")
print("๐Ÿงฎ [XAI] Loading pre-processed background training data...")
# Ensure the background data is a dense array for LIME compatibility
if scipy.sparse.issparse(X_train_processed):
X_train_processed = X_train_processed.toarray()
# Extract feature names directly from the preprocessor to label the LIME output
features = preprocessor.get_feature_names_out()
# Initialize the Tabular Explainer with the pre-processed background data
explainer = lime.lime_tabular.LimeTabularExplainer(
training_data=X_train_processed,
feature_names=features,
class_names=target_label,
mode="classification",
random_state=RANDOM_SEED
)
print("๐Ÿ“Š [XAI] Processing single transaction instance for explanation...")
# Isolate the single row to explain and apply the preprocessing pipeline
transaction_raw = df_testing.iloc[[0]]
processed_data = preprocessor.transform(transaction_raw)
# Convert the processed single instance to a dense array if necessary
if scipy.sparse.issparse(processed_data):
processed_data = processed_data.toarray()
# Flatten to a 1D array as required by LIME's explain_instance method
transaction_data = processed_data[0]
print("๐Ÿงฉ [XAI] Generating instance explanation (Top 10 features)...")
# Generate the explanation using the isolated ML model's predict_proba method
explanation = explainer.explain_instance(
data_row=transaction_data,
predict_fn=ml_model.predict_proba,
num_features=10
)
print("โœ… [XAI] LIME HTML generation complete.")
return explanation.as_html()