Update app.py
Browse files
app.py
CHANGED
@@ -2,15 +2,13 @@ from flask import Flask, request, jsonify, send_from_directory
|
|
2 |
from flask_cors import CORS
|
3 |
import joblib
|
4 |
import numpy as np
|
5 |
-
import json
|
6 |
-
import math
|
7 |
import xgboost as xgb
|
8 |
-
import
|
9 |
|
10 |
app = Flask(__name__, static_folder='.', static_url_path='/')
|
11 |
CORS(app)
|
12 |
|
13 |
-
# Load
|
14 |
try:
|
15 |
rf = joblib.load("rf_model.pkl")
|
16 |
xgb_model = xgb.Booster()
|
@@ -20,7 +18,7 @@ except Exception as e:
|
|
20 |
print(f"β Error loading models: {e}")
|
21 |
raise e
|
22 |
|
23 |
-
#
|
24 |
tile_catalog = [
|
25 |
{"name": "Travertino Light Beige", "type": "Floor", "price": 780, "coverage": 15.5, "size": "1200x1200 MM", "url": "https://arqonz.ae/products/6775"},
|
26 |
{"name": "Marquina Glossy Black", "type": "Wall", "price": 620, "coverage": 12.0, "size": "600x600 MM", "url": "https://arqonz.ae/products/6776"},
|
@@ -36,7 +34,6 @@ tile_catalog = [
|
|
36 |
{"name": "Classic White Glossy", "type": "Wall", "price": 580, "coverage": 12.0, "size": "600x600 MM", "url": "https://arqonz.ae/products/89"},
|
37 |
{"name": "Dark Stone Matte", "type": "Floor", "price": 830, "coverage": 15.0, "size": "1200x600 MM", "url": "https://arqonz.ae/products/107"},
|
38 |
{"name": "Polished Statuario", "type": "Wall", "price": 810, "coverage": 10.5, "size": "600x1200 MM", "url": "https://arqonz.ae/products/116"}
|
39 |
-
# NOTE: Truncated for brevity β continue adding your full product list here
|
40 |
]
|
41 |
|
42 |
@app.route("/")
|
@@ -50,70 +47,27 @@ def recommend():
|
|
50 |
tile_type = data.get("tile_type", "").strip().lower()
|
51 |
coverage = float(data.get("coverage", 1))
|
52 |
area = float(data.get("area", 1))
|
53 |
-
price_range = data.get("price_range", [
|
54 |
preferred_sizes = data.get("preferred_sizes", [])
|
55 |
|
56 |
if area <= 0 or coverage <= 0:
|
57 |
-
return jsonify({"error": "
|
58 |
|
59 |
features = prepare_features(tile_type, coverage, area, price_range)
|
60 |
xgb_pred = xgb_model.predict(xgb.DMatrix(features))[0]
|
61 |
rf_pred = rf.predict_proba(features)[0][1]
|
62 |
score = (xgb_pred + rf_pred) / 2
|
63 |
|
64 |
-
|
65 |
-
return jsonify({
|
66 |
-
"recommendation_score": round(float(score), 3),
|
67 |
-
"recommended_products": products[:4],
|
68 |
-
"total_matches": len(products),
|
69 |
-
})
|
70 |
-
except Exception as e:
|
71 |
-
print("β Error in /recommend:", str(e))
|
72 |
-
return jsonify({"error": "Server error"}), 500
|
73 |
-
|
74 |
-
@app.route("/calculate", methods=["POST"])
|
75 |
-
def calculate():
|
76 |
-
try:
|
77 |
-
data = request.get_json()
|
78 |
-
tile_type = data.get("tile_type", "").strip().lower()
|
79 |
-
area = float(data.get("area", 0))
|
80 |
-
tile_size_raw = data.get("tile_size", "").strip()
|
81 |
-
|
82 |
-
if not tile_type:
|
83 |
-
return jsonify({"error": "Please select a tile type (e.g., floor, wall)."}), 400
|
84 |
-
if area <= 0:
|
85 |
-
return jsonify({"error": "Area must be greater than 0."}), 400
|
86 |
-
|
87 |
-
match = re.match(r"(\d+(\.\d+)?)\s*(ft|feet)?\s*[xXΓ*]\s*(\d+(\.\d+)?)\s*(ft|feet)?", tile_size_raw)
|
88 |
-
if not match:
|
89 |
-
return jsonify({"error": f"Invalid tile size format: '{tile_size_raw}'. Please enter like '2 x 2 ft'."}), 400
|
90 |
-
|
91 |
-
length_ft = float(match.group(1))
|
92 |
-
width_ft = float(match.group(4))
|
93 |
-
if length_ft <= 0 or width_ft <= 0:
|
94 |
-
return jsonify({"error": "Tile dimensions must be greater than 0."}), 400
|
95 |
-
|
96 |
-
tile_area = length_ft * width_ft
|
97 |
-
tiles_needed = math.ceil((area / tile_area) * 1.1)
|
98 |
-
boxes = math.ceil(tiles_needed / 10)
|
99 |
-
|
100 |
-
matches = [
|
101 |
-
p for p in tile_catalog
|
102 |
-
if p["type"].lower() == tile_type
|
103 |
-
]
|
104 |
|
105 |
return jsonify({
|
106 |
-
"
|
107 |
-
"
|
108 |
-
"tile_size": f"{length_ft:.2f} ft x {width_ft:.2f} ft",
|
109 |
-
"tiles_needed": tiles_needed,
|
110 |
-
"boxes_needed": boxes,
|
111 |
-
"matching_products": matches[:3],
|
112 |
"total_matches": len(matches)
|
113 |
})
|
114 |
except Exception as e:
|
115 |
-
print("β
|
116 |
-
return jsonify({"error": "
|
117 |
|
118 |
def prepare_features(tile_type, coverage, area, price_range):
|
119 |
tile_type_num = 0 if tile_type == "floor" else 1
|
@@ -125,18 +79,31 @@ def prepare_features(tile_type, coverage, area, price_range):
|
|
125 |
def filter_products(tile_type, price_range, preferred_sizes):
|
126 |
min_price, max_price = price_range
|
127 |
filtered = []
|
128 |
-
for
|
129 |
-
if
|
130 |
continue
|
131 |
-
if not (min_price <=
|
132 |
continue
|
133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
continue
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
filtered.append({**product, "recommendation_score": score})
|
139 |
return sorted(filtered, key=lambda x: x["recommendation_score"], reverse=True)
|
140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
141 |
if __name__ == "__main__":
|
142 |
app.run(host="0.0.0.0", port=7860)
|
|
|
2 |
from flask_cors import CORS
|
3 |
import joblib
|
4 |
import numpy as np
|
|
|
|
|
5 |
import xgboost as xgb
|
6 |
+
import math
|
7 |
|
8 |
app = Flask(__name__, static_folder='.', static_url_path='/')
|
9 |
CORS(app)
|
10 |
|
11 |
+
# Load models
|
12 |
try:
|
13 |
rf = joblib.load("rf_model.pkl")
|
14 |
xgb_model = xgb.Booster()
|
|
|
18 |
print(f"β Error loading models: {e}")
|
19 |
raise e
|
20 |
|
21 |
+
# Hardcoded product catalog
|
22 |
tile_catalog = [
|
23 |
{"name": "Travertino Light Beige", "type": "Floor", "price": 780, "coverage": 15.5, "size": "1200x1200 MM", "url": "https://arqonz.ae/products/6775"},
|
24 |
{"name": "Marquina Glossy Black", "type": "Wall", "price": 620, "coverage": 12.0, "size": "600x600 MM", "url": "https://arqonz.ae/products/6776"},
|
|
|
34 |
{"name": "Classic White Glossy", "type": "Wall", "price": 580, "coverage": 12.0, "size": "600x600 MM", "url": "https://arqonz.ae/products/89"},
|
35 |
{"name": "Dark Stone Matte", "type": "Floor", "price": 830, "coverage": 15.0, "size": "1200x600 MM", "url": "https://arqonz.ae/products/107"},
|
36 |
{"name": "Polished Statuario", "type": "Wall", "price": 810, "coverage": 10.5, "size": "600x1200 MM", "url": "https://arqonz.ae/products/116"}
|
|
|
37 |
]
|
38 |
|
39 |
@app.route("/")
|
|
|
47 |
tile_type = data.get("tile_type", "").strip().lower()
|
48 |
coverage = float(data.get("coverage", 1))
|
49 |
area = float(data.get("area", 1))
|
50 |
+
price_range = data.get("price_range", [3, 10000])
|
51 |
preferred_sizes = data.get("preferred_sizes", [])
|
52 |
|
53 |
if area <= 0 or coverage <= 0:
|
54 |
+
return jsonify({"error": "Invalid area or coverage"}), 400
|
55 |
|
56 |
features = prepare_features(tile_type, coverage, area, price_range)
|
57 |
xgb_pred = xgb_model.predict(xgb.DMatrix(features))[0]
|
58 |
rf_pred = rf.predict_proba(features)[0][1]
|
59 |
score = (xgb_pred + rf_pred) / 2
|
60 |
|
61 |
+
matches = filter_products(tile_type, price_range, preferred_sizes)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
|
63 |
return jsonify({
|
64 |
+
"recommendation_score": round(float(score), 3),
|
65 |
+
"recommended_products": matches[:4],
|
|
|
|
|
|
|
|
|
66 |
"total_matches": len(matches)
|
67 |
})
|
68 |
except Exception as e:
|
69 |
+
print("β /recommend error:", e)
|
70 |
+
return jsonify({"error": "Server error"}), 500
|
71 |
|
72 |
def prepare_features(tile_type, coverage, area, price_range):
|
73 |
tile_type_num = 0 if tile_type == "floor" else 1
|
|
|
79 |
def filter_products(tile_type, price_range, preferred_sizes):
|
80 |
min_price, max_price = price_range
|
81 |
filtered = []
|
82 |
+
for p in tile_catalog:
|
83 |
+
if p["type"].lower() != tile_type:
|
84 |
continue
|
85 |
+
if not (min_price <= p["price"] <= max_price):
|
86 |
continue
|
87 |
+
|
88 |
+
size_match = False
|
89 |
+
for size in preferred_sizes:
|
90 |
+
if similar_size(p["size"], size):
|
91 |
+
size_match = True
|
92 |
+
break
|
93 |
+
if not size_match:
|
94 |
continue
|
95 |
+
|
96 |
+
price_score = 1 - (p["price"] - min_price) / (max_price - min_price + 1e-6)
|
97 |
+
filtered.append({**p, "recommendation_score": round(price_score, 2)})
|
|
|
98 |
return sorted(filtered, key=lambda x: x["recommendation_score"], reverse=True)
|
99 |
|
100 |
+
def similar_size(size_a, size_b, tolerance=10):
|
101 |
+
try:
|
102 |
+
w1, h1 = map(int, size_a.lower().replace("mm", "").split("x"))
|
103 |
+
w2, h2 = map(int, size_b.lower().replace("mm", "").split("x"))
|
104 |
+
return abs(w1 - w2) <= tolerance and abs(h1 - h2) <= tolerance
|
105 |
+
except:
|
106 |
+
return False
|
107 |
+
|
108 |
if __name__ == "__main__":
|
109 |
app.run(host="0.0.0.0", port=7860)
|