Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -6,16 +6,19 @@ from datetime import datetime, timedelta
|
|
| 6 |
import altair as alt
|
| 7 |
import time
|
| 8 |
|
| 9 |
-
# ----
|
| 10 |
-
st.
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
# ---- Constants ----
|
| 13 |
POLES_PER_SITE = 12
|
| 14 |
POLE_GRID_ROWS = 3
|
| 15 |
POLE_GRID_COLS = 4
|
| 16 |
POLE_SPACING_FEET = 10
|
| 17 |
-
FEET_TO_LAT_DEG = 0.00003
|
| 18 |
-
FEET_TO_LON_DEG = 0.00003
|
| 19 |
|
| 20 |
SITES = {
|
| 21 |
"Hyderabad": {"coords": [17.385044, 78.486671], "zone": "Dairy Farm Zone"},
|
|
@@ -24,7 +27,6 @@ SITES = {
|
|
| 24 |
"Ballari": {"coords": [12.9716, 77.5946], "zone": "Urban Grid"}
|
| 25 |
}
|
| 26 |
|
| 27 |
-
# ---- Fixed Pole Grid Placement ----
|
| 28 |
def generate_fixed_pole_locations(base_lat, base_lon, num_poles):
|
| 29 |
locations = []
|
| 30 |
for i in range(POLE_GRID_ROWS):
|
|
@@ -34,26 +36,21 @@ def generate_fixed_pole_locations(base_lat, base_lon, num_poles):
|
|
| 34 |
locations.append([lat, lon])
|
| 35 |
return locations[:num_poles]
|
| 36 |
|
| 37 |
-
# ---- Simulate a Single Pole ----
|
| 38 |
def simulate_pole(pole_id, site_name, lat, lon):
|
| 39 |
solar_kwh = round(random.uniform(3.0, 7.5), 2)
|
| 40 |
wind_kwh = round(random.uniform(0.5, 2.0), 2)
|
| 41 |
power_required = round(random.uniform(4.0, 8.0), 2)
|
| 42 |
total_power = solar_kwh + wind_kwh
|
| 43 |
power_status = 'Sufficient' if total_power >= power_required else 'Insufficient'
|
| 44 |
-
|
| 45 |
vibration = round(random.uniform(0, 5), 2)
|
| 46 |
camera_status = random.choice(['Online', 'Offline'])
|
| 47 |
-
|
| 48 |
alert_level = 'Green'
|
| 49 |
if vibration > 3:
|
| 50 |
alert_level = 'Yellow'
|
| 51 |
if vibration > 4.5:
|
| 52 |
alert_level = 'Red'
|
| 53 |
-
|
| 54 |
health_score = max(0, 100 - (vibration * 10))
|
| 55 |
timestamp = datetime.now() - timedelta(minutes=random.randint(0, 59))
|
| 56 |
-
|
| 57 |
return {
|
| 58 |
'Pole ID': f'{site_name[:3].upper()}-{pole_id:03}',
|
| 59 |
'Site': site_name,
|
|
@@ -72,21 +69,18 @@ def simulate_pole(pole_id, site_name, lat, lon):
|
|
| 72 |
'Last Checked': timestamp.strftime('%Y-%m-%d %H:%M:%S')
|
| 73 |
}
|
| 74 |
|
| 75 |
-
# ---- Cache Data for 1 Hour ----
|
| 76 |
@st.cache_data(ttl=3600)
|
| 77 |
def get_simulated_data():
|
| 78 |
all_data = []
|
| 79 |
for site_name, site_data in SITES.items():
|
| 80 |
base_lat, base_lon = site_data["coords"]
|
| 81 |
locations = generate_fixed_pole_locations(base_lat, base_lon, POLES_PER_SITE)
|
| 82 |
-
|
| 83 |
for i, (lat, lon) in enumerate(locations):
|
| 84 |
pole_data = simulate_pole(i + 1, site_name, lat, lon)
|
| 85 |
all_data.append(pole_data)
|
| 86 |
return pd.DataFrame(all_data)
|
| 87 |
|
| 88 |
-
# ----
|
| 89 |
-
st.set_page_config(page_title="Smart Pole Monitoring", layout="wide")
|
| 90 |
st.title("π Smart Renewable Pole Monitoring - Multi-Site")
|
| 91 |
|
| 92 |
selected_site = st.selectbox("Select a site to view:", options=list(SITES.keys()), index=0)
|
|
@@ -97,13 +91,11 @@ with st.spinner(f"Loading data for {selected_site}..."):
|
|
| 97 |
df = get_simulated_data()
|
| 98 |
site_df = df[df['Site'] == selected_site]
|
| 99 |
|
| 100 |
-
# ---- Summary Metrics ----
|
| 101 |
col1, col2, col3 = st.columns(3)
|
| 102 |
col1.metric("Total Poles", site_df.shape[0])
|
| 103 |
col2.metric("Red Alerts", site_df[site_df['Alert Level'] == 'Red'].shape[0])
|
| 104 |
col3.metric("Power Insufficiencies", site_df[site_df['Power Status'] == 'Insufficient'].shape[0])
|
| 105 |
|
| 106 |
-
# ---- Table & Filters ----
|
| 107 |
st.subheader(f"π Pole Data Table for {selected_site}")
|
| 108 |
with st.expander("Filter Options"):
|
| 109 |
alert_filter = st.multiselect("Alert Level", options=site_df['Alert Level'].unique(), default=site_df['Alert Level'].unique())
|
|
@@ -115,7 +107,6 @@ filtered_df = site_df[
|
|
| 115 |
]
|
| 116 |
st.dataframe(filtered_df, use_container_width=True)
|
| 117 |
|
| 118 |
-
# ---- Energy Chart ----
|
| 119 |
st.subheader("π Energy Generation per Pole")
|
| 120 |
|
| 121 |
energy_long_df = site_df[['Pole ID', 'Solar (kWh)', 'Wind (kWh)']].melt(
|
|
@@ -137,9 +128,7 @@ bar_chart = alt.Chart(energy_long_df).mark_bar().encode(
|
|
| 137 |
|
| 138 |
st.altair_chart(bar_chart, use_container_width=True)
|
| 139 |
|
| 140 |
-
# ---- Fault Filter ----
|
| 141 |
st.subheader("β οΈ Map Filter: Select Fault Type(s)")
|
| 142 |
-
|
| 143 |
fault_options = ['High Vibration (>3g)', 'Camera Offline', 'Power Insufficient']
|
| 144 |
selected_faults = st.multiselect("Show poles with these fault conditions:", options=fault_options, default=fault_options)
|
| 145 |
|
|
@@ -152,7 +141,6 @@ def fault_condition(row):
|
|
| 152 |
|
| 153 |
fault_df = site_df[site_df.apply(fault_condition, axis=1)] if selected_faults else site_df
|
| 154 |
|
| 155 |
-
# ---- Color Mapping ----
|
| 156 |
def get_color(alert):
|
| 157 |
if alert == 'Green':
|
| 158 |
return [0, 255, 0, 160]
|
|
@@ -164,7 +152,6 @@ def get_color(alert):
|
|
| 164 |
|
| 165 |
fault_df['color'] = fault_df['Alert Level'].apply(get_color)
|
| 166 |
|
| 167 |
-
# ---- Map Visualization ----
|
| 168 |
st.subheader("π Pole Locations with Selected Faults")
|
| 169 |
st.pydeck_chart(pdk.Deck(
|
| 170 |
initial_view_state=pdk.ViewState(
|
|
@@ -203,4 +190,3 @@ st.pydeck_chart(pdk.Deck(
|
|
| 203 |
}
|
| 204 |
}
|
| 205 |
))
|
| 206 |
-
|
|
|
|
| 6 |
import altair as alt
|
| 7 |
import time
|
| 8 |
|
| 9 |
+
# ---- SET PAGE CONFIG MUST BE FIRST ----
|
| 10 |
+
st.set_page_config(page_title="Smart Pole Monitoring", layout="wide")
|
| 11 |
+
|
| 12 |
+
# ---- Update query params every hour to trigger refresh
|
| 13 |
+
st.query_params["updated"] = str(int(time.time() // 3600))
|
| 14 |
|
| 15 |
# ---- Constants ----
|
| 16 |
POLES_PER_SITE = 12
|
| 17 |
POLE_GRID_ROWS = 3
|
| 18 |
POLE_GRID_COLS = 4
|
| 19 |
POLE_SPACING_FEET = 10
|
| 20 |
+
FEET_TO_LAT_DEG = 0.00003
|
| 21 |
+
FEET_TO_LON_DEG = 0.00003
|
| 22 |
|
| 23 |
SITES = {
|
| 24 |
"Hyderabad": {"coords": [17.385044, 78.486671], "zone": "Dairy Farm Zone"},
|
|
|
|
| 27 |
"Ballari": {"coords": [12.9716, 77.5946], "zone": "Urban Grid"}
|
| 28 |
}
|
| 29 |
|
|
|
|
| 30 |
def generate_fixed_pole_locations(base_lat, base_lon, num_poles):
|
| 31 |
locations = []
|
| 32 |
for i in range(POLE_GRID_ROWS):
|
|
|
|
| 36 |
locations.append([lat, lon])
|
| 37 |
return locations[:num_poles]
|
| 38 |
|
|
|
|
| 39 |
def simulate_pole(pole_id, site_name, lat, lon):
|
| 40 |
solar_kwh = round(random.uniform(3.0, 7.5), 2)
|
| 41 |
wind_kwh = round(random.uniform(0.5, 2.0), 2)
|
| 42 |
power_required = round(random.uniform(4.0, 8.0), 2)
|
| 43 |
total_power = solar_kwh + wind_kwh
|
| 44 |
power_status = 'Sufficient' if total_power >= power_required else 'Insufficient'
|
|
|
|
| 45 |
vibration = round(random.uniform(0, 5), 2)
|
| 46 |
camera_status = random.choice(['Online', 'Offline'])
|
|
|
|
| 47 |
alert_level = 'Green'
|
| 48 |
if vibration > 3:
|
| 49 |
alert_level = 'Yellow'
|
| 50 |
if vibration > 4.5:
|
| 51 |
alert_level = 'Red'
|
|
|
|
| 52 |
health_score = max(0, 100 - (vibration * 10))
|
| 53 |
timestamp = datetime.now() - timedelta(minutes=random.randint(0, 59))
|
|
|
|
| 54 |
return {
|
| 55 |
'Pole ID': f'{site_name[:3].upper()}-{pole_id:03}',
|
| 56 |
'Site': site_name,
|
|
|
|
| 69 |
'Last Checked': timestamp.strftime('%Y-%m-%d %H:%M:%S')
|
| 70 |
}
|
| 71 |
|
|
|
|
| 72 |
@st.cache_data(ttl=3600)
|
| 73 |
def get_simulated_data():
|
| 74 |
all_data = []
|
| 75 |
for site_name, site_data in SITES.items():
|
| 76 |
base_lat, base_lon = site_data["coords"]
|
| 77 |
locations = generate_fixed_pole_locations(base_lat, base_lon, POLES_PER_SITE)
|
|
|
|
| 78 |
for i, (lat, lon) in enumerate(locations):
|
| 79 |
pole_data = simulate_pole(i + 1, site_name, lat, lon)
|
| 80 |
all_data.append(pole_data)
|
| 81 |
return pd.DataFrame(all_data)
|
| 82 |
|
| 83 |
+
# ---- UI Starts ----
|
|
|
|
| 84 |
st.title("π Smart Renewable Pole Monitoring - Multi-Site")
|
| 85 |
|
| 86 |
selected_site = st.selectbox("Select a site to view:", options=list(SITES.keys()), index=0)
|
|
|
|
| 91 |
df = get_simulated_data()
|
| 92 |
site_df = df[df['Site'] == selected_site]
|
| 93 |
|
|
|
|
| 94 |
col1, col2, col3 = st.columns(3)
|
| 95 |
col1.metric("Total Poles", site_df.shape[0])
|
| 96 |
col2.metric("Red Alerts", site_df[site_df['Alert Level'] == 'Red'].shape[0])
|
| 97 |
col3.metric("Power Insufficiencies", site_df[site_df['Power Status'] == 'Insufficient'].shape[0])
|
| 98 |
|
|
|
|
| 99 |
st.subheader(f"π Pole Data Table for {selected_site}")
|
| 100 |
with st.expander("Filter Options"):
|
| 101 |
alert_filter = st.multiselect("Alert Level", options=site_df['Alert Level'].unique(), default=site_df['Alert Level'].unique())
|
|
|
|
| 107 |
]
|
| 108 |
st.dataframe(filtered_df, use_container_width=True)
|
| 109 |
|
|
|
|
| 110 |
st.subheader("π Energy Generation per Pole")
|
| 111 |
|
| 112 |
energy_long_df = site_df[['Pole ID', 'Solar (kWh)', 'Wind (kWh)']].melt(
|
|
|
|
| 128 |
|
| 129 |
st.altair_chart(bar_chart, use_container_width=True)
|
| 130 |
|
|
|
|
| 131 |
st.subheader("β οΈ Map Filter: Select Fault Type(s)")
|
|
|
|
| 132 |
fault_options = ['High Vibration (>3g)', 'Camera Offline', 'Power Insufficient']
|
| 133 |
selected_faults = st.multiselect("Show poles with these fault conditions:", options=fault_options, default=fault_options)
|
| 134 |
|
|
|
|
| 141 |
|
| 142 |
fault_df = site_df[site_df.apply(fault_condition, axis=1)] if selected_faults else site_df
|
| 143 |
|
|
|
|
| 144 |
def get_color(alert):
|
| 145 |
if alert == 'Green':
|
| 146 |
return [0, 255, 0, 160]
|
|
|
|
| 152 |
|
| 153 |
fault_df['color'] = fault_df['Alert Level'].apply(get_color)
|
| 154 |
|
|
|
|
| 155 |
st.subheader("π Pole Locations with Selected Faults")
|
| 156 |
st.pydeck_chart(pdk.Deck(
|
| 157 |
initial_view_state=pdk.ViewState(
|
|
|
|
| 190 |
}
|
| 191 |
}
|
| 192 |
))
|
|
|