From bf5e92e65621775f1829ab5b093d03f549d80213 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sun, 10 May 2026 18:07:33 +0800 Subject: [PATCH] =?UTF-8?q?AMOS=20=E6=B8=A9=E5=BA=A6=E5=A4=84=E7=90=86?= =?UTF-8?q?=EF=BC=9AMETAR=20=E4=BC=98=E5=85=88=EF=BC=8C=E8=B7=91=E9=81=93?= =?UTF-8?q?=E4=B8=AD=E4=BD=8D=E6=95=B0=E5=85=9C=E5=BA=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 温度优先级: 1. METAR 温度(官方机场传感器,权威值) 2. 跑道传感器中位数(fallback;不同跑道传感器因位置/海拔可能差 0.5-1°C) - 新增 temp_source 字段标注来源("metar" / "runway_median") - 新增 runway_temps 数组保留每条跑道的原始 (温度, 露点) - 回退逻辑用中位数而非第一个值(跑道数据可能是乱序的) - 温度合理性检查:只取 -50°C ~ 60°C 范围内的值 Tested: python -m ruff check ., npx tsc --noEmit --- src/data_collection/amos_station_sources.py | 52 +++++++++++++++++---- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/src/data_collection/amos_station_sources.py b/src/data_collection/amos_station_sources.py index 3ac57b8f..6c760220 100644 --- a/src/data_collection/amos_station_sources.py +++ b/src/data_collection/amos_station_sources.py @@ -182,8 +182,14 @@ class AmosStationSourceMixin: ) -> Optional[Dict[str, Any]]: """Fetch AMOS runway-level observations for Seoul or Busan. - Returns a dict with keys: temp_c, dew_c, pressure_hpa, wind_kt, - wind_dir, visibility_m, raw_metar, raw_taf, runway_data, source. + Temperature priority: + 1. METAR temperature (official aerodrome sensor, authoritative) + 2. Median of runway sensor temperatures (fallback; individual runway + sensors may differ by 0.5-1.0°C due to location/altitude on the airfield) + + Returns a dict with: temp, temp_c, dew, dew_c, pressure_hpa, wind_kt, + temp_source ("metar" or "runway_median"), runway_temps (list of per-runway + (temp, dew) tuples), raw_metar, raw_taf, runway_data, source. """ started = time.perf_counter() city_key = str(city or "").strip().lower() @@ -207,19 +213,43 @@ class AmosStationSourceMixin: taf_line = taf_match.group(0) if taf_match else "" taf_line = re.sub(r"\s+", " ", taf_line).strip() - temp_c, dew_c = _amos_extract_metar_temperature(metar_line) + # METAR is the authoritative aerodrome observation + metar_temp_c, metar_dew_c = _amos_extract_metar_temperature(metar_line) pressure_hpa = _amos_extract_metar_qnh(metar_line) wind_kt = _amos_extract_metar_wind(metar_line) + # Runway-level temperatures from individual sensor pairs runway_data = _amos_parse_runway_table(html) + runway_temps = runway_data.get("temperatures") or [] + runway_pressures = runway_data.get("pressures_hpa") or [] - # Get temperature from runway data if METAR parsing failed - if temp_c is None and runway_data.get("temperatures"): - temp_c = runway_data["temperatures"][0][0] - if dew_c is None and runway_data.get("temperatures"): - dew_c = runway_data["temperatures"][0][1] - if pressure_hpa is None and runway_data.get("pressures_hpa"): - pressure_hpa = runway_data["pressures_hpa"][0] + # Primary: METAR (official aerodrome sensor) + # Fallback: median of runway sensors (if METAR unavailable) + # Runway sensors may differ by 0.5-1.0°C from METAR due to + # different locations/altitudes on the airfield + temp_c: Optional[float] = metar_temp_c + dew_c: Optional[float] = metar_dew_c + temp_source = "metar" + + if temp_c is None and runway_temps: + runway_temps_only = [t[0] for t in runway_temps if t[0] is not None and -50 < float(t[0]) < 60] + if runway_temps_only: + sorted_t = sorted(runway_temps_only) + mid = len(sorted_t) // 2 + temp_c = float(sorted_t[mid]) if len(sorted_t) % 2 else float((sorted_t[mid-1] + sorted_t[mid]) / 2) + temp_source = "runway_median" + + if dew_c is None and runway_temps: + runway_dews = [t[1] for t in runway_temps if t[1] is not None and -50 < float(t[1]) < 60] + if runway_dews: + sorted_d = sorted(runway_dews) + mid = len(sorted_d) // 2 + dew_c = float(sorted_d[mid]) if len(sorted_d) % 2 else float((sorted_d[mid-1] + sorted_d[mid]) / 2) + + if pressure_hpa is None and runway_pressures: + sorted_p = sorted(runway_pressures) + mid = len(sorted_p) // 2 + pressure_hpa = float(sorted_p[mid]) if len(sorted_p) % 2 else float((sorted_p[mid-1] + sorted_p[mid]) / 2) temp = round(temp_c * 9 / 5 + 32, 1) if use_fahrenheit and temp_c is not None else temp_c dew = round(dew_c * 9 / 5 + 32, 1) if use_fahrenheit and dew_c is not None else dew_c @@ -231,6 +261,8 @@ class AmosStationSourceMixin: "dew_c": dew_c, "pressure_hpa": pressure_hpa, "wind_kt": wind_kt, + "temp_source": temp_source, + "runway_temps": runway_temps, "source": "amos", "source_label": f"AMOS {airport_meta['label_en']} ({icao})", "source_code": "amos",