File size: 5,899 Bytes
80d2311
 
 
 
 
 
b2348a1
 
80d2311
 
 
 
b2348a1
80d2311
 
 
 
b2348a1
80d2311
 
 
 
 
 
b2348a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80d2311
 
b2348a1
80d2311
 
 
 
 
 
b2348a1
80d2311
 
 
b2348a1
 
 
80d2311
b2348a1
80d2311
b2348a1
80d2311
 
 
b2348a1
80d2311
 
b2348a1
 
 
 
80d2311
 
 
 
 
 
b2348a1
80d2311
 
 
 
 
 
 
 
 
b2348a1
80d2311
 
 
 
 
 
b2348a1
80d2311
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b2348a1
80d2311
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import streamlit as st
import pandas as pd
import numpy as np
import torch
import openai
import os
import io
import re
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
import matplotlib.pyplot as plt

# --- Groq API setup ---
openai.api_key = os.getenv("GROQ_API_KEY")
openai.api_base = "https://api.groq.com/openai/v1"
GROQ_MODEL = "llama3-8b-8192"

# --- Load Excel ---
@st.cache_data
def load_excel(file):
    xl = pd.read_excel(file, sheet_name=None)
    all_data = pd.concat(xl.values(), ignore_index=True)
    return all_data

# --- Chunk using regex instead of nltk ---
def chunk_data(df, max_tokens=100):
    text = "\n".join(df.astype(str).apply(lambda row: " | ".join(row), axis=1))
    # Split on period, newline, or semicolon
    sentences = re.split(r'(?<=[.;])\s+|\n+', text)
    chunks, current_chunk, current_len = [], [], 0

    for sent in sentences:
        tokens = sent.split()
        if current_len + len(tokens) > max_tokens:
            chunks.append(" ".join(current_chunk))
            current_chunk, current_len = [], 0
        current_chunk.append(sent)
        current_len += len(tokens)

    if current_chunk:
        chunks.append(" ".join(current_chunk))

    return chunks

# --- Embed chunks ---
@st.cache_resource
def embed_chunks(chunks):
    model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
    embeddings = model.encode(chunks)
    return embeddings, model

# --- Query chunks ---
def query_embedding(user_query, chunks, embeddings, model):
    query_vec = model.encode([user_query])
    similarities = cosine_similarity(query_vec, embeddings)[0]
    top_indices = similarities.argsort()[::-1][:5]
    top_chunks = "\n\n".join([chunks[i] for i in top_indices])
    return top_chunks

# --- Generate Estimate from Groq ---
def generate_estimate(context, user_input):
    prompt = f"""You are a construction estimator in Pakistan. Use the following schedule of rates:

{context}

Estimate a full itemized construction BOQ for:
{user_input}

Include all relevant items for a complete house: excavation, foundation, RCC, masonry, plastering, flooring, doors, windows, paint, distemper, fans, lights, wiring, DBs, plumbing, sanitary fittings, water supply, cupboards, wardrobes, gate, etc.

Provide output in a markdown table with columns: Item No, Description, Qty, Unit, Rate, Amount in Rs.
"""
    response = openai.ChatCompletion.create(
        model=GROQ_MODEL,
        messages=[{"role": "user", "content": prompt}]
    )
    return response['choices'][0]['message']['content']

# --- Quantity Calculator ---
def calculate_quantities(rooms, area, baths, car_porch, living):
    return {
        "Total Area (sqft)": area,
        "No. of Rooms": rooms,
        "No. of Bathrooms": baths,
        "Living Rooms": living,
        "Car Porch Area (est.)": car_porch * 200
    }

# --- Floor Plan Sketch ---
def draw_floor_plan(rooms, baths, living, car_porch, area):
    total_spaces = rooms + baths + living + car_porch
    cols = int(np.ceil(np.sqrt(total_spaces)))
    rows = int(np.ceil(total_spaces / cols))

    fig, ax = plt.subplots(figsize=(10, 8))
    scale = np.sqrt(area) / 10
    width, height = scale, scale * 0.75

    labels = (["Room"] * rooms + ["Bath"] * baths +
              ["Living"] * living + ["Car Porch"] * car_porch)

    for i, label in enumerate(labels):
        row = i // cols
        col = i % cols
        x = col * width
        y = (rows - 1 - row) * height
        ax.add_patch(plt.Rectangle((x, y), width, height, edgecolor='black', facecolor='lightblue'))
        ax.text(x + width / 2, y + height / 2, label, ha='center', va='center', fontsize=8)

    ax.set_xlim(0, cols * width)
    ax.set_ylim(0, rows * height)
    ax.set_aspect('equal')
    ax.set_title(f"Tentative Floor Plan (Scale: 1 unit = {int(scale)} sqft)")
    ax.axis('off')

    buf = io.BytesIO()
    plt.savefig(buf, format='png')
    buf.seek(0)
    return buf

# --- Main App ---
def main():
    st.set_page_config(page_title="Construction Estimator", layout="centered")
    st.title("🧱 Construction Estimator (RAG + LLaMA 3 + Sketch)")

    excel_file = st.file_uploader("Upload Schedule of Rates (.xlsx or .xlsm)", type=["xlsx", "xlsm"])
    if excel_file:
        df = load_excel(excel_file)
        st.success("Excel file loaded successfully.")
        chunks = chunk_data(df)
        embeddings, model = embed_chunks(chunks)

        st.subheader("🏗️ Enter Project Details")
        rooms = st.number_input("Number of Rooms", min_value=1, value=3)
        area = st.number_input("Total Covered Area (sqft)", min_value=100, value=1200)
        baths = st.number_input("Number of Washrooms", min_value=1, value=2)
        living = st.number_input("Number of Living Rooms", min_value=0, value=1)
        car_porch = st.number_input("Number of Car Porches", min_value=0, value=1)

        if st.button("Generate Estimate"):
            quantities = calculate_quantities(rooms, area, baths, car_porch, living)
            user_query = f"Estimate cost for {rooms} rooms, {baths} bathrooms, {living} living rooms, total area {area} sqft, and {car_porch} car porch(es)."
            context = query_embedding(user_query, chunks, embeddings, model)
            response = generate_estimate(context, user_query)

            st.subheader("📊 Input Quantities")
            st.json(quantities)

            st.subheader("💸 Estimated Construction Cost (BOQ Style)")
            st.markdown(response)

            buf = draw_floor_plan(rooms, baths, living, car_porch, area)
            st.subheader("🏠 Tentative Floor Plan Sketch")
            st.image(buf, caption="Auto-generated Line Plan", use_column_width=True)
            st.download_button("📥 Download Sketch", buf, file_name="floor_plan.png", mime="image/png")

if __name__ == "__main__":
    main()