File size: 6,867 Bytes
5d5165f
 
 
 
a43c45a
 
 
e09d218
61ffb8e
0616b98
5d5165f
d12096d
1139b4c
61ffb8e
5d5165f
61ffb8e
 
0616b98
61ffb8e
0616b98
5d5165f
61ffb8e
2c2d005
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fc94d32
0616b98
 
 
5d5165f
1139b4c
 
857baf5
 
e09d218
1139b4c
 
2c2d005
 
857baf5
e09d218
 
857baf5
1139b4c
 
 
 
857baf5
2c2d005
857baf5
1139b4c
2c2d005
 
857baf5
0616b98
1139b4c
0616b98
 
1139b4c
 
0616b98
 
1139b4c
0616b98
e09d218
857baf5
1139b4c
e09d218
1139b4c
e09d218
 
 
 
 
1139b4c
e09d218
 
 
 
1139b4c
fc94d32
 
 
0616b98
e09d218
 
 
 
0616b98
 
e09d218
 
 
1139b4c
 
 
 
0616b98
857baf5
1139b4c
e09d218
0616b98
 
 
 
1139b4c
 
0616b98
 
 
 
5d5165f
a43c45a
0616b98
 
 
 
2c2d005
0616b98
 
2c2d005
0616b98
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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

# Tile product catalog (hardcoded, replacing tile_catalog.json)
tile_catalog = [
    {"name": "Travertino Light Beige", "type": "Floor", "price": 780, "coverage": 15.5, "size": "1200x1200 MM", "url": "https://arqonz.ae/products/6775"},
    {"name": "Marquina Glossy Black", "type": "Wall", "price": 620, "coverage": 12.0, "size": "600x600 MM", "url": "https://arqonz.ae/products/6776"},
    {"name": "Carrara White Polished", "type": "Floor", "price": 1100, "coverage": 23.23, "size": "1800x1200 MM", "url": "https://arqonz.ae/products/103"},
    {"name": "Noir Marble Effect", "type": "Wall", "price": 550, "coverage": 6.89, "size": "800x800 MM", "url": "https://arqonz.ae/products/104"},
    {"name": "Sandstone Beige Matte", "type": "Floor", "price": 670, "coverage": 13.78, "size": "1600x800 MM", "url": "https://arqonz.ae/products/6644"},
    {"name": "Onyx Mist Polished", "type": "Wall", "price": 890, "coverage": 15.5, "size": "1200x1200 MM", "url": "https://arqonz.ae/products/6880"},
    {"name": "Terrazzo Pearl", "type": "Floor", "price": 720, "coverage": 7.75, "size": "1200x600 MM", "url": "https://arqonz.ae/products/6878"},
    {"name": "Statuario Silver", "type": "Wall", "price": 660, "coverage": 3.87, "size": "600x600 MM", "url": "https://arqonz.ae/products/6883"},
    {"name": "Grigio Urban", "type": "Floor", "price": 500, "coverage": 2.52, "size": "1200x195 MM", "url": "https://arqonz.ae/products/6653"},
    {"name": "Calacatta Nero", "type": "Wall", "price": 740, "coverage": 10.33, "size": "800x1200 MM", "url": "https://arqonz.ae/products/6885"},
    {"name": "Ivory Rock Slate", "type": "Floor", "price": 690, "coverage": 13.0, "size": "1200x600 MM", "url": "https://arqonz.ae/products/6777"},
    {"name": "Classic White Glossy", "type": "Wall", "price": 580, "coverage": 12.0, "size": "600x600 MM", "url": "https://arqonz.ae/products/89"},
    {"name": "Dark Stone Matte", "type": "Floor", "price": 830, "coverage": 15.0, "size": "1200x600 MM", "url": "https://arqonz.ae/products/107"},
    {"name": "Polished Statuario", "type": "Wall", "price": 810, "coverage": 10.5, "size": "600x1200 MM", "url": "https://arqonz.ae/products/116"}
    # NOTE: Truncated for brevity β€” continue adding your full product list here
]

@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, 10000])
        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)