Tigernawin commited on
Commit
ce6418b
ยท
verified ยท
1 Parent(s): b6ed4c2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +141 -150
app.py CHANGED
@@ -4,46 +4,37 @@ import streamlit as st
4
  import pydeck as pdk
5
  from datetime import datetime, timedelta
6
  import altair as alt
 
 
 
 
7
 
8
  # ---- Constants ----
9
  POLES_PER_SITE = 12
 
 
10
  POLE_SPACING_FEET = 10
11
- FEET_TO_LAT_DEG = 0.00003 # Rough conversion
 
12
 
13
  SITES = {
14
  "Hyderabad": {"coords": [17.385044, 78.486671], "zone": "Dairy Farm Zone"},
15
  "Gadwal": {"coords": [16.2351, 77.8052], "zone": "City Center"},
16
  "Kurnool": {"coords": [15.8281, 78.0373], "zone": "West Industrial"},
17
- "Ballari": {"coords": [12.9716, 77.5946], "zone": "Solar Power station"}
18
  }
19
 
20
- # ---- Fixed Placement for Each Site ----
21
  def generate_fixed_pole_locations(base_lat, base_lon, num_poles):
22
- # Define a fixed area for pole placement (e.g., 0.01 degrees in latitude and longitude)
23
- area_width = 0.01 # 0.01 degree latitude distance (approx. 1.1 km)
24
- area_height = 0.01 # 0.01 degree longitude distance (approx. 1.1 km)
25
-
26
- # Calculate number of rows and columns for the grid
27
- rows = 3 # Number of rows in the grid
28
- cols = 4 # Number of columns in the grid
29
-
30
- # Calculate the spacing in both directions
31
- lat_spacing = area_height / rows
32
- lon_spacing = area_width / cols
33
-
34
- # Generate the fixed grid of pole locations
35
- return [
36
- [base_lat + i * lat_spacing, base_lon + j * lon_spacing]
37
- for i in range(rows) for j in range(cols)
38
- ][:num_poles] # Only take num_poles (12 in this case)
39
-
40
- # ---- Helper Functions ----
41
- def generate_location(base_lat, base_lon):
42
- return [
43
- base_lat + random.uniform(-0.02, 0.02),
44
- base_lon + random.uniform(-0.02, 0.02)
45
- ]
46
-
47
  def simulate_pole(pole_id, site_name, lat, lon):
48
  solar_kwh = round(random.uniform(3.0, 7.5), 2)
49
  wind_kwh = round(random.uniform(0.5, 2.0), 2)
@@ -61,7 +52,7 @@ def simulate_pole(pole_id, site_name, lat, lon):
61
  alert_level = 'Red'
62
 
63
  health_score = max(0, 100 - (vibration * 10))
64
- timestamp = datetime.now() - timedelta(hours=random.randint(0, 6))
65
 
66
  return {
67
  'Pole ID': f'{site_name[:3].upper()}-{pole_id:03}',
@@ -81,134 +72,134 @@ def simulate_pole(pole_id, site_name, lat, lon):
81
  'Last Checked': timestamp.strftime('%Y-%m-%d %H:%M:%S')
82
  }
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  # ---- Streamlit UI ----
85
  st.set_page_config(page_title="Smart Pole Monitoring", layout="wide")
86
  st.title("๐ŸŒ Smart Renewable Pole Monitoring - Multi-Site")
87
 
88
  selected_site = st.selectbox("Select a site to view:", options=list(SITES.keys()), index=0)
89
 
