thermograph/static/legend.html
Emi Griffith 1d27cfd37d Scope app to /thermograph, free the domain root for a portfolio (#10)
- Drop the app-level "/" redirect so the FastAPI app no longer claims the
  domain root; it stays scoped to THERMOGRAPH_BASE (/thermograph). Root now
  404s at the app, leaving it for another service behind the proxy.
- Caddyfile: serve a static portfolio at / and reverse-proxy /thermograph* to
  uvicorn, on emigriffith.dev with automatic Let's Encrypt TLS.
- Default THERMOGRAPH_BASE to /thermograph in the env example to match. (#11)

* Add dev CI/CD pipeline deploying to a LAN server (#6)

* Weekly page: no-rain visuals adopt the dry-streak look

On the Weekly page, no-rain (0") days now warm with the dry-streak ramp
(tan→red, deepening to a 14-day cap) instead of a flat blue/tan, matching
the Dry chart and calendar's dry-period language:

- Precip trend chart: no-rain day dots colored by drynessColor(dsr).
- "Daily, graded" timeline strip: Rain-row dry cells tinted by dsr via a
  new optional color override on rdCell.

Rain days keep their intensity-tier colors; unknown streaks fall back to
the prior flat color, so nothing regresses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VP23wKmjS2ozk1g5a9g1Z

* Calendar totals: compact per-category status lines

Replace the stacked share bar + labeled percentage chips above the
calendar grid with a compact deviation strip: one thin status-colored
line per category (height scaled to the tallest non-median category),
with each share printed above it in that category's own status color.

The median tier — the neutral "Normal" center of the diverging
temperature scale — draws no line, just a faint baseline tick, so it
reads as the reference the other categories deviate from. Precip and
dry-streak have no natural median, so every category there keeps a line.

Percentage text is lifted toward the theme text color (color-mix) so
even the darkest/lightest tiers stay legible in both light and dark.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Calendar totals strip: wider bars, 0.1%-precision small shares (#5)

Widen the per-category deviation bars (3px→10px) so each reads
clearly, and print sub-1% shares as e.g. "0.4%" instead of "<1%".


Claude-Session: https://claude.ai/code/session_019VP23wKmjS2ozk1g5a9g1Z

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* Weekly timeline: label dry streak instead of a dot

On the precip row of the recent/forecast timeline, dry days now show the
running dry-streak count ("3d" = 3 days since measurable rain), tinted by
the same dryness ramp as the chart, rather than a bare "·".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add dev CI/CD pipeline deploying to a LAN server

PRs into dev run a build + boot/health check, auto-merge on green, and
deploy the merged branch to a self-hosted runner on the LAN box, which
runs the app as a sudo-free systemd --user service on 0.0.0.0:8137.

- ci-cd.yml: build -> auto-merge -> deploy (self-hosted)
- deploy-dev.yml: deploy on direct pushes / manual dispatch
- deploy-dev.sh + thermograph-dev.service + provision-dev-lan.sh
- CLAUDE.md: dev is the PR base branch; commit/PR message conventions
- DEPLOY-DEV.md: pipeline docs

* Match runner service name in docs to the installed user unit

* Pin CI Python to 3.12 for prebuilt pyarrow/pandas wheels

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* Build LAN dev venv on pinned Python 3.12 via uv (#7)

This machine's default python3 is 3.14, which has no prebuilt pyarrow/pandas
wheels, so plain venv + pip fell back to a failing source build. Use uv to pin
the interpreter (fetching a managed CPython 3.12 if needed), independent of the
runner's PATH and pyenv state.

* 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.

* Scope app to /thermograph, free the domain root for a portfolio (#10)

- Drop the app-level "/" redirect so the FastAPI app no longer claims the
  domain root; it stays scoped to THERMOGRAPH_BASE (/thermograph). Root now
  404s at the app, leaving it for another service behind the proxy.
- Caddyfile: serve a static portfolio at / and reverse-proxy /thermograph* to
  uvicorn, on emigriffith.dev with automatic Let's Encrypt TLS.
- Default THERMOGRAPH_BASE to /thermograph in the env example to match.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 20:03:39 -07:00

92 lines
4.3 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Thermograph — reading the grades</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header>
<div class="brand">
<span class="logo"></span>
<div>
<h1>Thermograph · Guide</h1>
<p class="tag">What the metrics and percentile grades mean</p>
</div>
<nav class="view-nav" aria-label="Views">
<a href="./" data-view="map">Weekly</a>
<a href="calendar" data-view="calendar">Calendar</a>
<a href="day" data-view="day">Day</a>
<a href="compare" data-view="compare">Compare</a>
</nav>
</div>
</header>
<main class="guide">
<section class="guide-block">
<h2>How the grades work</h2>
<p>Every grade is <b>relative to this place's own climate history</b>, not an absolute
temperature. For each day Thermograph builds a &plusmn;7-day seasonal distribution from
decades of local records, then reports the <b>percentile</b> where the day falls in it.</p>
<p>So a 60&deg; day can be &ldquo;Above Normal&rdquo; in one place or season and
&ldquo;Below Normal&rdquo; in another. That's why the categories read
&ldquo;Above/Below Normal&rdquo;, &ldquo;High/Low&rdquo;, and &ldquo;Near Record&rdquo;
never &ldquo;hot&rdquo; or &ldquo;cold&rdquo;.</p>
</section>
<section class="guide-block">
<h2>Grade scale — percentile of the local &plusmn;7-day history</h2>
<div class="guide-scale" id="temp-scale"></div>
</section>
<section class="guide-block">
<h2>Rain intensity — percentile among rain days</h2>
<p class="muted">Precipitation is graded only across days that actually saw rain, so
&ldquo;Heavy&rdquo; means heavy <i>for a rainy day here</i>. Dry days are colored by
their dry streak instead (below).</p>
<div class="guide-scale" id="rain-scale"></div>
</section>
<section class="guide-block">
<h2>The metrics</h2>
<dl class="guide-metrics">
<dt>High / Low</dt><dd>The day's highest and lowest air temperature (&deg;F).</dd>
<dt>Feels</dt><dd>Apparent temperature — what the air actually felt like once humidity
and wind are factored in, taking whichever apparent extreme (heat-index high or
wind-chill low) sits further from a temperate baseline, graded against its own history.</dd>
<dt>Humid</dt><dd>Absolute humidity: grams of water vapor per cubic meter of air (g/m&sup3;).</dd>
<dt>Wind</dt><dd>Average sustained wind speed (mph).</dd>
<dt>Gust</dt><dd>Peak wind gust for the day (mph).</dd>
<dt>Precip</dt><dd>Total precipitation (inches), graded by intensity among rain days.</dd>
<dt>Dry streak</dt><dd>Consecutive days since the last measurable rain — the color deepens
the longer it's been dry, and resets to blue on a rain day.</dd>
</dl>
</section>
<p class="guide-back"><a href="./">&larr; Back to Thermograph</a></p>
</main>
<script src="nav.js"></script>
<script>
// The scale rows mirror the tiers used across the app (see calendar.js / app.js).
const TEMP = [
["rec-cold", "Near Record", "<1"], ["very-cold", "Very Low", "110"],
["cold", "Low", "1025"], ["cool", "Below Normal", "2540"],
["normal", "Normal", "4060"], ["warm", "Above Normal", "6075"],
["hot", "High", "7590"], ["very-hot", "Very High", "9099"],
["rec-hot", "Near Record", ">99"],
];
const RAIN = [
["wet-1", "Trace", "<1"], ["wet-2", "Very Light", "110"], ["wet-3", "Light", "1025"],
["wet-4", "LightMod", "2540"], ["wet-5", "Moderate", "4060"], ["wet-6", "ModHeavy", "6075"],
["wet-7", "Heavy", "7590"], ["wet-8", "Very Heavy", "9099"], ["wet-9", "Extreme", ">99"],
];
const row = ([cls, label, range]) =>
`<div class="guide-seg"><span class="guide-sw" style="background:var(--${cls})"></span>` +
`<span class="guide-lbl">${label}</span><span class="guide-rng">${range}</span></div>`;
document.getElementById("temp-scale").innerHTML = TEMP.map(row).join("");
document.getElementById("rain-scale").innerHTML = RAIN.map(row).join("");
</script>
</body>
</html>