Coots commited on
Commit
416ce05
Β·
verified Β·
1 Parent(s): e6f8970

Update app.py

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