SEO: show both temperature extremes per metric in month/season records (#112)

The month and season records tables showed only the record high (of the daytime
high) and the record low (of the overnight low) — one extreme each. Show both
ends of both metrics: for the daytime high, its record hottest (▲) and the
coldest a day ever stayed (▼, its record-low high); for the overnight low, its
mildest night (▲, its record-high low) and its record low (▼). All four already
came out of all_time_records; they were just being dropped.

Restructure each table to Period · Daytime high · Overnight low, with each metric
cell carrying two tinted, dated chips (▲ warmest / ▼ coldest). Fits mobile in
three columns; the all-time cards already showed both extremes, so this brings
the per-period tables in line.
This commit is contained in:
Emi Griffith 2026-07-15 21:57:13 -07:00 committed by GitHub
parent b3e7f75e50
commit da94b082c5
3 changed files with 50 additions and 22 deletions

View file

@ -226,10 +226,22 @@ def _monthly_normals(history) -> list[dict]:
return rows
def _extreme(metric_rec, key: str) -> dict | None:
"""One extreme of a metric — key 'max' (warmest) or 'min' (coldest) — as the
two-unit value, its heat-map tier, and the date it occurred."""
if not metric_rec:
return None
v = metric_rec[key]
return {"txt": _temp(v), "f": v, "cls": temp_class(v), "date": metric_rec[f"{key}_date"]}
def _period_records(history, months: list[int]) -> dict:
"""Record high (tmax) and record low (tmin), with the dates they occurred, over a
set of calendar months the shared primitive behind the monthly and seasonal
records tables. Reuses grading.all_time_records on the month-filtered archive."""
"""For a set of calendar months, the record *warmest and coldest* of BOTH the
daytime high (tmax) and the overnight low (tmin) four extremes with dates.
So each metric shows both ends: the daytime high's hottest day and the coldest a
day ever stayed (its record-low high), and the overnight low's mildest night and
its record low. Reuses grading.all_time_records on the month-filtered archive."""
if len(months) == 1:
sub = history.filter(pl.col("date").dt.month() == months[0])
else:
@ -237,12 +249,8 @@ def _period_records(history, months: list[int]) -> dict:
rec = grading.all_time_records(sub) if not sub.is_empty() else {}
tmax, tmin = rec.get("tmax"), rec.get("tmin")
return {
"high": _temp(tmax["max"]) if tmax else "",
"high_f": tmax["max"] if tmax else None,
"high_date": tmax["max_date"] if tmax else "",
"low": _temp(tmin["min"]) if tmin else "",
"low_f": tmin["min"] if tmin else None,
"low_date": tmin["min_date"] if tmin else "",
"high": {"warm": _extreme(tmax, "max"), "cold": _extreme(tmax, "min")},
"low": {"warm": _extreme(tmin, "max"), "cold": _extreme(tmin, "min")},
}

View file

@ -4,6 +4,15 @@
{% block canonical_path %}{{ canonical_path }}{% endblock %}
{% block jsonld %}<script type="application/ld+json">{{ jsonld_str | safe }}</script>{% endblock %}
{% block content %}
{# A metric's two extremes — ▲ warmest and ▼ coldest it has ever been — as tinted
chips with the date each occurred. Used for the daytime-high and overnight-low
columns of the month and season tables. #}
{% macro extremes(metric) %}
{% if metric and metric.warm and metric.cold %}
<div class="rec-ext"><span class="rec-arrow up" aria-hidden="true">▲</span><span class="t-inline t-{{ metric.warm.cls }}">{{ metric.warm.txt }}</span><span class="rec-on">{{ metric.warm.date }}</span></div>
<div class="rec-ext"><span class="rec-arrow down" aria-hidden="true">▼</span><span class="t-inline t-{{ metric.cold.cls }}">{{ metric.cold.txt }}</span><span class="rec-on">{{ metric.cold.date }}</span></div>
{% else %}—{% endif %}
{% endmacro %}
<nav class="breadcrumb" aria-label="Breadcrumb">
{% for label, href in breadcrumb %}{% if href %}<a href="{{ href }}">{{ label }}</a>{% else %}<span>{{ label }}</span>{% endif %}{% if not loop.last %} <span class="sep"></span> {% endif %}{% endfor %}
</nav>
@ -20,17 +29,18 @@
<section class="climate-records-section">
<h2>Records by month</h2>
<p>The warmest and coldest {{ name }} has ever been in each calendar month. Tap a month for its full
averages and typical range.</p>
<p>The warmest (▲) and coldest (▼) both the daytime high and the overnight low have ever been in
each calendar month — so each column shows its record high <i>and</i> its record low. Tap a
month for its full averages and typical range.</p>
<div class="table-wrap">
<table class="normals-table records-table">
<thead><tr><th>Month</th><th>Record high</th><th>Record low</th></tr></thead>
<table class="normals-table records-table records-ext">
<thead><tr><th>Month</th><th>Daytime high</th><th>Overnight low</th></tr></thead>
<tbody>
{% for m in monthly %}
<tr>
<td><a href="{{ base }}/climate/{{ city.slug }}/{{ m.slug }}">{{ m.name }}</a></td>
<td class="t-cell t-{{ temp_class(m.high_f) }}">{{ m.high }}<span class="rec-on">{{ m.high_date }}</span></td>
<td class="t-cell t-{{ temp_class(m.low_f) }}">{{ m.low }}<span class="rec-on">{{ m.low_date }}</span></td>
<td>{{ extremes(m.high) }}</td>
<td>{{ extremes(m.low) }}</td>
</tr>
{% endfor %}
</tbody>
@ -40,17 +50,17 @@
<section class="climate-records-section">
<h2>Records by season</h2>
<p>Meteorological seasons for the {{ hemisphere }} Hemisphere — each a three-month span — and the
most extreme {{ name }} has recorded within each.</p>
<p>The warmest (▲) and coldest (▼) both the daytime high and the overnight low have reached in each
meteorological season ({{ hemisphere }} Hemisphere — each a three-month span).</p>
<div class="table-wrap">
<table class="normals-table records-table">
<thead><tr><th>Season</th><th>Record high</th><th>Record low</th></tr></thead>
<table class="normals-table records-table records-ext">
<thead><tr><th>Season</th><th>Daytime high</th><th>Overnight low</th></tr></thead>
<tbody>
{% for s in seasonal %}
<tr>
<td>{{ s.name }} <span class="season-span">({{ s.span }})</span></td>
<td class="t-cell t-{{ temp_class(s.high_f) }}">{{ s.high }}<span class="rec-on">{{ s.high_date }}</span></td>
<td class="t-cell t-{{ temp_class(s.low_f) }}">{{ s.low }}<span class="rec-on">{{ s.low_date }}</span></td>
<td>{{ extremes(s.high) }}</td>
<td>{{ extremes(s.low) }}</td>
</tr>
{% endfor %}
</tbody>

View file

@ -134,11 +134,21 @@ def test_climate_pages_are_colour_coded(client):
assert "t-cell t-" in city # tinted temperature cells in the normals table
assert "range-fill" in city and "--c1:var(--" in city # the monthly range strip's gradient bars
recs = client.get(f"{B}/climate/{SLUG}/records").text
assert "t-cell t-" in recs
assert "t-inline t-" in recs # tinted record chips
month = client.get(f"{B}/climate/{SLUG}/july").text
assert "t-inline t-" in month # tinted hero numbers + records
def test_records_show_both_extremes_per_metric(client):
# Each metric column shows BOTH its record warmest (▲) and coldest (▼) — so the
# daytime high carries its record-low high, and the overnight low its record-high low.
b = client.get(f"{B}/climate/{SLUG}/records").text
assert "Daytime high" in b and "Overnight low" in b
assert "" in b and "" in b
# 12 months × 2 metric columns × 2 extremes = 48 chips just in the monthly table.
assert b.count('class="rec-ext"') >= 48
def test_hub_glossary_about(client):
assert client.get(f"{B}/climate").status_code == 200
assert client.get(f"{B}/glossary").status_code == 200