Spaces:
Sleeping
Sleeping
| 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 --- | |
| 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 --- | |
| 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() | |