1212from .common import (
1313 COMPASS_DIRS_16 , COMPASS_DIRS_8 , WIND_SPEED_BINS , WIND_SPEED_LABELS ,
1414 WIND_SPEED_COLORS , BEAUFORT_SCALE , VENTILATION_COLORS , WIND_CLASSIFICATIONS ,
15- KN_TO_KPH , TIMEZONE ,
15+ KN_TO_KPH , TIMEZONE , CALM_THRESHOLD_KPH ,
1616 spike_filter , compass_bin , beaufort_number , weibull_fit , to_eat_ms ,
1717 get_season_boundaries ,
1818)
@@ -40,10 +40,10 @@ def process(df):
4040
4141 # ── Summary Statistics ────────────────────────────────────────────────
4242 total_readings = len (wdf )
43- calm_readings = (wdf ["avg_wind_kph" ] == 0 ).sum ()
43+ calm_readings = (wdf ["avg_wind_kph" ] <= CALM_THRESHOLD_KPH ).sum ()
4444 calm_pct = round (calm_readings / total_readings * 100 , 1 ) if total_readings else 0
4545
46- non_calm = wdf [wdf ["avg_wind_kph" ] > 0 ]
46+ non_calm = wdf [wdf ["avg_wind_kph" ] > CALM_THRESHOLD_KPH ]
4747 mean_speed = round (wdf ["avg_wind_kph" ].mean (), 1 )
4848 mean_speed_noncalm = round (non_calm ["avg_wind_kph" ].mean (), 1 ) if len (non_calm ) else 0
4949 max_speed = round (wdf ["avg_wind_kph" ].max (), 1 )
@@ -108,9 +108,9 @@ def _build_wind_rose(wdf, n_points):
108108 dir_labels = [d [0 ] for d in dirs ]
109109 col = "compass_16" if n_points == 16 else "compass_8"
110110
111- non_calm = wdf [wdf ["avg_wind_kph" ] > 0 ].copy ()
111+ non_calm = wdf [wdf ["avg_wind_kph" ] > CALM_THRESHOLD_KPH ].copy ()
112112 total = len (wdf )
113- calm_pct = round ((wdf ["avg_wind_kph" ] == 0 ).sum () / total * 100 , 1 ) if total else 0
113+ calm_pct = round ((wdf ["avg_wind_kph" ] <= CALM_THRESHOLD_KPH ).sum () / total * 100 , 1 ) if total else 0
114114
115115 # Speed bins: 0-5, 5-10, 10-15, 15-20, 20+
116116 traces = []
@@ -176,9 +176,9 @@ def _build_wind_timeseries(wdf):
176176 """Build wind speed time series with average and gust."""
177177 timestamps = [to_eat_ms (t ) for t in wdf ["timestamp" ]]
178178
179- # 24 -hour running mean
179+ # 12 -hour running mean
180180 wdf_sorted = wdf .set_index ("timestamp" ).sort_index ()
181- running_mean = wdf_sorted ["avg_wind_kph" ].rolling ("24h" , min_periods = 1 ).mean ()
181+ running_mean = wdf_sorted ["avg_wind_kph" ].rolling ("12h" , center = True , min_periods = 1 ).mean ()
182182 rm_ts = [to_eat_ms (t ) for t in running_mean .index ]
183183 rm_vals = [round (v , 1 ) if not pd .isna (v ) else None for v in running_mean .values ]
184184
@@ -208,7 +208,7 @@ def _build_wind_timeseries(wdf):
208208 {
209209 "type" : "scatter" ,
210210 "mode" : "lines" ,
211- "name" : "24h Mean" ,
211+ "name" : "12h Mean" ,
212212 "x_ms" : rm_ts ,
213213 "y" : rm_vals ,
214214 "line" : {"color" : "#d62728" , "width" : 2 },
@@ -242,7 +242,7 @@ def _build_diurnal_wind(wdf):
242242
243243 # Calm percentage by hour
244244 calm_by_hour = wdf_c .groupby ("hour" ).apply (
245- lambda g : (g ["avg_wind_kph" ] == 0 ).sum () / len (g ) * 100
245+ lambda g : (g ["avg_wind_kph" ] <= CALM_THRESHOLD_KPH ).sum () / len (g ) * 100
246246 ).round (1 )
247247
248248 # Optional: separate by month
@@ -293,7 +293,7 @@ def _build_diurnal_wind(wdf):
293293 },
294294 {
295295 "type" : "bar" ,
296- "name" : "Calm %" ,
296+ "name" : "Calm % ( \u2264 0.1 m/s) " ,
297297 "x" : hours ,
298298 "y" : calm_pcts ,
299299 "yaxis" : "y2" ,
@@ -334,7 +334,7 @@ def _build_wind_distribution(wdf, k_val, c_val):
334334 hist , bin_edges = np .histogram (speeds , bins = bins )
335335
336336 # Separate calm bar
337- calm_count = int ((speeds == 0 ).sum ())
337+ calm_count = int ((speeds <= CALM_THRESHOLD_KPH ).sum ())
338338 bin_centers = [(bin_edges [i ] + bin_edges [i + 1 ]) / 2 for i in range (len (hist ))]
339339
340340 traces = [{
@@ -387,7 +387,7 @@ def _build_wind_distribution(wdf, k_val, c_val):
387387
388388def _build_gust_factor (wdf ):
389389 """Build gust factor scatter plot (gust factor vs avg speed, colored by hour)."""
390- valid = wdf [(wdf ["avg_wind_kph" ] > 0 ) & wdf ["peak_wind_kph" ].notna ()].copy ()
390+ valid = wdf [(wdf ["avg_wind_kph" ] > CALM_THRESHOLD_KPH ) & wdf ["peak_wind_kph" ].notna ()].copy ()
391391 valid ["gust_factor" ] = valid ["peak_wind_kph" ] / valid ["avg_wind_kph" ]
392392 valid ["hour" ] = valid ["timestamp" ].dt .hour
393393
@@ -440,7 +440,7 @@ def _build_gust_factor(wdf):
440440
441441def _build_calm_periods (wdf ):
442442 """Build calm period analysis: distribution of consecutive calm durations."""
443- is_calm = (wdf ["avg_wind_kph" ] == 0 ).values
443+ is_calm = (wdf ["avg_wind_kph" ] <= CALM_THRESHOLD_KPH ).values
444444 timestamps = wdf ["timestamp" ].values
445445
446446 # Find consecutive calm periods
@@ -528,8 +528,8 @@ def _build_ventilation_availability(wdf):
528528 for date , group in wdf_c .groupby ("date" ):
529529 total = len (group )
530530 effective = (group ["avg_wind_kph" ] >= thresh ).sum ()
531- marginal = ((group ["avg_wind_kph" ] > 0 ) & (group ["avg_wind_kph" ] < thresh )).sum ()
532- calm = (group ["avg_wind_kph" ] == 0 ).sum ()
531+ marginal = ((group ["avg_wind_kph" ] > CALM_THRESHOLD_KPH ) & (group ["avg_wind_kph" ] < thresh )).sum ()
532+ calm = (group ["avg_wind_kph" ] <= CALM_THRESHOLD_KPH ).sum ()
533533
534534 # Convert to hours (assuming ~5-min intervals)
535535 interval_h = 5 / 60
@@ -545,8 +545,8 @@ def _build_ventilation_availability(wdf):
545545 # Default threshold stats
546546 default_thresh = 3.5
547547 all_effective = (wdf_c ["avg_wind_kph" ] >= default_thresh ).sum ()
548- all_marginal = ((wdf_c ["avg_wind_kph" ] > 0 ) & (wdf_c ["avg_wind_kph" ] < default_thresh )).sum ()
549- all_calm = (wdf_c ["avg_wind_kph" ] == 0 ).sum ()
548+ all_marginal = ((wdf_c ["avg_wind_kph" ] > CALM_THRESHOLD_KPH ) & (wdf_c ["avg_wind_kph" ] < default_thresh )).sum ()
549+ all_calm = (wdf_c ["avg_wind_kph" ] <= CALM_THRESHOLD_KPH ).sum ()
550550 total = len (wdf_c )
551551 eff_pct = round (all_effective / total * 100 , 1 ) if total else 0
552552
@@ -583,7 +583,7 @@ def _build_ventilation_availability(wdf):
583583 {
584584 "type" : "scatter" ,
585585 "mode" : "lines" ,
586- "name" : "Calm" ,
586+ "name" : "Calm ( \u2264 0.1 m/s) " ,
587587 "x_ms" : dates_ms ,
588588 "y" : calm_hours ,
589589 "fill" : "tonexty" ,
0 commit comments