nakas Claude commited on
Commit
3c0bfa8
·
1 Parent(s): 9085499

Add snow depth change calculation method

Browse files

New Features:
- Calculate new snow from snow depth changes (more accurate than 3-hour reports)
- Display both methods side-by-side in grouped bar chart
- Show both totals in statistics summary
- Added explanation of both calculation methods in UI

The depth change method tracks the maximum increase in snow depth per day
(9 AM reset), which is generally more accurate than summing 3-hour reports
which can be unreliable or missing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +100 -22
app.py CHANGED
@@ -164,14 +164,13 @@ def calculate_total_new_snow(df):
164
  Calculate total new snow by:
165
  1. Using ONLY the 3-hour snowfall amounts
166
  2. Using 9 AM as the daily reset point
167
- 3. Filtering out obvious anomalies (>9 inches in 3 hours)
168
  """
169
  # Sort by datetime to ensure correct calculation
170
  df = df.sort_values('datetime')
171
-
172
  # Create a copy of the dataframe with ONLY datetime and 3-hour snowfall
173
  snow_df = df[['datetime', 'snowfall_3hr']].copy()
174
-
175
  # Create a day group that starts at 9 AM instead of midnight
176
  snow_df['day_group'] = snow_df['datetime'].apply(
177
  lambda x: x.date() if x.hour >= 9 else (x - pd.Timedelta(days=1)).date()
@@ -182,6 +181,59 @@ def calculate_total_new_snow(df):
182
 
183
  return daily_totals.sum()
184
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  def process_daily_snow(group):
186
  """Sum up ONLY the 3-hour snowfall amounts for each day period"""
187
  # Sort by time to ensure proper sequence
@@ -234,27 +286,47 @@ def create_plots(df):
234
  ax3.grid(True)
235
  ax3.tick_params(axis='x', rotation=45)
236
 
237
- # Daily new snow bar plot
238
  ax4 = fig.add_subplot(gs[3])
 
 
239
  snow_df = df[['datetime', 'snowfall_3hr']].copy()
240
  snow_df['day_group'] = snow_df['datetime'].apply(
241
  lambda x: x.date() if x.hour >= 9 else (x - pd.Timedelta(days=1)).date()
242
  )
243
- daily_snow = snow_df.groupby('day_group').apply(process_daily_snow).reset_index()
244
- daily_snow.columns = ['date', 'new_snow']
245
-
246
- # Create the bar plot
247
- ax4.bar(daily_snow['date'], daily_snow['new_snow'], color='blue')
248
- ax4.set_title('Daily New Snow (Sum of 3-hour amounts, 9 AM Reset)', pad=20)
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  ax4.set_xlabel('Date')
250
  ax4.set_ylabel('New Snow (inches)')
251
- ax4.tick_params(axis='x', rotation=45)
 
 
252
  ax4.grid(True, axis='y', linestyle='--', alpha=0.7)
253
-
254
- # Add value labels on top of each bar
255
- for i, v in enumerate(daily_snow['new_snow']):
256
- if v > 0: # Only label bars with snow
257
- ax4.text(i, v, f'{v:.1f}"', ha='center', va='bottom')
 
 
 
258
 
259
  # SWE bar plot
260
  ax5 = fig.add_subplot(gs[4])
@@ -293,13 +365,14 @@ def analyze_weather_data(site_id, hours):
293
  if not raw_data:
294
  return "Error: Could not retrieve weather data.", None, None
295
 
296
- print("Parsing data...")
297
  df = parse_weather_data(raw_data)
298
-
299
- # Calculate total new snow using the new method
300
- total_new_snow = calculate_total_new_snow(df)
 
301
  current_swe = df['swe'].iloc[0] # Get most recent SWE measurement
302
-
303
  print("Calculating statistics...")
304
  stats = {
305
  'Temperature Range': f"{df['temp'].min():.1f}°F to {df['temp'].max():.1f}°F",
@@ -308,7 +381,8 @@ def analyze_weather_data(site_id, hours):
308
  'Max Wind Gust': f"{df['wind_gust'].max():.1f} mph",
309
  'Average Humidity': f"{df['humidity'].mean():.1f}%",
310
  'Current Snow Depth': f"{df['snow_depth'].iloc[0]:.1f} inches",
311
- 'Total New Snow': f"{total_new_snow:.1f} inches",
 
312
  'Current Snow/Water Equivalent': f"{current_swe:.2f} inches"
313
  }
314
 
@@ -333,6 +407,10 @@ with gr.Blocks(title="Weather Station Data Analyzer") as demo:
333
  gr.Markdown("# Weather Station Data Analyzer")
334
  gr.Markdown("""
