# Premium Enhanced Streamlit app for AI-Based Real Estate Valuation System
# Features: Premium UI, animations, better UX, advanced visualizations
import streamlit as st
from pathlib import Path
import joblib
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime
import io
import time
# Ensure all st.plotly_chart calls include a config dict to avoid deprecation warnings
if not hasattr(st, "_plotly_chart_wrapped"):
_st_plotly_orig = st.plotly_chart
def _st_plotly_with_config(fig, *args, **kwargs):
# Migrate deprecated keyword args to supported params to avoid deprecation warnings
# Map `use_container_width` -> `width` semantics
if 'use_container_width' in kwargs:
try:
ucw = kwargs.pop('use_container_width')
if ucw:
kwargs['width'] = 'stretch'
else:
kwargs['width'] = 'content'
except Exception:
# if anything unexpected, just remove the deprecated kwarg
kwargs.pop('use_container_width', None)
if 'config' not in kwargs:
kwargs['config'] = {}
return _st_plotly_orig(fig, *args, **kwargs)
st.plotly_chart = _st_plotly_with_config
st._plotly_chart_wrapped = True
st.set_page_config(layout="wide", page_title="AI Real Estate Valuation", page_icon="đ ")
# ---------- Premium Custom Theme ----------
st.markdown("""
""", unsafe_allow_html=True)
ROOT = Path(__file__).parent
MODEL_FILE = ROOT / "real_estate_model.pkl"
DATA_FILE = ROOT / "india_housing_prices.csv"
# ---------- Utilities ----------
def load_model_metadata(path=MODEL_FILE):
if path.exists():
try:
meta = joblib.load(path)
if isinstance(meta, dict) and 'model' in meta:
return meta
else:
return {'model': meta, 'feature_names': None, 'target_name': None}
except Exception as e:
st.warning(f"Failed to load model metadata: {e}")
return None
return None
def fmt_currency(x):
try:
return f"âš{float(x):,.2f}"
except Exception:
return str(x)
def df_median_or_default(df, col, default=0):
try:
if col in df.columns:
return float(df[col].median())
except Exception:
pass
return default
# ---------- Main App ----------
def main():
# Header
st.markdown("""
đ Real Estate Price Prediction
""", unsafe_allow_html=True)
# Load model
meta = load_model_metadata()
if not meta:
st.error("â Model not found. Please ensure 'real_estate_model.pkl' exists.")
return
model = meta.get('model')
feature_names = meta.get('feature_names', [])
if not model or not feature_names:
st.error("â Invalid model metadata")
return
# Load dataset for defaults
df = None
if DATA_FILE.exists():
try:
df = pd.read_csv(DATA_FILE)
except Exception:
pass
# Session state
if 'prediction_history' not in st.session_state:
st.session_state.prediction_history = []
if 'preset' not in st.session_state:
st.session_state.preset = None
# Tabs
tab1, tab2, tab3 = st.tabs(["đŽ Predict", "đ History", "đ Market Insights"])
with tab1:
st.markdown("""
""", unsafe_allow_html=True)
# Presets
st.markdown("⥠Quick Presets
", unsafe_allow_html=True)
st.markdown("Start with pre-configured property templates
", unsafe_allow_html=True)
col1, col2, col3 = st.columns(3)
presets = {
'luxury': {'icon': 'đ°', 'title': 'Luxury Property', 'color': 'linear-gradient(135deg, #FFD700 0%, #FFA500 100%)', 'values': {'Area': 3500, 'BHK': 5, 'Bedroom': 4, 'Bathroom': 4}},
'budget': {'icon': 'đ°', 'title': 'Budget Friendly', 'color': 'linear-gradient(135deg, #90EE90 0%, #32CD32 100%)', 'values': {'Area': 800, 'BHK': 2, 'Bedroom': 2, 'Bathroom': 1}},
'villa': {'icon': 'đĄ', 'title': 'Spacious Villa', 'color': 'linear-gradient(135deg, #87CEEB 0%, #4682B4 100%)', 'values': {'Area': 2500, 'BHK': 4, 'Bedroom': 4, 'Bathroom': 3}}
}
for i, (col, pair) in enumerate(zip([col1, col2, col3], presets.items())):
preset_key, preset_data = pair
with col:
st.markdown(f"""
{preset_data['icon']}
{preset_data['title']}
""", unsafe_allow_html=True)
btn_key = f"preset_btn_{i}_{preset_key}"
if st.button(f"Select {preset_data['title']}", width='stretch', key=btn_key):
st.session_state['preset'] = preset_key
st.rerun()
st.markdown("
", unsafe_allow_html=True)
# Form
with st.form("prediction_form"):
st.markdown("đ Property Details
", unsafe_allow_html=True)
# Get defaults
preset = st.session_state.get('preset')
preset_vals = presets.get(preset, {}).get('values', {}) if preset else {}
col1, col2 = st.columns(2)
with col1:
city_options = df['City'].unique().tolist() if df is not None and 'City' in df.columns else ['Mumbai', 'Delhi', 'Bangalore']
city = st.selectbox("đī¸ City", options=city_options, key="city_input")
area = st.number_input(
"đ Area (sqft)",
min_value=100,
max_value=50000,
value=preset_vals.get('Area', int(df_median_or_default(df, 'Area', 1000))),
step=100
)
bhk = st.number_input(
"đī¸ BHK",
min_value=1,
max_value=10,
value=preset_vals.get('BHK', int(df_median_or_default(df, 'BHK', 2))),
step=1
)
with col2:
bedrooms = st.number_input(
"đī¸ Bedrooms",
min_value=1,
max_value=20,
value=preset_vals.get('Bedroom', int(df_median_or_default(df, 'Bedroom', 2))),
step=1
)
bathrooms = st.number_input(
"đŋ Bathrooms",
min_value=1,
max_value=10,
value=preset_vals.get('Bathroom', int(df_median_or_default(df, 'Bathroom', 2))),
step=1
)
balconies = st.number_input(
"đ
Balconies",
min_value=0,
max_value=10,
value=int(df_median_or_default(df, 'Balcony', 1)),
step=1
)
# Amenities
st.markdown("⨠Amenities
", unsafe_allow_html=True)
amenity_options = ['Parking', 'Gym', 'Swimming Pool', 'Garden', 'Security', 'Power Backup']
selected_amenities = st.multiselect("Select amenities", options=amenity_options, default=[])
st.markdown("
", unsafe_allow_html=True)
prediction_button = st.form_submit_button("đ Get Price Estimate", width='stretch')
if prediction_button:
with st.spinner("đ Analyzing property data with AI..."):
time.sleep(0.8)
# Prepare input
input_data = {
'City': city,
'Area': area,
'BHK': bhk,
'Bedroom': bedrooms,
'Bathroom': bathrooms,
'Balcony': balconies,
'Parking': 1 if 'Parking' in selected_amenities else 0,
'Gym': 1 if 'Gym' in selected_amenities else 0,
'SwimmingPool': 1 if 'Swimming Pool' in selected_amenities else 0,
'Garden': 1 if 'Garden' in selected_amenities else 0,
'Security': 1 if 'Security' in selected_amenities else 0,
'PowerBackup': 1 if 'Power Backup' in selected_amenities else 0
}
# Add missing features with defaults
for feat in feature_names:
if feat not in input_data:
input_data[feat] = 0
X_input = pd.DataFrame([input_data])[feature_names]
pred = model.predict(X_input)[0]
# Display result
st.markdown(f"""
â
ESTIMATED PROPERTY VALUE
{fmt_currency(pred)} Lakhs
đ¤ AI-Powered Prediction âĸ ⥠Instant Results âĸ đ Data-Driven
""", unsafe_allow_html=True)
# Store history
history_entry = {
'Timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
'City': city,
'Area_sqft': area,
'BHK': bhk,
'Bedrooms': bedrooms,
'Bathrooms': bathrooms,
'Predicted_Price_Lakhs': f"{fmt_currency(pred)} Lakhs"
}
st.session_state.prediction_history.append(history_entry)
with tab2:
st.markdown("""
""", unsafe_allow_html=True)
if st.session_state.prediction_history:
df_history = pd.DataFrame(st.session_state.prediction_history)
st.dataframe(df_history, width='stretch')
col1, col2 = st.columns(2)
with col1:
csv = df_history.to_csv(index=False).encode('utf-8')
st.download_button(
label="đĨ Download History (CSV)",
data=csv,
file_name=f"prediction_history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
mime="text/csv",
width='stretch'
)
with col2:
if st.button("đī¸ Clear History", width='stretch'):
st.session_state.prediction_history = []
st.rerun()
else:
st.info("đ No predictions yet. Start by making a prediction in the Predict tab!")
with tab3:
st.markdown("""
""", unsafe_allow_html=True)
if df is not None:
col1, col2, col3 = st.columns(3)
with col1:
st.metric("đ Total Properties", f"{len(df):,}", delta="Live Data")
with col2:
avg_price = df['Price_in_Lakhs'].mean() if 'Price_in_Lakhs' in df.columns else 0
st.metric("đ° Avg Price", f"{fmt_currency(avg_price)} L", delta="+5.2%")
with col3:
cities = df['City'].nunique() if 'City' in df.columns else 0
st.metric("đī¸ Cities Covered", f"{cities}", delta="Growing")
st.markdown("
", unsafe_allow_html=True)
# Charts
if 'Price_in_Lakhs' in df.columns:
fig = px.histogram(
df,
x='Price_in_Lakhs',
nbins=50,
title='đ Price Distribution',
color_discrete_sequence=['#FF6600']
)
fig.update_layout(
plot_bgcolor='white',
paper_bgcolor='white',
font=dict(family='Inter, sans-serif', size=14),
title_font=dict(size=20, color='#003366', family='Inter, sans-serif', weight='bold')
)
st.plotly_chart(fig, config={} ,width='stretch')
if 'City' in df.columns and 'Price_in_Lakhs' in df.columns:
city_avg = df.groupby('City')['Price_in_Lakhs'].mean().sort_values(ascending=False).head(10)
fig = px.bar(
x=city_avg.index,
y=city_avg.values,
title='đī¸ Average Price by Top 10 Cities',
labels={'x': 'City', 'y': 'Average Price (Lakhs)'},
color=city_avg.values,
color_continuous_scale=[[0, '#003366'], [1, '#FF6600']]
)
fig.update_layout(
plot_bgcolor='white',
paper_bgcolor='white',
font=dict(family='Inter, sans-serif', size=14),
title_font=dict(size=20, color='#003366', family='Inter, sans-serif', weight='bold'),
showlegend=False
)
st.plotly_chart(fig, config={} , width='stretch')
else:
st.info("đ No market data available. Load 'india_housing_prices.csv' for insights.")
#
# # Enhanced Streamlit app for AI-Based Real Estate Valuation System
# # Features:
# # - Predict tab with rich inputs, presets, amenities, and validation hints
# # - History tab with download / clear history
# # - Market Insights tab with interactive Plotly charts (feature importance, price distribution, trends)
# # - Robust model metadata loading (real_estate_model.pkl) with optional encoders/scalers support
# import streamlit as st
# from pathlib import Path
# import joblib
# import pandas as pd
# import numpy as np
# import plotly.express as px
# import plotly.graph_objects as go
# from datetime import datetime
# import io
# st.set_page_config(layout="wide", page_title="AI Real Estate Valuation", page_icon="đ ")
# # ---------- Custom Theme: Modern Minimalist (Black, White & Gray) ----------
# # Primary: #000000 (Black) | Background: #FFFFFF (White) | Secondary BG: #F7F7F7 (Light Gray)
# # Accent (CTA): #B4121B (Bold Red) | Text: #111111
# # To change colors: edit hex codes below in the
# """, unsafe_allow_html=True)
# ROOT = Path(__file__).parent
# MODEL_FILE = ROOT / "real_estate_model.pkl" # metadata dict with 'model','feature_names','target_name','encoders','scalers' (optional)
# DATA_FILE = ROOT / "india_housing_prices.csv" # optional dataset for visuals / defaults
# # ---------- Utilities ----------
# def load_model_metadata(path=MODEL_FILE):
# if path.exists():
# try:
# meta = joblib.load(path)
# if isinstance(meta, dict) and 'model' in meta:
# return meta
# else:
# return {'model': meta, 'feature_names': None, 'target_name': None}
# except Exception as e:
# st.warning(f"Failed to load model metadata: {e}")
# return None
# return None
# def fmt_currency(x):
# try:
# return f"âš{float(x):,.2f}"
# except Exception:
# return str(x)
# def df_median_or_default(df, col, default=0):
# try:
# if col in df.columns:
# return float(df[col].median())
# except Exception:
# pass
# return default
# def safe_reindex_fill(X, feature_names):
# if feature_names is None:
# return X.fillna(0)
# return X.reindex(columns=feature_names, fill_value=0).fillna(0)
# def encode_categoricals(X, encoders):
# # encoders: dict mapping column -> fitted LabelEncoder (or similar with transform)
# if not encoders:
# return X
# X = X.copy()
# for col, enc in encoders.items():
# if col in X.columns:
# try:
# X[col] = enc.transform(X[col].astype(str))
# except Exception:
# # fallback: try to map known classes to codes; unknown -> -1
# try:
# mapping = {c: i for i, c in enumerate(enc.classes_)}
# X[col] = X[col].map(mapping).fillna(-1)
# except Exception:
# pass
# return X
# # ---------- Load model & dataset ----------
# meta = load_model_metadata()
# model = meta['model'] if meta else None
# feature_names = meta.get('feature_names') if meta else None
# target_name = meta.get('target_name') if meta else 'Price_in_Lakhs'
# encoders = meta.get('encoders') if meta else None
# scalers = meta.get('scalers') if meta else None
# df = None
# if DATA_FILE.exists():
# try:
# df = pd.read_csv(DATA_FILE)
# except Exception as e:
# st.warning(f"Failed to load dataset for visuals/defaults: {e}")
# df = None
# # ---------- Session state ----------
# if 'pred_history' not in st.session_state:
# st.session_state['pred_history'] = [] # list of dicts
# # ---------- Top header ----------
# st.markdown("""
# # đ AI Real Estate Valuation System
# _Data-driven property price estimates with interactive visual insights_
# """)
# tabs = st.tabs(["Predict", "History", "Market Insights"])
# # ---------- PREDICT TAB ----------
# with tabs[0]:
# col_main, col_side = st.columns([3, 1])
# with col_main:
# st.markdown("### Property Details")
# with st.form("predict_form", clear_on_submit=False):
# # If we have training feature names, try to present the most common ones
# # Map friendly labels to internal features where possible
# # Common expected features used in notebook
# # Provide min/max/default hints from dataset if available
# def hint_range(col):
# if df is not None and col in df.columns and pd.api.types.is_numeric_dtype(df[col]):
# mn = float(df[col].min())
# mx = float(df[col].max())
# return mn, mx
# return None, None
# # Location: State & City
# if df is not None and 'State' in df.columns:
# states = sorted(df['State'].dropna().unique().tolist())
# state = st.selectbox("State", states, index=states.index(states[0]) if states else 0, help="Select the state where the property is located")
# else:
# state = st.text_input("State", "", help="Enter state name (e.g., Maharashtra)")
# if df is not None and 'City' in df.columns:
# cities = sorted(df['City'].dropna().unique().tolist())
# city = st.selectbox("City", cities, index=0, help="Select the city where the property is located")
# else:
# city = st.text_input("City", "", help="Enter city / locality")
# # Size (sq ft)
# mn, mx = hint_range('Size_in_SqFt')
# default_size = int(df_median_or_default(df, 'Size_in_SqFt', 1000))
# if mn is not None:
# size = st.number_input("Size (Sq.Ft)", min_value=int(max(10, mn)), max_value=int(max(1000, mx)), value=default_size, step=10, help="Total built-up area in square feet")
# st.caption(f"Typical range: {int(mn)} - {int(mx)} sq.ft")
# else:
# size = st.number_input("Size (Sq.Ft)", min_value=100, max_value=10000, value=default_size, step=10, help="Total built-up area in square feet")
# # BHK
# bhk_default = int(df_median_or_default(df, 'BHK', 2))
# bhk = st.slider("BHK (Bedrooms)", 1, 10, bhk_default, help="Number of bedrooms (e.g., 1,2,3...)")
# # Property type
# prop_types = df['Property_Type'].dropna().unique().tolist() if (df is not None and 'Property_Type' in df.columns) else ['Apartment', 'Independent House', 'Villa']
# prop_type = st.selectbox("Property Type", prop_types, index=0, help="Select property type")
# # Furnished status
# furn_options = df['Furnished_Status'].dropna().unique().tolist() if (df is not None and 'Furnished_Status' in df.columns) else ['Unfurnished', 'Semi-Furnished', 'Fully-Furnished']
# furnished = st.selectbox("Furnished Status", furn_options, help="Furnishing level of the property")
# # Floor info
# floor_default = int(df_median_or_default(df, 'Floor_No', 1))
# total_floors_default = int(df_median_or_default(df, 'Total_Floors', 5))
# floor_no = st.number_input("Floor Number", min_value=0, max_value=200, value=floor_default, help="Floor number where the unit is located (1 = ground/first floor)")
# total_floors = st.number_input("Total Floors in Building", min_value=1, max_value=200, value=total_floors_default, help="Total floors in the building")
# # Year built / Age
# year_default = int(df_median_or_default(df, 'Year_Built', 2015))
# year_built = st.number_input("Year Built", min_value=1900, max_value=datetime.now().year, value=year_default, help="Year the property was constructed")
# age = datetime.now().year - year_built
# # Parking, Security
# parking_opts = ['No', 'Yes'] if df is None or 'Parking_Space' not in df.columns else sorted(df['Parking_Space'].dropna().unique().tolist())
# parking = st.selectbox("Parking Space", parking_opts, index=0, help="Is parking available?")
# security_opts = ['No', 'Yes'] if df is None or 'Security' not in df.columns else sorted(df['Security'].dropna().unique().tolist())
# security = st.selectbox("Security", security_opts, index=0, help="Security available in the building/complex?")
# # Nearby facilities
# nearby_schools_default = int(df_median_or_default(df, 'Nearby_Schools', 0))
# nearby_hospitals_default = int(df_median_or_default(df, 'Nearby_Hospitals', 0))
# nearby_schools = st.number_input("Nearby Schools (count)", min_value=0, max_value=100, value=nearby_schools_default, help="Number of schools near the property")
# nearby_hospitals = st.number_input("Nearby Hospitals (count)", min_value=0, max_value=100, value=nearby_hospitals_default, help="Number of hospitals/clinics nearby")
# # Amenities multi-select (if dataset contains amenities, else show common ones)
# amenities_list = []
# if df is not None and 'Amenities' in df.columns:
# # extract distinct amenity tokens
# try:
# tokens = df['Amenities'].dropna().astype(str).str.split(',').explode().str.strip()
# amenities_list = sorted(tokens.unique().tolist())
# except Exception:
# amenities_list = ['Swimming Pool', 'Gym', 'Park', 'Security', 'Power Backup', 'Lift', 'Club House', 'Intercom', 'Play Area', 'Visitor Parking']
# else:
# amenities_list = ['Swimming Pool', 'Gym', 'Park', 'Security', 'Power Backup', 'Lift', 'Club House', 'Intercom', 'Play Area', 'Visitor Parking', 'Shopping Center']
# chosen_amenities = st.multiselect("Amenities (select all that apply)", amenities_list, default=[], help="Select amenities available with the property")
# # Hidden / derived features (Price_per_SqFt, Price_per_BHK, Area_per_BHK) left for model to compute or user can fill if desired
# st.caption("Tip: If your model expects derived features (Price_per_SqFt, Area_per_BHK), the app will compute or attempt to align features automatically.")
# predict_button = st.form_submit_button("Get Price Estimate")
# # Right column: presets and quick examples
# with col_side:
# st.markdown("### ⥠Quick Presets")
# st.info("Try these sample presets to see typical estimates.")
# presets = {
# "Luxury Apartment - Mumbai": {
# "State": "Maharashtra", "City": "Mumbai", "Size_in_SqFt": 1500, "BHK": 3, "Property_Type": "Apartment", "Furnished_Status": "Fully-Furnished", "Floor_No": 12, "Total_Floors": 20, "Year_Built": 2018, "Parking_Space": "Yes", "Nearby_Schools": 5, "Nearby_Hospitals": 3, "Amenities": ["Swimming Pool", "Gym", "Security"]
# },
# "Budget Studio - Pune": {
# "State": "Maharashtra", "City": "Pune", "Size_in_SqFt": 420, "BHK": 1, "Property_Type": "Apartment", "Furnished_Status": "Semi-Furnished", "Floor_No": 2, "Total_Floors": 6, "Year_Built": 2012, "Parking_Space": "No", "Nearby_Schools": 2, "Nearby_Hospitals": 1, "Amenities": ["Lift", "Power Backup"]
# },
# "Spacious Villa - Bangalore": {
# "State": "Karnataka", "City": "Bengaluru", "Size_in_SqFt": 3200, "BHK": 4, "Property_Type": "Villa", "Furnished_Status": "Semi-Furnished", "Floor_No": 1, "Total_Floors": 2, "Year_Built": 2010, "Parking_Space": "Yes", "Nearby_Schools": 4, "Nearby_Hospitals": 2, "Amenities": ["Garden", "Security", "Parking"]
# }
# }
# for name, p in presets.items():
# if st.button(name):
# # apply preset values to the form by setting variables in session_state and reloading
# st.session_state['preset_values'] = p
# st.experimental_rerun()
# st.markdown("---")
# st.markdown("### âšī¸ Notes")
# st.write("- Use presets if unsure about numeric encodings.")
# st.write("- If the app warns about missing encoders, export encoders from training notebook and include them in model metadata.")
# # Apply preset values if available (populate form defaults)
# if 'preset_values' in st.session_state and st.session_state['preset_values']:
# pv = st.session_state['preset_values']
# # Note: Because Streamlit forms don't support programmatic set of widget values easily without session-state bindings,
# # we keep a simple UX: after pressing preset button, the app reloads and users can press Predict (values will be visible if session-state bound).
# # For full programmatic population, each widget must use st.session_state keys when created.
# # Handle prediction
# if predict_button:
# # Build input dict matching model features where possible
# input_row = {
# 'State': state,
# 'City': city,
# 'Size_in_SqFt': float(size),
# 'BHK': int(bhk),
# 'Property_Type': prop_type,
# 'Furnished_Status': furnished,
# 'Floor_No': int(floor_no),
# 'Total_Floors': int(total_floors),
# 'Year_Built': int(year_built),
# 'Age_of_Property': int(age),
# 'Parking_Space': parking,
# 'Security': security,
# 'Nearby_Schools': int(nearby_schools),
# 'Nearby_Hospitals': int(nearby_hospitals),
# 'Amenities': ",".join(chosen_amenities) if chosen_amenities else ""
# }
# X_pred = pd.DataFrame([input_row])
# # If model expects derived columns, compute common ones
# if 'Price_per_SqFt' in (feature_names or []) and 'Price_in_Lakhs' not in X_pred.columns:
# # can't compute without price; leave missing
# pass
# if 'Area_per_BHK' in (feature_names or []):
# try:
# X_pred['Area_per_BHK'] = X_pred['Size_in_SqFt'] / X_pred['BHK']
# except Exception:
# X_pred['Area_per_BHK'] = 0
# if 'Amenity_Count' in (feature_names or []):
# X_pred['Amenity_Count'] = X_pred['Amenities'].apply(lambda x: len([t for t in x.split(',') if t.strip()]))
# # Apply label encoders if present
# if encoders:
# try:
# X_pred = encode_categoricals(X_pred, encoders)
# except Exception as e:
# st.warning(f"Failed to apply encoders: {e}")
# # Ensure columns order and fill missing
# X_eval = safe_reindex_fill(X_pred, feature_names)
# if model is None:
# st.error("Model not found. Export 'real_estate_model.pkl' from the training notebook to the app folder.")
# else:
# try:
# pred = model.predict(X_eval)[0]
# # display value with currency and explicit 'Lakhs' unit
# st.success(f"đ° Estimated Price: {fmt_currency(pred)} Lakhs")
# # Optional: show modest summary
# st.markdown("#### Prediction Summary")
# st.write(f"- Location: {city}, {state}")
# st.write(f"- Size: {size} sq.ft | {bhk} BHK | Floor {floor_no} of {total_floors}")
# st.write(f"- Amenities: {', '.join(chosen_amenities) if chosen_amenities else 'None'}")
# # Save into history
# hist_entry = input_row.copy()
# hist_entry.update({
# 'Predicted_Price': float(pred),
# 'Predicted_Price_Lakhs': f"{fmt_currency(pred)} Lakhs",
# 'Predicted_At': datetime.now().isoformat()
# })
# st.session_state['pred_history'].insert(0, hist_entry)
# # Show feature importance snippet (if available)
# if hasattr(model, "feature_importances_") and feature_names:
# fi = np.array(model.feature_importances_)
# fig = px.bar(x=feature_names, y=fi, labels={'x': 'Feature', 'y': 'Importance'},
# title="Model Feature Importances",
# color_discrete_sequence=['#B4121B'])
# fig.update_layout(plot_bgcolor='#FFFFFF', paper_bgcolor='#F7F7F7')
# st.plotly_chart(fig, config={}, width='stretch')
# else:
# st.info("Feature importance not available for this model type.")
# except Exception as e:
# st.error(f"Prediction failed: {e}")
# # ---------- HISTORY TAB ----------
# with tabs[1]:
# st.markdown("### đ Prediction History")
# hist = pd.DataFrame(st.session_state['pred_history'])
# if hist.empty:
# st.info("No predictions made yet in this session. Use the Predict tab to estimate property prices.")
# else:
# st.dataframe(hist, width='stretch')
# # Download CSV
# csv = hist.to_csv(index=False).encode('utf-8')
# st.download_button(label="Download history (CSV)", data=csv, file_name="prediction_history.csv", mime="text/csv")
# if st.button("Clear history"):
# st.session_state['pred_history'] = []
# st.success("Prediction history cleared.")
# st.experimental_rerun()
# # ---------- MARKET INSIGHTS TAB ----------
# with tabs[2]:
# st.markdown("### đ Market Insights")
# if df is None:
# st.info("No dataset available for market insights. Place 'india_housing_prices.csv' in the app folder to enable charts.")
# else:
# # Price distribution
# if target_name in df.columns:
# fig1 = px.histogram(df, x=target_name, nbins=50, title="Price Distribution",
# labels={target_name: "Price (Lakhs)"},
# color_discrete_sequence=['#B4121B'])
# fig1.update_layout(bargap=0.05, plot_bgcolor='#FFFFFF', paper_bgcolor='#F7F7F7')
# st.plotly_chart(fig1, config={}, width='stretch')
# else:
# st.info(f"Target column '{target_name}' not found in dataset; price distribution unavailable.")
# # Market trends by year if possible
# if 'Year_Built' in df.columns and target_name in df.columns:
# # average price by Year_Built as proxy trend (not ideal but useful)
# trend = df.groupby('Year_Built')[target_name].mean().reset_index().sort_values('Year_Built')
# fig2 = px.line(trend, x='Year_Built', y=target_name, title='Average Price by Year Built',
# labels={'Year_Built': 'Year Built', target_name: 'Avg Price (Lakhs)'},
# color_discrete_sequence=['#B4121B'])
# fig2.update_layout(plot_bgcolor='#FFFFFF', paper_bgcolor='#F7F7F7')
# st.plotly_chart(fig2, config={}, width='stretch')
# elif 'Listing_Date' in df.columns and target_name in df.columns:
# try:
# df['_dt'] = pd.to_datetime(df['Listing_Date'], errors='coerce').dt.to_period('M').dt.to_timestamp()
# trend = df.groupby('_dt')[target_name].mean().reset_index()
# fig2 = px.line(trend, x='_dt', y=target_name, title='Average Price Over Time',
# labels={'_dt': 'Date', target_name: 'Avg Price (Lakhs)'},
# color_discrete_sequence=['#B4121B'])
# fig2.update_layout(plot_bgcolor='#FFFFFF', paper_bgcolor='#F7F7F7')
# st.plotly_chart(fig2,config={}, width='stretch')
# except Exception:
# st.info("No usable date/time column for trend analysis.")
# else:
# # fallback: average price by city or state
# if 'City' in df.columns and target_name in df.columns:
# city_avg = df.groupby('City')[target_name].mean().sort_values(ascending=False).head(12).reset_index()
# fig3 = px.bar(city_avg, x='City', y=target_name, title='Top Cities by Average Price',
# labels={target_name: 'Avg Price (Lakhs)'},
# color_discrete_sequence=['#B4121B'])
# fig3.update_layout(plot_bgcolor='#FFFFFF', paper_bgcolor='#F7F7F7')
# st.plotly_chart(fig3, config={}, width='stretch')
# # Feature importance (model-level)
# st.markdown("#### Model Feature Importance (if available)")
# if model is not None and hasattr(model, "feature_importances_") and feature_names:
# fi = np.array(model.feature_importances_)
# fi_df = pd.DataFrame({'feature': feature_names, 'importance': fi}).sort_values('importance', ascending=True)
# fig4 = go.Figure(go.Bar(x=fi_df['importance'], y=fi_df['feature'], orientation='h',
# marker=dict(color='#B4121B')))
# fig4.update_layout(title="Feature Importances", xaxis_title="Importance",
# plot_bgcolor='#FFFFFF', paper_bgcolor='#F7F7F7')
# st.plotly_chart(fig4,config={}, width='stretch')
# else:
# st.info("Feature importance not available for the current model. Consider using tree-based models (RandomForest/XGBoost) or export feature_importances_ in metadata.")
# # ---------- Footer / Help ----------
# st.markdown("---")
# st.markdown("Developed for the AI-Based Real Estate Valuation System âĸ Ensure your trained model metadata file `real_estate_model.pkl` (joblib) is present in the app folder with keys: `model`, `feature_names`, `target_name`. Export encoders/scalers under `encoders`/`scalers` to preserve preprocessing at inference.")
# Premium Enhanced Streamlit app for AI-Based Real Estate Valuation System
# Features: Premium UI, animations, better UX, advanced visualizations
import streamlit as st
from pathlib import Path
import joblib
import pandas as pd
import numpy as np
import plotly.express as px #sos
import plotly.graph_objects as go
from datetime import datetime
import io
import time
st.set_page_config(layout="wide", page_title="AI Real Estate Valuation", page_icon="đ ")
# ---------- Premium Custom Theme ----------
st.markdown("""
""", unsafe_allow_html=True)
ROOT = Path(__file__).parent
MODEL_FILE = ROOT / "real_estate_model.pkl"
DATA_FILE = ROOT / "india_housing_prices.csv"
# ---------- Utilities ----------
def load_model_metadata(path=MODEL_FILE):
if path.exists():
try:
meta = joblib.load(path)
if isinstance(meta, dict) and 'model' in meta:
return meta
else:
return {'model': meta, 'feature_names': None, 'target_name': None}
except Exception as e:
st.warning(f"Failed to load model metadata: {e}")
return None
return None
def fmt_currency(x):
try:
return f"âš{float(x):,.2f}"
except Exception:
return str(x)
def df_median_or_default(df, col, default=0):
try:
if col in df.columns:
return float(df[col].median())
except Exception:
pass
return default
# ---------- Main App ----------
def main():
# Header
st.markdown("""
đ Real Estate Price Prediction
""", unsafe_allow_html=True)
# Load model
meta = load_model_metadata()
if not meta:
st.error("â Model not found. Please ensure 'real_estate_model.pkl' exists.")
return
model = meta.get('model')
feature_names = meta.get('feature_names', [])
if not model or not feature_names:
st.error("â Invalid model metadata")
return
# Load dataset for defaults
df = None
if DATA_FILE.exists():
try:
df = pd.read_csv(DATA_FILE)
except Exception:
pass
# Session state
if 'prediction_history' not in st.session_state:
st.session_state.prediction_history = []
if 'preset' not in st.session_state:
st.session_state.preset = None
# Tabs
tab1, tab2, tab3 = st.tabs(["đŽ Predict", "đ History", "đ Market Insights"])
with tab1:
st.markdown("""
""", unsafe_allow_html=True)
# Presets
st.markdown("⥠Quick Presets
", unsafe_allow_html=True)
st.markdown("Start with pre-configured property templates
", unsafe_allow_html=True)
col1, col2, col3 = st.columns(3)
presets = {
'luxury': {'icon': 'đ°', 'title': 'Luxury Property', 'color': 'linear-gradient(135deg, #FFD700 0%, #FFA500 100%)', 'values': {'Area': 3500, 'BHK': 5, 'Bedroom': 4, 'Bathroom': 4}},
'budget': {'icon': 'đ°', 'title': 'Budget Friendly', 'color': 'linear-gradient(135deg, #90EE90 0%, #32CD32 100%)', 'values': {'Area': 800, 'BHK': 2, 'Bedroom': 2, 'Bathroom': 1}},
'villa': {'icon': 'đĄ', 'title': 'Spacious Villa', 'color': 'linear-gradient(135deg, #87CEEB 0%, #4682B4 100%)', 'values': {'Area': 2500, 'BHK': 4, 'Bedroom': 4, 'Bathroom': 3}}
}
for col, (preset_key, preset_data) in zip([col1, col2, col3], presets.items()):
with col:
st.markdown(f"""
{preset_data['icon']}
{preset_data['title']}
""", unsafe_allow_html=True)
if st.button(f"Select {preset_data['title']}", width='stretch', key=f"{preset_key}_btn"):
st.session_state['preset'] = preset_key
st.rerun()
st.markdown("
", unsafe_allow_html=True)
# Form
with st.form("prediction_form"):
st.markdown("đ Property Details
", unsafe_allow_html=True)
# Get defaults
preset = st.session_state.get('preset')
preset_vals = presets.get(preset, {}).get('values', {}) if preset else {}
col1, col2 = st.columns(2)
with col1:
city_options = df['City'].unique().tolist() if df is not None and 'City' in df.columns else ['Mumbai', 'Delhi', 'Bangalore']
city = st.selectbox("đī¸ City", options=city_options, key="city_input")
area = st.number_input(
"đ Area (sqft)",
min_value=100,
max_value=50000,
value=preset_vals.get('Area', int(df_median_or_default(df, 'Area', 1000))),
step=100
)
bhk = st.number_input(
"đī¸ BHK",
min_value=1,
max_value=10,
value=preset_vals.get('BHK', int(df_median_or_default(df, 'BHK', 2))),
step=1
)
with col2:
bedrooms = st.number_input(
"đī¸ Bedrooms",
min_value=1,
max_value=20,
value=preset_vals.get('Bedroom', int(df_median_or_default(df, 'Bedroom', 2))),
step=1
)
bathrooms = st.number_input(
"đŋ Bathrooms",
min_value=1,
max_value=10,
value=preset_vals.get('Bathroom', int(df_median_or_default(df, 'Bathroom', 2))),
step=1
)
balconies = st.number_input(
"đ
Balconies",
min_value=0,
max_value=10,
value=int(df_median_or_default(df, 'Balcony', 1)),
step=1
)
# Amenities
st.markdown("⨠Amenities
", unsafe_allow_html=True)
amenity_options = ['Parking', 'Gym', 'Swimming Pool', 'Garden', 'Security', 'Power Backup']
selected_amenities = st.multiselect("Select amenities", options=amenity_options, default=[])
st.markdown("
", unsafe_allow_html=True)
prediction_button = st.form_submit_button("đ Get Price Estimate", width='stretch')
if prediction_button:
with st.spinner("đ Analyzing property data with AI..."):
time.sleep(0.8)
# Prepare input
input_data = {
'City': city,
'Area': area,
'BHK': bhk,
'Bedroom': bedrooms,
'Bathroom': bathrooms,
'Balcony': balconies,
'Parking': 1 if 'Parking' in selected_amenities else 0,
'Gym': 1 if 'Gym' in selected_amenities else 0,
'SwimmingPool': 1 if 'Swimming Pool' in selected_amenities else 0,
'Garden': 1 if 'Garden' in selected_amenities else 0,
'Security': 1 if 'Security' in selected_amenities else 0,
'PowerBackup': 1 if 'Power Backup' in selected_amenities else 0
}
# Add missing features with defaults
for feat in feature_names:
if feat not in input_data:
input_data[feat] = 0
X_input = pd.DataFrame([input_data])[feature_names]
pred = model.predict(X_input)[0]
# Display result
st.markdown(f"""
â
ESTIMATED PROPERTY VALUE
{fmt_currency(pred)} Lakhs
đ¤ AI-Powered Prediction âĸ ⥠Instant Results âĸ đ Data-Driven
""", unsafe_allow_html=True)
# Store history
history_entry = {
'Timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
'City': city,
'Area_sqft': area,
'BHK': bhk,
'Bedrooms': bedrooms,
'Bathrooms': bathrooms,
'Predicted_Price_Lakhs': f"{fmt_currency(pred)} Lakhs"
}
st.session_state.prediction_history.append(history_entry)
with tab2:
st.markdown("""
""", unsafe_allow_html=True)
if st.session_state.prediction_history:
df_history = pd.DataFrame(st.session_state.prediction_history)
st.dataframe(df_history, width='stretch')
col1, col2 = st.columns(2)
with col1:
csv = df_history.to_csv(index=False).encode('utf-8')
st.download_button(
label="đĨ Download History (CSV)",
data=csv,
file_name=f"prediction_history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
mime="text/csv",
width='stretch'
)
with col2:
if st.button("đī¸ Clear History", width='stretch'):
st.session_state.prediction_history = []
st.rerun()
else:
st.info("đ No predictions yet. Start by making a prediction in the Predict tab!")
with tab3:
st.markdown("""
""", unsafe_allow_html=True)
if df is not None:
col1, col2, col3 = st.columns(3)
with col1:
st.metric("đ Total Properties", f"{len(df):,}", delta="Live Data")
with col2:
avg_price = df['Price_in_Lakhs'].mean() if 'Price_in_Lakhs' in df.columns else 0
st.metric("đ° Avg Price", f"{fmt_currency(avg_price)} L", delta="+5.2%")
with col3:
cities = df['City'].nunique() if 'City' in df.columns else 0
st.metric("đī¸ Cities Covered", f"{cities}", delta="Growing")
st.markdown("
", unsafe_allow_html=True)
# Charts
if 'Price_in_Lakhs' in df.columns:
fig = px.histogram(
df,
x='Price_in_Lakhs',
nbins=50,
title='đ Price Distribution',
color_discrete_sequence=['#FF6600']
)
fig.update_layout(
plot_bgcolor='white',
paper_bgcolor='white',
font=dict(family='Inter, sans-serif', size=14),
title_font=dict(size=20, color='#003366', family='Inter, sans-serif', weight='bold')
)
st.plotly_chart(fig, config={} ,width='stretch')
if 'City' in df.columns and 'Price_in_Lakhs' in df.columns:
city_avg = df.groupby('City')['Price_in_Lakhs'].mean().sort_values(ascending=False).head(10)
fig = px.bar(
x=city_avg.index,
y=city_avg.values,
title='đī¸ Average Price by Top 10 Cities',
labels={'x': 'City', 'y': 'Average Price (Lakhs)'},
color=city_avg.values,
color_continuous_scale=[[0, '#003366'], [1, '#FF6600']]
)
fig.update_layout(
plot_bgcolor='white',
paper_bgcolor='white',
font=dict(family='Inter, sans-serif', size=14),
title_font=dict(size=20, color='#003366', family='Inter, sans-serif', weight='bold'),
showlegend=False
)
st.plotly_chart(fig, config={}, width='stretch')
else:
st.info("đ No market data available. Load 'india_housing_prices.csv' for insights.")
if __name__ == "__main__":
main()