|
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) |
|
|
|
|
|
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 |
|
|
|
|
|
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 |
|
|
|
|
|
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 |
|
tiles_needed = math.ceil((area / tile_area) * 1.1) |
|
boxes = math.ceil(tiles_needed / 10) |
|
|
|
|
|
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) |
|
|