Estimator_Tool / app.py
Ze101's picture
Update app.py
7d97845 verified
Raw
History Blame Contribute Delete
7.71 kB
import streamlit as st
import pandas as pd
# Load the Excel file
file_path = "Estimate Refer Database.xlsx"
data = pd.read_excel(file_path, sheet_name=0)
# Adjust column names for clarity
data.columns = [
"Subtype", "Vancouver_Low", "Vancouver_High",
"Calgary_Low", "Calgary_High", "Edmonton_Low", "Edmonton_High",
"Winnipeg_Low", "Winnipeg_High", "Greater Toronto Area_Low", "Greater Toronto Area_High",
"Ottawa/Gatineau_Low", "Ottawa/Gatineau_High",
"Montreal_Low", "Montreal_High",
"Halifax_Low", "Halifax_High",
"St. John’s_Low", "St. John’s_High"
]
# Remove the first row of labels (already captured in column names)
data = data.iloc[1:].reset_index(drop=True)
# Define cities and project types
cities = [
"Vancouver", "Calgary", "Edmonton", "Winnipeg", "Greater Toronto Area",
"Ottawa/Gatineau", "Montreal", "Halifax", "St. John’s"
]
project_types = {
"Office Buildings": [
"Under 5 Storeys (Class B)", "5- 30 Storeys (Class B)", "5- 30 Storeys (Class A)",
"31 - 60 Storeys (Class A)", "Interior Fitout (Class B)", "Interior Fitout (Class A)"
],
"Retail": ["Strip Plaza", "Supermarket", "Big Box Store", "Enclosed Mall"],
"Hotels": ["Budget", "Suite Hotel", "4-star Full Service", "Premium for Luxury"],
"Parking": [
"Surface Parking", "Freestanding Parking Garages (Above Grade)",
"Underground Parking Garages", "Underground Parking Garages - Premium for Unusual Circumstances"
],
"Industrial Facilities": ["Warehouse", "Distribution Facility", "Urban Storage Facility"]
}
def calculate_estimate(city, project_type, subtype, square_footage):
"""Calculate the cost estimate based on inputs."""
low_column = f"{city}_Low"
high_column = f"{city}_High"
# Filter data for the selected subtype
filtered_data = data[data["Subtype"] == subtype]
if filtered_data.empty or low_column not in data.columns or high_column not in data.columns:
return None, None, None
low_cost = float(filtered_data[low_column].values[0])
high_cost = float(filtered_data[high_column].values[0])
# Calculate estimates
low_estimate = low_cost * square_footage
high_estimate = high_cost * square_footage
average_estimate = (low_estimate + high_estimate) / 2
return low_estimate, high_estimate, average_estimate
# Custom CSS for styling
st.markdown(
"""
<style>
.st-emotion-cache-yw8pof {
padding-bottom: 20px;
}
.stApp {
max-width: 650px;
margin: auto;
padding: 20px;
}
.stButton button {
background-color: rgb(253, 190, 64);
color: #ffffff;
border-radius: 5px;
border: none;
padding: 10px 20px;
margin: auto;
display: block;
}
.stButton button:hover {
background-color: #f7a930;
color: #ffffff;
}
.result-box {
background-color: #ffffff;
color: #000000;
text-align: center;
font-size: 1.2em;
padding: 20px;
border-radius: 10px;
margin-top: 20px;
}
.back-button {
margin-top: 10px;
text-align: left;
}
.email-button {
background-color: rgb(253, 190, 64);
color: #ffffff; /* Text color already white */
border-radius: 5px;
padding: 10px 15px;
text-decoration: none !important; /* Remove underline */
font-size: 1em;
display: inline-block;
margin-top: 20px;
border: none; /* Remove default button border */
cursor: pointer;
}
/* Optional: Add hover effect */
.email-button:hover {
background-color: rgb(245, 180, 54);
text-decoration: none !important; /* Ensure no underline on hover */
}
.st-emotion-cache-1104ytp a {
color: rgb(255 255 255);
}
</style>
""",
unsafe_allow_html=True
)
# State management for displaying inputs/results
if "page" not in st.session_state:
st.session_state.page = "input" # Default to input form
# Render the input form
if st.session_state.page == "input":
# Custom CSS to center-align the text labels
st.markdown("""
<style>
.stSelectbox label {
display: block;
text-align: center;
}
.stSlider label {
display: block;
text-align: center;
}
.stSlider > div > div > div {
height: 10px; /* Make slider line bold */
}
.stSlider > div > div > div > div {
width: 20px; /* Make slider button larger */
height: 20px; /* Make slider button larger */
border-radius: 50%; /* Make slider button round */
background:rgb(253, 190, 64);
}
.stSlider > div > div > div > div > div {
font-size: 18px; /* Make text on top of slider button larger */
color:rgb(253, 190, 64);
font-weight: bold;
}
</style>
""", unsafe_allow_html=True)
city = st.selectbox("Select a City", cities, index=4)
project_type = st.selectbox("Select a Project Type", list(project_types.keys()))
subtype = st.selectbox("Select a Subtype", project_types[project_type])
square_footage = st.slider("Select Square Footage", 0, 100000, 1000)
if st.button("Get Estimate"):
low_estimate, high_estimate, average_estimate = calculate_estimate(city, project_type, subtype, square_footage)
if low_estimate is not None:
st.session_state.result = {
"city": city,
"project_type": project_type,
"subtype": subtype,
"square_footage": square_footage,
"low_estimate": low_estimate,
"high_estimate": high_estimate,
"average_estimate": average_estimate
}
st.session_state.page = "result"
st.rerun()
else:
st.error("No data available for the selected inputs.")
# Render the results page
elif st.session_state.page == "result":
st.markdown("""
<style>
.st-emotion-cache-yw8pof {
padding-top: 76px;
padding-bottom: 0px;
}
</style>""", unsafe_allow_html=True)
result = st.session_state.result
if st.button("Back"):
st.session_state.page = "input"
st.rerun()
st.markdown(f"""
<div class="result-box">
<b>💰 Average Estimate:</b><br>
<span style="font-size: 1.5em;">${result['average_estimate']:,.2f}</span><br>
Estimated Cost Range:<br>
${result['low_estimate']:,.2f} - ${result['high_estimate']:,.2f}<br>
<br>
<a class="email-button" href="mailto:robertor@hkcconstruction.com?subject=Estimate%20Request&body=Hello,%0A%0AI%20would%20like%20a%20detailed%20estimate.%0A%0ADetails:%0A- City: {result['city']}%0A- Project Type: {result['project_type']}%0A- Subtype: {result['subtype']}%0A- Square Footage: {result['square_footage']} sq ft%0A- Average Estimate: ${result['average_estimate']:,.2f}" target="_blank">Request Detailed Budget</a>
<br><br>
<p style="font-size: 1em;">To get a more detailed breakdown of your budget, contact us directly using the button above.</p>
<div style="position: absolute; bottom: 10px; right: 10px;">
<span style="font-size: 0.5em; cursor: pointer;" title="The construction cost data contained herein are of a general nature only and subject to confirmation with respect to specific circumstances.">ℹ️</span>
</div>
</div>
""", unsafe_allow_html=True)