Mtkhang90 commited on
Commit
bc0c17f
Β·
verified Β·
1 Parent(s): 059ae9a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +100 -117
app.py CHANGED
@@ -1,124 +1,107 @@
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, floors):
10
- """
11
- base_qty_df: DataFrame with columns: Material, Unit, Quantity_per_1000sqft
12
- covered_area: float, in sqft
13
- floors: int, total floors (including ground floor)
14
-
15
- Returns a DataFrame with scaled quantities according to covered area and floors
16
- """
17
- scale_factor = covered_area / 1000
18
- ground_floor_qty = base_qty_df['Quantity_per_1000sqft'] * scale_factor
19
- # Additional floors at 80% of ground floor quantities
20
- additional_floors_qty = ground_floor_qty * 0.8 * (floors - 1) if floors > 1 else 0
21
-
22
- base_qty_df['Scaled Quantity'] = ground_floor_qty + additional_floors_qty
23
- return base_qty_df
24
-
25
- def calculate_costs(qty_df, rates_df):
26
- df = pd.merge(qty_df, rates_df, on=['Material', 'Unit'], how='left')
27
- df['Amount'] = df['Scaled Quantity'] * df['Rate_per_Unit']
28
- df.fillna({'Rate_per_Unit':0, 'Amount':0}, inplace=True)
29
- return df
30
-
31
- def generate_boq_table(df):
32
- df = df.reset_index(drop=True)
33
- df.index += 1
34
- df_out = df.rename(columns={
35
- 'Material': 'Description',
36
- 'Scaled Quantity': 'Qty',
37
- 'Unit': 'Unit',
38
- 'Rate_per_Unit': 'Rate (PKR)',
39
- 'Amount': 'Amount (PKR)'
40
- })[['Description', 'Qty', 'Unit', 'Rate (PKR)', 'Amount (PKR)']]
41
- return df_out
42
-
43
- def draw_floor_plan(rooms, baths, living, car_porch, covered_area):
44
- total_spaces = rooms + baths + living + car_porch
45
- if total_spaces == 0:
46
- total_spaces = 1
47
- cols = int(np.ceil(np.sqrt(total_spaces)))
48
- rows = int(np.ceil(total_spaces / cols))
49
-
50
- fig, ax = plt.subplots(figsize=(10, 8))
51
- scale = np.sqrt(covered_area) / 10
52
- width, height = scale, scale * 0.75
53
-
54
- labels = (["Room"] * rooms + ["Bath"] * baths + ["Living"] * living + ["Car Porch"] * car_porch)
55
- for i, label in enumerate(labels):
56
- row = i // cols
57
- col = i % cols
58
- x = col * width
59
- y = (rows - 1 - row) * height
60
- ax.add_patch(plt.Rectangle((x, y), width, height, edgecolor='black', facecolor='lightblue'))
61
- ax.text(x + width/2, y + height/2, label, ha='center', va='center', fontsize=10)
62
-
63
- ax.set_xlim(0, cols*width)
64
- ax.set_ylim(0, rows*height)
65
- ax.set_aspect('equal')
66
- ax.set_title(f"Tentative Floor Plan (Scale: 1 unit = {int(scale)} sqft)")
67
- ax.axis('off')
68
-
69
- buf = io.BytesIO()
70
- plt.savefig(buf, format='png')
71
- buf.seek(0)
72
- return buf
73
-
74
- def main():
75
- st.title("🧱 Construction Material Estimator with Floors")
76
-
77
- st.markdown("Upload **Base Quantities per 1000 sqft** and **Material Rates** Excel files. Then enter project details including number of floors.")
78
-
79
- qty_file = st.file_uploader("Upload Base Quantities Excel (Material, Unit, Quantity_per_1000sqft)", type=["xlsx", "xls"])
80
- rate_file = st.file_uploader("Upload Material Rates Excel (Material, Unit, Rate_per_Unit)", type=["xlsx", "xls"])
81
-
82
- if qty_file and rate_file:
83
- try:
84
- qty_df = pd.read_excel(qty_file)
85
- rate_df = pd.read_excel(rate_file)
86
-
87
- required_qty_cols = {'Material', 'Unit', 'Quantity_per_1000sqft'}
88
- required_rate_cols = {'Material', 'Unit', 'Rate_per_Unit'}
89
-
90
- if not required_qty_cols.issubset(set(qty_df.columns)):
91
- st.error(f"Base Quantities Excel missing columns: {required_qty_cols - set(qty_df.columns)}")
92
- return
93
- if not required_rate_cols.issubset(set(rate_df.columns)):
94
- st.error(f"Material Rates Excel missing columns: {required_rate_cols - set(rate_df.columns)}")
95
- return
96
-
97
- covered_area = st.number_input("Covered Area (sqft)", min_value=100, value=1200)
98
- floors = st.number_input("Number of Floors", min_value=1, value=1)
99
- rooms = st.number_input("Number of Rooms", min_value=0, value=3)
100
- baths = st.number_input("Number of Bathrooms", min_value=0, value=2)
101
- living = st.number_input("Number of Living Rooms", min_value=0, value=1)
102
- car_porch = st.number_input("Number of Car Porches", min_value=0, value=1)
103
-
104
- if st.button("Calculate BOQ & Cost"):
105
- scaled_qty_df = calculate_scaled_quantities(qty_df, covered_area, floors)
106
- cost_df = calculate_costs(scaled_qty_df, rate_df)
107
- boq_table = generate_boq_table(cost_df)
108
-
109
- st.subheader("Scaled Material Quantities & Cost Estimate")
110
- st.dataframe(boq_table.style.format({"Qty": "{:.2f}", "Rate (PKR)": "₨ {:,.2f}", "Amount (PKR)": "₨ {:,.2f}"}))
111
-
112
- total_cost = boq_table['Amount (PKR)'].sum()
113
- st.markdown(f"### Total Estimated Cost: ₨ {total_cost:,.2f}")
114
-
115
- buf = draw_floor_plan(rooms, baths, living, car_porch, covered_area)
116
- st.subheader("Tentative Floor Plan Sketch")
117
- st.image(buf, use_column_width=True)
118
- st.download_button("Download Floor Plan Sketch", buf, file_name="floor_plan.png", mime="image/png")
119
-
120
- except Exception as e:
121
- st.error(f"Error processing files: {e}")
122
-
123
- if __name__ == "__main__":
124
- main()
 
