Tigernawin commited on
Commit
8d9022b
ยท
verified ยท
1 Parent(s): 843aaf6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +154 -132
app.py CHANGED
@@ -4,21 +4,11 @@ import streamlit as st
4
  import pydeck as pdk
5
  from datetime import datetime, timedelta
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,14 +17,32 @@ SITES = {
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):
33
- for j in range(POLE_GRID_COLS):
34
- lat = base_lat + i * POLE_SPACING_FEET * FEET_TO_LAT_DEG
35
- lon = base_lon + j * POLE_SPACING_FEET * FEET_TO_LON_DEG
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)
@@ -42,15 +50,19 @@ def simulate_pole(pole_id, site_name, lat, lon):
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,124 +81,134 @@ def simulate_pole(pole_id, site_name, lat, lon):
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)
87
 
88
- st.caption(f"๐Ÿ”„ Data last refreshed: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
89
-
90
- with st.spinner(f"Loading data for {selected_site}..."):
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())
102
- camera_filter = st.multiselect("Camera Status", options=site_df['Camera Status'].unique(), default=site_df['Camera Status'].unique())
103
-
104
- filtered_df = site_df[
105
- (site_df['Alert Level'].isin(alert_filter)) &
106
- (site_df['Camera Status'].isin(camera_filter))
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(
113
- id_vars='Pole ID',
114
- value_vars=['Solar (kWh)', 'Wind (kWh)'],
115
- var_name='Energy Source',
116
- value_name='kWh'
117
- )
118
-
119
- bar_chart = alt.Chart(energy_long_df).mark_bar().encode(
120
- x=alt.X('Pole ID:N', sort=None, title='Pole ID'),
121
- y=alt.Y('kWh:Q'),
122
- color='Energy Source:N',
123
- tooltip=['Pole ID', 'Energy Source', 'kWh']
124
- ).properties(
125
- width=800,
126
- height=400
127
- ).configure_axisX(labelAngle=45)
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
-
135
- def fault_condition(row):
136
- return (
137
- ('High Vibration (>3g)' in selected_faults and row['Vibration (g)'] > 3) or
138
- ('Camera Offline' in selected_faults and row['Camera Status'] == 'Offline') or
139
- ('Power Insufficient' in selected_faults and row['Power Status'] == 'Insufficient')
140
  )
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]
147
- elif alert == 'Yellow':
148
- return [255, 255, 0, 160]
149
- elif alert == 'Red':
150
- return [255, 0, 0, 160]
151
- return [128, 128, 128, 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(
158
- latitude=SITES[selected_site]["coords"][0],
159
- longitude=SITES[selected_site]["coords"][1],
160
- zoom=12,
161
- pitch=50
162
- ),
163
- layers=[
164
- pdk.Layer(
165
- 'ScatterplotLayer',
166
- data=fault_df,
167
- get_position='[Longitude, Latitude]',
168
- get_color='color',
169
- get_radius=100,
170
- pickable=True,
171
  )
172
- ],
173
- tooltip={
174
- "html": """
175
- <b>Pole ID:</b> {Pole ID}<br/>
176
- <b>Zone:</b> {Zone}<br/>
177
- <b>Alert Level:</b> {Alert Level}<br/>
178
- <b>Health Score:</b> {Health Score}<br/>
179
- <b>Power Status:</b> {Power Status}<br/>
180
- <b>Vibration (g):</b> {Vibration (g)}<br/>
181
- <b>Camera:</b> {Camera Status}<br/>
182
- <b>Solar (kWh):</b> {Solar (kWh)}<br/>
183
- <b>Wind (kWh):</b> {Wind (kWh)}<br/>
184
- <b>Last Checked:</b> {Last Checked}
185
- """,
186
- "style": {
187
- "backgroundColor": "steelblue",
188
- "color": "white",
189
- "fontSize": "12px"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  }
191
- }
192
- ))
 
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"},
 
17
  "Ballari": {"coords": [12.9716, 77.5946], "zone": "Urban Grid"}
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)
 
50
  power_required = round(random.uniform(4.0, 8.0), 2)
51
  total_power = solar_kwh + wind_kwh
52
  power_status = 'Sufficient' if total_power >= power_required else 'Insufficient'
53
+
54
  vibration = round(random.uniform(0, 5), 2)
55
  camera_status = random.choice(['Online', 'Offline'])
56
+
57
  alert_level = 'Green'
58
  if vibration > 3:
59
  alert_level = 'Yellow'
60
  if vibration > 4.5:
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}',
68
  'Site': site_name,
 
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
+ ))