Mtkhang90 commited on
Commit
c563bde
·
verified ·
1 Parent(s): 963979a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +90 -95
app.py CHANGED
@@ -1,68 +1,68 @@
1
  import streamlit as st
2
  import pandas as pd
3
  import numpy as np
4
- import os
5
- from sentence_transformers import SentenceTransformer
6
- from sklearn.metrics.pairwise import cosine_similarity
7
  import matplotlib.pyplot as plt
8
  import io
9
- from openai import OpenAI
10
 
11
- # Set page config FIRST
12
  st.set_page_config(page_title="Construction Estimator", layout="centered")
13
 
14
- # Setup OpenAI/Groq client
15
- client = OpenAI(
16
- api_key=os.getenv("GROQ_API_KEY"),
17
- base_url="https://api.groq.com/openai/v1"
18
- )
19
- GROQ_MODEL = "llama3-8b-8192"
20
-
21
- # Embed chunks - keep for similarity search if needed in future
22
- @st.cache_resource
23
- def embed_chunks(chunks):
24
- model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
25
- embeddings = model.encode(chunks)
26
- return embeddings, model
27
-
28
- # Generate estimate with Groq
29
- def generate_estimate(context, user_input):
30
- prompt = f"""You are a construction estimator working in Pakistan. Using the following schedule of rates and quantities:
31
-
32
- {context}
33
-
34
- Generate a detailed BOQ estimate including item numbers, full descriptions, unit rates, quantities, and total amount in Rs for:
35
- {user_input}
36
-
37
- Present the result in a markdown table with columns: Item No, Description, Qty, Unit, Rate, Amount."""
38
-
39
- response = client.chat.completions.create(
40
- model=GROQ_MODEL,
41
- messages=[{"role": "user", "content": prompt}]
42
- )
43
- return response.choices[0].message.content
44
-
45
- # Sketch generator
46
- def draw_floor_plan(rooms, baths, living, car_porch, area):
 
 
 
47
  total_spaces = rooms + baths + living + car_porch
 
 
48
  cols = int(np.ceil(np.sqrt(total_spaces)))
49
  rows = int(np.ceil(total_spaces / cols))
50
 
51
  fig, ax = plt.subplots(figsize=(10, 8))
52
- scale = np.sqrt(area) / 10
53
  width, height = scale, scale * 0.75
54
- labels = (["Room"] * rooms + ["Bath"] * baths + ["Living"] * living + ["Car Porch"] * car_porch)
55
 
 
56
  for i, label in enumerate(labels):
57
  row = i // cols
58
  col = i % cols
59
  x = col * width
60
  y = (rows - 1 - row) * height
61
  ax.add_patch(plt.Rectangle((x, y), width, height, edgecolor='black', facecolor='lightblue'))
62
- ax.text(x + width / 2, y + height / 2, label, ha='center', va='center', fontsize=8)
63
 
64
- ax.set_xlim(0, cols * width)
65
- ax.set_ylim(0, rows * height)
66
  ax.set_aspect('equal')
67
  ax.set_title(f"Tentative Floor Plan (Scale: 1 unit = {int(scale)} sqft)")
68
  ax.axis('off')
@@ -72,59 +72,54 @@ def draw_floor_plan(rooms, baths, living, car_porch, area):
72
  buf.seek(0)
73
  return buf
74
 
75
- # -------------------- MAIN APP --------------------
76
  def main():
