Skip to content

fix: treat KMA Missing sentinels (|v|>=900) as None; add public is_missing() - #31

Merged
digitie merged 6 commits into
mainfrom
fix/kma-missing-sentinels
Oct 5, 2026
Merged

digitie merged 6 commits into
mainfrom
fix/kma-missing-sentinels

Conversation

@digitie

@digitie digitie commented Oct 5, 2026

Copy link
Copy Markdown
Owner

Summary

The KMA 단기예보 service guide defines values of +900 or more and -900 or less as Missing. In production, getUltraSrtNcst returned these sentinels for a station with no observation: REH/VEC -998, RN1/WSD/UUU/VVV -998.9 and T1H -999. float_or_none passed them through, so typed fields carried them (WeatherSnapshot.humidity == -998, temperature == -999.0).

  • New public kma.is_missing(value: str | float | Decimal | None) -> bool and kma.KMA_MISSING_ABS_THRESHOLD (Decimal("900")), documented in the README under "Missing 값".
    • Returns True for None, "", whitespace, and finite abs(v) >= 900. Strings are parsed as Decimal, so "1e400" counts as a sentinel.
    • Returns False for non-numeric labels (강수없음, 1.0mm 미만) and for NaN/Infinity. Those are not sentinels, and the caller decides whether they are invalid.
  • WeatherSnapshot.temperature/humidity/wind_speed/wind_direction/precipitation now go through the KMA-only kma_value_or_none/kma_int_or_none, or _observed_amount for RN1. They are None when missing.
  • normalize_value returns None for missing or blank values. As a result, ForecastItem.value, BeachForecastItem.value and ForecastTimepoint.values become ... | None. This changes the type contract. The raw string stays in raw.

Not changed, on purpose

  • The shared float_or_none/int_or_none are unchanged.
  • ASOS daily/hourly values are unchanged. Pressure pa/ps is normally above 900 hPa, and ASOS is a different service.
  • Beach wave height, water temperature and tide level are unchanged. tilevel is in cm and can exceed 900, and these come from a different observation network.
  • Grid nx/ny are unchanged.

Tests

  • tests/test_missing_values.py uses the production payload shape. It went RED on 4ac9a32: now() returned -999.0 -998 -998.9 -998 -998.9, and the forecast values were the sentinels. It is GREEN here.
  • Full suite: 231 passed, 12 skipped. ruff check and mypy src/kma are clean. All of this ran on n150.

Version stays 0.1.0 (unreleased), per the repo convention. The CHANGELOG entry is under 0.1.0 - 미배포.

Consumer: kor-travel-weather chore/kma-lib-missing-helper pins this branch head and needs a repin to the merge SHA after this merges.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RuoPRjkdpZNYjTZ1RYQoHj

digitie and others added 3 commits October 5, 2026 11:57
…ecast values

getUltraSrtNcst answered a station with no observation with REH/VEC -998,
RN1/WSD/UUU/VVV -998.9 and T1H -999; the 단기예보 guide defines +900/-900 and
beyond as Missing, but float_or_none passed them straight into typed fields
(WeatherSnapshot.humidity == -998).

- add public kma.is_missing() / KMA_MISSING_ABS_THRESHOLD (None, blank and
  finite |v|>=900 are missing; NaN/Inf and non-numeric labels are not)
- WeatherSnapshot temperature/humidity/wind_speed/wind_direction/precipitation
  use the KMA-only kma_value_or_none/kma_int_or_none; shared parsers unchanged
- normalize_value returns None for missing, so ForecastItem.value /
  BeachForecastItem.value / ForecastTimepoint.values are now Optional
- ASOS pressure, beach tide level/buoys and grid coordinates keep their values

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RuoPRjkdpZNYjTZ1RYQoHj
digitie and others added 3 commits October 5, 2026 12:27
…aN/Inf; document None in ForecastTimepoint.values

- abs(Decimal) applies the context: Overflow on 1e1000000 and rounding
  899.999...(33 digits) up to 900. copy_abs() is exact.
- kma_int_or_none("NaN"/"Infinity") raised ValueError from int(); a
  non-finite REH/VEC now gives None instead of failing now().
- ForecastTimepoint docstring: a present key with None means present but
  missing/blank; an absent key means no row.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RuoPRjkdpZNYjTZ1RYQoHj
@digitie
digitie merged commit 12e7f1f into main Oct 5, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant