feat weather card title and natural language summary

Card header is now 'Weather in Versonnex' with a subtitle like: 'Currently 24°C, 65% humidity. Next 48h: 18–32°C, dry.' Summarises current conditions and the forecast trend.

Commit
542234a230249c4ac06b1ecca179aa83fb43a7af
Author
GPT-5 medium <codex@openai.com>
Author date
Committer
GPT-5 medium <codex@openai.com>
Committer date
Changed files
roles/dashboard/public/js/dashboard/weather-forecast.js
index 9a472557..acb517dc 100644..100644
@@ -9,15 +9,14 @@
9 9 }
10 10
11 11 function updateCurrentConditions(points) {
12 Removed: const tempEl = document.querySelector("[data-weather-current-temp]");
13 Removed: const detailsEl = document.querySelector("[data-weather-current-details]");
14 Removed: if (!tempEl || !detailsEl) return;
12 Added: const summaryEl = document.querySelector("[data-weather-summary]");
13 Added: if (!summaryEl) return;
15 14
16 Removed: // Find the point closest to now
17 15 const now = Date.now();
16 Added:
17 Added: // Find current point
18 18 let closest = points[0];
19 19 let minDiff = Infinity;
20 Removed:
21 20 for (const p of points) {
22 21 const diff = Math.abs(new Date(p.timestamp).getTime() - now);
23 22 if (diff < minDiff) {
@@ -26,17 +25,40 @@
26 25 }
27 26 }
28 27
29 Removed: if (closest && closest.temperature != null) {
30 Removed: tempEl.textContent = `${closest.temperature.toFixed(1)}°C`;
31 Removed: const parts = [];
32 Removed: if (closest.humidity != null) parts.push(`${Math.round(closest.humidity)}%`);
33 Removed: if (closest.rain != null && closest.rain > 0) parts.push(`${closest.rain.toFixed(1)} mm`);
34 Removed: else parts.push("no rain");
35 Removed: detailsEl.textContent = parts.join(", ");
28 Added: if (!closest || closest.temperature == null) {
29 Added: summaryEl.textContent = "No weather data available.";
30 Added: return;
31 Added: }
32 Added:
33 Added: // Current conditions sentence
34 Added: const currentTemp = closest.temperature.toFixed(0);
35 Added: const currentHumidity = closest.humidity != null ? Math.round(closest.humidity) : null;
36 Added: let currentSentence = `Currently ${currentTemp}°C`;
37 Added: if (currentHumidity != null) currentSentence += `, ${currentHumidity}% humidity`;
38 Added: if (closest.rain != null && closest.rain > 0) currentSentence += `, ${closest.rain.toFixed(1)} mm rain`;
39 Added: currentSentence += ".";
40 Added:
41 Added: // 48h trend: compute min, max temp and total rain
42 Added: const futurePoints = points.filter(p => new Date(p.timestamp).getTime() >= now);
43 Added: if (!futurePoints.length) {
44 Added: summaryEl.textContent = currentSentence;
45 Added: return;
46 Added: }
47 Added:
48 Added: const temps = futurePoints.map(p => p.temperature).filter(t => t != null);
49 Added: const rains = futurePoints.map(p => p.rain || 0);
50 Added: const minTemp = Math.min(...temps).toFixed(0);
51 Added: const maxTemp = Math.max(...temps).toFixed(0);
52 Added: const totalRain = rains.reduce((a, b) => a + b, 0);
53 Added:
54 Added: let trendSentence = `Next 48h: ${minTemp}–${maxTemp}°C`;
55 Added: if (totalRain > 0.5) {
56 Added: trendSentence += `, ${totalRain.toFixed(1)} mm rain expected.`;
36 57 } else {
37 Removed: tempEl.textContent = "—";
38 Removed: detailsEl.textContent = "No data";
58 Added: trendSentence += ", dry.";
39 59 }
60 Added:
61 Added: summaryEl.textContent = `${currentSentence} ${trendSentence}`;
40 62 }
41 63
42 64 // Chart.js plugin to draw a vertical "now" line
roles/dashboard/templates/dashboard/index.html.ep
index 9516ad95..21d2cc3a 100644..100644
@@ -34,12 +34,9 @@
34 34 <section aria-label="Weather forecast">
35 35 <div class="card" data-weather-forecast>
36 36 <div class="card-body py-2 px-3">
37 Removed: <div class="d-flex align-items-baseline justify-content-between mb-1">
38 Removed: <div class="d-flex align-items-baseline gap-2">
39 Removed: <span class="fs-4 fw-bold" data-weather-current-temp>—</span>
40 Removed: <span class="text-body-secondary small" data-weather-current-details>Loading…</span>
41 Removed: </div>
42 Removed: <span class="text-body-secondary small">Versonnex</span>
37 Added: <div class="mb-1">
38 Added: <h2 class="h6 fw-bold mb-0">Weather in Versonnex</h2>
39 Added: <p class="text-body-secondary small mb-0" data-weather-summary>Loading forecast…</p>
43 40 </div>
44 41 <div class="chart-wrap" style="height: 130px;">
45 42 <canvas id="chart-weather-forecast" role="img" aria-label="48-hour weather forecast"></canvas>