Coots commited on
Commit
80471bd
Β·
verified Β·
1 Parent(s): 842b288

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +31 -64
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 re
9
 
10
  app = Flask(__name__, static_folder='.', static_url_path='/')
11
  CORS(app)
12
 
13
- # Load ML models
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
- # Tile product catalog (hardcoded, replacing tile_catalog.json)
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", [1, 10000])
54
  preferred_sizes = data.get("preferred_sizes", [])
55
 
56
  if area <= 0 or coverage <= 0:
57
- return jsonify({"error": "Please enter valid area and coverage values."}), 400
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
- products = filter_products(tile_type, price_range, preferred_sizes)
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
- "tile_type": tile_type,
107
- "area_sqft": area,
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("❌ Error in /calculate:", str(e))
116
- return jsonify({"error": "An error occurred. Please check your input values."}), 500
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 product in tile_catalog:
129
- if product["type"].lower() != tile_type:
130
  continue
131
- if not (min_price <= product["price"] <= max_price):
132
  continue
133
- if preferred_sizes and product["size"] not in preferred_sizes:
 
 
 
 
 
 
134
  continue
135
- price_score = 1 - (product["price"] - min_price) / (max_price - min_price + 1e-6)
136
- size_score = 1 if product["size"] in preferred_sizes else 0.5
137
- score = round((price_score + size_score) / 2, 2)
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)