1
  import streamlit as st
2
  import pandas as pd
3
  import numpy as np
 
4
  import io
5
+ import os
6
+ import replicate
7
+ from sentence_transformers import SentenceTransformer
8
+ from sklearn.metrics.pairwise import cosine_similarity
9
+ from openai import OpenAI
10
 
11
  st.set_page_config(page_title="Construction Estimator", layout="centered")
12
 
13
+ client = OpenAI(
14
+ api_key=os.getenv("GROQ_API_KEY"),
15
+ base_url="https://api.groq.com/openai/v1"
16
+ )
17
+ GROQ_MODEL = "llama3-8b-8192"
18
+
19
+ @st.cache_data
20
+ def load_excel(file):
21
+ return pd.read_excel(file)
22
+
23
+ @st.cache_resource
24
+ def embed_chunks(chunks):
25
+ model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
26
+ embeddings = model.encode(chunks)
27
+ return embeddings, model
28
+
29
+ def query_embedding(user_query, chunks, embeddings, model):
30
+ query_vec = model.encode([user_query])
31
+ similarities = cosine_similarity(query_vec, embeddings)[0]
32
+ top_idx = np.argmax(similarities)
33
+ return chunks[top_idx]
34
+
35
+ def generate_estimate(context, user_input):
36
+ prompt = f"""You are a construction estimator in Pakistan. Using the following schedule:
37
+
38
+ {context}
39
+
40
+ Generate a BOQ with item number, description, quantity, unit, rate, and total amount for:
41
+ {user_input}
42
+
43
+ Output as a markdown table."""
44
+
45
+ response = client.chat.completions.create(
46
+ model=GROQ_MODEL,
47
+ messages=[{"role": "user", "content": prompt}]
48
+ )
49
+ return response.choices[0].message.content
50
+
51
+ def compute_total_quantities(base_df, covered_area, floors):
52
+ base_df = base_df.copy()
53
+ base_df["Adjusted Qty"] = base_df["Qty per 1000 sft"] * (covered_area / 1000)
54
+ floor_factor = 1 + max(0, floors - 1) * 0.8
55
+ base_df["Total Qty"] = base_df["Adjusted Qty"] * floor_factor
56
+ return base_df
57
+
58
+ def generate_realistic_plan(rooms, baths, living, car_porch):
59
+ prompt = f"floor plan for {rooms} rooms, {baths} bathrooms, {living} living rooms, and {car_porch} car porch in modern style"
60
+ output = replicate.run(
61
+ "cjwbw/floor-plan-generator",
62
+ input={"prompt": prompt}
63
+ )
64
+ return output
65
+
66
+ st.title("πŸ—οΈ Construction Estimator (Material + Cost + BOQ + Sketch)")
67
+
68
+ quantity_file = st.file_uploader("Upload Material Quantities Excel (per 1000 sft)", type=["xlsx"])
69
+ cost_file = st.file_uploader("Upload Material Costs Excel", type=["xlsx"])
70
+
71
+ if quantity_file and cost_file:
72
+ base_df = load_excel(quantity_file)
73
+ cost_df = load_excel(cost_file)
74
+
75
+ if "Material" in base_df.columns and "Qty per 1000 sft" in base_df.columns:
76
+ st.success("Files loaded successfully.")
77
+
78
+ rooms = st.number_input("Number of Rooms", min_value=1, value=3)
79
+ baths = st.number_input("Number of Bathrooms", min_value=1, value=2)
80
+ living = st.number_input("Number of Living Rooms", min_value=0, value=1)
81
+ car_porch = st.number_input("Number of Car Porches", min_value=0, value=1)
82
+ covered_area = st.number_input("Total Covered Area (sft)", min_value=100, value=1200)
83
+ floors = st.number_input("Number of Floors", min_value=1, value=1)
84
+
85
+ if st.button("Generate Estimate"):
86
+ computed_df = compute_total_quantities(base_df, covered_area, floors)
87
+ boq = computed_df.merge(cost_df, on="Material", how="left")
88
+ boq["Amount"] = boq["Total Qty"] * boq["Rate"]
89
+
90
+ st.subheader("πŸ“‹ Bill of Quantities (BOQ)")
91
+ st.dataframe(boq[["Material", "Total Qty", "Unit", "Rate", "Amount"]])
92
+
93
+ total_cost = boq["Amount"].sum()
94
+ st.metric("Total Estimated Cost (Rs)", f"{total_cost:,.0f}")
95
+
96
+ st.subheader("🏠 AI-Generated Floor Plan Sketch")
97
+ try:
98
+ image_url = generate_realistic_plan(rooms, baths, living, car_porch)
99
+ st.image(image_url, caption="Generated by AI (Replicate)", use_column_width=True)
100
+ except Exception as e:
101
+ st.warning("Could not generate sketch. Please check Replicate API setup.")
102
+ st.text(str(e))
103
+
104
+ else:
105
+ st.error("Quantity sheet must contain 'Material' and 'Qty per 1000 sft' columns.")
106
+ else:
107
+ st.info("Please upload both the quantity and cost Excel sheets.")