Coots commited on
Commit
0616b98
Β·
verified Β·
1 Parent(s): d8bd776

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +76 -140
app.py CHANGED
@@ -4,180 +4,116 @@ import joblib
4
  import numpy as np
5
  import json
6
  import math
7
- import os
8
  import xgboost as xgb
9
- import logging
10
 
11
- app = Flask(__name__, static_folder=".", static_url_path="")
12
  CORS(app)
13
- logging.basicConfig(level=logging.INFO)
14
 
15
- # === Load models ===
16
  try:
17
  rf = joblib.load("rf_model.pkl")
18
  xgb_model = xgb.Booster()
19
  xgb_model.load_model("xgb_model.json")
20
- app.logger.info("βœ… Models loaded successfully.")
21
  except Exception as e:
22
- app.logger.error(f"❌ Error loading models: {e}")
23
  raise e
24
 
25
- # === Load data ===
26
  with open("tile_catalog.json", "r", encoding="utf-8") as f:
27
  tile_catalog = json.load(f)
28
 
29
  with open("tile_sizes.json", "r", encoding="utf-8") as f:
30
- tile_sizes = json.load(f)
31
-
32
- # === Serve Frontend ===
33
- @app.route('/')
34
- def serve_index():
35
- return send_from_directory('.', 'index.html')
36
-
37
- @app.route('/<path:path>')
38
- def serve_static(path):
39
- return send_from_directory('.', path)
40
-
41
- # === Recommend Endpoint ===
42
- @app.route('/recommend', methods=['POST'])
43
- def recommend():
44
- try:
45
- data = request.get_json()
46
- required_fields = ['tile_type', 'coverage', 'area', 'price_range']
47
- if not all(field in data for field in required_fields):
48
- return jsonify({"error": "Missing required fields"}), 400
49
 
50
- tile_type = data['tile_type'].lower()
51
- if tile_type not in ['floor', 'wall']:
52
- return jsonify({"error": "Invalid tile type"}), 400
53
-
54
- validate_positive_number(data['coverage'], "coverage")
55
- validate_positive_number(data['area'], "area")
56
-
57
- if (not isinstance(data['price_range'], list) or len(data['price_range']) != 2 or
58
- data['price_range'][0] < 0 or data['price_range'][1] <= 0 or
59
- data['price_range'][0] >= data['price_range'][1]):
60
- return jsonify({"error": "Invalid price range"}), 400
61
-
62
- features = prepare_features(data)
63
- xgb_pred = xgb_model.predict(xgb.DMatrix(features))[0]
64
- rf_pred = rf.predict_proba(features)[0][1]
65
- combined_score = (xgb_pred + rf_pred) / 2
66
-
67
- recommended_products = filter_products(
68
- tile_type=tile_type,
69
- min_price=data['price_range'][0],
70
- max_price=data['price_range'][1],
71
- preferred_sizes=data.get('preferred_sizes', []),
72
- min_score=0.5
73
- )
74
-
75
- response = {
76
- "recommendation_score": round(float(combined_score), 3),
77
- "total_matches": len(recommended_products),
78
- "recommended_products": recommended_products[:5],
79
- "calculation": calculate_requirements(data['area'], data['coverage'])
80
- }
81
- return jsonify(response)
82
-
83
- except Exception as e:
84
- app.logger.error(f"Error in /recommend: {str(e)}")
85
- return jsonify({"error": "Internal server error"}), 500
86
 
87
- # === Calculate Endpoint ===
88
- @app.route('/calculate', methods=['POST'])
89
  def calculate():
90
  try:
91
  data = request.get_json()
92
- if 'tile_type' not in data or 'area' not in data or 'tile_length' not in data or 'tile_width' not in data:
93
- return jsonify({"error": "Missing required fields"}), 400
 
94
 
95
- tile_type = data['tile_type'].lower()
96
- if tile_type not in ['floor', 'wall']:
97
- return jsonify({"error": "Invalid tile type"}), 400
98
 
