Skip to content

Weather: JMA area forecast on tapping a place - #9

Merged
TokyoDanInJapan merged 2 commits into
mainfrom
feat/jma-weather-report
Jul 25, 2026
Merged

Weather: JMA area forecast on tapping a place#9
TokyoDanInJapan merged 2 commits into
mainfrom
feat/jma-weather-report

Conversation

@TokyoDanInJapan

@TokyoDanInJapan TokyoDanInJapan commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Tap the rider dot or a dropped pin → a menu (Weather here / Clear pin) →
JMA's area forecast for that spot, laid out the way the JMA page lays it out.
The radar says what the rain is doing in the next hour; this says whether to
set off at all.

Keyless, like every other source in the app.

北東部                          長柄町 · 4 km
─────────────────────────────────────────
Forecast
              Today      Tomorrow    7/27
Weather        ☁          ☁          ☁
Temp          ↑35°      ↑33° ↓26°

Rain %      00-06  06-12  12-18  18-24
Today          –      –      20     20
Tomorrow      20     20      40     50
─────────────────────────────────────────
Day by day
Today     くもり時々晴れ夜のはじめ頃一時雨…
          Wind: 北の風 · Waves: 1.5メートル
─────────────────────────────────────────
Week ahead
        7/28  7/29  7/30  7/31  8/1
Weather   ☁     ☁     ☁     ☀    ☀
Rain %   40    40    30    20    20
Max     31°   32°   33°   33°   33°
Min     25°   24°   25°   26°   26°
Conf.     C     C     B     B     A
─────────────────────────────────────────
Outlook / Source …

Resolving a position to a forecast

JMA keys forecasts by area code, never by coordinates, and the codes aren't
derivable from the JIS prefecture code — Hokkaido splits seven ways, Kagoshima's
mainland office is 460100, Okinawa splits four. tool/prep_forecast_areas.dart
walks JMA's own area.json (class20 → class15 → class10 → office) against the
municipality table already in the repo and bakes a 1716-row lookup. Runtime
resolution is nearest-municipality, which gets the subdivision (千葉県北東部,
not just "Chiba") — a rider in south Chiba shouldn't read Chiba city's forecast.

Verified against live JMA across four regions: Chiba → 北東部 via 長柄町 (4 km),
渋峠 → 長野北部, Sapporo → 石狩地方/札幌管区気象台, Naha → 本島中南部/沖縄気象台.

Things the data forces

  • Series are identified by the fields their areas carry, not by position.
    JMA has reordered timeSeries before; inland offices omit waves entirely.
  • Temperatures pair by clock time, not index. A midday issue echoes the
    day's high into the already-past 00:00 low slot, so reading positionally
    reports "high 35, low 35". A low equal to the high is dropped, not shown.
  • Rain columns are fixed. JMA drops a block once passed, so derived columns
    would shuffle between morning and afternoon. A dropped block reads , never
    0 — "no longer forecast" and "0% chance" are different claims.
  • Only days with numbers are held back from the weekly table. The detailed
    block's last day is routinely wording alone, and the weekly block does carry
    its temps and rain chance; treating "mentioned above" as "covered above" threw
    them away. Caught against live data, not in review.

Icons

From JMA's 3-digit 天気コード, bucketed by leading digit (1 晴れ, 2 くもり,
3 雨, 4 雪). JMA publishes ~130 codes but no machine-readable table — there is
no telop.json, only the page's JavaScript — so the rule was checked against
live data from ten offices spread across the country: 87 of 87 periods
agreed with JMA's own wording. Four buckets on purpose, with JMA's wording
carrying what the icon drops. An absent code draws a dash rather than a guess.

Translation

English mode translates on device like closures do, except place names use
JMA's own English
("North-eastern Region", "Sapporo City"), baked into the
generated table. Machine translation is at its worst exactly where JMA is
already authoritative. A test asserts the translator is never asked for 北東部
or 長柄町, so a regression that quietly routed place names through ML Kit fails
rather than just getting worse.

The Japanese shows the moment it arrives and the English swaps in behind it —
the first translation ever can be a ~30 MB model download, and someone deciding
whether to set off shouldn't watch a spinner while the forecast is already in
hand. A failed translation degrades to Japanese, the same contract the closure
list has had. ClosureTranslator._translate becomes public translateText; the
translator instance is shared because it owns the model and the string cache.

Also

  • Forecast fetches get their own AssetKind, so they group in the debug sheet.
  • The rider dot's tap target grows to 44 px around an unchanged 22 px dot — it
    marks a position and a bigger dot would claim precision the fix doesn't have,
    but a 22 px target on a moving bike is not one.

Verification

flutter analyze clean, 145/145 tests (43 new), release APK builds.

Three bugs the tests caught before they shipped:

  • The fetch future could fail before FutureBuilder subscribed, which Flutter
    reports as an unhandled async error — a red screen over the map in debug.
  • setState as the first statement of an async method called from initState
    runs synchronously, i.e. during the build mounting that widget. Harmless
    today only by accident.
  • revealInSheet needed ensureVisible: being in the tree isn't being on
    screen, so a tap aimed below the fold landed on nothing. A latent flake.

