Spaces:
Sleeping
Sleeping
# Add SQLite fix for non-Windows platforms | |
import platform | |
if platform.system() != "Windows": # Skip on Windows | |
__import__('pysqlite3') | |
import sys | |
sys.modules['sqlite3'] = sys.modules.pop('pysqlite3') | |
import streamlit as st | |
from datetime import datetime | |
import time | |
import plotly.express as px | |
import pandas as pd | |
from src.utils.config import * | |
from src.utils.helper import * | |
from src.utils.styles import * | |
from src.db_agent import VarturRealEstateSearch | |
from src.analytics import RealEstateAnalytics | |
class EnhancedStreamlitApp: | |
def __init__(self): | |
self.config = AppConfig() | |
init_folders() | |
# Initialize the search system only once and store it in session state | |
if 'search_system' not in st.session_state: | |
st.session_state.search_system = VarturRealEstateSearch(persist_directory=DB_FOLDER) | |
# Assign the search system from session state | |
self.search_system = st.session_state.search_system | |
def render_login(self): | |
st.markdown(""" | |
<div style='text-align: center; padding: 50px;'> | |
<h1>๐ข VARTUR ยฎ Real Estate Brokerage</h1> | |
<p>Advanced Property Search Engine</p> | |
</div> | |
""", unsafe_allow_html=True) | |
with st.form("login_form", clear_on_submit=True): | |
st.markdown("<h3 style='text-align: center;'>Welcome Back</h3>", unsafe_allow_html=True) | |
password = st.text_input("Password", type="password") | |
if st.form_submit_button("Login", use_container_width=True): | |
if self.config.verify_password(password): | |
st.session_state.authenticated = True | |
st.rerun() | |
else: | |
st.error("โ Invalid password") | |
def render_dashboard(self): | |
stats = self.config.get_stats() | |
total_properties = self.search_system.collection.count() | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
st.markdown(f""" | |
<div class='stats-card'> | |
<h3>๐ Indexed Files</h3> | |
<h2>{stats["total_files"]}</h2> | |
</div> | |
""", unsafe_allow_html=True) | |
with col2: | |
st.markdown(f""" | |
<div class='stats-card'> | |
<h3>๐ข Total Properties</h3> | |
<h2>{total_properties}</h2> | |
</div> | |
""", unsafe_allow_html=True) | |
with col3: | |
st.markdown(f""" | |
<div class='stats-card'> | |
<h3>๐ Last Updated</h3> | |
<div style="display: inline-flex; align-items: baseline; gap: 0px;"> | |
<h2 style="margin: 0;"> | |
{datetime.fromisoformat(stats["last_updated"]).astimezone(DUBAI_TZ).strftime("%d %b, %H:%M") if stats["last_updated"] else "Never"} | |
</h2> | |
<span style="font-size: 1.0em; opacity: 0.7;">(Dubai TZ)</span> | |
</div> | |
</div> | |
""", unsafe_allow_html=True) | |
def render_search(self): | |
"""Render the search interface with filters""" | |
st.markdown(""" | |
<div style='background: linear-gradient(120deg, #1a5f7a 0%, #66a6ff 100%); | |
padding: 15px; | |
border-radius: 10px; | |
margin-bottom: 10px; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);'> | |
<h3 style='color: white; margin: 0;'>๐ Search</h3> | |
<p style='color: #e0e0e0; margin: 5px 0 0 0;'>search with your custom needs</p> | |
</div> | |
""", unsafe_allow_html=True) | |
# reference information box | |
with st.expander("๐ Search Functionality Guidelines", expanded=False): | |
st.markdown(""" | |
##### ๐ Search System Overview | |
The search functionality works in two primary ways: | |
**1. ๐ฃ๏ธ Natural Language Search Box** | |
- Located at the top of the page | |
- Useful for broad, descriptive searches | |
- Examples: "sea view apartments", "large penthouses" | |
- Note: As this searches structured data, results may vary | |
**2. โก Filter System** | |
- More precise filtering options | |
- Exact matching on specific criteria | |
- Filters applied after text search | |
- More reliable for specific requirements | |
**๐ก Best Practices** | |
- For exact requirements: Use filters | |
- For exploration: Use text search | |
- For specific items with context: Combine both | |
- Filters always take precedence over text search | |
**๐ Power of Combinations:** | |
- Combine filters for precise results: | |
* "Penthouse" search + Sea view + Price 2-5M + High floor | |
* 4BR + Burj view + EMAAR + 2000-3000 sq.ft | |
* Downtown area + 2BR + Floor 10+ + Price up to 2M | |
- Mix and match any number of filters to narrow down exactly what you want | |
- The more filters you combine, the more specific your results | |
""") | |
# Search Configuration | |
config_col1, config_col2 = st.columns([3, 1]) | |
with config_col1: | |
query = st.text_input( | |
"๐ Search Properties", | |
value="", | |
placeholder="Type Naturally: e.g. 4 bedroom with sea view, 2br downtown, penthouse burj view", | |
help="Search is flexible - Try different ways of describing what you want", | |
) | |
with config_col2: | |
n_results = st.number_input( | |
"๐ Results Limit", | |
min_value=1, | |
max_value=1000, | |
value=10, | |
help="Maximum number of results" | |
) | |
# Organize filters in tabs | |
filter_tabs = st.tabs(["๐ Basic Filters", "โ๏ธ Advanced Filters"]) | |
with filter_tabs[0]: | |
col1, col2 = st.columns(2) | |
# Column 1: Price and Unit Type | |
with col1: | |
price_col1, price_col2 = st.columns(2) | |
with price_col1: | |
min_price = st.number_input( | |
"๐ฐ Min Price (M)", | |
value=0.0, | |
step=0.1, | |
format="%.1f", | |
help="Min Price in millions of AED", | |
) | |
with price_col2: | |
max_price = st.number_input( | |
"๐ฐ Max Price (M)", | |
value=0.0, | |
step=0.1, | |
format="%.1f", | |
help="Max Price in millions of AED", | |
) | |
unit_type = st.text_input( | |
"๐ Unit Type", | |
placeholder="e.g. 4BR, 2 bed, studio, TH...", | |
help="Flexible input: '4BR', '2 bed', 'studio', etc." | |
) | |
# Column 2: Developer and View | |
with col2: | |
developer = st.text_input( | |
"๐๏ธ Developer", | |
placeholder="Type developer name", | |
help="Case-insensitive search" | |
) | |
view = st.text_input( | |
"๐๏ธ View", | |
placeholder="e.g. sea, burj...", | |
help="Type any part of the view" | |
) | |
with filter_tabs[1]: | |
col1, col2 = st.columns(2) | |
# Column 1: Area Range | |
with col1: | |
area_col1, area_col2 = st.columns(2) | |
with area_col1: | |
min_area = st.number_input( | |
"๐ Min Area (sq.ft)", | |
value=0, | |
step=100, | |
help="Min Area in square feet", | |
) | |
with area_col2: | |
max_area = st.number_input( | |
"๐ Max Area (sq.ft)", | |
value=0, | |
step=100, | |
help="Max Area in square feet", | |
) | |
# Column 2: Floor Range | |
with col2: | |
floor_col1, floor_col2 = st.columns(2) | |
with floor_col1: | |
min_floor = st.text_input( | |
"๐ข Min Floor", | |
placeholder="e.g., G, 1, 2...", | |
help="Min Floor Number" | |
) | |
with floor_col2: | |
max_floor = st.text_input( | |
"๐ข Max Floor", | |
placeholder="e.g., 50, PH...", | |
help="Max Floor Number" | |
) | |
# Search execution | |
if st.button("๐ Search", use_container_width=True): | |
filters = { | |
'query': query.strip() if query else None, | |
'min_price': min_price * 1_000_000 if min_price > 0 else None, | |
'max_price': max_price * 1_000_000 if max_price > 0 else None, | |
'min_area': min_area if min_area > 0 else None, | |
'max_area': max_area if max_area > 0 else None, | |
'min_floor': min_floor.strip() if min_floor else None, | |
'max_floor': max_floor.strip() if max_floor else None, | |
'developer': developer.strip() if developer else None, | |
'view': view.strip() if view else None, | |
'unit_type': unit_type.strip() if unit_type else None, | |
'n_results': n_results | |
} | |
# Execute search with all parameters | |
self.execute_search(filters) | |
def execute_search(self, filters): | |
"""Execute search with post-filtering and results display""" | |
try: | |
active_filters = {k: v for k, v in filters.items() | |
if v is not None and k != 'n_results'} | |
if not active_filters: | |
st.warning("Please enter at least one search criterion") | |
return | |
with st.spinner("Searching..."): | |
results = self.search_system.filter_search(**filters) | |
if not results: | |
st.info("No properties found matching your criteria") | |
return | |
# Create results DataFrame | |
df = pd.DataFrame([{ | |
'Match': f"{r['similarity']:.1%}", | |
'Unit': r['metadata']['unit_code'], | |
'Type': r['metadata']['unit_type'].title(), | |
'Floor': r['metadata']['floor'], | |
'View': r['metadata']['view'].title(), | |
'Area': f"{float(str(r['metadata']['total_area']).replace(',', '')):,.0f}", | |
'Price': f"{float(str(r['metadata']['price']).replace(',', '')):,.2f}", | |
'Developer': r['metadata']['developer'].title() | |
} for r in results]) | |
# Display results | |
# used_filters = [k for k, v in active_filters.items() if v is not None] | |
st.success(f"Found {len(results)} properties matching your criteria") | |
# Results tabs | |
tabs = st.tabs(["Results", "Analytics"]) | |
with tabs[0]: | |
st.dataframe( | |
df, | |
use_container_width=True, | |
height=min(400, 35 + 35 * len(df)) | |
) | |
with tabs[1]: | |
col1, col2 = st.columns(2) | |
with col1: | |
price_data = pd.to_numeric( | |
df['Price'].str.replace(',', ''), | |
errors='coerce' | |
) | |
fig_price = px.histogram( | |
price_data, | |
title="Price Distribution", | |
labels={'value': 'Price (AED)', 'count': 'Properties'}, | |
color_discrete_sequence=['#4a90e2'] | |
) | |
st.plotly_chart(fig_price, use_container_width=True) | |
with col2: | |
area_data = pd.to_numeric( | |
df['Area'].str.replace(',', ''), | |
errors='coerce' | |
) | |
fig_area = px.histogram( | |
area_data, | |
title="Area Distribution", | |
labels={'value': 'Area (sq.ft)', 'count': 'Properties'}, | |
color_discrete_sequence=['#2ecc71'] | |
) | |
st.plotly_chart(fig_area, use_container_width=True) | |
# Export option | |
st.download_button( | |
"๐ฅ Export Results", | |
df.to_csv(index=False), | |
f"property_results_{datetime.now().strftime('%Y%m%d_%H%M')}.csv", | |
"text/csv", | |
use_container_width=True | |
) | |
except Exception as e: | |
st.error(f"Search error: {str(e)}") | |
def render_file_management(self): | |
st.markdown(""" | |
<div style='background: linear-gradient(120deg, #1a5f7a 0%, #66a6ff 100%); | |
padding: 15px; | |
border-radius: 10px; | |
margin-bottom: 10px; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);'> | |
<h3 style='color: white; margin: 0;'>๐ File Management</h3> | |
<p style='color: #e0e0e0; margin: 5px 0 0 0;'>upload and index your files</p> | |
</div> | |
""", unsafe_allow_html=True) | |
# Add this right after your file management header | |
with st.expander("๐ File Upload Guidelines", expanded=False): | |
st.markdown(""" | |
##### ๐ Required Data Structure | |
Your CSV file must follow this structure: | |
""") | |
# Create sample DataFrame with realistic property data | |
sample_df = pd.DataFrame({ | |
'UnitCode': ['UNIT001', 'UNIT002', 'UNIT003'], | |
'UnitType': ['4 Bedroom', '2 BR', 'Studio'], | |
'Floor': ['15', 'G', 'PH'], | |
'Developer': ['EMAAR', 'DAMAC', 'Select Group'], | |
'TotalArea': [2500, 1200, 550], | |
'AskingPrice': [3500000, 1800000, 950000], | |
'View': ['Sea View', 'Burj View', 'City View'] | |
}) | |
# Show sample data | |
st.dataframe(sample_df) | |
st.markdown(""" | |
##### โ๏ธ Column Specifications | |
- **UnitCode**: Unique identifier for each property | |
- **UnitType**: Property type (e.g., 4 Bedroom, 2 BR, Studio) | |
- **Floor**: Floor level (e.g., G, 1, 15, PH) | |
- **Developer**: Developer name | |
- **TotalArea**: Area in square feet (numeric) | |
- **AskingPrice**: Price in AED (numeric) | |
- **View**: View description | |
##### ๐ Notes: | |
- All columns are required | |
- Use consistent format for unit types | |
- Numeric values should not contain currency symbols | |
- Floor can be text (G = Ground, PH = Penthouse, etc.) | |
""") | |
# Download sample | |
st.download_button( | |
"๐ฅ Download Sample Template", | |
sample_df.to_csv(index=False), | |
"property_data_template.csv", | |
"text/csv", | |
help="Download a sample CSV template with correct structure" | |
) | |
# File Upload Section with Preview | |
col1, col2 = st.columns([2, 3]) | |
with col1: | |
st.markdown("#### Upload New File") | |
uploaded_file = st.file_uploader("Choose CSV file", type=['csv']) | |
if uploaded_file: | |
try: | |
df = pd.read_csv(uploaded_file) | |
file_hash = get_file_hash(uploaded_file.getvalue()) | |
# File validation | |
is_valid, missing_cols = validate_csv_structure(df) | |
if not is_valid: | |
st.error(f"Missing columns: {', '.join(missing_cols)}") | |
return | |
# Show data preview | |
st.markdown("##### Data Preview") | |
st.dataframe( | |
df.head(5), | |
use_container_width=True, | |
height=200 | |
) | |
# File stats | |
st.markdown("##### File Statistics") | |
st.markdown(""" | |
<style> | |
.small-font { | |
font-size: 14px !important; /* Adjust font size */ | |
font-weight: normal !important; /* Use normal font weight */ | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
# Displaying metrics with custom HTML and smaller font size | |
stats_col1, stats_col2 = st.columns(2) | |
with stats_col1: | |
st.markdown(f"<div class='small-font'>Total Rows: {len(df)}</div>", unsafe_allow_html=True) | |
with stats_col2: | |
st.markdown(f"<div class='small-font'>File Size: {uploaded_file.size/1024:.1f} KB</div>", unsafe_allow_html=True) | |
# Index button with status | |
if st.button("๐ฅ Index File", use_container_width=True): | |
with st.spinner("Indexing..."): | |
file_path = save_uploaded_file(uploaded_file) | |
if file_path: | |
result = self.search_system.load_data(file_path, reset_collection=True) | |
if result["status"] == "success": | |
self.config.update_indexed_files(uploaded_file.name, file_hash) | |
st.success(f"โ Indexed {result['count']} properties") | |
st.balloons() | |
else: | |
st.error(f"โ Failed: {result['message']}") | |
except Exception as e: | |
st.error(f"Error reading file: {str(e)}") | |
with col2: | |
st.markdown("#### Indexed Files") | |
if self.config.config["indexed_files"]: | |
for filename, info in self.config.config["indexed_files"].items(): | |
with st.container(): | |
st.markdown( | |
f""" | |
<div style='background-color: white; padding: 15px; border-radius: 8px; margin: 10px 0; border-left: 4px solid #4a90e2;'> | |
<h5 style='margin: 0;'>{filename}</h5> | |
<p style='margin: 5px 0; color: #666;'> | |
Indexed: {datetime.fromisoformat(info['timestamp']).astimezone(DUBAI_TZ).strftime('%d %b, %H:%M')} | |
</p> | |
</div> | |
""", | |
unsafe_allow_html=True | |
) | |
col1, col2, col3 = st.columns([1,1,1]) | |
with col1: | |
if st.button("๐๏ธ Preview", key=f"preview_{filename}"): | |
try: | |
df = pd.read_csv(os.path.join(UPLOAD_FOLDER, filename)) | |
st.dataframe(df.head()) | |
except Exception as e: | |
st.error(f"Error previewing file: {str(e)}") | |
with col2: | |
if st.button("๐ฅ Export", key=f"export_{filename}"): | |
try: | |
df = pd.read_csv(os.path.join(UPLOAD_FOLDER, filename)) | |
st.download_button( | |
"Download CSV", | |
df.to_csv(index=False), | |
filename, | |
"text/csv" | |
) | |
except Exception as e: | |
st.error(f"Error exporting file: {str(e)}") | |
with col3: | |
if st.button("๐๏ธ Remove", key=f"remove_{filename}"): | |
self.remove_file(filename) | |
st.rerun() | |
else: | |
st.info("No files indexed yet") | |
def remove_file(self, filename): | |
try: | |
# Remove from database | |
self.search_system.delete_properties(files=[filename]) | |
# Remove from config | |
self.config.remove_indexed_file(filename) | |
# Remove physical file | |
file_path = os.path.join(UPLOAD_FOLDER, filename) | |
if os.path.exists(file_path): | |
os.remove(file_path) | |
st.success(f"โ Removed {filename}") | |
time.sleep(1) # Give time for success message | |
except Exception as e: | |
st.error(f"โ Error removing file: {str(e)}") | |
def render_analytics(self): | |
"""Render the analytics dashboard with enhanced styling and error handling.""" | |
try: | |
st.markdown(""" | |
<div style='background: linear-gradient(120deg, #1a5f7a 0%, #66a6ff 100%); | |
padding: 15px; | |
border-radius: 10px; | |
margin-bottom: 10px; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);'> | |
<h3 style='color: white; margin: 0;'>๐ Analytics</h3> | |
<p style='color: #e0e0e0; margin: 5px 0 0 0;'>market analysis and insights</p> | |
</div> | |
""", unsafe_allow_html=True) | |
# reference | |
with st.expander("๐ Analytics Guidelines", expanded=False): | |
st.markdown(""" | |
##### ๐ Analytics Overview | |
Our analytics dashboard offers five key analysis areas: | |
**1. ๐ Price Analysis** | |
- Price distribution analysis | |
- Price per square foot trends | |
- Price segmentation across properties | |
- Distribution patterns and outliers | |
**2. ๐ข Property Distribution** | |
- Property type breakdown | |
- Average price by property type | |
- Type-wise market share | |
- Detailed property metrics | |
**3. ๐ฅ Developer Insights** | |
- Developer market share | |
- Average price by developer | |
- Developer performance metrics | |
- Portfolio size analysis | |
**4. ๐ Location Analysis** | |
- View distribution analysis | |
- Price premium by view | |
- Location-based pricing | |
- View preference patterns | |
**5. ๐ Market Overview** | |
- Time-based price trends | |
- Property inventory trends | |
- Market momentum analysis | |
- Monthly price variations | |
##### ๐ก Interactive Features | |
Each visualization offers: | |
- Hover for detailed information | |
- Click on legends to filter | |
- Download charts as images | |
- Dynamic data filtering | |
""") | |
# Initialize analytics | |
analytics = RealEstateAnalytics(self.search_system) | |
# Get data with error handling | |
with st.spinner("Loading data..."): | |
df = analytics.get_all_properties() | |
if df.empty: | |
st.warning("No data available for analysis. Please ensure properties are indexed.") | |
return | |
# Summary Statistics Cards with consistent styling | |
col1, col2, col3, col4 = st.columns(4) | |
with col1: | |
total_value = df['AskingPrice'].sum() | |
st.metric( | |
"Portfolio Value", | |
f"{total_value/1e9:.2f}B AED", | |
f"{len(df):,} properties" | |
) | |
with col2: | |
avg_price = df['AskingPrice'].mean() | |
st.metric( | |
"Average Price", | |
f"{avg_price/1e6:.2f}M AED", | |
f"ยฑ{df['AskingPrice'].std()/1e6:.1f}M" | |
) | |
with col3: | |
avg_area = df['TotalArea'].mean() | |
st.metric( | |
"Average Area", | |
f"{avg_area:,.0f} sq.ft", | |
f"ยฑ{df['TotalArea'].std():,.0f}" | |
) | |
with col4: | |
st.metric( | |
"Total Units", | |
f"{len(df):,}", | |
f"{df['UnitType'].nunique()} types" | |
) | |
# Create analysis tabs | |
analysis_tabs = st.tabs([ | |
"๐ Price Analysis", | |
"๐ข Property Distribution", | |
"๐ฅ Developer Insights", | |
"๐ Location Analysis", | |
"๐ Market Overview" | |
]) | |
# Price Analysis Tab | |
with analysis_tabs[0]: | |
st.subheader("Price Distribution Analysis") | |
col1, col2 = st.columns(2) | |
with col1: | |
# Price histogram | |
fig_price = px.histogram( | |
df, | |
x='AskingPrice', | |
nbins=30, | |
title='Property Price Distribution', | |
labels={'AskingPrice': 'Price (AED)'}, | |
color_discrete_sequence=['#4a90e2'] | |
) | |
fig_price.update_layout( | |
showlegend=False, | |
plot_bgcolor='white', | |
paper_bgcolor='white' | |
) | |
st.plotly_chart(fig_price, use_container_width=True) | |
with col2: | |
# Price per sqft analysis | |
fig_psf = px.histogram( | |
df, | |
x='PricePerSqft', | |
nbins=30, | |
title='Price per Sqft Distribution', | |
labels={'PricePerSqft': 'Price per Sqft (AED)'}, | |
color_discrete_sequence=['#2ecc71'] | |
) | |
fig_psf.update_layout( | |
showlegend=False, | |
plot_bgcolor='white', | |
paper_bgcolor='white' | |
) | |
st.plotly_chart(fig_psf, use_container_width=True) | |
# Property Distribution Tab | |
with analysis_tabs[1]: | |
st.subheader("Property Type Analysis") | |
col1, col2 = st.columns(2) | |
with col1: | |
# Property type distribution | |
type_dist = df['UnitType'].value_counts() | |
fig_types = px.pie( | |
values=type_dist.values, | |
names=type_dist.index, | |
title='Property Type Distribution', | |
hole=0.4, | |
color_discrete_sequence=px.colors.sequential.Blues_r | |
) | |
st.plotly_chart(fig_types, use_container_width=True) | |
with col2: | |
# Average price by type | |
avg_price_type = df.groupby('UnitType')['AskingPrice'].mean().sort_values() | |
fig_price_type = px.bar( | |
x=avg_price_type.values, | |
y=avg_price_type.index, | |
orientation='h', | |
title='Average Price by Property Type', | |
labels={'x': 'Average Price (AED)', 'y': 'Property Type'}, | |
color_discrete_sequence=['#4a90e2'] | |
) | |
st.plotly_chart(fig_price_type, use_container_width=True) | |
# Developer Insights Tab | |
with analysis_tabs[2]: | |
st.subheader("Developer Analysis") | |
col1, col2 = st.columns(2) | |
with col1: | |
# Developer market share | |
dev_share = df.groupby('Developer')['UnitCode'].count().sort_values() | |
fig_dev = px.bar( | |
x=dev_share.values, | |
y=dev_share.index, | |
orientation='h', | |
title='Developer Market Share', | |
labels={'x': 'Number of Properties', 'y': 'Developer'}, | |
color_discrete_sequence=['#4a90e2'] | |
) | |
st.plotly_chart(fig_dev, use_container_width=True) | |
with col2: | |
# Average price by developer | |
avg_price_dev = df.groupby('Developer')['PricePerSqft'].mean().sort_values() | |
fig_dev_price = px.bar( | |
x=avg_price_dev.values, | |
y=avg_price_dev.index, | |
orientation='h', | |
title='Average Price/Sqft by Developer', | |
labels={'x': 'Average Price/Sqft (AED)', 'y': 'Developer'}, | |
color_discrete_sequence=['#2ecc71'] | |
) | |
st.plotly_chart(fig_dev_price, use_container_width=True) | |
# Location Analysis Tab | |
with analysis_tabs[3]: | |
st.subheader("View and Location Analysis") | |
col1, col2 = st.columns(2) | |
with col1: | |
# View distribution | |
view_dist = df['View'].value_counts() | |
fig_view = px.pie( | |
values=view_dist.values, | |
names=view_dist.index, | |
title='View Distribution', | |
hole=0.4, | |
color_discrete_sequence=px.colors.sequential.Blues_r | |
) | |
st.plotly_chart(fig_view, use_container_width=True) | |
with col2: | |
# Price premium by view | |
avg_price_view = df.groupby('View')['PricePerSqft'].mean().sort_values() | |
fig_view_price = px.bar( | |
x=avg_price_view.values, | |
y=avg_price_view.index, | |
orientation='h', | |
title='Average Price/Sqft by View', | |
labels={'x': 'Average Price/Sqft (AED)', 'y': 'View'}, | |
color_discrete_sequence=['#4a90e2'] | |
) | |
st.plotly_chart(fig_view_price, use_container_width=True) | |
# Market Overview Tab | |
with analysis_tabs[4]: | |
st.subheader("Market Overview") | |
# Time series analysis | |
df['IndexedDate'] = pd.to_datetime(df['IndexedDate']) | |
df['Month'] = df['IndexedDate'].dt.to_period('M') | |
monthly_trends = df.groupby('Month').agg({ | |
'AskingPrice': 'mean', | |
'UnitCode': 'count' | |
}).reset_index() | |
monthly_trends['Month'] = monthly_trends['Month'].dt.to_timestamp() | |
# Price trends | |
fig_trends = px.line( | |
monthly_trends, | |
x='Month', | |
y='AskingPrice', | |
title='Average Property Price Trend', | |
labels={'AskingPrice': 'Average Price (AED)', 'Month': 'Month'}, | |
color_discrete_sequence=['#4a90e2'] | |
) | |
st.plotly_chart(fig_trends, use_container_width=True) | |
# Inventory trends | |
fig_inventory = px.line( | |
monthly_trends, | |
x='Month', | |
y='UnitCode', | |
title='Property Inventory Trend', | |
labels={'UnitCode': 'Number of Properties', 'Month': 'Month'}, | |
color_discrete_sequence=['#2ecc71'] | |
) | |
st.plotly_chart(fig_inventory, use_container_width=True) | |
except Exception as e: | |
st.error(f"Error in analytics dashboard: {str(e)}") | |
st.info("Please check your data and try refreshing the page.") | |
def run(self): | |
"""Main application entry point with authentication and navigation.""" | |
st.set_page_config( | |
page_title="VARTUR ยฎ Real Estate Brokerage Search Engine", | |
page_icon="๐ข", | |
layout="wide" | |
) | |
st.markdown(CUSTOM_CSS, unsafe_allow_html=True) | |
if 'authenticated' not in st.session_state: | |
st.session_state.authenticated = False | |
if not st.session_state.authenticated: | |
self.render_login() | |
return | |
# Top navigation bar | |
col1, col2 = st.columns([6,1]) | |
with col1: | |
st.title("๐ข Vartur Dashboard") | |
with col2: | |
if st.button("๐ช Logout", use_container_width=True): | |
st.session_state.authenticated = False | |
st.rerun() | |
self.render_dashboard() | |
# Main navigation tabs | |
tabs = st.tabs(["๐ Search", "๐ File Management", "๐ Analytics"]) | |
with tabs[0]: | |
self.render_search() | |
with tabs[1]: | |
self.render_file_management() | |
with tabs[2]: | |
self.render_analytics() | |
if __name__ == "__main__": | |
app = EnhancedStreamlitApp() | |
app.run() | |