Add a climate-score page from recent-vs-baseline percentile divergence (#196)
Score how far a location's last 6 years have drifted from its full 45-year
record. For each metric and percentile category (p10/p25/p50/p75/p90), the
recent-years value is placed on the baseline distribution and the gap from the
expected percentile is the divergence — unit-free, so metrics compare directly.
Scored per meteorological season plus annual, weighted into per-metric and
overall scores (temps, humidity and feels-like weighted heaviest).
- backend/scoring.py: divergence math, seasonal slicing, precip zero-inflation
split (wet-day frequency + amount), tier mapping onto the existing temp scale.
- climate.py: derive a wet-bulb column (Stull 2011) at the read boundary, before
the humidity column is converted to absolute — via a shared _derive_metrics
wrapper at all four read sites.
- api/v2/score endpoint + build_score payload, cached on the history token with
a scoring-version key.
- frontend score page: overall hero, per-metric cards, by-season chips, and a
button-revealed summary (sentences + metrics×season table + per-percentile
detail). Score nav link across all headers.
- Tests for the scoring math, wet-bulb formula, payload shape and route.
2026-07-19 23:02:33 +00:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "utf-8" / >
< meta name = "viewport" content = "width=device-width, initial-scale=1" / >
Remove em-dashes from site copy and tighten the prose (#206)
Replace em-dashes in user-facing copy across the server-rendered pages,
static frontend views, and the strings the app injects at runtime, using
colons, commas, parentheses or full stops as the context wants. Data
placeholder glyphs (a lone "—" for a missing reading) are left alone,
since a hyphen there reads as a minus sign in temperature columns.
Also tighten the high-visibility surfaces (home hero and meta, about,
privacy, city and records ledes, glossary blurbs) toward a plainer,
more direct voice while keeping every factual claim intact.
Claude-Session: https://claude.ai/code/session_01XXxmNFy9cZ6Gh8Y9thZn62
2026-07-20 01:48:33 +00:00
< title > Thermograph: climate score< / title >
Add a climate-score page from recent-vs-baseline percentile divergence (#196)
Score how far a location's last 6 years have drifted from its full 45-year
record. For each metric and percentile category (p10/p25/p50/p75/p90), the
recent-years value is placed on the baseline distribution and the gap from the
expected percentile is the divergence — unit-free, so metrics compare directly.
Scored per meteorological season plus annual, weighted into per-metric and
overall scores (temps, humidity and feels-like weighted heaviest).
- backend/scoring.py: divergence math, seasonal slicing, precip zero-inflation
split (wet-day frequency + amount), tier mapping onto the existing temp scale.
- climate.py: derive a wet-bulb column (Stull 2011) at the read boundary, before
the humidity column is converted to absolute — via a shared _derive_metrics
wrapper at all four read sites.
- api/v2/score endpoint + build_score payload, cached on the history token with
a scoring-version key.
- frontend score page: overall hero, per-metric cards, by-season chips, and a
button-revealed summary (sentences + metrics×season table + per-percentile
detail). Score nav link across all headers.
- Tests for the scoring math, wet-bulb formula, payload shape and route.
2026-07-19 23:02:33 +00:00
< meta name = "description" content = "How far a place's last six years have drifted from its 45-year climate: a per-metric and overall climate-shift score across temperature, feels-like, humidity, wet bulb, wind and rain." / >
< meta name = "theme-color" content = "#f0803c" / >
<!-- Link previews (Discord, Slack, iMessage…). The server fills the og:url /
og:image origins in from this request's scheme://host + base path —
crawlers need absolute URLs and don't run JS. -->
< meta property = "og:type" content = "website" / >
< meta property = "og:site_name" content = "Thermograph" / >
Remove em-dashes from site copy and tighten the prose (#206)
Replace em-dashes in user-facing copy across the server-rendered pages,
static frontend views, and the strings the app injects at runtime, using
colons, commas, parentheses or full stops as the context wants. Data
placeholder glyphs (a lone "—" for a missing reading) are left alone,
since a hyphen there reads as a minus sign in temperature columns.
Also tighten the high-visibility surfaces (home hero and meta, about,
privacy, city and records ledes, glossary blurbs) toward a plainer,
more direct voice while keeping every factual claim intact.
Claude-Session: https://claude.ai/code/session_01XXxmNFy9cZ6Gh8Y9thZn62
2026-07-20 01:48:33 +00:00
< meta property = "og:title" content = "Thermograph: climate score" / >
Add a climate-score page from recent-vs-baseline percentile divergence (#196)
Score how far a location's last 6 years have drifted from its full 45-year
record. For each metric and percentile category (p10/p25/p50/p75/p90), the
recent-years value is placed on the baseline distribution and the gap from the
expected percentile is the divergence — unit-free, so metrics compare directly.
Scored per meteorological season plus annual, weighted into per-metric and
overall scores (temps, humidity and feels-like weighted heaviest).
- backend/scoring.py: divergence math, seasonal slicing, precip zero-inflation
split (wet-day frequency + amount), tier mapping onto the existing temp scale.
- climate.py: derive a wet-bulb column (Stull 2011) at the read boundary, before
the humidity column is converted to absolute — via a shared _derive_metrics
wrapper at all four read sites.
- api/v2/score endpoint + build_score payload, cached on the history token with
a scoring-version key.
- frontend score page: overall hero, per-metric cards, by-season chips, and a
button-revealed summary (sentences + metrics×season table + per-percentile
detail). Score nav link across all headers.
- Tests for the scoring math, wet-bulb formula, payload shape and route.
2026-07-19 23:02:33 +00:00
< meta property = "og:description" content = "How far a place's last six years have drifted from its 45-year climate: a per-metric and overall climate-shift score across temperature, feels-like, humidity, wet bulb, wind and rain." / >
< meta property = "og:url" content = "__ORIGIN__/score" / >
< meta property = "og:image" content = "__ORIGIN__/logo.png?v=2" / >
< meta property = "og:image:width" content = "512" / >
< meta property = "og:image:height" content = "512" / >
< meta property = "og:image:alt" content = "Thermograph logo" / >
< meta name = "twitter:card" content = "summary" / >
< link rel = "canonical" href = "__ORIGIN__/score" / >
< link rel = "icon" href = "favicon.svg" type = "image/svg+xml" / >
< link rel = "icon" href = "favicon-48.png" type = "image/png" sizes = "48x48" / >
< link rel = "icon" href = "favicon-32.png" type = "image/png" sizes = "32x32" / >
< link rel = "icon" href = "favicon-16.png" type = "image/png" sizes = "16x16" / >
< link rel = "apple-touch-icon" href = "apple-touch-icon.png" / >
< link rel = "manifest" href = "manifest.webmanifest" / >
< link rel = "stylesheet" href = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" / >
< link rel = "stylesheet" href = "style.css" / >
< / head >
< body >
< header >
< div class = "brand" >
< div >
< h1 > < a href = "./" > < span class = "logo" > < svg viewBox = "0 0 512 512" width = "28" height = "28" aria-hidden = "true" > < rect x = "32" y = "32" width = "448" height = "448" rx = "96" fill = "#10141B" stroke = "#fff" stroke-opacity = ".08" stroke-width = "4" / > < rect x = "64" y = "170" width = "384" height = "48" rx = "8" fill = "#27131C" / > < rect x = "64" y = "222" width = "384" height = "48" rx = "8" fill = "#38322E" / > < rect x = "64" y = "274" width = "384" height = "48" rx = "8" fill = "#1A2D27" / > < rect x = "64" y = "326" width = "384" height = "48" rx = "8" fill = "#26333F" / > < rect x = "64" y = "378" width = "384" height = "48" rx = "8" fill = "#18293A" / > < line x1 = "64" y1 = "132" x2 = "448" y2 = "132" stroke = "#46586E" stroke-width = "8" stroke-dasharray = "26 20" / > < path d = "M64 410H126V386H160" fill = "none" stroke = "var(--cold)" stroke-width = "34" / > < path d = "M160 403V352H244V368H282" fill = "none" stroke = "var(--cool)" stroke-width = "34" / > < path d = "M282 385V320H312" fill = "none" stroke = "var(--normal)" stroke-width = "34" / > < path d = "M312 337V248H338" fill = "none" stroke = "var(--warm)" stroke-width = "34" / > < path d = "M338 265V196H366" fill = "none" stroke = "var(--hot)" stroke-width = "34" / > < path d = "M366 213V132H396" fill = "none" stroke = "var(--very-hot)" stroke-width = "34" / > < rect x = "382" y = "100" width = "64" height = "64" rx = "12" fill = "var(--rec-hot)" stroke = "#10141B" stroke-width = "12" / > < / svg > < / span > Thermograph< / a > < / h1 >
< p class = "tag" > How far the last 6 years have drifted from 45 years of local climate< / p >
< / div >
< details class = "nav-menu" >
< summary class = "nav-toggle" aria-label = "Menu" > < svg viewBox = "0 0 24 24" width = "22" height = "22" aria-hidden = "true" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" > < path d = "M4 7h16M4 12h16M4 17h16" / > < / svg > < / summary >
< div class = "nav-panel" >
< 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 Detail< / a >
< a href = "compare" data-view = "compare" > Compare< / a >
< a href = "score" data-view = "score" class = "active" > Score< / a >
< a href = "climate" > Climate< / a >
< a href = "alerts" > Alerts< / a >
< / nav >
< / div >
< / details >
< / div >
< / header >
< main >
< section class = "controls" >
< div class = "find-bar" >
< button type = "button" id = "find-btn" class = "find-btn" > < / button >
< span id = "loc-label" class = "loc-label" hidden > < / span >
< / div >
< / section >
< div id = "score-head" hidden > < / div >
< div class = "placeholder" id = "score-placeholder" >
Remove em-dashes from site copy and tighten the prose (#206)
Replace em-dashes in user-facing copy across the server-rendered pages,
static frontend views, and the strings the app injects at runtime, using
colons, commas, parentheses or full stops as the context wants. Data
placeholder glyphs (a lone "—" for a missing reading) are left alone,
since a hyphen there reads as a minus sign in temperature columns.
Also tighten the high-visibility surfaces (home hero and meta, about,
privacy, city and records ledes, glossary blurbs) toward a plainer,
more direct voice while keeping every factual claim intact.
Claude-Session: https://claude.ai/code/session_01XXxmNFy9cZ6Gh8Y9thZn62
2026-07-20 01:48:33 +00:00
< p > Pick a place, or open this from the map or calendar, to see how much its
Add a climate-score page from recent-vs-baseline percentile divergence (#196)
Score how far a location's last 6 years have drifted from its full 45-year
record. For each metric and percentile category (p10/p25/p50/p75/p90), the
recent-years value is placed on the baseline distribution and the gap from the
expected percentile is the divergence — unit-free, so metrics compare directly.
Scored per meteorological season plus annual, weighted into per-metric and
overall scores (temps, humidity and feels-like weighted heaviest).
- backend/scoring.py: divergence math, seasonal slicing, precip zero-inflation
split (wet-day frequency + amount), tier mapping onto the existing temp scale.
- climate.py: derive a wet-bulb column (Stull 2011) at the read boundary, before
the humidity column is converted to absolute — via a shared _derive_metrics
wrapper at all four read sites.
- api/v2/score endpoint + build_score payload, cached on the history token with
a scoring-version key.
- frontend score page: overall hero, per-metric cards, by-season chips, and a
button-revealed summary (sentences + metrics×season table + per-percentile
detail). Score nav link across all headers.
- Tests for the scoring math, wet-bulb formula, payload shape and route.
2026-07-19 23:02:33 +00:00
recent climate has drifted from its long-term normal, metric by metric.< / p >
< / div >
< div id = "score-body" > < / div >
< / main >
< script src = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" > < / script >
< script type = "module" src = "score.js" > < / script >
< / body >
< / html >