99
- validate_positive_number(data['area'], "area")
100
- validate_positive_number(data['tile_length'], "tile_length")
101
- validate_positive_number(data['tile_width'], "tile_width")
102
-
103
- # Calculate area per tile
104
- area_per_tile = data['tile_length'] * data['tile_width']
105
- tiles_needed = math.ceil((data['area'] / area_per_tile) * 1.1) # 10% buffer
106
- tiles_per_box = 10 # Assuming 10 tiles per box
107
- boxes_needed = math.ceil(tiles_needed / tiles_per_box)
108
 
109
  matching_products = [
110
  p for p in tile_catalog
111
- if p['type'].lower() == tile_type and p['size'] == f"{data['tile_length']}x{data['tile_width']}"
112
  ]
113
 
114
  return jsonify({
115
  "tile_type": tile_type,
116
- "area": data['area'],
117
- "tile_length": data['tile_length'],
118
- "tile_width": data['tile_width'],
119
  "tiles_needed": tiles_needed,
120
- "boxes_needed": boxes_needed,
121
  "matching_products": matching_products[:3],
122
  "total_matches": len(matching_products)
123
  })
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  except Exception as e:
126
- app.logger.error(f"Error in /calculate: {str(e)}")
127
- return jsonify({"error": "Internal server error"}), 500
128
-
129
- # === Utils ===
130
-
131
- def prepare_features(data):
132
- tile_type_num = 0 if data['tile_type'] == 'floor' else 1
133
- price_per_sqft = data['price_range'][1] / data['coverage']
134
- budget_efficiency = data['coverage'] / data['price_range'][1]
135
-
136
- return np.array([[
137
- tile_type_num,
138
- data['area'],
139
- data['coverage'],
140
- data['price_range'][0],
141
- data['price_range'][1],
142
- price_per_sqft,
143
- budget_efficiency
144
- ]])
145
-
146
- def filter_products(tile_type, min_price, max_price, preferred_sizes, min_score=0.5):
147
  filtered = []
148
  for product in tile_catalog:
149
- if (product['type'].lower() == tile_type and
150
- min_price <= product['price'] <= max_price and
151
- (not preferred_sizes or product['size'] in preferred_sizes)):
152
-
153
- price_score = 1 - ((product['price'] - min_price) / (max_price - min_price + 1e-6))
154
- size_score = 1 if not preferred_sizes or product['size'] in preferred_sizes else 0.5
155
- product_score = (price_score + size_score) / 2
156
-
157
- if product_score >= min_score:
158
- filtered.append({
159
- **product,
160
- "recommendation_score": round(product_score, 2)
161
- })
162
-
163
- return sorted(filtered, key=lambda x: x['recommendation_score'], reverse=True)
164
-
165
- def calculate_requirements(area, coverage):
166
- min_tiles = math.ceil(area / coverage)
167
- suggested_tiles = math.ceil(min_tiles * 1.1)
168
- return {
169
- "minimum_tiles": min_tiles,
170
- "suggested_tiles": suggested_tiles,
171
- "estimated_cost_range": [
172
- round(area * 3, 2),
173
- round(area * 10, 2)
174
- ]
175
- }
176
-
177
- def validate_positive_number(value, field):
178
- if not isinstance(value, (int, float)) or value <= 0:
179
- raise ValueError(f"{field} must be a positive number")
180
-
181
- # === Run App ===
182
- if __name__ == '__main__':
183
- app.run(host='0.0.0.0', port=7860, debug=False)
 
4
  import numpy as np
5
  import json
6
  import math
 
7
  import xgboost as xgb
8
+ import os
9
 
10
+ app = Flask(__name__, static_folder='.', static_url_path='/')
11
  CORS(app)
 
12
 
13
+ # Load models
14
  try:
15
  rf = joblib.load("rf_model.pkl")
16
  xgb_model = xgb.Booster()
17
  xgb_model.load_model("xgb_model.json")
18
+ print("βœ… Models loaded successfully.")
19
  except Exception as e:
20
+ print(f"❌ Error loading models: {e}")
21
  raise e
22
 
23
+ # Load tile catalog and tile size data
24
  with open("tile_catalog.json", "r", encoding="utf-8") as f:
25
  tile_catalog = json.load(f)
26
 
27
  with open("tile_sizes.json", "r", encoding="utf-8") as f:
