Coots commited on
Commit
857baf5
·
verified ·
1 Parent(s): cef6a45

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +76 -86
app.py CHANGED
@@ -8,8 +8,7 @@ import os
8
  import xgboost as xgb
9
  import logging
10
 
11
- # === Flask Setup ===
12
- app = Flask(__name__, static_folder='.', static_url_path='')
13
  CORS(app)
14
  logging.basicConfig(level=logging.INFO)
15
 
@@ -30,96 +29,37 @@ with open("tile_catalog.json", "r", encoding="utf-8") as f:
30
  with open("tile_sizes.json", "r", encoding="utf-8") as f:
31
  tile_sizes = json.load(f)
32
 
33
- # === Serve index.html ===
34
  @app.route('/')
35
  def serve_index():
36
- return app.send_static_file('index.html')
37
 
38
- # === Serve static assets (if any JS, CSS, images) ===
39
  @app.route('/<path:path>')
40
- def serve_static_files(path):
41
  return send_from_directory('.', path)
42
 
43
- # === Calculate tiles endpoint ===
44
- @app.route('/calculate', methods=['POST'])
45
- def calculate():
46
  try:
47
  data = request.get_json()
48
- for field in ['tile_type', 'length', 'width', 'tile_size']:
49
- if field not in data:
50
- return jsonify({"error": f"Missing field: {field}"}), 400
51
 
52
  tile_type = data['tile_type'].lower()
53
- length = float(data['length'])
54
- width = float(data['width'])
55
- validate_positive_number(length, "length")
56
- validate_positive_number(width, "width")
57
-
58
  if tile_type not in ['floor', 'wall']:
59
  return jsonify({"error": "Invalid tile type"}), 400
60
- if data['tile_size'] not in tile_sizes:
61
- return jsonify({"error": "Invalid tile size"}), 400
62
-
63
- area = length * width
64
- tile_info = tile_sizes[data['tile_size']]
65
- area_per_tile = tile_info['length'] * tile_info['width']
66
- tiles_needed = math.ceil((area / area_per_tile) * 1.1)
67
- tiles_per_box = tile_info.get('tiles_per_box', 10)
68
- boxes_needed = math.ceil(tiles_needed / tiles_per_box)
69
-
70
- matching_products = [
71
- {
72
- **p,
73
- "link": p.get("url", "#")
74
- }
75
- for p in tile_catalog
76
- if p['type'].lower() == tile_type and p['size'] == data['tile_size']
77
- ]
78
-
79
- return jsonify({
80
- "tile_type": tile_type,
81
- "tile_size": data['tile_size'],
82
- "length": round(length, 2),
83
- "width": round(width, 2),
84
- "area": round(area, 2),
85
- "tiles_needed": tiles_needed,
86
- "boxes_needed": boxes_needed,
87
- "matching_products": matching_products[:5],
88
- "total_matches": len(matching_products)
89
- })
90
 
91
- except Exception as e:
92
- app.logger.error(f"Error in /calculate: {str(e)}")
93
- return jsonify({"error": "Internal server error"}), 500
94
-
95
- # === Recommend endpoint ===
96
- @app.route('/recommend', methods=['POST'])
97
- def recommend():
98
- try:
99
- data = request.get_json()
100
- required_fields = ['tile_type', 'coverage', 'length', 'width', 'price_range']
101
- for field in required_fields:
102
- if field not in data:
103
- return jsonify({"error": f"Missing field: {field}"}), 400
104
 
105
- tile_type = data['tile_type'].lower()
106
- length = float(data['length'])
107
- width = float(data['width'])
108
- validate_positive_number(length, "length")
109
- validate_positive_number(width, "width")
110
- area = length * width
111
- validate_positive_number(area, "area")
112
- coverage = float(data['coverage'])
113
- validate_positive_number(coverage, "coverage")
114
-
115
- if not isinstance(data['price_range'], list) or len(data['price_range']) != 2:
116
  return jsonify({"error": "Invalid price range"}), 400
117
 
118
- features = prepare_features({
119
- **data,
120
- "area": area
121
- })
122
-
123
  xgb_pred = xgb_model.predict(xgb.DMatrix(features))[0]
124
  rf_pred = rf.predict_proba(features)[0][1]
125
  combined_score = (xgb_pred + rf_pred) / 2
@@ -132,25 +72,76 @@ def recommend():
132
  min_score=0.5
133
  )
134
 
135
- return jsonify({
136
  "recommendation_score": round(float(combined_score), 3),
 
137
  "recommended_products": recommended_products[:5],
138
- "calculation": calculate_requirements(area, coverage)
139
- })
 
140
 
141
  except Exception as e:
142
  app.logger.error(f"Error in /recommend: {str(e)}")
143
  return jsonify({"error": "Internal server error"}), 500
144
 
145
- # === Helper Functions ===
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  def prepare_features(data):
147
  tile_type_num = 0 if data['tile_type'] == 'floor' else 1
148
  price_per_sqft = data['price_range'][1] / data['coverage']
149
  budget_efficiency = data['coverage'] / data['price_range'][1]
150
 
151
- return np.array([[tile_type_num, data['area'], data['coverage'],
152
- data['price_range'][0], data['price_range'][1],
153
- price_per_sqft, budget_efficiency]])
 
 
 
 
 
 
154
 
155
  def filter_products(tile_type, min_price, max_price, preferred_sizes, min_score=0.5):
156
  filtered = []
@@ -166,8 +157,7 @@ def filter_products(tile_type, min_price, max_price, preferred_sizes, min_score=
166
  if product_score >= min_score:
167
  filtered.append({
168
  **product,
169
- "recommendation_score": round(product_score, 2),
170
- "link": product.get("url", "#")
171
  })
172
 
173
  return sorted(filtered, key=lambda x: x['recommendation_score'], reverse=True)
@@ -188,6 +178,6 @@ def validate_positive_number(value, field):
188
  if not isinstance(value, (int, float)) or value <= 0:
189
  raise ValueError(f"{field} must be a positive number")
190
 
191
- # === Start the server ===
192
  if __name__ == '__main__':
193
  app.run(host='0.0.0.0', port=7860, debug=False)
 
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
 
 
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
 
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 = []
 
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)
 
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)