Coots commited on
Commit
d12096d
Β·
verified Β·
1 Parent(s): 14765ac

Update app.py

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