IMS 数据源切换至 10 分钟频率 hourly_observations_full 端点
原 hourly_observations 端点仅整点更新 TT 字段。 hourly_observations_full 提供每 10 分钟 TD (Temperature Dry) 字段, 精度 0.01°C,与 TT 完全一致(r=1.000)。 WS 风速单位 m/s → 内部转换为 km/h 和 knots。 日最高/最低从当天全部 10 分钟槽位扫描计算。 Constraint: TD 字段命名与露点温度易混淆,已在 docstring 注明 Confidence: high Tested: TD vs TT 在 23 个整点时刻完全吻合
This commit is contained in:
@@ -11,15 +11,15 @@ from src.utils.metrics import record_source_call
|
|||||||
class ImsSourceMixin:
|
class ImsSourceMixin:
|
||||||
"""Fetch realtime observations from Israel Meteorological Service (IMS).
|
"""Fetch realtime observations from Israel Meteorological Service (IMS).
|
||||||
|
|
||||||
The IMS public API at /en/hourly_observations returns a JSON payload with
|
Uses the hourly_observations_full endpoint which provides 10-minute data.
|
||||||
data.hourly_observations_map keyed by timestamp and station ID, covering all
|
In this feed, TD = Temperature Dry (dry-bulb in °C), NOT dew point.
|
||||||
active IMS stations for the current day.
|
TT / TX1 / TN1 only exist in the plain hourly_observations endpoint.
|
||||||
|
|
||||||
Station 225 = Lod Airport (Ben Gurion / LLBG), elevation 40 m.
|
Station 225 = Lod Airport (Ben Gurion / LLBG), elevation 40 m.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
IMS_LOD_AIRPORT_STATION = "225"
|
IMS_LOD_AIRPORT_STATION = "225"
|
||||||
IMS_OBSERVATIONS_URL = "https://ims.gov.il/en/hourly_observations"
|
IMS_OBSERVATIONS_URL = "https://ims.gov.il/en/hourly_observations_full"
|
||||||
|
|
||||||
def fetch_from_ims(self, station_id: str = "225") -> Optional[Dict]:
|
def fetch_from_ims(self, station_id: str = "225") -> Optional[Dict]:
|
||||||
started = datetime.now()
|
started = datetime.now()
|
||||||
@@ -46,8 +46,8 @@ class ImsSourceMixin:
|
|||||||
record_source_call("ims", "station", "empty", _elapsed_ms())
|
record_source_call("ims", "station", "empty", _elapsed_ms())
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _f(key: str) -> Optional[float]:
|
def _f(key: str, source: dict = latest) -> Optional[float]:
|
||||||
raw = latest.get(key)
|
raw = source.get(key)
|
||||||
if raw is None:
|
if raw is None:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
@@ -55,15 +55,25 @@ class ImsSourceMixin:
|
|||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
temp = _f("TT")
|
# TD = Temperature Dry (dry-bulb °C) in the 10-minute feed
|
||||||
|
temp = _f("TD")
|
||||||
rh = _f("RH")
|
rh = _f("RH")
|
||||||
wind_kmh = _f("FF")
|
# WS = wind speed in m/s (10-min average); convert to km/h and knots
|
||||||
wind_dir = _f("DD")
|
wind_ms = _f("WS")
|
||||||
tx1 = _f("TX1")
|
wind_kmh = round(wind_ms * 3.6, 1) if wind_ms is not None else None
|
||||||
tn1 = _f("TN1")
|
wind_kt = round(wind_ms * 1.94384, 1) if wind_ms is not None else None
|
||||||
td = _f("TD")
|
wind_dir = _f("WD")
|
||||||
|
|
||||||
wind_kt = round(wind_kmh / 1.852, 1) if wind_kmh is not None else None
|
# Compute max / min so far today from all 10-min slots
|
||||||
|
today_prefix = latest_time[:10]
|
||||||
|
td_vals = []
|
||||||
|
for t, stations in obs_map.items():
|
||||||
|
if t.startswith(today_prefix):
|
||||||
|
td = _f("TD", source=(stations.get(station_id) or {}))
|
||||||
|
if td is not None:
|
||||||
|
td_vals.append(td)
|
||||||
|
max_so_far = round(max(td_vals), 1) if td_vals else None
|
||||||
|
min_so_far = round(min(td_vals), 1) if td_vals else None
|
||||||
|
|
||||||
result: Dict = {
|
result: Dict = {
|
||||||
"current": {
|
"current": {
|
||||||
@@ -72,9 +82,8 @@ class ImsSourceMixin:
|
|||||||
"wind_speed_kmh": wind_kmh,
|
"wind_speed_kmh": wind_kmh,
|
||||||
"wind_speed_kt": wind_kt,
|
"wind_speed_kt": wind_kt,
|
||||||
"wind_dir": wind_dir,
|
"wind_dir": wind_dir,
|
||||||
"dew_point": td,
|
"max_temp_so_far": max_so_far,
|
||||||
"max_temp_so_far": tx1,
|
"min_temp_so_far": min_so_far,
|
||||||
"min_temp_so_far": tn1,
|
|
||||||
},
|
},
|
||||||
"obs_time": latest_time,
|
"obs_time": latest_time,
|
||||||
"station_id": station_id,
|
"station_id": station_id,
|
||||||
@@ -85,12 +94,14 @@ class ImsSourceMixin:
|
|||||||
}
|
}
|
||||||
record_source_call("ims", "station", "success", _elapsed_ms())
|
record_source_call("ims", "station", "success", _elapsed_ms())
|
||||||
logger.info(
|
logger.info(
|
||||||
"IMS Lod Airport (s{}) {}: temp={}°C RH={}% wind={}km/h",
|
"IMS Lod Airport (s{}) {}: temp={}°C RH={}% wind={}km/h max={} min={}",
|
||||||
station_id,
|
station_id,
|
||||||
latest_time,
|
latest_time,
|
||||||
temp,
|
temp,
|
||||||
rh,
|
rh,
|
||||||
wind_kmh,
|
wind_kmh,
|
||||||
|
max_so_far,
|
||||||
|
min_so_far,
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user