Tap the rider dot or a dropped pin for a menu (weather, or clear the pin)
leading to JMA's area forecast for that spot: today onward in JMA's own
wording, highs and lows, 6-hourly rain chances, wind and waves, the prose
outlook, and any warning headline. The radar says what the rain is doing
in the next hour; this says whether to set off at all.

JMA keys forecasts by area code, never coordinates, and the codes aren't
derivable from the JIS prefecture code - Hokkaido splits seven ways,
Kagoshima's mainland office is 460100, Okinawa splits four. So
tool/prep_forecast_areas.dart walks JMA's own area.json (class20 ->
class15 -> class10 -> office) against the municipality table already in
the repo and bakes a lookup; resolution at runtime is
nearest-municipality, which gets the subdivision (千葉県北東部, not just
"Chiba") - a rider in south Chiba shouldn't read Chiba city's forecast.

Two things the response shape forces:

- Series are identified by the fields their areas carry, not by
  position. JMA has reordered timeSeries before, and inland offices omit
  waves entirely.
- Temperatures are paired by clock time, not index. A midday issue
  echoes the day's high into the already-past 00:00 low slot, so reading
  them positionally reports "high 35, low 35"; the 00:00 slot is the
  low, 09:00 the high, and a low equal to the high is dropped rather
  than shown as fact.

In English mode the report translates on device like closures do, with
one deliberate exception: place names use JMA's own English ("North-
eastern Region", "Sapporo City"), baked into the generated table.
Machine translation is at its worst exactly where JMA is already
authoritative. The Japanese shows the moment it arrives and the English
swaps in behind it, so a first-run ~30 MB model download never blocks
the forecast, and a failed translation degrades to Japanese rather than
to an error - the same contract the closure list has had.

ClosureTranslator._translate becomes public translateText; the
translator instance is shared through the controllers because it owns
the downloaded model and the string cache, and a second one would mean a
second download.

Forecast fetches get their own AssetKind so they group in the debug
sheet instead of falling into "Other".
The sheet listed each day vertically; the linked JMA page puts the same
data in two tables, and the tabular form is easier to read a decision
out of. So:

- Near-term table: a column per day carrying the icon and temperatures,
  with rain chance below it as a grid of fixed 00-06/06-12/12-18/18-24
  blocks. Fixed rather than derived - JMA drops a block once it has
  passed, so derived columns would shuffle between morning and
  afternoon. A dropped block reads as "–", never "0": "no longer
  forecast" and "0% chance" are different claims.
- Week ahead: the 週間予報 block, which was previously parsed and
  discarded. Different shape to the short-term one - one area for the
  whole office, temperatures at a single observation point, no wording -
  and it carries JMA's own A/B/C confidence grade, which is the
  difference between planning on a forecast and hoping.

JMA's wording is deliberately *not* a table cell. 「くもり時々晴れ夜のは
じめ頃一時雨所により夕方から雷を伴い激しく降る」in a column two fingers
wide is a vertical smear; as a sentence under the table it stays the most
informative thing on the sheet. Both tables scroll inside their own box,
so the sheet never scrolls sideways.

Icons come from JMA's 3-digit 天気コード, bucketed by leading digit
(1 晴れ, 2 くもり, 3 雨, 4 雪). JMA publishes ~130 codes but no
machine-readable table - there is no telop.json, only the page's
JavaScript - so the rule was checked against live data from ten offices
spread across the country: 87 of 87 periods agreed with JMA's own
wording. Four buckets on purpose, with the wording carrying the rest; an
absent code draws a dash rather than a guess.

Only days the detailed block put *numbers* against are held back from
the weekly table. Its last day is routinely wording alone - no
temperatures, no rain chance - and the weekly block does have those, so
treating "is mentioned above" as "is covered above" threw them away.
Caught against live data, not in review.

Five blocks that look alike at a glance - two tables of numbers, two runs
of Japanese prose, and the attribution - now each sit under a quiet
heading with a rule above it, built by one _Section wrapper so they stay
consistent by construction. Sections JMA didn't send drop out entirely
rather than leaving a rule and a title over nothing.

Test infrastructure: revealInSheet now calls ensureVisible after
scrolling. Being in the tree is not being on screen - a ListView builds
past the viewport - so a tap aimed below the fold landed on nothing. That
was a latent flake in the existing tests, not something the tables
introduced.
@TokyoDanInJapan
TokyoDanInJapan merged commit 1e4daa0 into main Jul 25, 2026
1 check passed
@TokyoDanInJapan
TokyoDanInJapan deleted the feat/jma-weather-report branch July 25, 2026 02:46
TokyoDanInJapan added a commit that referenced this pull request Jul 25, 2026
`gh pr edit <n> --body ...` leaves the PR untouched. It asks for
projectCards in the same GraphQL query it uses to edit, so GitHub's
Projects-classic deprecation error aborts the mutation - and because the
error talks about Projects rather than the edit, it reads as a warning
about something you didn't ask for. Cost a silent no-op on #9 that was
only caught by reading the PR back afterwards.

CLAUDE.md didn't exist yet; this starts it with the workaround
(gh api -X PATCH, which doesn't touch Projects) and, more importantly,
the habit of verifying the write landed.
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