import numpy as np import pandas as pd from model import model_columns from schema import ShopperInput MONTH_ORDER = { "Jan": 1, "Feb": 2, "Mar": 3, "Apr": 4, "May": 5, "June": 6, "Jul": 7, "Aug": 8, "Sep": 9, "Oct": 10, "Nov": 11, "Dec": 12, } LOG_COLUMNS = [ "Administrative_Duration", "Informational_Duration", "ProductRelated_Duration", "TotalDuration", "ProductDurationPerPage", "AdminDurationPerPage", "InfoDurationPerPage", "AvgDurationPerPage", "ProductEngagement", "DurationWeightedExit", "DurationWeightedBounce", ] def create_model_features(input_data: ShopperInput) -> pd.DataFrame: """Create the same engineered features that were used during training.""" df = pd.DataFrame([input_data.model_dump()]) base_numeric_columns = [ "Administrative", "Administrative_Duration", "Informational", "Informational_Duration", "ProductRelated", "ProductRelated_Duration", "BounceRates", "ExitRates", "PageValues", "SpecialDay", ] for col in base_numeric_columns: df[col] = pd.to_numeric(df[col], errors="coerce") # Step 1: totals df["TotalDuration"] = ( df["Administrative_Duration"].fillna(0) + df["Informational_Duration"].fillna(0) + df["ProductRelated_Duration"].fillna(0) ) df["TotalPages"] = ( df["Administrative"].fillna(0) + df["Informational"].fillna(0) + df["ProductRelated"].fillna(0) ) # Step 2: averages and ratios df["ProductDurationPerPage"] = df["ProductRelated_Duration"] / df["ProductRelated"].replace(0, np.nan) df["AdminDurationPerPage"] = df["Administrative_Duration"] / df["Administrative"].replace(0, np.nan) df["InfoDurationPerPage"] = df["Informational_Duration"] / df["Informational"].replace(0, np.nan) df["ProductPageRatio"] = df["ProductRelated"] / df["TotalPages"].replace(0, np.nan) df["AdminPageRatio"] = df["Administrative"] / df["TotalPages"].replace(0, np.nan) df["InfoPageRatio"] = df["Informational"] / df["TotalPages"].replace(0, np.nan) df["ProductDurationRatio"] = df["ProductRelated_Duration"] / df["TotalDuration"].replace(0, np.nan) df["AdminDurationRatio"] = df["Administrative_Duration"] / df["TotalDuration"].replace(0, np.nan) df["InfoDurationRatio"] = df["Informational_Duration"] / df["TotalDuration"].replace(0, np.nan) # Step 3: engagement signals df["AvgDurationPerPage"] = df["TotalDuration"] / df["TotalPages"].replace(0, np.nan) df["ExitBounceGap"] = df["ExitRates"] - df["BounceRates"] df["ExitBounceRatio"] = df["ExitRates"] / df["BounceRates"].replace(0, np.nan) df["ProductEngagement"] = df["ProductRelated"] * df["ProductDurationPerPage"] df["DurationWeightedExit"] = df["TotalDuration"] * (1 - df["ExitRates"]) df["DurationWeightedBounce"] = df["TotalDuration"] * (1 - df["BounceRates"]) # Step 4: month features df["MonthNum"] = df["Month"].map(MONTH_ORDER) df["IsHolidaySeason"] = df["Month"].isin(["Nov", "Dec"]).astype(int) # Step 5: log-transform skewed duration features for col in LOG_COLUMNS: df[col + "_log"] = np.log1p(df[col].clip(lower=0)) return df[model_columns]