77
- st.title("🧱 Construction Estimator (RAG + LLaMA 3 + Sketch)")
78
-
79
- st.subheader("Upload Material Data")
80
-
81
- material_qty_file = st.file_uploader("Upload Material Quantities Excel", type=["xlsx", "xlsm"])
82
- material_cost_file = st.file_uploader("Upload Material Costs Excel", type=["xlsx", "xlsm"])
83
-
84
- if material_qty_file and material_cost_file:
85
- qty_df = pd.read_excel(material_qty_file)
86
- cost_df = pd.read_excel(material_cost_file)
87
-
88
- st.write("### Material Quantities")
89
- st.dataframe(qty_df)
90
-
91
- st.write("### Material Costs")
92
- st.dataframe(cost_df)
93
-
94
- # Attempt to get covered area from quantities file if exists
95
- area = None
96
- if "Covered Area (sqft)" in qty_df.columns:
97
- try:
98
- area = float(qty_df["Covered Area (sqft)"].iloc[0])
99
- except Exception:
100
- area = None
101
-
102
- context_qty = qty_df.to_string(index=False)
103
- context_cost = cost_df.to_string(index=False)
104
- context = f"Material Quantities:\n{context_qty}\n\nMaterial Costs:\n{context_cost}"
105
-
106
- st.subheader("🏗️ Enter Other Project Details")
107
- rooms = st.number_input("Number of Rooms", min_value=1, value=3)
108
- baths = st.number_input("Number of Washrooms", min_value=1, value=2)
109
- living = st.number_input("Number of Living Rooms", min_value=0, value=1)
110
- car_porch = st.number_input("Number of Car Porches", min_value=0, value=1)
111
-
112
- if st.button("Generate Estimate"):
113
- user_query = f"Estimate cost for {rooms} rooms, {baths} bathrooms, {living} living rooms, and {car_porch} car porch(es)."
114
- response = generate_estimate(context, user_query)
115
-
116
- st.subheader("💸 Estimated Construction Cost (BOQ Style)")
117
- st.markdown("**This BOQ is a simplified estimate. Use it for planning, not execution.**")
118
- st.markdown(response)
119
-
120
- # Use area if available, else default 1200 sqft for sketch scaling
121
- sketch_area = area if area else 1200
122
- buf = draw_floor_plan(rooms, baths, living, car_porch, sketch_area)
123
- st.subheader("🏠 Tentative Floor Plan Sketch")
124
- st.image(buf, caption="Auto-generated Line Plan", use_column_width=True)
125
- st.download_button("📥 Download Sketch", buf, file_name="floor_plan.png", mime="image/png")
126
- else:
127
- st.info("Please upload both Material Quantities and Material Costs Excel files.")
128
 
129
  if __name__ == "__main__":
130
  main()
 
1
  import streamlit as st
2
  import pandas as pd
3
  import numpy as np
 
 
 
4
  import matplotlib.pyplot as plt
5
  import io
 
6
 
 
7
  st.set_page_config(page_title="Construction Estimator", layout="centered")
8
 
9
+ def calculate_scaled_quantities(base_qty_df, covered_area):
10
+ """
11
+ base_qty_df: DataFrame with columns: Material, Unit, Quantity_per_1000sqft
12
+ covered_area: float, in sqft
13
+
14
+ Returns a DataFrame with scaled quantities according to covered area
15
+ """
16
+ scale_factor = covered_area / 1000
17
+ base_qty_df['Scaled Quantity'] = base_qty_df['Quantity_per_1000sqft'] * scale_factor
18
+ return base_qty_df
19
+
20
+ def calculate_costs(qty_df, rates_df):
21
+ """
22
+ qty_df: DataFrame with scaled quantities (Material, Unit, Scaled Quantity)
23
+ rates_df: DataFrame with columns: Material, Unit, Rate_per_Unit
24
+
25
+ Returns a DataFrame with cost calculations
26
+ """
27
+ df = pd.merge(qty_df, rates_df, on=['Material', 'Unit'], how='left')
28
+ df['Amount'] = df['Scaled Quantity'] * df['Rate_per_Unit']
29
+ df.fillna({'Rate_per_Unit':0, 'Amount':0}, inplace=True)
30
+ return df
31
+
32
+ def generate_boq_table(df):
33
+ df = df.reset_index(drop=True)
34
+ df.index += 1
35
+ df_out = df.rename(columns={
36
+ 'Material': 'Description',
37
+ 'Scaled Quantity': 'Qty',
38
+ 'Unit': 'Unit',
39
+ 'Rate_per_Unit': 'Rate (PKR)',
40
+ 'Amount': 'Amount (PKR)'
41
+ })[['Description', 'Qty', 'Unit', 'Rate (PKR)', 'Amount (PKR)']]
42
+ return df_out
43
+
44
+ def draw_floor_plan(rooms, baths, living, car_porch, covered_area):
45
  total_spaces = rooms + baths + living + car_porch
46
+ if total_spaces == 0:
47
+ total_spaces = 1
48
  cols = int(np.ceil(np.sqrt(total_spaces)))
49
  rows = int(np.ceil(total_spaces / cols))
