Tile / app.py
Coots's picture
Update app.py
ea92343 verified
raw
history blame
5.3 kB
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
import joblib
import numpy as np
import json
import math
import xgboost as xgb
import re
app = Flask(__name__, static_folder='.', static_url_path='/')
CORS(app)
# Load ML 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
# 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", [1, 10000])
preferred_sizes = data.get("preferred_sizes", [])
if area <= 0 or coverage <= 0:
return jsonify({"error": "Please enter valid area and coverage values."}), 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
products = filter_products(tile_type, price_range, preferred_sizes)
return jsonify({
"recommendation_score": round(float(score), 3),
"recommended_products": products[:4],
"total_matches": len(products),
})
except Exception as e:
print("❌ Error in /recommend:", str(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
match_found = False
for pref in preferred_sizes:
if similar_size_mm(p["size"], pref):
match_found = True
break
if not match_found:
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_mm(a, b, tolerance=10):
try:
w1, h1 = map(int, a.lower().replace("mm", "").split("x"))
w2, h2 = map(int, 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)