feat: add OWM alerts, UV index support, and provider info UI
- Parse OWM One Call 3.0 weather alerts and map to Warning structs - Map hourly UVI from OWM response to HourlyForecast.UVIndex - Add severity helper mapping OWM alert tags to severity levels - Extract UVIndex through compute layer to timeline slots - Smart warnings: use provider-supplied alerts, fall back to DWD - Show UV index in dashboard timeline tooltips - Add provider description below forecast dropdown with i18n
This commit is contained in:
66
web/js/db.js
66
web/js/db.js
@@ -255,34 +255,53 @@ async function fetchForecastForProfile(profileId) {
|
||||
sunshineMin: h.SunshineMin ?? h.sunshineMin ?? null,
|
||||
apparentTempC: h.ApparentTempC ?? h.apparentTempC ?? null,
|
||||
pressureHpa: h.PressureHpa ?? h.pressureHpa ?? null,
|
||||
uvIndex: h.UVIndex ?? h.uvIndex ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch warnings (optional — don't fail if this errors)
|
||||
// Warnings: use provider-supplied warnings if available, otherwise fall back to DWD
|
||||
let warningCount = 0;
|
||||
try {
|
||||
const wResp = await fetch("/api/weather/warnings", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ lat: profile.latitude, lon: profile.longitude }),
|
||||
});
|
||||
if (wResp.ok) {
|
||||
const wData = await wResp.json();
|
||||
await deleteByIndex("warnings", "profileId", profileId);
|
||||
for (const w of (wData.warnings || [])) {
|
||||
await dbAdd("warnings", {
|
||||
profileId,
|
||||
headline: w.Headline || w.headline || "",
|
||||
severity: w.Severity || w.severity || "",
|
||||
description: w.Description || w.description || "",
|
||||
instruction: w.Instruction || w.instruction || "",
|
||||
onset: w.Onset || w.onset || "",
|
||||
expires: w.Expires || w.expires || "",
|
||||
});
|
||||
warningCount++;
|
||||
}
|
||||
const providerWarnings = data.Warnings || data.warnings || [];
|
||||
if (providerWarnings.length > 0) {
|
||||
await deleteByIndex("warnings", "profileId", profileId);
|
||||
for (const w of providerWarnings) {
|
||||
await dbAdd("warnings", {
|
||||
profileId,
|
||||
headline: w.Headline || w.headline || "",
|
||||
severity: w.Severity || w.severity || "",
|
||||
description: w.Description || w.description || "",
|
||||
instruction: w.Instruction || w.instruction || "",
|
||||
onset: w.Onset || w.onset || "",
|
||||
expires: w.Expires || w.expires || "",
|
||||
});
|
||||
warningCount++;
|
||||
}
|
||||
} catch (_) { /* warnings are optional */ }
|
||||
} else {
|
||||
// Fall back to DWD warnings (optional — don't fail if this errors)
|
||||
try {
|
||||
const wResp = await fetch("/api/weather/warnings", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ lat: profile.latitude, lon: profile.longitude }),
|
||||
});
|
||||
if (wResp.ok) {
|
||||
const wData = await wResp.json();
|
||||
await deleteByIndex("warnings", "profileId", profileId);
|
||||
for (const w of (wData.warnings || [])) {
|
||||
await dbAdd("warnings", {
|
||||
profileId,
|
||||
headline: w.Headline || w.headline || "",
|
||||
severity: w.Severity || w.severity || "",
|
||||
description: w.Description || w.description || "",
|
||||
instruction: w.Instruction || w.instruction || "",
|
||||
onset: w.Onset || w.onset || "",
|
||||
expires: w.Expires || w.expires || "",
|
||||
});
|
||||
warningCount++;
|
||||
}
|
||||
}
|
||||
} catch (_) { /* warnings are optional */ }
|
||||
}
|
||||
|
||||
await setSetting("lastFetched", new Date().toISOString());
|
||||
return { forecasts: hourly.length, warnings: warningCount };
|
||||
@@ -416,6 +435,7 @@ async function getComputePayload(profileId, dateStr) {
|
||||
sunshineMin: f.sunshineMin ?? null,
|
||||
apparentTempC: f.apparentTempC ?? null,
|
||||
pressureHpa: f.pressureHpa ?? null,
|
||||
uvIndex: f.uvIndex ?? null,
|
||||
})),
|
||||
warnings: warnings.map(w => ({
|
||||
headline: w.headline || "",
|
||||
|
||||
Reference in New Issue
Block a user