335
  Select a weather station and number of hours to analyze.
 
 
 
 
336
  """)
337
 
338
  with gr.Row():
 
164
  Calculate total new snow by:
165
  1. Using ONLY the 3-hour snowfall amounts
166
  2. Using 9 AM as the daily reset point
 
167
  """
168
  # Sort by datetime to ensure correct calculation
169
  df = df.sort_values('datetime')
170
+
171
  # Create a copy of the dataframe with ONLY datetime and 3-hour snowfall
172
  snow_df = df[['datetime', 'snowfall_3hr']].copy()
173
+
174
  # Create a day group that starts at 9 AM instead of midnight
175
  snow_df['day_group'] = snow_df['datetime'].apply(
176
  lambda x: x.date() if x.hour >= 9 else (x - pd.Timedelta(days=1)).date()
 
181
 
182
  return daily_totals.sum()
183
 
184
+ def calculate_new_snow_from_depth(df):
185
+ """
186
+ Calculate new snow from changes in snow depth.
187
+ More accurate than 3-hour readings which can be unreliable.
188
+ Uses 9 AM as the daily reset point.
189
+ """
190
+ # Sort by datetime
191
+ df = df.sort_values('datetime').copy()
192
+
193
+ # Create day groups starting at 9 AM
194
+ df['day_group'] = df['datetime'].apply(
195
+ lambda x: x.date() if x.hour >= 9 else (x - pd.Timedelta(days=1)).date()
196
+ )
197
+
198
+ daily_snow_change = []
199
+
200
+ for day, group in df.groupby('day_group'):
201
+ group = group.sort_values('datetime')
202
+ valid_depths = group['snow_depth'].dropna()
203
+
204
+ if len(valid_depths) > 0:
205
+ # Get max depth increase for the day
206
+ max_depth = valid_depths.max()
207
+ min_depth = valid_depths.min()
208
+
209
+ # New snow is the increase in depth (ignore decreases from settling/melting)
210
+ new_snow = max(0, max_depth - min_depth)
211
+ daily_snow_change.append(new_snow)
212
+
213
+ return sum(daily_snow_change)
214
+
215
+ def calculate_daily_snow_from_depth(df):
216
+ """Calculate daily new snow from depth changes, returns a dataframe"""
217
+ df = df.sort_values('datetime').copy()
218
+
219
+ df['day_group'] = df['datetime'].apply(
220
+ lambda x: x.date() if x.hour >= 9 else (x - pd.Timedelta(days=1)).date()
221
+ )
222
+
223
+ daily_data = []
224
+
225
+ for day, group in df.groupby('day_group'):
226
+ group = group.sort_values('datetime')
227
+ valid_depths = group['snow_depth'].dropna()
228
+
229
+ if len(valid_depths) > 0:
230
+ max_depth = valid_depths.max()
231
+ min_depth = valid_depths.min()
232
+ new_snow = max(0, max_depth - min_depth)
233
+ daily_data.append({'date': day, 'new_snow_from_depth': new_snow})
234
+
235
+ return pd.DataFrame(daily_data)
236
+
237
  def process_daily_snow(group):
238
  """Sum up ONLY the 3-hour snowfall amounts for each day period"""
239
  # Sort by time to ensure proper sequence
 
286
  ax3.grid(True)
287
  ax3.tick_params(axis='x', rotation=45)
288
 
289
+ # Daily new snow comparison plot - showing both methods
290
  ax4 = fig.add_subplot(gs[3])
291
+
292
+ # Calculate from 3-hour reports
293
  snow_df = df[['datetime', 'snowfall_3hr']].copy()
294
  snow_df['day_group'] = snow_df['datetime'].apply(
295
  lambda x: x.date() if x.hour >= 9 else (x - pd.Timedelta(days=1)).date()
296
  )
297
+ daily_snow_3hr = snow_df.groupby('day_group').apply(process_daily_snow).reset_index()
298
+ daily_snow_3hr.columns = ['date', 'new_snow_3hr']
299
+
300
+ # Calculate from depth changes
301
+ daily_snow_depth = calculate_daily_snow_from_depth(df)
302
+
303
+ # Merge the two calculations
304
+ daily_combined = pd.merge(daily_snow_3hr, daily_snow_depth, on='date', how='outer').fillna(0)
305
+
306
+ # Create grouped bar chart
307
+ x = range(len(daily_combined))
308
+ width = 0.35
309
+
310
+ bars1 = ax4.bar([i - width/2 for i in x], daily_combined['new_snow_3hr'],
311
+ width, label='3-Hour Reports', color='skyblue', alpha=0.8)
312
+ bars2 = ax4.bar([i + width/2 for i in x], daily_combined['new_snow_from_depth'],
313
+ width, label='Snow Depth Change', color='darkblue', alpha=0.8)
314
+
315
+ ax4.set_title('Daily New Snow - Two Methods (9 AM Reset)', pad=20, fontsize=12, fontweight='bold')
316
  ax4.set_xlabel('Date')
317
  ax4.set_ylabel('New Snow (inches)')
318
+ ax4.set_xticks(x)
319
+ ax4.set_xticklabels([str(d) for d in daily_combined['date']], rotation=45, ha='right')
320
+ ax4.legend()
321
  ax4.grid(True, axis='y', linestyle='--', alpha=0.7)
322
+
323
+ # Add value labels on bars
324
+ for bars in [bars1, bars2]:
325
+ for bar in bars:
326
+ height = bar.get_height()
327
+ if height > 0:
328
+ ax4.text(bar.get_x() + bar.get_width()/2., height,
329
+ f'{height:.1f}"', ha='center', va='bottom', fontsize=8)
330
 
331
  # SWE bar plot
332
  ax5 = fig.add_subplot(gs[4])
 
365
  if not raw_data:
366
  return "Error: Could not retrieve weather data.", None, None
367
 
368
+ print("Parsing data...")
369
  df = parse_weather_data(raw_data)
370
+
371
+ # Calculate total new snow using both methods
372
+ total_new_snow_3hr = calculate_total_new_snow(df)
373
+ total_new_snow_depth = calculate_new_snow_from_depth(df)
374
  current_swe = df['swe'].iloc[0] # Get most recent SWE measurement
375
+
376
  print("Calculating statistics...")
377
  stats = {
378
  'Temperature Range': f"{df['temp'].min():.1f}°F to {df['temp'].max():.1f}°F",
 
381
  'Max Wind Gust': f"{df['wind_gust'].max():.1f} mph",
382
  'Average Humidity': f"{df['humidity'].mean():.1f}%",
383
  'Current Snow Depth': f"{df['snow_depth'].iloc[0]:.1f} inches",
384
+ 'Total New Snow (3-hr reports)': f"{total_new_snow_3hr:.1f} inches",
385
+ 'Total New Snow (depth change)': f"{total_new_snow_depth:.1f} inches",
386
  'Current Snow/Water Equivalent': f"{current_swe:.2f} inches"
387
  }
388
 
 
407
  gr.Markdown("# Weather Station Data Analyzer")
408
  gr.Markdown("""
409
  Select a weather station and number of hours to analyze.
410
+
411
+ **New Snow Calculation Methods:**
412
+ - **3-Hour Reports**: Sum of reported 3-hour snowfall amounts
413
+ - **Depth Change**: Calculated from snow depth increases (generally more accurate)
414
  """)
415
 
416
  with gr.Row():