diff --git a/_pycache_/air_quality.cpython-310.pyc b/_pycache_/air_quality.cpython-310.pyc
deleted file mode 100644
index fb0827ab..00000000
Binary files a/_pycache_/air_quality.cpython-310.pyc and /dev/null differ
diff --git a/_pycache_/chatbot.cpython-310.pyc b/_pycache_/chatbot.cpython-310.pyc
deleted file mode 100644
index 033a52e9..00000000
Binary files a/_pycache_/chatbot.cpython-310.pyc and /dev/null differ
diff --git a/_pycache_/config.cpython-310.pyc b/_pycache_/config.cpython-310.pyc
deleted file mode 100644
index 21a04f66..00000000
Binary files a/_pycache_/config.cpython-310.pyc and /dev/null differ
diff --git a/_pycache_/crime.cpython-310.pyc b/_pycache_/crime.cpython-310.pyc
deleted file mode 100644
index 1b1a904a..00000000
Binary files a/_pycache_/crime.cpython-310.pyc and /dev/null differ
diff --git a/_pycache_/events.cpython-310.pyc b/_pycache_/events.cpython-310.pyc
deleted file mode 100644
index 0164097c..00000000
Binary files a/_pycache_/events.cpython-310.pyc and /dev/null differ
diff --git a/_pycache_/tourist.cpython-310.pyc b/_pycache_/tourist.cpython-310.pyc
deleted file mode 100644
index 0b78053b..00000000
Binary files a/_pycache_/tourist.cpython-310.pyc and /dev/null differ
diff --git a/_pycache_/transport.cpython-310.pyc b/_pycache_/transport.cpython-310.pyc
deleted file mode 100644
index 98bf53c8..00000000
Binary files a/_pycache_/transport.cpython-310.pyc and /dev/null differ
diff --git a/_pycache_/weather.cpython-310.pyc b/_pycache_/weather.cpython-310.pyc
deleted file mode 100644
index dc8ca4db..00000000
Binary files a/_pycache_/weather.cpython-310.pyc and /dev/null differ
diff --git a/app.py b/app.py
index 82ed4e24..3dd11993 100644
--- a/app.py
+++ b/app.py
@@ -1,8 +1,12 @@
import streamlit as st
import pandas as pd
import datetime
+import time
+import plotly.graph_objects as go
+import plotly.express as px
+from plotly.subplots import make_subplots
+import numpy as np
-# Import your existing utility functions
from utils.weather import get_current_weather, get_monthly_weather
from utils.tourist import get_recommendations
from config import CITY_COORDS
@@ -10,134 +14,752 @@
from utils.crime import get_crime_news
from utils.chatbot import search_google
+st.set_page_config(page_title="City Pulse", layout="wide", initial_sidebar_state="expanded")
-st.set_page_config(page_title="City Pulse", layout="wide")
+st.markdown("""
+
+""", unsafe_allow_html=True)
+
+# Enhanced Title with Animation
+st.markdown("
🌃 City Pulse
", unsafe_allow_html=True)
+
+st.markdown("", unsafe_allow_html=True)
+
+# Enhanced City Selection with Animation
+col_select1, col_select2, col_select3 = st.columns([1, 2, 1])
+with col_select2:
+ city = st.selectbox(
+ "Select a City",
+ list(CITY_COORDS.keys()),
+ help="Choose a city to explore real-time data"
+ )
if city:
lat = CITY_COORDS[city]["lat"]
lon = CITY_COORDS[city]["lon"]
- # --- Initialize chat history in session state ---
if "messages" not in st.session_state:
st.session_state.messages = []
- # Create tabs
- tabs = st.tabs(["Weather", "Air Quality", "Tourist Info", "Crime News", "Trends", "Find with City Pulse"])
+ # Enhanced Tabs with Icons
+ tabs = st.tabs([
+ "Weather",
+ "Air Quality",
+ "Tourist Info",
+ "Crime News",
+ "Trends",
+ "City Assistant"
+ ])
- # --- Existing Tabs (No changes needed for these sections) ---
with tabs[0]:
- st.header(f"Current Weather in {city}")
- weather = get_current_weather(city, lat, lon)
+ st.markdown(f"Current Weather in {city}
", unsafe_allow_html=True)
+
+ # Create weather container with loading animation
+ weather_container = st.container()
+ with weather_container:
+ with st.spinner("Fetching weather data..."):
+ weather = get_current_weather(city, lat, lon)
+ time.sleep(0.5) # Brief pause for animation effect
+
if "error" in weather:
- st.error(weather["error"])
+ st.error(f"❌ {weather['error']}")
else:
+ # Enhanced Weather Cards
col1, col2, col3 = st.columns(3)
- col1.metric("Temperature (°C)", weather["temperature"])
- col2.metric("Feels Like (°C)", weather["feels_like"])
- col3.metric("Humidity (%)", weather["humidity"])
- st.write(f"**Description:** {weather['description'].capitalize()}")
- st.image(f"http://openweathermap.org/img/wn/{weather['icon']}@2x.png")
+
+ with col1:
+ st.markdown(f"""
+
+
Temperature
+ {weather["temperature"]}°C
+
+ """, unsafe_allow_html=True)
+
+ with col2:
+ st.markdown(f"""
+
+
Feels Like
+ {weather["feels_like"]}°C
+
+ """, unsafe_allow_html=True)
+
+ with col3:
+ st.markdown(f"""
+
+
Humidity
+ {weather["humidity"]}%
+
+ """, unsafe_allow_html=True)
+
+ # Weather Description with Icon
+ st.markdown(f"""
+
+
+

+
+
{weather['description']}
+
Current conditions in {city}
+
+
+
+ """, unsafe_allow_html=True)
+ # Enhanced Monthly Weather Chart
monthly_weather = get_monthly_weather(city)
if isinstance(monthly_weather, list) and monthly_weather:
- st.subheader("Monthly Weather Summary")
+ st.markdown("Monthly Weather Trends
", unsafe_allow_html=True)
+
df_monthly = pd.DataFrame(monthly_weather)
- df_monthly = df_monthly.rename(columns={
- "month": "Month",
- "avg_temp": "Avg Temp (°C)",
- "humidity": "Humidity (%)",
- "precip": "Precipitation (mm)"
- })
- st.dataframe(df_monthly)
+
+ # Create animated plotly chart
+ fig = make_subplots(
+ rows=2, cols=2,
+ subplot_titles=('Temperature Trend', 'Humidity Levels', 'Precipitation', 'Weather Overview'),
+ specs=[[{"secondary_y": False}, {"secondary_y": False}],
+ [{"secondary_y": False}, {"secondary_y": False}]]
+ )
+
+ # Temperature line
+ fig.add_trace(
+ go.Scatter(
+ x=df_monthly['month'],
+ y=df_monthly['avg_temp'],
+ mode='lines+markers',
+ name='Temperature (°C)',
+ line=dict(color='#ff6b6b', width=3),
+ marker=dict(size=8)
+ ),
+ row=1, col=1
+ )
+
+ # Humidity bar
+ fig.add_trace(
+ go.Bar(
+ x=df_monthly['month'],
+ y=df_monthly['humidity'],
+ name='Humidity (%)',
+ marker_color='#4ecdc4'
+ ),
+ row=1, col=2
+ )
+
+ # Precipitation area
+ fig.add_trace(
+ go.Scatter(
+ x=df_monthly['month'],
+ y=df_monthly['precip'],
+ fill='tonexty',
+ mode='lines',
+ name='Precipitation (mm)',
+ line=dict(color='#45b7d1')
+ ),
+ row=2, col=1
+ )
+
+ # Combined overview
+ fig.add_trace(
+ go.Scatter(
+ x=df_monthly['month'],
+ y=df_monthly['avg_temp'],
+ mode='lines+markers',
+ name='Temp Overview',
+ line=dict(color='#96ceb4', width=2)
+ ),
+ row=2, col=2
+ )
+
+ fig.update_layout(
+ height=600,
+ showlegend=True,
+ title_text="Weather Analytics Dashboard",
+ font=dict(family="Poppins, sans-serif")
+ )
+
+ st.plotly_chart(fig, use_container_width=True)
with tabs[1]:
- st.header(f"Air Quality in {city}")
- air_quality = get_air_quality(city)
+ st.markdown(f"Air Quality in {city}
", unsafe_allow_html=True)
+
+ with st.spinner("Analyzing air quality..."):
+ air_quality = get_air_quality(city)
+ time.sleep(0.3)
+
if "error" in air_quality:
- st.error(air_quality["error"])
+ st.error(f"❌ {air_quality['error']}")
else:
aqi_level = {1: "Good", 2: "Fair", 3: "Moderate", 4: "Poor", 5: "Very Poor"}
- st.markdown(f"### AQI Level: {air_quality['aqi']} - **{aqi_level.get(air_quality['aqi'], 'Unknown')}**")
- with st.expander("Pollutant Details (μg/m³)"):
- comp_df = pd.DataFrame(list(air_quality["components"].items()), columns=["Pollutant", "Value"])
- comp_df["Pollutant"] = comp_df["Pollutant"].str.upper()
- st.table(comp_df)
+ aqi_colors = {1: "#00e400", 2: "#ffff00", 3: "#ff7e00", 4: "#ff0000", 5: "#8f3f97"}
+
+ # AQI Gauge Chart
+ fig_gauge = go.Figure(go.Indicator(
+ mode = "gauge+number+delta",
+ value = air_quality['aqi'],
+ domain = {'x': [0, 1], 'y': [0, 1]},
+ title = {'text': "Air Quality Index"},
+ delta = {'reference': 3},
+ gauge = {
+ 'axis': {'range': [None, 5]},
+ 'bar': {'color': aqi_colors.get(air_quality['aqi'], "#gray")},
+ 'steps': [
+ {'range': [0, 1], 'color': "#e8f5e8"},
+ {'range': [1, 2], 'color': "#fff8e1"},
+ {'range': [2, 3], 'color': "#fff3e0"},
+ {'range': [3, 4], 'color': "#ffebee"},
+ {'range': [4, 5], 'color': "#f3e5f5"}
+ ],
+ 'threshold': {
+ 'line': {'color': "red", 'width': 4},
+ 'thickness': 0.75,
+ 'value': 4
+ }
+ }
+ ))
+
+ fig_gauge.update_layout(
+ height=400,
+ font=dict(family="Poppins, sans-serif")
+ )
+
+ col1, col2 = st.columns([2, 1])
+ with col1:
+ st.plotly_chart(fig_gauge, use_container_width=True)
+
+ with col2:
+ st.markdown(f"""
+
+
AQI Status
+
{air_quality['aqi']}
+
{aqi_level.get(air_quality['aqi'], 'Unknown')}
+
+
+ """, unsafe_allow_html=True)
+
+ # Pollutant Details with Interactive Chart
+ st.markdown("Pollutant Breakdown
", unsafe_allow_html=True)
+
+ pollutants = air_quality["components"]
+ pollutant_df = pd.DataFrame(list(pollutants.items()), columns=["Pollutant", "Value"])
+ pollutant_df["Pollutant"] = pollutant_df["Pollutant"].str.upper()
+
+ fig_pollutants = px.bar(
+ pollutant_df,
+ x="Pollutant",
+ y="Value",
+ color="Value",
+ color_continuous_scale="Viridis",
+ title="Pollutant Concentrations (μg/m³)"
+ )
+ fig_pollutants.update_layout(
+ xaxis_title="Pollutants",
+ yaxis_title="Concentration (μg/m³)",
+ font=dict(family="Poppins, sans-serif")
+ )
+ st.plotly_chart(fig_pollutants, use_container_width=True)
with tabs[2]:
- st.header(f"Tourist Recommendations in {city}")
- tourist_data = get_recommendations(city)
+ st.markdown(f"Tourist Recommendations in {city}
", unsafe_allow_html=True)
+
+ with st.spinner("Discovering amazing places..."):
+ tourist_data = get_recommendations(city)
+ time.sleep(0.4)
+
places = tourist_data.get("places", [])
if places and not places[0].get("error"):
- with st.expander("Top Tourist Places"):
- for place in places:
- st.markdown(f"**{place.get('name', 'N/A')}**")
- st.write(f"Address: {place.get('address', 'N/A')}")
- if place.get('rating'):
- st.write(f"Rating: {place.get('rating', 'N/A')}")
- st.markdown("---")
+ st.markdown("Top Tourist Destinations
", unsafe_allow_html=True)
+
+ for i, place in enumerate(places[:8]):
+ st.markdown(f"""
+
+
{place.get('name', 'N/A')}
+
{place.get('address', 'N/A')}
+ {f"
Rating: {place.get('rating', 'N/A')}/5
" if place.get('rating') else ""}
+
+ """, unsafe_allow_html=True)
else:
if places and places[0].get("error"):
- st.error(f"Error fetching tourist places: {places[0]['error']}")
+ st.error(f"❌ Error fetching tourist places: {places[0]['error']}")
else:
- st.info("No tourist places data available or could not be fetched.")
+ st.info("No tourist places data available at the moment.")
with tabs[3]:
- st.header(f"Recent Crime News in {city}")
- crime_news = get_crime_news(city)
+ st.markdown(f"Recent Crime News in {city}
", unsafe_allow_html=True)
+
+ with st.spinner("Gathering latest news..."):
+ crime_news = get_crime_news(city)
+ time.sleep(0.3)
+
if crime_news and not crime_news[0].get("error"):
- with st.expander("Show Crime News Articles"):
- for news in crime_news:
- st.markdown(f"**[{news['title']}]({news['url']})**")
- st.write(news["description"])
- st.write(f"*Published at: {news['publishedAt']}*")
- st.markdown("---")
+ st.markdown("Latest Crime Reports
", unsafe_allow_html=True)
+
+ for i, news in enumerate(crime_news[:6]): # Limit to 6 articles
+ st.markdown(f"""
+
+
+
📢 {news['description'][:200]}{'...' if len(news['description']) > 200 else ''}
+
{news['publishedAt']}
+
+ """, unsafe_allow_html=True)
else:
if crime_news and crime_news[0].get("error"):
- st.error(crime_news[0]["error"])
+ st.error(f"❌ {crime_news[0]['error']}")
else:
- st.info("No crime news found.")
+ st.info("📰 No recent crime news found.")
with tabs[4]:
- st.header("How Popular is Your City? 📈")
+ st.markdown("How Popular is Your City?
", unsafe_allow_html=True)
+
trends = tourist_data.get("trends", [])
if trends and not trends[0].get("error"):
+ # Create animated trend chart
dates = [trend["date"] for trend in trends]
interests = [trend["interest"] for trend in trends]
- df_trends = pd.DataFrame({"Date": pd.to_datetime(dates), "Interest": interests})
- df_trends = df_trends.set_index("Date")
- st.line_chart(df_trends)
+
+ fig_trends = go.Figure()
+ fig_trends.add_trace(go.Scatter(
+ x=dates,
+ y=interests,
+ mode='lines+markers',
+ name='Interest Level',
+ line=dict(color='#667eea', width=4),
+ marker=dict(size=10, color='#764ba2'),
+ fill='tonexty',
+ fillcolor='rgba(102, 126, 234, 0.1)'
+ ))
+
+ fig_trends.update_layout(
+ title=f"Tourist Interest Trends for {city}",
+ xaxis_title="Date",
+ yaxis_title="Interest Level",
+ height=500,
+ font=dict(family="Poppins, sans-serif"),
+ hovermode='x unified'
+ )
+
+ st.plotly_chart(fig_trends, use_container_width=True)
+
+ # Add trend insights
+ avg_interest = np.mean(interests)
+ max_interest = max(interests)
+ st.markdown(f"""
+
+
Trend Insights
+
Average Interest Level: {avg_interest:.1f}
+
Peak Interest: {max_interest}
+
Tracking Period: Last 7 days
+
+ """, unsafe_allow_html=True)
else:
if trends and trends[0].get("error"):
- st.error(trends[0]["error"])
+ st.error(f"❌ {trends[0]['error']}")
else:
- st.info("No trends data available.")
-
+ st.info("No trends data available at the moment.")
- # --- Chatbot Tab ---
-
-with tabs[5]:
- st.header("🤖 Search CityBot")
+ with tabs[5]:
+ st.markdown("City Assistant
", unsafe_allow_html=True)
+ st.markdown("Ask me anything about your city - restaurants, events, attractions, and more!
", unsafe_allow_html=True)
- if "search_history" not in st.session_state:
- st.session_state.search_history = []
+ if "search_history" not in st.session_state:
+ st.session_state.search_history = []
- user_input = st.chat_input("Ask a question (e.g., top cafes, weekend events)")
+ # Chat interface
+ user_input = st.chat_input("Ask me about restaurants, events, attractions...")
- if user_input:
- st.session_state.search_history.append({"role": "user", "text": user_input})
+ if user_input:
+ st.session_state.search_history.append({"role": "user", "text": user_input})
+
+ # Show typing indicator
+ typing_placeholder = st.empty()
+ typing_placeholder.markdown('Assistant is thinking...
', unsafe_allow_html=True)
+
+ with st.spinner("Searching for information..."):
+ time.sleep(1) # Simulate thinking time
+ answer = search_google(user_input)
+
+ typing_placeholder.empty()
+ st.session_state.search_history.append({"role": "bot", "text": answer})
- with st.spinner("Searching Google..."):
- answer = search_google(user_input)
+ # Display chat history with animations
+ for i, msg in enumerate(st.session_state.search_history):
+ if msg["role"] == "user":
+ st.chat_message("user").markdown(f'{msg["text"]}
', unsafe_allow_html=True)
+ else:
+ st.chat_message("assistant").markdown(f'{msg["text"]}
', unsafe_allow_html=True)
- st.session_state.search_history.append({"role": "bot", "text": answer})
+ # Quick action buttons
+ st.markdown("Quick Questions
", unsafe_allow_html=True)
+
+ col1, col2, col3, col4 = st.columns(4)
+
+ with col1:
+ if st.button("🍕 Best Restaurants", use_container_width=True):
+ st.session_state.search_history.append({"role": "user", "text": f"best restaurants in {city}"})
+ with st.spinner("Finding great restaurants..."):
+ answer = search_google(f"best restaurants in {city}")
+ st.session_state.search_history.append({"role": "bot", "text": answer})
+ st.rerun()
+
+ with col2:
+ if st.button("🎉 Weekend Events", use_container_width=True):
+ st.session_state.search_history.append({"role": "user", "text": f"weekend events in {city}"})
+ with st.spinner("Discovering events..."):
+ answer = search_google(f"weekend events in {city}")
+ st.session_state.search_history.append({"role": "bot", "text": answer})
+ st.rerun()
+
+ with col3:
+ if st.button("☕ Coffee Shops", use_container_width=True):
+ st.session_state.search_history.append({"role": "user", "text": f"best coffee shops in {city}"})
+ with st.spinner("Finding cozy cafes..."):
+ answer = search_google(f"best coffee shops in {city}")
+ st.session_state.search_history.append({"role": "bot", "text": answer})
+ st.rerun()
+
+ with col4:
+ if st.button("🛍️ Shopping", use_container_width=True):
+ st.session_state.search_history.append({"role": "user", "text": f"shopping places in {city}"})
+ with st.spinner("Locating shopping areas..."):
+ answer = search_google(f"shopping places in {city}")
+ st.session_state.search_history.append({"role": "bot", "text": answer})
+ st.rerun()
- # Display chat messages
- for msg in st.session_state.search_history:
- if msg["role"] == "user":
- st.chat_message("user").markdown(msg["text"])
- else:
- st.chat_message("assistant").markdown(msg["text"])
+# Sidebar with Real-time City Pulse
+with st.sidebar:
+ st.markdown("""
+
+
🌆 City Pulse Monitor
+
+
+ """, unsafe_allow_html=True)
+
+ # Real-time data indicators
+ if city:
+ st.markdown(f"📍 {city} Status
", unsafe_allow_html=True)
+
+ # Simulate real-time data updates
+ current_time = datetime.datetime.now().strftime("%H:%M:%S")
+ st.markdown(f"**Last Update:** {current_time}")
+
+ # Data freshness indicators
+ indicators = [
+ ("Weather Data", "Fresh", "#00c851"),
+ ("Air Quality", "Live", "#00c851"),
+ ("News Feed", "Updated", "#ffa726"),
+ ("Tourist Info", "Active", "#00c851"),
+ ("Trends", "Real-time", "#00c851")
+ ]
+
+ for indicator, status, color in indicators:
+ st.markdown(f"""
+
+ {indicator}
+ ● {status}
+
+ """, unsafe_allow_html=True)
+
+ # Auto-refresh toggle
+ auto_refresh = st.toggle("🔄 Auto-refresh data", value=False)
+ if auto_refresh:
+ time.sleep(30) # Refresh every 30 seconds
+ st.rerun()
+
+ # City statistics
+ st.markdown("Quick Stats
", unsafe_allow_html=True)
+ if city and city in CITY_COORDS:
+ coords = CITY_COORDS[city]
+ st.markdown(f"""
+
+
Coordinates:
+ Lat: {coords['lat']}
+ Lon: {coords['lon']}
+
Local Time:
{datetime.datetime.now().strftime('%I:%M %p')}
+
Date:
{datetime.datetime.now().strftime('%B %d, %Y')}
+
+ """, unsafe_allow_html=True)