Add comfort-temperature compare view; simplify Feels calendar filter (#9)

Compare page (frontend/compare.{html,js} + /thermograph/compare route):
line up several places over a date range and rank which best matches a
comfort temperature. Per location it pulls the same daily record the
Calendar uses and, per day, takes a chosen temperature (daytime high /
daily mean / overnight low / feels-like) against the comfort target. A
day "hits comfort" when it lands within an adjustable band; otherwise it
counts as colder or warmer and the average miss is tracked. Results are
ranked by comfort-day share (tie-broken by the smaller typical miss),
with a diverging below/comfort/above bar and per-bucket stats. Comfort,
band and judged temperature re-rank instantly client-side; only the
location set or date range trigger a data load (shared calendar cache).

Feels calendar filter: drop the comfort-temperature slider and the
client-side felt-high/felt-low re-pick. The Feels metric now colors by
the server's combined feels-like value like the other metrics, so its
tab matches the rest of the metric selector.
This commit is contained in:
Emi Griffith 2026-07-10 19:58:56 -07:00 committed by GitHub
parent 8d90dcb456
commit 1bcd56d473

3
app.py
View file

@ -81,7 +81,7 @@ async def no_store_static(request, call_next):
skipping the cache entirely is fine and avoids "my change isn't showing" bugs."""
response = await call_next(request)
path = request.url.path
pages = (BASE, f"{BASE}/", f"{BASE}/calendar", f"{BASE}/day", f"{BASE}/legend")
pages = (BASE, f"{BASE}/", f"{BASE}/calendar", f"{BASE}/day", f"{BASE}/compare", f"{BASE}/legend")
if path.endswith((".js", ".css", ".html")) or path in pages:
response.headers["Cache-Control"] = "no-store, must-revalidate"
return response
@ -437,6 +437,7 @@ app.add_api_route(BASE, lambda: RedirectResponse(url=f"{BASE}/"), methods=["GET"
app.add_api_route(f"{BASE}/", lambda: _page("index.html"), methods=["GET"], include_in_schema=False)
app.add_api_route(f"{BASE}/calendar", lambda: _page("calendar.html"), methods=["GET"], include_in_schema=False)
app.add_api_route(f"{BASE}/day", lambda: _page("day.html"), methods=["GET"], include_in_schema=False)
app.add_api_route(f"{BASE}/compare", lambda: _page("compare.html"), methods=["GET"], include_in_schema=False)
app.add_api_route(f"{BASE}/legend", lambda: _page("legend.html"), methods=["GET"], include_in_schema=False)
# Everything else under BASE (app.js, style.css, nav.js, …) is a static asset.