Mtkhang90's picture
Update app.py
d3322b1 verified
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()