bluenevus commited on
Commit
880474a
·
1 Parent(s): c857657

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +216 -74
app.py CHANGED
@@ -3,19 +3,51 @@ from dash import dcc, html, Input, Output, State, clientside_callback
3
  import dash_bootstrap_components as dbc
4
  import plotly.graph_objs as go
5
  import requests
6
- import pandas as pd
7
  from datetime import datetime
8
  import os
9
  from dotenv import load_dotenv
10
  import threading
11
  import json
 
 
 
12
 
13
  # Load environment variables
14
  load_dotenv()
15
 
16
- # Initialize the Dash app
17
- app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
18
- server = app.server
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  # AccuWeather API key
21
  API_KEY = os.getenv('ACCUWEATHER_API_KEY')
@@ -23,6 +55,9 @@ API_KEY = os.getenv('ACCUWEATHER_API_KEY')
23
  # Base URL for AccuWeather API
24
  BASE_URL = "http://dataservice.accuweather.com"
25
 
 
 
 
26
  def get_location_key(lat, lon):
27
  url = f"{BASE_URL}/locations/v1/cities/geoposition/search"
28
  params = {
@@ -37,7 +72,7 @@ def get_location_key(lat, lon):
37
  raise ValueError("Location key not found in API response")
38
  return data["Key"]
39
  except requests.RequestException as e:
40
- print(f"Error in get_location_key: {e}")
41
  return None
42
 
43
  def get_current_conditions(location_key):
@@ -56,7 +91,7 @@ def get_current_conditions(location_key):
56
  raise ValueError("No current conditions data in API response")
57
  return data[0]
58
  except requests.RequestException as e:
59
- print(f"Error in get_current_conditions: {e}")
60
  return None
61
 
62
  def get_forecast(location_key):
@@ -65,14 +100,30 @@ def get_forecast(location_key):
65
  url = f"{BASE_URL}/forecasts/v1/daily/5day/{location_key}"
66
  params = {
67
  "apikey": API_KEY,
68
- "metric": "true",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  }
70
  try:
71
  response = requests.get(url, params=params)
72
  response.raise_for_status()
73
  return response.json()
74
  except requests.RequestException as e:
75
- print(f"Error in get_forecast: {e}")
76
  return None
77
 
78
  def get_indices(location_key):
@@ -87,7 +138,7 @@ def get_indices(location_key):
87
  response.raise_for_status()
88
  return response.json()
89
  except requests.RequestException as e:
90
- print(f"Error in get_indices: {e}")
91
  return None
92
 
93
  def get_alerts(location_key):
@@ -102,41 +153,64 @@ def get_alerts(location_key):
102
  response.raise_for_status()
103
  return response.json()
104
  except requests.RequestException as e:
105
- print(f"Error in get_alerts: {e}")
106
  return None
107
 
108
  def create_current_weather_card(current):
109
  return dbc.Card([
110
  dbc.CardBody([
111
  html.H4("Current Weather", className="card-title"),
112
- html.P(f"Temperature: {current['Temperature']['Metric']['Value']}°C"),
113
  html.P(f"Condition: {current['WeatherText']}"),
114
- html.P(f"Feels Like: {current['RealFeelTemperature']['Metric']['Value']}°C"),
115
- html.P(f"Wind: {current['Wind']['Speed']['Metric']['Value']} km/h"),
116
  html.P(f"Humidity: {current['RelativeHumidity']}%"),
117
  ])
118
  ], className="mb-4")
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  def create_forecast_card(forecast):
121
  daily_forecasts = forecast['DailyForecasts']
122
-
123
  fig = go.Figure()
124
-
125
  dates = [datetime.strptime(day['Date'], "%Y-%m-%dT%H:%M:%S%z").strftime("%m-%d") for day in daily_forecasts]
126
  max_temps = [day['Temperature']['Maximum']['Value'] for day in daily_forecasts]
127
  min_temps = [day['Temperature']['Minimum']['Value'] for day in daily_forecasts]
128
-
129
  fig.add_trace(go.Scatter(x=dates, y=max_temps, name="Max Temp", line=dict(color="red")))
130
  fig.add_trace(go.Scatter(x=dates, y=min_temps, name="Min Temp", line=dict(color="blue")))
131
-
132
  fig.update_layout(
133
  title="5-Day Temperature Forecast",
134
  xaxis_title="Date",
135
- yaxis_title="Temperature (°C)",
136
  legend_title="Temperature",
137
  height=400
138
  )
139
-
140
  return dbc.Card([
141
  dbc.CardBody([
142
  html.H4("5-Day Forecast", className="card-title"),
@@ -145,12 +219,32 @@ def create_forecast_card(forecast):
145
  ], className="mb-4")
146
 
147
  def create_indices_card(indices):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  return dbc.Card([
149
  dbc.CardBody([
150
- html.H4("Weather Indices", className="card-title"),
151
  html.Div([
152
  html.P(f"{index['Name']}: {index['Category']}")
153
- for index in indices[:5] # Display first 5 indices
154
  ])
155
  ])
156
  ], className="mb-4")
@@ -158,7 +252,6 @@ def create_indices_card(indices):
158
  def create_alert_card(alerts):
159
  if not alerts:
160
  return html.Div()
161
-
162
  return dbc.Card([
163
  dbc.CardBody([
164
  html.H4("Weather Alerts", className="card-title text-danger"),
@@ -170,28 +263,54 @@ def create_alert_card(alerts):
170
  ], className="mb-4")
171
 
172
  # App layout
 
 
 
173
  app.layout = dbc.Container([
 
 
 
174
  dbc.Row([
175
  dbc.Col([
176
  html.H1("Weather Dashboard", className="text-center mb-4"),
177
- html.Div(id="loading-output"),
178
- html.Div(id="alert-output"),
179
- dbc.Row([
180
- dbc.Col([
181
- html.Div(id="current-weather-output"),
182
- ], width=6),
183
- dbc.Col([
184
- html.Div(id="forecast-output"),
185
- ], width=6),
186
- ]),
187
- html.Div(id="indices-output"),
188
  ], width=12)
189
  ]),
190
- dcc.Store(id="location-store"),
191
- dcc.Interval(id="location-interval", interval=1000, max_intervals=1),
192
- ], fluid=True, className="mt-4")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
 
194
- # Clientside callback to get user's location
195
  clientside_callback(
196
  """
197
  function(n_intervals) {
@@ -218,65 +337,88 @@ clientside_callback(
218
  Input("location-interval", "n_intervals"),
219
  )
220
 
 
 
 
 
 
 
 
 
221
  @app.callback(
222
- [Output("loading-output", "children"),
223
- Output("current-weather-output", "children"),
224
- Output("forecast-output", "children"),
225
- Output("indices-output", "children"),
226
- Output("alert-output", "children")],
227
- Input("location-store", "data")
 
 
 
228
  )
229
- def update_weather(location):
 
 
 
230
  if not location or 'error' in location:
231
  error_message = location.get('error', 'Waiting for location data...') if location else 'Waiting for location data...'
232
- return error_message, "", "", "", ""
 
233
 
234
  lat, lon = location["latitude"], location["longitude"]
235
-
236
- loading_output = html.Div([
237
- html.P("Loading weather data...", className="text-center"),
238
- dbc.Spinner(color="primary", type="grow"),
239
- ])
240
-
241
  def fetch_weather_data():
242
  try:
243
- location_key = get_location_key(lat, lon)
244
- if location_key is None:
 
 
 
245
  raise ValueError("Failed to get location key")
246
 
247
  current = get_current_conditions(location_key)
248
  forecast = get_forecast(location_key)
 
249
  indices = get_indices(location_key)
250
  alerts = get_alerts(location_key)
251
 
252
  if current is None or forecast is None or indices is None:
253
  raise ValueError("Failed to fetch weather data")
254
 
255
- current_weather = create_current_weather_card(current)
256
- forecast_output = create_forecast_card(forecast)
257
- indices_output = create_indices_card(indices)
258
- alert_output = create_alert_card(alerts)
259
-
260
- return current_weather, forecast_output, indices_output, alert_output
261
-
262
  except Exception as e:
263
- error_message = f"Error fetching weather data: {str(e)}"
264
- print(error_message) # Log the error
265
- return "", "", "", html.Div(error_message, className="text-danger")
266
-
267
- # Use threading to fetch data asynchronously
268
- thread = threading.Thread(target=fetch_weather_data)
269
- thread.start()
270
- thread.join(timeout=10) # Wait for up to 10 seconds
 
 
271
 
272
- if thread.is_alive():
273
- error_message = "Weather data fetch timed out. Please try again."
274
- return html.Div(error_message, className="text-warning"), "", "", "", ""
275
 
276
- current_weather, forecast_output, indices_output, alert_output = fetch_weather_data()
277
- return "", current_weather, forecast_output, indices_output, alert_output
 
 
 
 
 
 
 
 
 
278
 
279
  if __name__ == '__main__':
280
  print("Starting the Dash application...")
281
- app.run(debug=True, host='0.0.0.0', port=7860)
282
  print("Dash application has finished running.")
 
3
  import dash_bootstrap_components as dbc
4
  import plotly.graph_objs as go
5
  import requests
 
6
  from datetime import datetime
7
  import os
8
  from dotenv import load_dotenv
9
  import threading
10
  import json
11
+ import uuid
12
+ import flask
13
+ import logging
14
 
15
  # Load environment variables
16
  load_dotenv()
17
 
18
+ # Logging setup
19
+ logging.basicConfig(level=logging.INFO)
20
+ logger = logging.getLogger("weather_dash")
21
+
22
+ # Session and threading management
23
+ session_locks = {}
24
+ session_data_store = {}
25
+ SESSION_COOKIE = "weather_dash_session_id"
26
+
27
+ def get_session_id():
28
+ ctx = flask.request.cookies.get(SESSION_COOKIE)
29
+ if not ctx:
30
+ ctx = str(uuid.uuid4())
31
+ return ctx
32
+
33
+ def get_session_lock(session_id):
34
+ if session_id not in session_locks:
35
+ session_locks[session_id] = threading.Lock()
36
+ return session_locks[session_id]
37
+
38
+ def get_session_data(session_id):
39
+ if session_id not in session_data_store:
40
+ session_data_store[session_id] = {}
41
+ return session_data_store[session_id]
42
+
43
+ def save_session_data(session_id, key, value):
44
+ session_data = get_session_data(session_id)
45
+ session_data[key] = value
46
+ session_data_store[session_id] = session_data
47
+
48
+ def get_data_from_session(session_id, key):
49
+ session_data = get_session_data(session_id)
50
+ return session_data.get(key, None)
51
 
52
  # AccuWeather API key
53
  API_KEY = os.getenv('ACCUWEATHER_API_KEY')
 
55
  # Base URL for AccuWeather API
56
  BASE_URL = "http://dataservice.accuweather.com"
57
 
58
+ # Health-related index keywords
59
+ HEALTH_INDEX_KEYWORDS = ["Air Quality", "Asthma", "Flu", "Allergy", "Respiratory", "Pollen"]
60
+
61
  def get_location_key(lat, lon):
62
  url = f"{BASE_URL}/locations/v1/cities/geoposition/search"
63
  params = {
 
72
  raise ValueError("Location key not found in API response")
73
  return data["Key"]
74
  except requests.RequestException as e:
75
+ logger.error(f"Error in get_location_key: {e}")
76
  return None
77
 
78
  def get_current_conditions(location_key):
 
91
  raise ValueError("No current conditions data in API response")
92
  return data[0]
93
  except requests.RequestException as e:
94
+ logger.error(f"Error in get_current_conditions: {e}")
95
  return None
96
 
97
  def get_forecast(location_key):
 
100
  url = f"{BASE_URL}/forecasts/v1/daily/5day/{location_key}"
101
  params = {
102
  "apikey": API_KEY,
103
+ "metric": "false", # Fahrenheit
104
+ }
105
+ try:
106
+ response = requests.get(url, params=params)
107
+ response.raise_for_status()
108
+ return response.json()
109
+ except requests.RequestException as e:
110
+ logger.error(f"Error in get_forecast: {e}")
111
+ return None
112
+
113
+ def get_hourly_forecast(location_key):
114
+ if location_key is None:
115
+ return None
116
+ url = f"{BASE_URL}/forecasts/v1/hourly/12hour/{location_key}"
117
+ params = {
118
+ "apikey": API_KEY,
119
+ "metric": "false", # Fahrenheit
120
  }
121
  try:
122
  response = requests.get(url, params=params)
123
  response.raise_for_status()
124
  return response.json()
125
  except requests.RequestException as e:
126
+ logger.error(f"Error in get_hourly_forecast: {e}")
127
  return None
128
 
129
  def get_indices(location_key):
 
138
  response.raise_for_status()
139
  return response.json()
140
  except requests.RequestException as e:
141
+ logger.error(f"Error in get_indices: {e}")
142
  return None
143
 
144
  def get_alerts(location_key):
 
153
  response.raise_for_status()
154
  return response.json()
155
  except requests.RequestException as e:
156
+ logger.error(f"Error in get_alerts: {e}")
157
  return None
158
 
159
  def create_current_weather_card(current):
160
  return dbc.Card([
161
  dbc.CardBody([
162
  html.H4("Current Weather", className="card-title"),
163
+ html.P(f"Temperature: {current['Temperature']['Imperial']['Value']}°F"),
164
  html.P(f"Condition: {current['WeatherText']}"),
165
+ html.P(f"Feels Like: {current['RealFeelTemperature']['Imperial']['Value']}°F"),
166
+ html.P(f"Wind: {current['Wind']['Speed']['Imperial']['Value']} mph"),
167
  html.P(f"Humidity: {current['RelativeHumidity']}%"),
168
  ])
169
  ], className="mb-4")
170
 
171
+ def create_hourly_forecast_card(hourly):
172
+ if not hourly:
173
+ return dbc.Card([
174
+ dbc.CardBody([
175
+ html.H4("12-Hour Hourly Forecast", className="card-title"),
176
+ html.P("No hourly forecast available")
177
+ ])
178
+ ], className="mb-4")
179
+ hours = [datetime.strptime(hr['DateTime'], "%Y-%m-%dT%H:%M:%S%z").strftime("%I %p") for hr in hourly]
180
+ temps = [hr['Temperature']['Value'] for hr in hourly]
181
+ conditions = [hr['IconPhrase'] for hr in hourly]
182
+ fig = go.Figure()
183
+ fig.add_trace(go.Scatter(x=hours, y=temps, name="Temperature", line=dict(color="orange")))
184
+ fig.update_layout(
185
+ title="Next 12 Hours Forecast",
186
+ xaxis_title="Time",
187
+ yaxis_title="Temperature (°F)",
188
+ legend_title="Legend",
189
+ height=350,
190
+ margin=dict(l=40, r=40, t=40, b=40)
191
+ )
192
+ return dbc.Card([
193
+ dbc.CardBody([
194
+ html.H4("12-Hour Hourly Forecast", className="card-title"),
195
+ dcc.Graph(figure=fig)
196
+ ])
197
+ ], className="mb-4")
198
+
199
  def create_forecast_card(forecast):
200
  daily_forecasts = forecast['DailyForecasts']
 
201
  fig = go.Figure()
 
202
  dates = [datetime.strptime(day['Date'], "%Y-%m-%dT%H:%M:%S%z").strftime("%m-%d") for day in daily_forecasts]
203
  max_temps = [day['Temperature']['Maximum']['Value'] for day in daily_forecasts]
204
  min_temps = [day['Temperature']['Minimum']['Value'] for day in daily_forecasts]
 
205
  fig.add_trace(go.Scatter(x=dates, y=max_temps, name="Max Temp", line=dict(color="red")))
206
  fig.add_trace(go.Scatter(x=dates, y=min_temps, name="Min Temp", line=dict(color="blue")))
 
207
  fig.update_layout(
208
  title="5-Day Temperature Forecast",
209
  xaxis_title="Date",
210
+ yaxis_title="Temperature (°F)",
211
  legend_title="Temperature",
212
  height=400
213
  )
 
214
  return dbc.Card([
215
  dbc.CardBody([
216
  html.H4("5-Day Forecast", className="card-title"),
 
219
  ], className="mb-4")
220
 
221
  def create_indices_card(indices):
222
+ if not indices:
223
+ return dbc.Card([
224
+ dbc.CardBody([
225
+ html.H4("Health Indices", className="card-title"),
226
+ html.P("No health indices available.")
227
+ ])
228
+ ], className="mb-4")
229
+ health_indices = []
230
+ for idx in indices:
231
+ for keyword in HEALTH_INDEX_KEYWORDS:
232
+ if keyword.lower() in idx['Name'].lower():
233
+ health_indices.append(idx)
234
+ break
235
+ if not health_indices:
236
+ return dbc.Card([
237
+ dbc.CardBody([
238
+ html.H4("Health Indices", className="card-title"),
239
+ html.P("No health-related indices available.")
240
+ ])
241
+ ], className="mb-4")
242
  return dbc.Card([
243
  dbc.CardBody([
244
+ html.H4("Health Indices", className="card-title"),
245
  html.Div([
246
  html.P(f"{index['Name']}: {index['Category']}")
247
+ for index in health_indices[:5]
248
  ])
249
  ])
250
  ], className="mb-4")
 
252
  def create_alert_card(alerts):
253
  if not alerts:
254
  return html.Div()
 
255
  return dbc.Card([
256
  dbc.CardBody([
257
  html.H4("Weather Alerts", className="card-title text-danger"),
 
263
  ], className="mb-4")
264
 
265
  # App layout
266
+ app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
267
+ server = app.server
268
+
269
  app.layout = dbc.Container([
270
+ dcc.Store(id="location-store", storage_type="session"),
271
+ dcc.Store(id="session-store", storage_type="session"),
272
+ dcc.Interval(id="location-interval", interval=1000, max_intervals=1),
273
  dbc.Row([
274
  dbc.Col([
275
  html.H1("Weather Dashboard", className="text-center mb-4"),
 
 
 
 
 
 
 
 
 
 
 
276
  ], width=12)
277
  ]),
278
+ dbc.Row([
279
+ dbc.Col([
280
+ dbc.Card([
281
+ dbc.CardBody([
282
+ html.H5("Navigation", className="card-title"),
283
+ dbc.Nav([
284
+ dbc.NavLink("Health Indices", href="#", id="nav-health"),
285
+ dbc.NavLink("Current Weather", href="#", id="nav-current"),
286
+ ], vertical=True, pills=True)
287
+ ])
288
+ ], className="mb-4"),
289
+ dcc.Loading(
290
+ id="loading",
291
+ type="default",
292
+ children=[
293
+ html.Div(id="indices-output"),
294
+ html.Div(id="current-weather-output")
295
+ ],
296
+ style={"width": "100%"}
297
+ )
298
+ ], width=4, style={"minWidth": "260px"}),
299
+ dbc.Col([
300
+ dcc.Loading(
301
+ id="loading-forecast",
302
+ type="default",
303
+ children=[
304
+ html.Div(id="hourly-forecast-output"),
305
+ html.Div(id="forecast-output"),
306
+ html.Div(id="alert-output")
307
+ ],
308
+ style={"width": "100%"}
309
+ )
310
+ ], width=8)
311
+ ], style={"marginTop": "16px"})
312
+ ], fluid=True, className="mt-2")
313
 
 
314
  clientside_callback(
315
  """
316
  function(n_intervals) {
 
337
  Input("location-interval", "n_intervals"),
338
  )
339
 
340
+ @app.server.after_request
341
+ def set_session_cookie(response):
342
+ session_id = flask.request.cookies.get(SESSION_COOKIE)
343
+ if not session_id:
344
+ session_id = str(uuid.uuid4())
345
+ response.set_cookie(SESSION_COOKIE, session_id, max_age=60*60*24*7, path="/")
346
+ return response
347
+
348
  @app.callback(
349
+ [
350
+ Output("indices-output", "children"),
351
+ Output("current-weather-output", "children"),
352
+ Output("hourly-forecast-output", "children"),
353
+ Output("forecast-output", "children"),
354
+ Output("alert-output", "children"),
355
+ ],
356
+ [Input("location-store", "data")],
357
+ [State("session-store", "data")]
358
  )
359
+ def update_weather(location, session_data):
360
+ session_id = get_session_id()
361
+ lock = get_session_lock(session_id)
362
+ logger.info(f"Session {session_id} update_weather called.")
363
  if not location or 'error' in location:
364
  error_message = location.get('error', 'Waiting for location data...') if location else 'Waiting for location data...'
365
+ logger.warning(f"Session {session_id} waiting for location: {error_message}")
366
+ return [dbc.Spinner(color="primary"), "", "", "", ""]
367
 
368
  lat, lon = location["latitude"], location["longitude"]
369
+ results = {"indices": "", "current": "", "hourly": "", "forecast": "", "alerts": ""}
 
 
 
 
 
370
  def fetch_weather_data():
371
  try:
372
+ location_key = get_data_from_session(session_id, "location_key")
373
+ if not location_key:
374
+ location_key = get_location_key(lat, lon)
375
+ save_session_data(session_id, "location_key", location_key)
376
+ if not location_key:
377
  raise ValueError("Failed to get location key")
378
 
379
  current = get_current_conditions(location_key)
380
  forecast = get_forecast(location_key)
381
+ hourly = get_hourly_forecast(location_key)
382
  indices = get_indices(location_key)
383
  alerts = get_alerts(location_key)
384
 
385
  if current is None or forecast is None or indices is None:
386
  raise ValueError("Failed to fetch weather data")
387
 
388
+ results["indices"] = create_indices_card(indices)
389
+ results["current"] = create_current_weather_card(current)
390
+ results["hourly"] = create_hourly_forecast_card(hourly)
391
+ results["forecast"] = create_forecast_card(forecast)
392
+ results["alerts"] = create_alert_card(alerts)
393
+ save_session_data(session_id, "weather_results", results)
 
394
  except Exception as e:
395
+ logger.error(f"Session {session_id} error: {str(e)}")
396
+ results["indices"] = ""
397
+ results["current"] = ""
398
+ results["hourly"] = ""
399
+ results["forecast"] = ""
400
+ results["alerts"] = dbc.Card([
401
+ dbc.CardBody([
402
+ html.P(f"Error fetching weather data: {str(e)}", className="text-danger")
403
+ ])
404
+ ], className="mb-4")
405
 
406
+ with lock:
407
+ fetch_weather_data()
 
408
 
409
+ weather_results = get_data_from_session(session_id, "weather_results")
410
+ if weather_results:
411
+ return [
412
+ weather_results.get("indices", ""),
413
+ weather_results.get("current", ""),
414
+ weather_results.get("hourly", ""),
415
+ weather_results.get("forecast", ""),
416
+ weather_results.get("alerts", ""),
417
+ ]
418
+ else:
419
+ return [dbc.Spinner(color="primary"), "", "", "", ""]
420
 
421
  if __name__ == '__main__':
422
  print("Starting the Dash application...")
423
+ app.run(debug=True, host='0.0.0.0', port=7860, threaded=True)
424
  print("Dash application has finished running.")