[Perl] DAQ system for the FAPG.
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.
Changed files
roles/dashboard/public/js/dashboard/weather-forecast.js
@@ -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
@@ -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>