50
 
51
  fig, ax = plt.subplots(figsize=(10, 8))
52
+ scale = np.sqrt(covered_area) / 10
53
  width, height = scale, scale * 0.75
 
54
 
55
+ labels = (["Room"] * rooms + ["Bath"] * baths + ["Living"] * living + ["Car Porch"] * car_porch)
56
  for i, label in enumerate(labels):
57
  row = i // cols
58
  col = i % cols
59
  x = col * width
60
  y = (rows - 1 - row) * height
61
  ax.add_patch(plt.Rectangle((x, y), width, height, edgecolor='black', facecolor='lightblue'))
62
+ ax.text(x + width/2, y + height/2, label, ha='center', va='center', fontsize=10)
63
 
64
+ ax.set_xlim(0, cols*width)
65
+ ax.set_ylim(0, rows*height)
66
  ax.set_aspect('equal')
67
  ax.set_title(f"Tentative Floor Plan (Scale: 1 unit = {int(scale)} sqft)")
68
  ax.axis('off')
 
72
  buf.seek(0)
73
  return buf
74
 
 
75
  def main():
76
+ st.title("🧱 Construction Material Estimator")
77
+
78
+ st.markdown("Upload **Base Quantities per 1000 sqft** and **Material Rates** Excel files. Then enter project details to calculate detailed BOQ and cost.")
79
+
80
+ qty_file = st.file_uploader("Upload Base Quantities Excel (Material, Unit, Quantity_per_1000sqft)", type=["xlsx", "xls"])
81
+ rate_file = st.file_uploader("Upload Material Rates Excel (Material, Unit, Rate_per_Unit)", type=["xlsx", "xls"])
82
+
83
+ if qty_file and rate_file:
84
+ try:
85
+ qty_df = pd.read_excel(qty_file)
86
+ rate_df = pd.read_excel(rate_file)
87
+
88
+ # Validate expected columns
89
+ required_qty_cols = {'Material', 'Unit', 'Quantity_per_1000sqft'}
90
+ required_rate_cols = {'Material', 'Unit', 'Rate_per_Unit'}
91
+
92
+ if not required_qty_cols.issubset(set(qty_df.columns)):
93
+ st.error(f"Base Quantities Excel missing columns: {required_qty_cols - set(qty_df.columns)}")
94
+ return
95
+ if not required_rate_cols.issubset(set(rate_df.columns)):
96
+ st.error(f"Material Rates Excel missing columns: {required_rate_cols - set(rate_df.columns)}")
97
+ return
98
+
99
+ covered_area = st.number_input("Covered Area (sqft)", min_value=100, value=1200)
100
+ rooms = st.number_input("Number of Rooms", min_value=0, value=3)
101
+ baths = st.number_input("Number of Bathrooms", min_value=0, value=2)
102
+ living = st.number_input("Number of Living Rooms", min_value=0, value=1)
103
+ car_porch = st.number_input("Number of Car Porches", min_value=0, value=1)
104
+
105
+ if st.button("Calculate BOQ & Cost"):
106
+ scaled_qty_df = calculate_scaled_quantities(qty_df, covered_area)
107
+ cost_df = calculate_costs(scaled_qty_df, rate_df)
108
+ boq_table = generate_boq_table(cost_df)
109
+
110
+ st.subheader("Scaled Material Quantities & Cost Estimate")
111
+ st.dataframe(boq_table.style.format({"Qty": "{:.2f}", "Rate (PKR)": "₨ {:,.2f}", "Amount (PKR)": "₨ {:,.2f}"}))
112
+
113
+ total_cost = boq_table['Amount (PKR)'].sum()
114
+ st.markdown(f"### Total Estimated Cost: ₨ {total_cost:,.2f}")
115
+
116
+ buf = draw_floor_plan(rooms, baths, living, car_porch, covered_area)
117
+ st.subheader("Tentative Floor Plan Sketch")
118
+ st.image(buf, use_column_width=True)
119
+ st.download_button("Download Floor Plan Sketch", buf, file_name="floor_plan.png", mime="image/png")
120
+
121
+ except Exception as e:
122
+ st.error(f"Error processing files: {e}")
 
 
 
 
123
 
124
  if __name__ == "__main__":
125
  main()