28
+ tile_sizes_list = json.load(f)
29
+ tile_sizes = {item["label"]: item["area_sqft"] for item in tile_sizes_list}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ @app.route("/")
32
+ def index():
33
+ return send_from_directory(".", "index.html")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
+ @app.route("/calculate", methods=["POST"])
 
36
  def calculate():
37
  try:
38
  data = request.get_json()
39
+ tile_type = data.get("tile_type", "").lower()
40
+ area = float(data.get("area", 0))
41
+ tile_size_label = data.get("tile_size", "")
42
 
43
+ if tile_size_label not in tile_sizes:
44
+ return jsonify({"error": "Invalid tile size"}), 400
 
45
 
46
+ per_tile_area = tile_sizes[tile_size_label]
47
+ tiles_needed = math.ceil((area / per_tile_area) * 1.1)
48
+ boxes = math.ceil(tiles_needed / 10)
 
 
 
 
 
 
49
 
50
  matching_products = [
51
  p for p in tile_catalog
52
+ if p["type"].lower() == tile_type and p["size"] == tile_size_label
53
  ]
54
 
55
  return jsonify({
56
  "tile_type": tile_type,
57
+ "area": area,
58
+ "tile_size": tile_size_label,
 
59
  "tiles_needed": tiles_needed,
60
+ "boxes_needed": boxes,
61
  "matching_products": matching_products[:3],
62
  "total_matches": len(matching_products)
63
  })
64
+ except Exception as e:
65
+ print(f"❌ Error in /calculate: {e}")
66
+ return jsonify({"error": "Server error"}), 500
67
+
68
+ @app.route("/recommend", methods=["POST"])
69
+ def recommend():
70
+ try:
71
+ data = request.get_json()
72
+ tile_type = data.get("tile_type", "").lower()
73
+ area = float(data.get("area", 0))
74
+ coverage = float(data.get("coverage", 1))
75
+ price_range = data.get("price_range", [1, 100000])
76
+ preferred_sizes = data.get("preferred_sizes", [])
77
 
78
+ features = prepare_features(tile_type, coverage, area, price_range)
79
+ xgb_pred = xgb_model.predict(xgb.DMatrix(features))[0]
80
+ rf_pred = rf.predict_proba(features)[0][1]
81
+ score = (xgb_pred + rf_pred) / 2
82
+
83
+ recommended_products = filter_products(tile_type, price_range, preferred_sizes)
84
+
85
+ return jsonify({
86
+ "recommendation_score": round(float(score), 3),
87
+ "recommended_products": recommended_products[:4],
88
+ "total_matches": len(recommended_products)
89
+ })
90
  except Exception as e:
91
+ print(f"❌ Error in /recommend: {e}")
92
+ return jsonify({"error": "Server error"}), 500
93
+
94
+ def prepare_features(tile_type, coverage, area, price_range):
95
+ tile_type_num = 0 if tile_type == "floor" else 1
96
+ min_price, max_price = price_range
97
+ price_per_sqft = max_price / coverage if coverage else 0
98
+ efficiency = coverage / max_price if max_price else 0
99
+ return np.array([[tile_type_num, area, coverage, min_price, max_price, price_per_sqft, efficiency]])
100
+
101
+ def filter_products(tile_type, price_range, preferred_sizes):
102
+ min_price, max_price = price_range
 
 
 
 
 
 
 
 
 
103
  filtered = []
104
  for product in tile_catalog:
105
+ if product["type"].lower() != tile_type:
106
+ continue
107
+ if not (min_price <= product["price"] <= max_price):
108
+ continue
109
+ if preferred_sizes and product["size"] not in preferred_sizes:
110
+ continue
111
+
112
+ price_score = 1 - (product["price"] - min_price) / (max_price - min_price + 1e-6)
113
+ size_score = 1 if not preferred_sizes else (1 if product["size"] in preferred_sizes else 0.5)
114
+ score = round((price_score + size_score) / 2, 2)
115
+ filtered.append({**product, "recommendation_score": score})
116
+ return sorted(filtered, key=lambda x: x["recommendation_score"], reverse=True)
117
+
118
+ if __name__ == "__main__":
119
+ app.run(host="0.0.0.0", port=7860)