|
15 | 15 | ) |
16 | 16 |
|
17 | 17 |
|
| 18 | +def build_driving_rain_hourly(df, precip_incr): |
| 19 | + """Build DRI chart from hourly-resampled data (ISO 15927-3 methodology). |
| 20 | +
|
| 21 | + 5-minute Omnisense data captures instantaneous rain-rate spikes that inflate |
| 22 | + DRI non-linearly via the r^(8/9) exponent. Resampling to hourly accumulations |
| 23 | + (summing incremental mm per hour) gives a stable directional signal consistent |
| 24 | + with the standard methodology and comparable to hourly reanalysis data. |
| 25 | +
|
| 26 | + Args: |
| 27 | + df: Quality-controlled weather station DataFrame. |
| 28 | + precip_incr: Reset-corrected incremental precipitation series (mm per reading). |
| 29 | + Returns: |
| 30 | + DRI chart config dict (same structure as _build_driving_rain_index output). |
| 31 | + """ |
| 32 | + df_h = df[['timestamp', 'avg_wind_kph', 'wind_dir']].copy() |
| 33 | + df_h['_precip_incr'] = precip_incr.values if hasattr(precip_incr, 'values') else list(precip_incr) |
| 34 | + df_h['_hour'] = df_h['timestamp'].dt.floor('h') |
| 35 | + |
| 36 | + wd_rad = np.radians(df_h['wind_dir'].astype(float)) |
| 37 | + df_h['_sin'] = np.sin(wd_rad) |
| 38 | + df_h['_cos'] = np.cos(wd_rad) |
| 39 | + |
| 40 | + agg = df_h.groupby('_hour', as_index=False).agg( |
| 41 | + avg_wind_kph=('avg_wind_kph', 'mean'), |
| 42 | + _sin=('_sin', 'mean'), |
| 43 | + _cos=('_cos', 'mean'), |
| 44 | + precip_rate_mmh=('_precip_incr', 'sum'), |
| 45 | + ) |
| 46 | + agg.rename(columns={'_hour': 'timestamp'}, inplace=True) |
| 47 | + agg['wind_dir'] = (np.degrees(np.arctan2(agg['_sin'], agg['_cos'])) % 360) |
| 48 | + agg['precip_rate_mmh'] = agg['precip_rate_mmh'].clip(lower=0) |
| 49 | + |
| 50 | + return _build_driving_rain_index(agg[['timestamp', 'avg_wind_kph', 'wind_dir', 'precip_rate_mmh']]) |
| 51 | + |
| 52 | + |
18 | 53 | def process(df, rain_events=None): |
19 | 54 | """Process cross-variable analyses and return chart configs, stats.""" |
20 | 55 | xdf = df.copy() |
|
0 commit comments