Coots commited on
Commit
211aa18
·
verified ·
1 Parent(s): bf75996

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +81 -58
app.py CHANGED
@@ -1,18 +1,29 @@
1
  from flask import Flask, request, jsonify
 
2
  import joblib
3
  import numpy as np
4
  import json
5
  import math
6
  import xgboost as xgb
 
7
 
8
  app = Flask(__name__)
 
 
 
 
9
 
10
  # Load models
11
- rf = joblib.load("rf_model.pkl")
12
- xgb_model = xgb.Booster()
13
- xgb_model.load_model("xgb_model.json") # Load the XGBoost model from JSON
 
 
 
 
 
14
 
15
- # Load tile catalog and sizes
16
  with open("tile_catalog.json", "r", encoding="utf-8") as f:
17
  tile_catalog = json.load(f)
18
 
@@ -34,44 +45,50 @@ def recommend():
34
  """
35
  try:
36
  data = request.get_json()
37
-
38
- # Validate input
39
  required_fields = ['tile_type', 'coverage', 'area', 'price_range']
40
  if not all(field in data for field in required_fields):
41
  return jsonify({"error": "Missing required fields"}), 400
42
-
43
  tile_type = data['tile_type'].lower()
44
  if tile_type not in ['floor', 'wall']:
45
  return jsonify({"error": "Invalid tile type. Use 'floor' or 'wall'"}), 400
46
-
47
- # Feature engineering for ML prediction
 
 
 
 
 
 
 
 
 
48
  features = prepare_features(data)
49
-
50
- # Get predictions from both models
51
- xgb_pred = xgb_model.predict(xgb.DMatrix(features))[0] # XGBoost prediction
52
- rf_pred = rf.predict_proba(features)[0][1] # Random Forest prediction
53
  combined_score = (xgb_pred + rf_pred) / 2
54
-
55
- # Filter products based on criteria
56
  recommended_products = filter_products(
57
  tile_type=tile_type,
58
  min_price=data['price_range'][0],
59
  max_price=data['price_range'][1],
60
  preferred_sizes=data.get('preferred_sizes', []),
61
- min_score=0.5 # Threshold for recommendation
62
  )
63
-
64
- # Prepare response
65
  response = {
66
  "recommendation_score": round(float(combined_score), 3),
67
- "recommended_products": recommended_products[:5], # Return top 5
 
68
  "calculation": calculate_requirements(data['area'], data['coverage'])
69
  }
70
-
71
  return jsonify(response)
72
-
73
  except Exception as e:
74
- return jsonify({"error": str(e)}), 500
 
75
 
76
  @app.route('/calculate', methods=['POST'])
77
  def calculate():
@@ -81,96 +98,102 @@ def calculate():
81
  {
82
  "tile_type": "floor"|"wall",
83
  "area": float,
84
- "tile_size": "12x12"|etc (from tile_sizes.json)
85
  }
86
  """
87
  try:
88
  data = request.get_json()
89
-
90
- # Validate input
91
  if 'tile_type' not in data or 'area' not in data or 'tile_size' not in data:
92
  return jsonify({"error": "Missing required fields"}), 400
93
-
94
  tile_type = data['tile_type'].lower()
95
  if tile_type not in ['floor', 'wall']:
96
  return jsonify({"error": "Invalid tile type"}), 400
97
-
98
  if data['tile_size'] not in tile_sizes:
99
  return jsonify({"error": "Invalid tile size"}), 400
100
-
101
- # Perform calculation
 
102
  tile_info = tile_sizes[data['tile_size']]
103
  area_per_tile = tile_info['length'] * tile_info['width']
104
- num_tiles = math.ceil((data['area'] / area_per_tile) * 1.1) # 10% buffer
105
- num_boxes = math.ceil(num_tiles / tile_info.get('tiles_per_box', 10))
106
-
107
- # Get matching products
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": num_tiles,
118
- "boxes_needed": num_boxes,
119
- "matching_products": matching_products[:3] # Return top 3 matches
 
120
  })
121
-
122
  except Exception as e:
123
- return jsonify({"error": str(e)}), 500
 
124
 
125
  def prepare_features(data):
126
- """Prepare feature vector for ML model prediction"""
127
  tile_type_num = 0 if data['tile_type'] == 'floor' else 1