90
- if selected_site in SITES:
91
- st.markdown(f"**Zone:** {SITES[selected_site]['zone']}")
92
-
93
- with st.spinner(f"Simulating poles at {selected_site}..."):
94
- all_data = []
95
-
96
- for site_name, site_data in SITES.items():
97
- base_lat, base_lon = site_data["coords"]
98
-
99
- # Fixed placement for all poles
100
- locations = generate_fixed_pole_locations(base_lat, base_lon, POLES_PER_SITE)
101
-
102
- for i, (lat, lon) in enumerate(locations):
103
- pole_data = simulate_pole(i + 1, site_name, lat, lon)
104
- all_data.append(pole_data)
105
-
106
- df = pd.DataFrame(all_data)
107
- site_df = df[df['Site'] == selected_site]
108
-
109
- # Summary
110
- col1, col2, col3 = st.columns(3)
111
- col1.metric("Total Poles", site_df.shape[0])
112
- col2.metric("Red Alerts", site_df[site_df['Alert Level'] == 'Red'].shape[0])
113
- col3.metric("Power Insufficiencies", site_df[site_df['Power Status'] == 'Insufficient'].shape[0])
114
-
115
- # Table
116
- st.subheader(f"๐Ÿ“‹ Pole Data Table for {selected_site}")
117
- with st.expander("Filter Options"):
118
- alert_filter = st.multiselect("Alert Level", options=site_df['Alert Level'].unique(), default=site_df['Alert Level'].unique())
119
- camera_filter = st.multiselect("Camera Status", options=site_df['Camera Status'].unique(), default=site_df['Camera Status'].unique())
120
-
121
- filtered_df = site_df[
122
- (site_df['Alert Level'].isin(alert_filter)) &
123
- (site_df['Camera Status'].isin(camera_filter))
124
- ]
125
- st.dataframe(filtered_df, use_container_width=True)
126
-
127
- # ---- Energy Chart ----
128
- st.subheader("๐Ÿ”‹ Energy Generation per Pole")
129
-
130
- energy_long_df = site_df[['Pole ID', 'Solar (kWh)', 'Wind (kWh)']].melt(
131
- id_vars='Pole ID',
132
- value_vars=['Solar (kWh)', 'Wind (kWh)'],
133
- var_name='Energy Source',
134
- value_name='kWh'
 
 
 
 
 
 
 
 
 
 
 
 
135
  )
136
 
137
- bar_chart = alt.Chart(energy_long_df).mark_bar().encode(
138
- x=alt.X('Pole ID:N', sort=None, title='Pole ID'),
139
- y=alt.Y('kWh:Q'),
140
- color='Energy Source:N',
141
- tooltip=['Pole ID', 'Energy Source', 'kWh']
142
- ).properties(
143
- width=800,
144
- height=400
145
- ).configure_axisX(labelAngle=45)
146
-
147
- st.altair_chart(bar_chart, use_container_width=True)
148
-
149
- # ---- Fault Type Filter ----
150
- st.subheader("โš ๏ธ Map Filter: Select Fault Type(s)")
151
-
152
- fault_options = ['High Vibration (>3g)', 'Camera Offline', 'Power Insufficient']
153
- selected_faults = st.multiselect("Show poles with these fault conditions:", options=fault_options, default=fault_options)
154
-
155
- def fault_condition(row):
156
- return (
157
- ('High Vibration (>3g)' in selected_faults and row['Vibration (g)'] > 3) or
158
- ('Camera Offline' in selected_faults and row['Camera Status'] == 'Offline') or
159
- ('Power Insufficient' in selected_faults and row['Power Status'] == 'Insufficient')
 
 
 
 
 
 
 
 
160
  )
161
-
162
- fault_df = site_df[site_df.apply(fault_condition, axis=1)] if selected_faults else site_df
163
-
164
- # ---- Map Color Logic ----
165
- def get_color(alert):
166
- if alert == 'Green':
167
- return [0, 255, 0, 160]
168
- elif alert == 'Yellow':
169
- return [255, 255, 0, 160]
170
- elif alert == 'Red':
171
- return [255, 0, 0, 160]
172
- return [128, 128, 128, 160]
173
-
174
- fault_df['color'] = fault_df['Alert Level'].apply(get_color)
175
-
176
- # ---- Map ----
177
- st.subheader("๐Ÿ“ Pole Locations with Selected Faults")
178
- st.pydeck_chart(pdk.Deck(
179
- initial_view_state=pdk.ViewState(
180
- latitude=SITES[selected_site]["coords"][0],
181
- longitude=SITES[selected_site]["coords"][1],
182
- zoom=12,
183
- pitch=50
184
- ),
185
- layers=[
186
- pdk.Layer(
187
- 'ScatterplotLayer',
188
- data=fault_df,
189
- get_position='[Longitude, Latitude]',
190
- get_color='color',
191
- get_radius=100,
192
- pickable=True,
193
- )
194
- ],
195
- tooltip={
196
- "html": """
197
- <b>Pole ID:</b> {Pole ID}<br/>
198
- <b>Zone:</b> {Zone}<br/>
199
- <b>Alert Level:</b> {Alert Level}<br/>
200
- <b>Health Score:</b> {Health Score}<br/>
201
- <b>Power Status:</b> {Power Status}<br/>
202
- <b>Vibration (g):</b> {Vibration (g)}<br/>
203
- <b>Camera:</b> {Camera Status}<br/>
204
- <b>Solar (kWh):</b> {Solar (kWh)}<br/>
205
- <b>Wind (kWh):</b> {Wind (kWh)}<br/>
206
- <b>Last Checked:</b> {Last Checked}
207
- """,
208
- "style": {
209
- "backgroundColor": "steelblue",
210
- "color": "white",
211
- "fontSize": "12px"
212
- }
213
  }
214
- ))
 
 
4
  import pydeck as pdk
