Tile / app.py
Coots's picture
Update app.py
e09d218 verified
raw
history blame
4.91 kB
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
import joblib
import numpy as np
import json
import math
import xgboost as xgb
import re
app = Flask(__name__, static_folder='.', static_url_path='/')
CORS(app)
# Load ML models
try:
rf = joblib.load("rf_model.pkl")
xgb_model = xgb.Booster()
xgb_model.load_model("xgb_model.json")
print("βœ… Models loaded successfully.")
except Exception as e:
print(f"❌ Error loading models: {e}")
raise e
# Load tile catalog
with open("tile_catalog.json", "r", encoding="utf-8") as f:
tile_catalog = json.load(f)
@app.route("/")
def index():
return send_from_directory(".", "index.html")
@app.route("/recommend", methods=["POST"])
def recommend():
try:
data = request.get_json()
tile_type = data.get("tile_type", "").strip().lower()
coverage = float(data.get("coverage", 1))
area = float(data.get("area", 1))
price_range = data.get("price_range", [1, 100])
preferred_sizes = data.get("preferred_sizes", [])
if area <= 0 or coverage <= 0:
return jsonify({"error": "Please enter valid area and coverage values."}), 400
features = prepare_features(tile_type, coverage, area, price_range)
xgb_pred = xgb_model.predict(xgb.DMatrix(features))[0]
rf_pred = rf.predict_proba(features)[0][1]
score = (xgb_pred + rf_pred) / 2
products = filter_products(tile_type, price_range, preferred_sizes)
return jsonify({
"recommendation_score": round(float(score), 3),
"recommended_products": products[:4],
"total_matches": len(products),
})
except Exception as e:
print("❌ Error in /recommend:", str(e))
return jsonify({"error": "Server error"}), 500
@app.route("/calculate", methods=["POST"])
def calculate():
try:
data = request.get_json()
tile_type = data.get("tile_type", "").strip().lower()
area = float(data.get("area", 0))
tile_size_raw = data.get("tile_size", "").strip()
if not tile_type:
return jsonify({"error": "Please select a tile type (e.g., floor, wall)."}), 400
if area <= 0:
return jsonify({"error": "Area must be greater than 0."}), 400
# Extract tile length and width in feet using regex
match = re.match(r"(\d+(\.\d+)?)\s*(ft|feet)?\s*[xXΓ—*]\s*(\d+(\.\d+)?)\s*(ft|feet)?", tile_size_raw)
if not match:
return jsonify({"error": f"Invalid tile size format: '{tile_size_raw}'. Please enter like '2 x 2 ft'."}), 400
length_ft = float(match.group(1))
width_ft = float(match.group(4))
if length_ft <= 0 or width_ft <= 0:
return jsonify({"error": "Tile dimensions must be greater than 0."}), 400
tile_area = length_ft * width_ft # in sq.ft
tiles_needed = math.ceil((area / tile_area) * 1.1) # +10% buffer
boxes = math.ceil(tiles_needed / 10) # assuming 10 tiles per box
# Filter matching products by type and approximate size
matches = [
p for p in tile_catalog
if p["type"].lower() == tile_type
]
return jsonify({
"tile_type": tile_type,
"area_sqft": area,
"tile_size": f"{length_ft:.2f} ft x {width_ft:.2f} ft",
"tiles_needed": tiles_needed,
"boxes_needed": boxes,
"matching_products": matches[:3],
"total_matches": len(matches)
})
except Exception as e:
print("❌ Error in /calculate:", str(e))
return jsonify({"error": "An error occurred. Please check your input values."}), 500
def prepare_features(tile_type, coverage, area, price_range):
tile_type_num = 0 if tile_type == "floor" else 1
min_price, max_price = price_range
price_per_sqft = max_price / coverage
efficiency = coverage / max_price
return np.array([[tile_type_num, area, coverage, min_price, max_price, price_per_sqft, efficiency]])
def filter_products(tile_type, price_range, preferred_sizes):
min_price, max_price = price_range
filtered = []
for product in tile_catalog:
if product["type"].lower() != tile_type:
continue
if not (min_price <= product["price"] <= max_price):
continue
if preferred_sizes and product["size"] not in preferred_sizes:
continue
price_score = 1 - (product["price"] - min_price) / (max_price - min_price + 1e-6)
size_score = 1 if product["size"] in preferred_sizes else 0.5
score = round((price_score + size_score) / 2, 2)
filtered.append({**product, "recommendation_score": score})
return sorted(filtered, key=lambda x: x["recommendation_score"], reverse=True)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7860)