from flask import Flask, request, jsonify, send_from_directory from flask_cors import CORS import joblib import numpy as np import xgboost as xgb import math app = Flask(__name__, static_folder='.', static_url_path='/') CORS(app) # Load models try: rf = joblib.load("rf_model.pkl") xgb_model = xgb.Booster() xgb_model.load_model("xgb_model.json") print("✅ Models loaded successfully.") except Exception as e: print(f"❌ Error loading models: {e}") raise e # Hardcoded product catalog tile_catalog = [ {"name": "Travertino Light Beige", "type": "Floor", "price": 780, "coverage": 15.5, "size": "1200x1200 MM", "url": "https://arqonz.ae/products/6775"}, {"name": "Marquina Glossy Black", "type": "Wall", "price": 620, "coverage": 12.0, "size": "600x600 MM", "url": "https://arqonz.ae/products/6776"}, {"name": "Carrara White Polished", "type": "Floor", "price": 1100, "coverage": 23.23, "size": "1800x1200 MM", "url": "https://arqonz.ae/products/103"}, {"name": "Noir Marble Effect", "type": "Wall", "price": 550, "coverage": 6.89, "size": "800x800 MM", "url": "https://arqonz.ae/products/104"}, {"name": "Sandstone Beige Matte", "type": "Floor", "price": 670, "coverage": 13.78, "size": "1600x800 MM", "url": "https://arqonz.ae/products/6644"}, {"name": "Onyx Mist Polished", "type": "Wall", "price": 890, "coverage": 15.5, "size": "1200x1200 MM", "url": "https://arqonz.ae/products/6880"}, {"name": "Terrazzo Pearl", "type": "Floor", "price": 720, "coverage": 7.75, "size": "1200x600 MM", "url": "https://arqonz.ae/products/6878"}, {"name": "Statuario Silver", "type": "Wall", "price": 660, "coverage": 3.87, "size": "600x600 MM", "url": "https://arqonz.ae/products/6883"}, {"name": "Grigio Urban", "type": "Floor", "price": 500, "coverage": 2.52, "size": "1200x195 MM", "url": "https://arqonz.ae/products/6653"}, {"name": "Calacatta Nero", "type": "Wall", "price": 740, "coverage": 10.33, "size": "800x1200 MM", "url": "https://arqonz.ae/products/6885"}, {"name": "Ivory Rock Slate", "type": "Floor", "price": 690, "coverage": 13.0, "size": "1200x600 MM", "url": "https://arqonz.ae/products/6777"}, {"name": "Classic White Glossy", "type": "Wall", "price": 580, "coverage": 12.0, "size": "600x600 MM", "url": "https://arqonz.ae/products/89"}, {"name": "Dark Stone Matte", "type": "Floor", "price": 830, "coverage": 15.0, "size": "1200x600 MM", "url": "https://arqonz.ae/products/107"}, {"name": "Polished Statuario", "type": "Wall", "price": 810, "coverage": 10.5, "size": "600x1200 MM", "url": "https://arqonz.ae/products/116"} ] @app.route("/") def index(): return send_from_directory(".", "index.html") @app.route("/recommend", methods=["POST"]) def recommend(): try: data = request.get_json() tile_type = data.get("tile_type", "").strip().lower() coverage = float(data.get("coverage", 1)) area = float(data.get("area", 1)) price_range = data.get("price_range", [3, 10000]) preferred_sizes = data.get("preferred_sizes", []) if area <= 0 or coverage <= 0: return jsonify({"error": "Invalid area or coverage"}), 400 features = prepare_features(tile_type, coverage, area, price_range) xgb_pred = xgb_model.predict(xgb.DMatrix(features))[0] rf_pred = rf.predict_proba(features)[0][1] score = (xgb_pred + rf_pred) / 2 matches = filter_products(tile_type, price_range, preferred_sizes) return jsonify({ "recommendation_score": round(float(score), 3), "recommended_products": matches[:4], "total_matches": len(matches) }) except Exception as e: print("❌ /recommend error:", e) return jsonify({"error": "Server error"}), 500 def prepare_features(tile_type, coverage, area, price_range): tile_type_num = 0 if tile_type == "floor" else 1 min_price, max_price = price_range price_per_sqft = max_price / coverage efficiency = coverage / max_price return np.array([[tile_type_num, area, coverage, min_price, max_price, price_per_sqft, efficiency]]) def filter_products(tile_type, price_range, preferred_sizes): min_price, max_price = price_range filtered = [] for p in tile_catalog: if p["type"].lower() != tile_type: continue if not (min_price <= p["price"] <= max_price): continue size_match = False for size in preferred_sizes: if similar_size(p["size"], size): size_match = True break if not size_match: continue price_score = 1 - (p["price"] - min_price) / (max_price - min_price + 1e-6) filtered.append({**p, "recommendation_score": round(price_score, 2)}) return sorted(filtered, key=lambda x: x["recommendation_score"], reverse=True) def similar_size(size_a, size_b, tolerance=10): try: w1, h1 = map(int, size_a.lower().replace("mm", "").split("x")) w2, h2 = map(int, size_b.lower().replace("mm", "").split("x")) return abs(w1 - w2) <= tolerance and abs(h1 - h2) <= tolerance except: return False if __name__ == "__main__": app.run(host="0.0.0.0", port=7860)