5
  from datetime import datetime, timedelta
6
  import altair as alt
7
+ import time
8
+
9
+ # ---- Auto-refresh trigger every 1 hour ----
10
+ st.experimental_set_query_params(updated=int(time.time() // 3600))
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 # Approximate conversion
18
+ FEET_TO_LON_DEG = 0.00003 # Can be tuned per site longitude
19
 
20
  SITES = {
21
  "Hyderabad": {"coords": [17.385044, 78.486671], "zone": "Dairy Farm Zone"},
22
  "Gadwal": {"coords": [16.2351, 77.8052], "zone": "City Center"},
23
  "Kurnool": {"coords": [15.8281, 78.0373], "zone": "West Industrial"},
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):
31
+ for j in range(POLE_GRID_COLS):
32
+ lat = base_lat + i * POLE_SPACING_FEET * FEET_TO_LAT_DEG
33
+ lon = base_lon + j * POLE_SPACING_FEET * FEET_TO_LON_DEG
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)
 
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}',
 
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
  # ---- Streamlit UI ----
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)
93
 
94
+ st.caption(f"๐Ÿ”„ Data last refreshed: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
95
+
96
+ 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())
110
+ camera_filter = st.multiselect("Camera Status", options=site_df['Camera Status'].unique(), default=site_df['Camera Status'].unique())
111
+
112
+ filtered_df = site_df[
113
+ (site_df['Alert Level'].isin(alert_filter)) &
114
+ (site_df['Camera Status'].isin(camera_filter))
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(
122
+ id_vars='Pole ID',
123
+ value_vars=['Solar (kWh)', 'Wind (kWh)'],
124
+ var_name='Energy Source',
125
+ value_name='kWh'
126
+ )
127
+
128
+ bar_chart = alt.Chart(energy_long_df).mark_bar().encode(
129
+ x=alt.X('Pole ID:N', sort=None, title='Pole ID'),
130
+ y=alt.Y('kWh:Q'),
131
+ color='Energy Source:N',
132
+ tooltip=['Pole ID', 'Energy Source', 'kWh']
133
+ ).properties(
134
+ width=800,
135
+ height=400
136
+ ).configure_axisX(labelAngle=45)
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
+
146
+ def fault_condition(row):
147
+ return (
148
+ ('High Vibration (>3g)' in selected_faults and row['Vibration (g)'] > 3) or
149
+ ('Camera Offline' in selected_faults and row['Camera Status'] == 'Offline') or
150
+ ('Power Insufficient' in selected_faults and row['Power Status'] == 'Insufficient')
151
  )
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]
159
+ elif alert == 'Yellow':
160
+ return [255, 255, 0, 160]
161
+ elif alert == 'Red':
162
+ return [255, 0, 0, 160]
163
+ return [128, 128, 128, 160]
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(
171
+ latitude=SITES[selected_site]["coords"][0],
172
+ longitude=SITES[selected_site]["coords"][1],
173
+ zoom=12,
174
+ pitch=50
175
+ ),
176
+ layers=[
177
+ pdk.Layer(
178
+ 'ScatterplotLayer',
179
+ data=fault_df,
180
+ get_position='[Longitude, Latitude]',
181
+ get_color='color',
182
+ get_radius=100,
183
+ pickable=True,
184
  )
185
+ ],
186
+ tooltip={
187
+ "html": """
188
+ <b>Pole ID:</b> {Pole ID}<br/>
189
+ <b>Zone:</b> {Zone}<br/>
190
+ <b>Alert Level:</b> {Alert Level}<br/>
191
+ <b>Health Score:</b> {Health Score}<br/>
192
+ <b>Power Status:</b> {Power Status}<br/>
193
+ <b>Vibration (g):</b> {Vibration (g)}<br/>
194
+ <b>Camera:</b> {Camera Status}<br/>
195
+ <b>Solar (kWh):</b> {Solar (kWh)}<br/>
196
+ <b>Wind (kWh):</b> {Wind (kWh)}<br/>
197
+ <b>Last Checked:</b> {Last Checked}
198
+ """,
199
+ "style": {
200
+ "backgroundColor": "steelblue",
201
+ "color": "white",
202
+ "fontSize": "12px"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  }
204
+ }
205
+ ))