Skip to content

Stop asking for a postal code on every check-in - #899

Open
CoryMCodes wants to merge 5 commits into
rubyforgood:masterfrom
CoryMCodes:573-weather-location-issue
Open

CoryMCodes wants to merge 5 commits into
rubyforgood:masterfrom
CoryMCodes:573-weather-location-issue

Conversation

@CoryMCodes

@CoryMCodes CoryMCodes commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #573 so users no longer need to re-enter their ZIP code for each check-in.

Changes

  • Reuses the position_id from the latest check-in with a saved location.
  • Supports intervening check-ins without location data.
  • Keeps location persistence scoped to check-ins rather than user profiles.
  • Gracefully handles weather vendor/network failures and malformed JSON responses.
  • Adds safe error logging and negative caching for failed weather lookups.
  • Adds regression coverage for location persistence and weather retrieval failures.

Testing

  • Backend test suite: 364 examples, 0 failures
  • StandardRB passes on changed files

  Weather is meant to be entered once: Checkin::Creator copies the previous
  check-in's position forward and re-fetches the forecast for the new day, and
  the weather step has a branch that renders the stored location as a tappable
  element to change it. In practice users were re-typing their postal code
  day after day, and re-typing it did not make the prompt go away.

  Two independent defects, one on each side.

  WeatherRetriever asked for the wrong day and then cached the answer under a
  key it would never look up again. The Dark Sky call this replaced in rubyforgood#689
  passed `time:` for the requested date, resolved in the position's own zone;
  tomorrowio_rb's `forecast(location, timesteps, units)` has no date parameter
  at all, so the argument became decorative: the request always returned the
  timeline starting at today, and the record was stored under whatever date the
  response led with. `Weather.find_by(date:, position_id:)` therefore missed on
  every subsequent call for that date, the re-fetch tripped the date/position
  uniqueness validation, and because the write went through `Weather.create`
  the failure was silent and handed back an unsaved record. Checkin::Creator
  stores `...get(date, postal_code)&.id`, so nil id became "this check-in has
  no weather". Rows are keyed by (date, position) and shared by every user with
  that postal code, so one fetch of today was enough to poison any request for
  a different date — which is every back-filled check-in, and every check-in by
  a user whose local day is not the UTC day.

  The retriever now selects the day it was asked for out of the timeline,
  comparing dates in the position's time zone (NearestTimeZone, as before the
  migration) so eastern-hemisphere positions stop landing a day early, and
  stores it under the requested date so the cache is reachable. A losing race
  returns the record that won rather than an unsaved one. A date outside the
  window logs and returns nothing: the forecast endpoint has no history, and
  filing today's forecast under a back-filled day is worse for a tracker that
  charts weather against symptoms than having no row. Back-filled days
  consequently show no weather now, and say so.

  The frontend asked for a location whenever there was no weather, which is not
  the same question. `willRender` forced `inputVisible` true on every render
  pass while `hasWeather` was false, so a check-in with a stored location was
  still prompted, and the `set(..., false)` after a successful save was undone
  by the next pass — the input could not be dismissed. Nothing else in the app
  displays `locationName`, so it also looked like the save had not happened,
  and a submission that geocoded fine but had no forecast reported "We couldn't
  find that location".

  `inputVisible` is now derived from `hasLocation` and whether the user opened
  the input, so it is no longer re-decided during render. A weather query that
  resolves empty or rejects no longer stops the location from being saved. The
  two failure modes are told apart by comparing the postal code the API echoes
  back — it only returns one once it has geocoded it into a position — and a
  day with a location but no forecast shows the location plus a note.

  Also in scope by necessity: get_icon_legacy read string keys out of a body
  parsed with `symbolize_names: true`, so every icon was "default" and every
  summary read "General conditions are default, with an average temperature of
  X". Specs asserting the intended forecast fields could not have been written
  around that. And the component now declares `store: service()` instead of
  leaning on the app-wide component/store injection, which does not exist in
  component tests.

  The creator spec stubbed WeatherRetriever.get and asserted it was called,
  which is why none of this showed up in CI; it now goes through the retriever
  against the cassette, and separately pins that the location carries forward
  even when the forecast does not. Backend 334 examples, frontend 460 tests,
  standardrb, erblint and eslint all clean. Reverting each fix in isolation
  fails 7 of the 18 retriever examples and 6 of the 9 component tests.

  Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@CoryMCodes
CoryMCodes marked this pull request as draft August 31, 2026 17:56
@suttondemlong

suttondemlong commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Sean mentioned over the weekend at Ruby for Good that we can just push directly instead of from forked repos since we are contributors. It should help with the feedback loop a bit

@CoryMCodes
CoryMCodes force-pushed the 573-weather-location-issue branch from e95b897 to f93b1bd Compare September 9, 2026 16:03
@CoryMCodes
CoryMCodes marked this pull request as ready for review September 9, 2026 16:03
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.

[bug] user reports having to enter weather zip code daily

2 participants