128
- price_per_sqft = data['price_range'][1] / data['coverage'] # Using max price
129
  budget_efficiency = data['coverage'] / data['price_range'][1]
130
-
131
  return np.array([[
132
  tile_type_num,
133
  data['area'],
134
  data['coverage'],
135
- data['price_range'][0], # min price
136
- data['price_range'][1], # max price
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
- """Filter products based on criteria"""
143
  filtered = []
144
-
145
  for product in tile_catalog:
146
  if (product['type'].lower() == tile_type and
147
  min_price <= product['price'] <= max_price and
148
  (not preferred_sizes or product['size'] in preferred_sizes)):
149
-
150
- # Calculate a simple score (could be enhanced)
151
- price_score = 1 - ((product['price'] - min_price) / (max_price - min_price))
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
- # Sort by recommendation score
162
  return sorted(filtered, key=lambda x: x['recommendation_score'], reverse=True)
163
 
164
  def calculate_requirements(area, coverage):
165
- """Calculate basic requirements"""
 
 
166
  return {
167
- "minimum_tiles": math.ceil(area / coverage),
168
- "suggested_tiles": math.ceil((area / coverage) * 1.1), # 10% buffer
169
  "estimated_cost_range": [
170
- round(area * 3, 2), # $3/sqft (example)
171
- round(area * 10, 2) # $10/sqft (example)
172
  ]
173
  }
174
 
 
 
 
 
 
175
  if __name__ == '__main__':
176
  app.run(host='0.0.0.0', port=5000, debug=True)
 
1
  from flask import Flask, request, jsonify
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 logging
9
 
10
  app = Flask(__name__)
11
+ CORS(app) # Allow cross-origin requests (important for frontend integration)
12
+
13
+ # Setup logging
14
+ logging.basicConfig(level=logging.INFO)
15
 
16
  # Load models
17
+ try:
18
+ rf = joblib.load("rf_model.pkl")
19
+ xgb_model = xgb.Booster()
20
+ xgb_model.load_model("xgb_model.json")
21
+ app.logger.info("✅ Models loaded successfully.")
22
+ except Exception as e:
23
+ app.logger.error(f"❌ Error loading models: {e}")
24
+ raise e
25
 
26
+ # Load tile data
27
  with open("tile_catalog.json", "r", encoding="utf-8") as f:
28
  tile_catalog = json.load(f)
29
 
 
45
  """
46
  try:
47
  data = request.get_json()
48
+
 
49
  required_fields = ['tile_type', 'coverage', 'area', 'price_range']
50
  if not all(field in data for field in required_fields):
51
  return jsonify({"error": "Missing required fields"}), 400
52
+
53
  tile_type = data['tile_type'].lower()
54
  if tile_type not in ['floor', 'wall']:
55
  return jsonify({"error": "Invalid tile type. Use 'floor' or 'wall'"}), 400
56
+
57
+ # Validate numeric inputs
58
+ validate_positive_number(data['coverage'], "coverage")
59
+ validate_positive_number(data['area'], "area")
60
+ if (not isinstance(data['price_range'], list) or
61
+ len(data['price_range']) != 2 or
62
+ data['price_range'][0] < 0 or
63
+ data['price_range'][1] <= 0 or
64
+ data['price_range'][0] >= data['price_range'][1]):
65
+ return jsonify({"error": "Invalid price range"}), 400
66
+
67
  features = prepare_features(data)
68
+
69
+ xgb_pred = xgb_model.predict(xgb.DMatrix(features))[0]
70
+ rf_pred = rf.predict_proba(features)[0][1]
 
71
  combined_score = (xgb_pred + rf_pred) / 2
72
+
 
73
  recommended_products = filter_products(
74
  tile_type=tile_type,
75
  min_price=data['price_range'][0],
76
  max_price=data['price_range'][1],
77
  preferred_sizes=data.get('preferred_sizes', []),
78
+ min_score=0.5
79
  )
80
+
 
81
  response = {
82
  "recommendation_score": round(float(combined_score), 3),
83
+ "total_matches": len(recommended_products),
84
+ "recommended_products": recommended_products[:5],
85
  "calculation": calculate_requirements(data['area'], data['coverage'])
86
  }
 
87
  return jsonify(response)
88
+
89
  except Exception as e:
90
+ app.logger.error(f"Error in /recommend: {str(e)}")
91
+ return jsonify({"error": "Internal server error"}), 500
92
 
93
  @app.route('/calculate', methods=['POST'])
94
  def calculate():
 
98
  {
99
  "tile_type": "floor"|"wall",
100
  "area": float,
101
+ "tile_size": "12x12"|etc
102
  }
103
  """
104
  try:
105
  data = request.get_json()
106
+
 
107
  if 'tile_type' not in data or 'area' not in data or 'tile_size' not in data:
108
  return jsonify({"error": "Missing required fields"}), 400
109
+
110
  tile_type = data['tile_type'].lower()
111
  if tile_type not in ['floor', 'wall']:
112
  return jsonify({"error": "Invalid tile type"}), 400
113
+
114
  if data['tile_size'] not in tile_sizes:
115
  return jsonify({"error": "Invalid tile size"}), 400
116
+
117
+ validate_positive_number(data['area'], "area")
118
+
119
  tile_info = tile_sizes[data['tile_size']]
120
  area_per_tile = tile_info['length'] * tile_info['width']
121
+ tiles_needed = math.ceil((data['area'] / area_per_tile) * 1.1)
122
+ tiles_per_box = tile_info.get('tiles_per_box', 10)
123
+ boxes_needed = math.ceil(tiles_needed / tiles_per_box)
124
+
125
  matching_products = [
126
+ p for p in tile_catalog
127
  if p['type'].lower() == tile_type and p['size'] == data['tile_size']
128
  ]
129
+
130
  return jsonify({
131
  "tile_type": tile_type,
132
  "area": data['area'],
133
  "tile_size": data['tile_size'],
134
+ "tiles_needed": tiles_needed,
135
+ "boxes_needed": boxes_needed,
136
+ "matching_products": matching_products[:3],
137
+ "total_matches": len(matching_products)
138
  })
139
+
140
  except Exception as e:
141
+ app.logger.error(f"Error in /calculate: {str(e)}")
142
+ return jsonify({"error": "Internal server error"}), 500
143
 
144
  def prepare_features(data):
145
+ """Prepare feature vector for ML prediction"""
146
  tile_type_num = 0 if data['tile_type'] == 'floor' else 1
147
+ price_per_sqft = data['price_range'][1] / data['coverage']
148
  budget_efficiency = data['coverage'] / data['price_range'][1]
149
+
150
  return np.array([[
151
  tile_type_num,
152
  data['area'],
153
  data['coverage'],
154
+ data['price_range'][0],
155
+ data['price_range'][1],
156
  price_per_sqft,
157
  budget_efficiency
158
  ]])
159
 
160
  def filter_products(tile_type, min_price, max_price, preferred_sizes, min_score=0.5):
161
+ """Filter and score products"""
162
  filtered = []
 
163
  for product in tile_catalog:
164
  if (product['type'].lower() == tile_type and
165
  min_price <= product['price'] <= max_price and
166
  (not preferred_sizes or product['size'] in preferred_sizes)):
167
+
168
+ price_score = 1 - ((product['price'] - min_price) / (max_price - min_price + 1e-6))
 
169
  size_score = 1 if not preferred_sizes or product['size'] in preferred_sizes else 0.5
170
  product_score = (price_score + size_score) / 2
171
+
172
  if product_score >= min_score:
173
  filtered.append({
174
  **product,
175
  "recommendation_score": round(product_score, 2)
176
  })
177
+
 
178
  return sorted(filtered, key=lambda x: x['recommendation_score'], reverse=True)
179
 
180
  def calculate_requirements(area, coverage):
181
+ """Calculate tile quantities and estimated costs"""
182
+ min_tiles = math.ceil(area / coverage)
183
+ suggested_tiles = math.ceil(min_tiles * 1.1)
184
  return {
185
+ "minimum_tiles": min_tiles,
186
+ "suggested_tiles": suggested_tiles,
187
  "estimated_cost_range": [
188
+ round(area * 3, 2), # example: ₹3 per sqft
189
+ round(area * 10, 2) # example: ₹10 per sqft
190
  ]
191
  }
192
 
193
+ def validate_positive_number(value, field):
194
+ """Raise ValueError if value is not a positive number"""
195
+ if not isinstance(value, (int, float)) or value <= 0:
196
+ raise ValueError(f"{field} must be a positive number")
197
+
198
  if __name__ == '__main__':
199
  app.run(host='0.0.0.0', port=5000, debug=True)