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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +70 -136
app.py CHANGED
@@ -5,173 +5,107 @@ import numpy as np
5
  import json
6
  import math
7
  import xgboost as xgb
8
- import logging
9
  import os
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 JSON files
26
- try:
27
- with open("tile_catalog.json", "r", encoding="utf-8") as f:
28
- tile_catalog = json.load(f)
29
-
30
- with open("tile_sizes.json", "r", encoding="utf-8") as f:
31
- tile_sizes = json.load(f)
32
- except Exception as e:
33
- app.logger.error(f"❌ Error loading tile data: {e}")
34
- tile_catalog = []
35
- tile_sizes = {}
36
 
37
- @app.route('/')
38
- def serve_index():
39
- return send_from_directory('.', 'index.html')
40
 
41
- @app.route('/recommend', methods=['POST'])
42
  def recommend():
43
  try:
44
  data = request.get_json()
45
- required_fields = ['tile_type', 'coverage', 'area', 'price_range']
46
- if not all(field in data for field in required_fields):
47
- return jsonify({"error": "Missing required fields"}), 400
48
-
49
- tile_type = data['tile_type'].lower()
50
- if tile_type not in ['floor', 'wall']:
51
- return jsonify({"error": "Invalid tile type. Use 'floor' or 'wall'"}), 400
52
-
53
- validate_positive_number(data['coverage'], "coverage")
54
- validate_positive_number(data['area'], "area")
55
- if (not isinstance(data['price_range'], list) or
56
- len(data['price_range']) != 2 or
57
- data['price_range'][0] < 0 or
58
- 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
  except Exception as e:
83
- app.logger.error(f"Error in /recommend: {str(e)}")
84
- return jsonify({"error": "Internal server error"}), 500
85
 
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
- def prepare_features(data):
128
- tile_type_num = 0 if data['tile_type'] == 'floor' else 1
129
- price_per_sqft = data['price_range'][1] / data['coverage']
130
- budget_efficiency = data['coverage'] / data['price_range'][1]
131
- return np.array([[
132
- tile_type_num,
133
- data['area'],
134
- data['coverage'],
135
- data['price_range'][0],
136
- data['price_range'][1],
137
- price_per_sqft,
138
- budget_efficiency
139
- ]])
140
-
141
- def filter_products(tile_type, min_price, max_price, preferred_sizes, min_score=0.5):
142
  filtered = []
143
  for product in tile_catalog:
144
- if (product['type'].lower() == tile_type and
145
- min_price <= product['price'] <= max_price and
146
- (not preferred_sizes or product['size'] in preferred_sizes)):
147
-
148
- price_score = 1 - ((product['price'] - min_price) / (max_price - min_price + 1e-6))
149
- size_score = 1 if not preferred_sizes or product['size'] in preferred_sizes else 0.5
150
- product_score = (price_score + size_score) / 2
151
-
152
- if product_score >= min_score:
153
- filtered.append({
154
- **product,
155
- "recommendation_score": round(product_score, 2)
156
- })
157
-
158
- return sorted(filtered, key=lambda x: x['recommendation_score'], reverse=True)
159
-
160
- def calculate_requirements(area, coverage):
161
- min_tiles = math.ceil(area / coverage)
162
- suggested_tiles = math.ceil(min_tiles * 1.1)
163
- return {
164
- "minimum_tiles": min_tiles,
165
- "suggested_tiles": suggested_tiles,
166
- "estimated_cost_range": [
167
- round(area * 3, 2),
168
- round(area * 10, 2)
169
- ]
170
- }
171
-
172
- def validate_positive_number(value, field):
173
- if not isinstance(value, (int, float)) or value <= 0:
174
- raise ValueError(f"{field} must be a positive number")
175
-
176
- if __name__ == '__main__':
177
- app.run(host='0.0.0.0', port=7860, debug=False)
 
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)