feat: implement shock_score (wind/cloud/pressure) as soft sigma amplifier
This commit is contained in:
@@ -161,6 +161,48 @@ def analyze_weather_trend(weather_data, temp_symbol, city_name=None):
|
||||
if hist_mae > sigma:
|
||||
sigma = hist_mae
|
||||
|
||||
# === Shock Score: 气象突变软评分 (0~1) ===
|
||||
# 用近 4 条 METAR 的风向/云量/气压变化评估环境稳定性
|
||||
# 越高 = 越不稳定 = σ 放宽
|
||||
shock_score = 0.0
|
||||
recent_obs = metar.get("recent_obs", [])
|
||||
if len(recent_obs) >= 2:
|
||||
oldest = recent_obs[-1] # 最早
|
||||
newest = recent_obs[0] # 最新
|
||||
|
||||
# ① 风向变化项 (0~0.4)
|
||||
# 角度差 × 风速放大系数(风速 > 10kt 时权重高,弱风忽略噪声)
|
||||
wdir_old = oldest.get("wdir")
|
||||
wdir_new = newest.get("wdir")
|
||||
wspd_new = newest.get("wspd") or 0
|
||||
if wdir_old is not None and wdir_new is not None:
|
||||
angle_diff = abs(wdir_new - wdir_old)
|
||||
if angle_diff > 180: angle_diff = 360 - angle_diff
|
||||
wind_weight = min(wspd_new / 15.0, 1.0) # 15kt 以上满权
|
||||
wind_shock = min(angle_diff / 90.0, 1.0) * wind_weight * 0.4
|
||||
shock_score += wind_shock
|
||||
|
||||
# ② 云量阶跃项 (0~0.35)
|
||||
# CLR=0, FEW=1, SCT=2, BKN=3, OVC=4
|
||||
cloud_old = oldest.get("cloud_rank", 0)
|
||||
cloud_new = newest.get("cloud_rank", 0)
|
||||
cloud_jump = abs(cloud_new - cloud_old)
|
||||
cloud_shock = min(cloud_jump / 3.0, 1.0) * 0.35
|
||||
shock_score += cloud_shock
|
||||
|
||||
# ③ 气压变化项 (0~0.25)
|
||||
# 2h 内气压变化超过 2hPa 视为异常
|
||||
altim_old = oldest.get("altim")
|
||||
altim_new = newest.get("altim")
|
||||
if altim_old is not None and altim_new is not None:
|
||||
press_diff = abs(altim_new - altim_old)
|
||||
press_shock = min(press_diff / 4.0, 1.0) * 0.25
|
||||
shock_score += press_shock
|
||||
|
||||
# 应用 shock_score: 放宽 σ
|
||||
if shock_score > 0.05:
|
||||
sigma *= (1 + 0.5 * shock_score)
|
||||
|
||||
# 时间修正:根据当前时间距峰值的位置调整 σ
|
||||
# 峰值前:σ 不变(不确定性最大)
|
||||
# 峰值窗口内:σ 缩小 30%(正在定型)
|
||||
|
||||
@@ -309,15 +309,31 @@ class WeatherDataCollector:
|
||||
except:
|
||||
continue
|
||||
|
||||
# 3. 提取最近 4 条报文的温度(用于趋势分析)
|
||||
# 3. 提取最近 4 条报文的多维数据(温度 + 风/云/压强,用于趋势和 shock_score)
|
||||
recent_temps_raw = [] # [(local_time_str, temp_c), ...]
|
||||
recent_obs_raw = [] # [{time, temp, wdir, wspd, clouds, altim}, ...]
|
||||
for obs in data[:4]: # data 已按时间倒序
|
||||
obs_temp = obs.get("temp")
|
||||
if obs_temp is not None:
|
||||
obs_dt_iter = _parse_rawob_time(obs)
|
||||
if obs_dt_iter:
|
||||
local_rt = obs_dt_iter + timedelta(seconds=utc_offset)
|
||||
recent_temps_raw.append((local_rt.strftime("%H:%M"), obs_temp))
|
||||
obs_dt_iter = _parse_rawob_time(obs)
|
||||
if obs_temp is not None and obs_dt_iter:
|
||||
local_rt = obs_dt_iter + timedelta(seconds=utc_offset)
|
||||
recent_temps_raw.append((local_rt.strftime("%H:%M"), obs_temp))
|
||||
# 云量码映射: CLR=0, FEW=1, SCT=2, BKN=3, OVC=4
|
||||
cloud_rank_map = {"CLR": 0, "SKC": 0, "FEW": 1, "SCT": 2, "BKN": 3, "OVC": 4}
|
||||
clouds = obs.get("clouds", [])
|
||||
max_cloud_rank = 0
|
||||
for c in clouds:
|
||||
rank = cloud_rank_map.get(c.get("cover", ""), 0)
|
||||
if rank > max_cloud_rank:
|
||||
max_cloud_rank = rank
|
||||
recent_obs_raw.append({
|
||||
"time": local_rt.strftime("%H:%M"),
|
||||
"temp": obs_temp,
|
||||
"wdir": obs.get("wdir"),
|
||||
"wspd": obs.get("wspd"),
|
||||
"cloud_rank": max_cloud_rank, # 0~4
|
||||
"altim": obs.get("altim"),
|
||||
})
|
||||
|
||||
# 转换为单位
|
||||
if use_fahrenheit:
|
||||
@@ -354,6 +370,7 @@ class WeatherDataCollector:
|
||||
"clouds": latest.get("clouds", []),
|
||||
},
|
||||
"recent_temps": recent_temps, # 最近4条: [("15:00", 5), ("14:20", 5), ...]
|
||||
"recent_obs": recent_obs_raw, # 最近4条多维数据(风/云/压强)
|
||||
"unit": unit,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user