import os import sys import joblib import pandas as pd from sklearn.pipeline import Pipeline from sklearn.preprocessing import FunctionTransformer, StandardScaler, OneHotEncoder from sklearn.compose import ColumnTransformer from sklearn.ensemble import RandomForestClassifier from custom_transformers import FeatureExtractor, cyclical_features def resource_path(relative_path): return os.path.join(getattr(sys, '_MEIPASS', os.path.abspath(".")), relative_path) MODEL_PATH = resource_path("model.pkl") _model = None _pipeline = None _last_model_mtime = None def build_pipeline(): cyclical_transform = FunctionTransformer(cyclical_features) preprocessor = ColumnTransformer([ ("num", StandardScaler(), ["price", "rating", "smart_score", "review_count"]), ("cat", OneHotEncoder(handle_unknown="ignore"), ["name"]) ], remainder="passthrough") pipeline = Pipeline([ ("extract", FeatureExtractor()), ("cyclical", cyclical_transform), ("preprocess", preprocessor) ]) return pipeline def load_model(): global _model, _last_model_mtime if not os.path.exists(MODEL_PATH): raise FileNotFoundError(f"Model file not found: {MODEL_PATH}") mtime = os.path.getmtime(MODEL_PATH) if _model is None or mtime > (_last_model_mtime or 0): _model = joblib.load(MODEL_PATH) _last_model_mtime = mtime def inference(input_df): global _pipeline load_model() if _pipeline is None: if not os.path.exists(PIPELINE_PATH): raise FileNotFoundError("Missing saved preprocessing pipeline (pipeline.pkl)") _pipeline = joblib.load(PIPELINE_PATH) processed = _pipeline.transform(input_df) # Only transform (no fit) return _model.predict(processed) PIPELINE_PATH = resource_path("pipeline.pkl") def retrain_model(df, target_col="decision"): global _pipeline pipeline = build_pipeline() _pipeline = pipeline # Save for reuse X = pipeline.fit_transform(df) y = df[target_col].map({"buy": 0, "wait": 1}).astype(int) model = RandomForestClassifier( n_estimators=100, random_state=42, max_depth=3, max_leaf_nodes=2, min_samples_split=3, min_samples_leaf=2, n_jobs=-1 ) model.fit(X, y) joblib.dump(model, MODEL_PATH) joblib.dump(pipeline, PIPELINE_PATH) print("✅ Model and pipeline saved.") if __name__ == "__main__": # dummy data data = { "name": ["Product A", "Product B", "Product C"], "price": [1000, 1500, 2000], "rating": [4.5, 4.0, 3.5], "smart_score": [0.05, 0.03, 0.02], "review_count": [100, 50, 30], "datetime": ["2023-10-01 12:00:00", "2023-10-02 13:00:00", "2023-10-03 14:00:00"], "decision": ["buy", "wait", "buy"] } df = pd.DataFrame(data) required_cols = [ "name", "price", "rating", "smart_score", "review_count", "datetime", ] if not all(col in df.columns for col in required_cols): raise ValueError("Missing required columns") df["date"] = df["datetime"] # match training format print("---- Retraining ----") retrain_model(df) print("Model and pipeline updated.") print("---- Inference ----") predictions = inference(df) print("Predictions:", predictions)