Records: month and year records with the period as the headline
Publish Container / publish (push) Successful in 3m55s
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:
1 parent
6e285bfef1
commit
72d813bd61
3 files changed
+100
-7
No files matched your search
+56
-2
@@ -452,13 +452,18 @@ function deriveRecords() {
|
||||
const agg = (keyFn) => {
|
||||
const g = {};
|
||||
for (const [d, v] of days) {
|
||||
const k = keyFn(d); const a = g[k] || (g[k] = { days: 0, rain: 0, hi: null, lo: null, gust: null });
|
||||
const k = keyFn(d); const a = g[k] || (g[k] = { days: 0, rain: 0, hi: null, lo: null, gust: null, tsum: 0, tn: 0 });
|
||||
a.days++; a.rain += v.rain || 0;
|
||||
if (v.tn) { a.tsum += v.tsum; a.tn += v.tn; }
|
||||
if (v.hi && (a.hi == null || v.hi[0] > a.hi)) a.hi = v.hi[0];
|
||||
if (v.lo && (a.lo == null || v.lo[0] < a.lo)) a.lo = v.lo[0];
|
||||
if (v.gust && (a.gust == null || v.gust[0] > a.gust)) a.gust = v.gust[0];
|
||||
}
|
||||
for (const a of Object.values(g)) a.rain = +a.rain.toFixed(2);
|
||||
for (const a of Object.values(g)) {
|
||||
a.rain = +a.rain.toFixed(2);
|
||||
a.avg = a.tn ? +(a.tsum / a.tn).toFixed(1) : null;
|
||||
delete a.tsum; delete a.tn;
|
||||
}
|
||||
return g;
|
||||
};
|
||||
const byYear = agg(d => d.slice(0, 4));
|
||||
@@ -468,6 +473,55 @@ function deriveRecords() {
|
||||
out.months = ym;
|
||||
out.wettestMonth = ym.reduce((b, m) => (!b || m.rain > b.rain ? m : b), null);
|
||||
out.wettestYear = out.years.reduce((b, y) => (!b || y.rain > b.rain ? y : b), null);
|
||||
|
||||
// Month / year records (partial periods are flagged so "coldest month" can't be a 2-week stub unnoticed)
|
||||
const dim = (ym) => new Date(+ym.slice(0, 4), +ym.slice(5, 7), 0).getDate();
|
||||
const mk = (p, key, val, total) => ({ key, value: val, days: p.days, partial: p.days < total * 0.9 });
|
||||
// preferComplete: averages / "driest" are only fair over whole periods, so rank complete ones first and
|
||||
// fall back to partial ones only when no complete period exists yet. Totals and extremes stay valid when partial.
|
||||
const pick = (list, keyName, valFn, dir, totalFn, preferComplete) => {
|
||||
const rank = (onlyComplete) => {
|
||||
let best = null;
|
||||
for (const p of list) {
|
||||
const v = valFn(p); if (v == null) continue;
|
||||
const total = totalFn(p[keyName]);
|
||||
if (onlyComplete && p.days < total * 0.9) continue;
|
||||
if (!best || (dir === 'max' ? v > best.value : v < best.value)) best = mk(p, p[keyName], v, total);
|
||||
}
|
||||
return best;
|
||||
};
|
||||
return (preferComplete && rank(true)) || rank(false);
|
||||
};
|
||||
const mPick = (valFn, dir, preferComplete) => pick(ym, 'month', valFn, dir, dim, preferComplete);
|
||||
const yPick = (valFn, dir, preferComplete) => pick(out.years, 'year', valFn, dir, () => 365, preferComplete);
|
||||
out.periods = {
|
||||
months: {
|
||||
wettest: mPick(p => p.rain, 'max'), driest: mPick(p => p.rain, 'min', true),
|
||||
hottest: mPick(p => p.avg, 'max', true), coldest: mPick(p => p.avg, 'min', true), windiest: mPick(p => p.gust, 'max'),
|
||||
},
|
||||
years: {
|
||||
wettest: yPick(p => p.rain, 'max'), hottest: yPick(p => p.avg, 'max', true),
|
||||
coldest: yPick(p => p.avg, 'min', true), windiest: yPick(p => p.gust, 'max'),
|
||||
},
|
||||
};
|
||||
|
||||
// Records for each calendar month across every year ("record July", "record January", …)
|
||||
const cal = Array.from({ length: 12 }, () => ({ hi: null, lo: null, wettestDay: null, gust: null, wettestTotal: null, years: new Set() }));
|
||||
const monthRain = {};
|
||||
for (const [d, v] of days) {
|
||||
const m = +d.slice(5, 7) - 1, c = cal[m];
|
||||
c.years.add(d.slice(0, 4));
|
||||
if (v.hi && (!c.hi || v.hi[0] > c.hi.value)) c.hi = { value: v.hi[0], day: d };
|
||||
if (v.lo && (!c.lo || v.lo[0] < c.lo.value)) c.lo = { value: v.lo[0], day: d };
|
||||
if (v.rain != null && v.rain > 0 && (!c.wettestDay || v.rain > c.wettestDay.value)) c.wettestDay = { value: v.rain, day: d };
|
||||
if (v.gust && (!c.gust || v.gust[0] > c.gust.value)) c.gust = { value: v.gust[0], day: d };
|
||||
monthRain[d.slice(0, 7)] = (monthRain[d.slice(0, 7)] || 0) + (v.rain || 0);
|
||||
}
|
||||
for (const [ymKey, total] of Object.entries(monthRain)) {
|
||||
const c = cal[+ymKey.slice(5, 7) - 1];
|
||||
if (total > 0 && (!c.wettestTotal || total > c.wettestTotal.value)) c.wettestTotal = { value: +total.toFixed(2), month: ymKey };
|
||||
}
|
||||
out.calendarMonths = cal.map((c, i) => ({ month: i + 1, years: c.years.size, hi: c.hi, lo: c.lo, wettestDay: c.wettestDay, gust: c.gust, wettestTotal: c.wettestTotal }));
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user