Records: month and year records with the period as the headline
Publish Container / publish (push) Successful in 3m55s

- Wettest/driest/hottest/coldest/windiest month and wettest/hottest/coldest/windiest year,
  each naming the period (e.g. 'September 2026') and flagging partial periods
- Average-based records prefer complete periods; counterparts say what they're waiting for
- 'Records by calendar month (any year)' table with the date/year of each record

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Nate EmmertandClaude Sonnet 5.5 committed 2026-10-15 13:21:55 -04:00
1 parent 6e285bfef1
commit 72d813bd61
3 files changed
+100 -7

No files matched your search

+9
View File
@@ -122,3 +122,12 @@ a { color: var(--ui); }
.chart-ctl select { font: inherit; font-size: 0.76rem; padding: 0.25rem 0.4rem; min-height: 30px; border-radius: 8px; border: 1px solid var(--border); background: var(--paper); color: var(--ink); }
@media (pointer: coarse) { .chart-ctl select { font-size: 16px; min-height: 40px; } }
.chart-card .chart-title { flex-wrap: wrap; row-gap: 0.4rem; }
/* Month / year record cards: the period is the headline */
.record-period { font-size: 0.92rem; font-weight: 800; color: var(--ink); letter-spacing: -0.005em; margin-top: 0.1rem; }
.rec-sub { text-transform: none; letter-spacing: 0; font-weight: 400; opacity: .75; }
.rec-cal td { vertical-align: top; line-height: 1.25; }
.rec-cal td b { display: block; font-variant-numeric: tabular-nums; }
.rec-yr { display: block; font-size: 0.68rem; color: var(--ink-light); font-weight: 600; white-space: nowrap; }
.rec-cal tr.empty { opacity: .4; }
+35 -5
View File
@@ -1238,11 +1238,41 @@ function renderRecordPeriods(d, ri) {
['Windiest Day', nv(n.windiestDay, v => v.toFixed(1) + ' mph'), n.windiestDay],
['Wettest Day', nv(n.wettestDay, v => v.toFixed(2) + '"'), n.wettestDay],
].map(([l, v, o]) => ri(l, v, o ? dayDate(o.day) : null)).join('') + '</div>';
if (d.wettestMonth || d.wettestYear) {
html += '<div class="rec-h">Wettest periods</div><div class="records-grid">'
+ (d.wettestMonth ? ri('Wettest Month', d.wettestMonth.rain.toFixed(2) + '"', dayDate(d.wettestMonth.month + '-15')) : '')
+ (d.wettestYear ? ri('Wettest Year', d.wettestYear.rain.toFixed(2) + '"', dayDate(d.wettestYear.year + '-07-01')) : '')
+ '</div>';
// Months & years: the period itself (e.g. "October 2026") is the headline of each card
const now = new Date();
const curMonth = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0'), curYear = String(now.getFullYear());
const longMonth = (m) => new Date(m + '-15T12:00:00Z').toLocaleDateString('en-US', { month: 'long', year: 'numeric', timeZone: 'UTC' });
const rp = (label, rec, fmtVal, isMonth, waitMsg) => {
if (!rec) return `<div class="record-item"><div class="record-label">${label}</div><div class="record-val">—</div><div class="record-period">${waitMsg || 'Not enough data yet'}</div></div>`;
const current = rec.key === (isMonth ? curMonth : curYear);
return `<div class="record-item${current ? ' record-best' : ''}"><div class="record-label">${label}</div><div class="record-val">${fmtVal(rec.value)}</div>
<div class="record-period">${isMonth ? longMonth(rec.key) : rec.key}</div><div class="record-date">${rec.days} day${rec.days === 1 ? '' : 's'} recorded${rec.partial ? ' · partial' : ''}</div></div>`;
};
const P = JSON.parse(JSON.stringify(d.periods || { months: {}, years: {} }));
// An extreme and its opposite landing on the same period just means there's nothing to compare yet
const same = (a, b) => a && b && a.key === b.key;
const waitM = 'Needs another full month', waitY = 'Needs another year of data';
let wM = {}, wY = {};
if (same(P.months.hottest, P.months.coldest)) { P.months.coldest = null; wM.coldest = waitM; }
if (same(P.months.wettest, P.months.driest)) { P.months.driest = null; wM.driest = waitM; }
if (same(P.years.hottest, P.years.coldest)) { P.years.coldest = null; wY.coldest = waitY; }
const deg = (v) => v.toFixed(1) + '°F', inch = (v) => v.toFixed(2) + '"', mph = (v) => v.toFixed(0) + ' mph';
html += '<div class="rec-h">Months <span class="rec-sub">(avg temp = mean of all readings; partial months are flagged)</span></div><div class="records-grid">'
+ rp('Wettest Month', P.months.wettest, inch, true) + rp('Driest Month', P.months.driest, inch, true, wM.driest)
+ rp('Hottest Month', P.months.hottest, deg, true) + rp('Coldest Month', P.months.coldest, deg, true, wM.coldest)
+ rp('Windiest Month', P.months.windiest, mph, true) + '</div>';
html += '<div class="rec-h">Years</div><div class="records-grid">'
+ rp('Wettest Year', P.years.wettest, inch, false) + rp('Hottest Year', P.years.hottest, deg, false)
+ rp('Coldest Year', P.years.coldest, deg, false, wY.coldest) + rp('Windiest Year', P.years.windiest, mph, false) + '</div>';
// Records for each calendar month, whichever year they happened in
if (d.calendarMonths && d.calendarMonths.some(c => c.years)) {
const mn = (i) => new Date(Date.UTC(2001, i, 15)).toLocaleDateString('en-US', { month: 'long', timeZone: 'UTC' });
const cell = (x, fmtV) => x ? `<b>${fmtV(x.value)}</b><span class="rec-yr">${x.day ? new Date(x.day + 'T12:00:00Z').toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' }) : ''}</span>` : '<span class="rec-yr">—</span>';
const rows = d.calendarMonths.map((c, i) => `<tr${c.years ? '' : ' class="empty"'}><td><b>${mn(i)}</b><span class="rec-yr">${c.years ? c.years + ' year' + (c.years > 1 ? 's' : '') : 'no data'}</span></td>
<td>${cell(c.hi, deg)}</td><td>${cell(c.lo, deg)}</td><td>${cell(c.wettestDay, inch)}</td>
<td>${c.wettestTotal ? `<b>${inch(c.wettestTotal.value)}</b><span class="rec-yr">${longMonth(c.wettestTotal.month)}</span>` : '<span class="rec-yr">—</span>'}</td><td>${cell(c.gust, mph)}</td></tr>`).join('');
html += `<div class="rec-h">Records by calendar month <span class="rec-sub">(any year)</span></div><div class="rec-scroll"><table class="rec-table rec-cal"><tr><th>Month</th><th>Record high</th><th>Record low</th><th>Wettest day</th><th>Wettest month</th><th>Peak gust</th></tr>${rows}</table></div>`;
}
const mname = (m) => new Date(m + '-15T12:00:00Z').toLocaleDateString('en-US', { month: 'short', year: 'numeric', timeZone: 'UTC' });
const row = (label, a) => `<tr><td>${label}</td><td>${a.hi != null ? a.hi.toFixed(1) : '—'}</td><td>${a.lo != null ? a.lo.toFixed(1) : '—'}</td><td>${a.gust != null ? a.gust.toFixed(0) : '—'}</td><td>${a.rain.toFixed(2)}</td><td>${a.days}</td></tr>`;