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:
@@ -122,6 +122,7 @@ func BuildDashboard(req ComputeRequest) (DashboardData, error) {
|
||||
cloudPct := 50.0
|
||||
sunMin := 0.0
|
||||
pressureHpa := 0.0
|
||||
uvIndex := 0.0
|
||||
if i < len(dayForecasts) {
|
||||
if dayForecasts[i].CloudCoverPct != nil {
|
||||
cloudPct = *dayForecasts[i].CloudCoverPct
|
||||
@@ -132,6 +133,9 @@ func BuildDashboard(req ComputeRequest) (DashboardData, error) {
|
||||
if dayForecasts[i].PressureHpa != nil {
|
||||
pressureHpa = *dayForecasts[i].PressureHpa
|
||||
}
|
||||
if dayForecasts[i].UVIndex != nil {
|
||||
uvIndex = *dayForecasts[i].UVIndex
|
||||
}
|
||||
}
|
||||
|
||||
budgets, worstStatus, worstMode := computeRoomBudgets(req, h.Hour, h.TempC, cloudPct, sunMin, toggles)
|
||||
@@ -144,6 +148,7 @@ func BuildDashboard(req ComputeRequest) (DashboardData, error) {
|
||||
TempC: h.TempC,
|
||||
HumidityPct: h.HumidityPct,
|
||||
PressureHpa: pressureHpa,
|
||||
UVIndex: uvIndex,
|
||||
RiskLevel: dayRisk.Level.String(),
|
||||
BudgetStatus: worstStatus.String(),
|
||||
IndoorTempC: indoorTempC,
|
||||
|
||||
@@ -30,6 +30,17 @@ func makeForecasts(baseTime time.Time, temps []float64) []Forecast {
|
||||
return forecasts
|
||||
}
|
||||
|
||||
func makeForecastsWithUVI(baseTime time.Time, temps []float64, uvis []float64) []Forecast {
|
||||
forecasts := makeForecasts(baseTime, temps)
|
||||
for i := range forecasts {
|
||||
if i < len(uvis) {
|
||||
uv := uvis[i]
|
||||
forecasts[i].UVIndex = &uv
|
||||
}
|
||||
}
|
||||
return forecasts
|
||||
}
|
||||
|
||||
func TestBuildDashboard_NoForecasts(t *testing.T) {
|
||||
req := ComputeRequest{
|
||||
Profile: Profile{Name: "Test", Timezone: "Europe/Berlin"},
|
||||
@@ -850,3 +861,40 @@ func TestBuildDashboard_MultipleRooms(t *testing.T) {
|
||||
t.Errorf("got %d room budgets, want 2", len(data.RoomBudgets))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildDashboard_UVIndex(t *testing.T) {
|
||||
loc, _ := time.LoadLocation("UTC")
|
||||
base := time.Date(2025, 7, 15, 0, 0, 0, 0, loc)
|
||||
|
||||
temps := make([]float64, 24)
|
||||
uvis := make([]float64, 24)
|
||||
for i := range temps {
|
||||
temps[i] = 30
|
||||
if i >= 10 && i <= 14 {
|
||||
uvis[i] = 8.5
|
||||
}
|
||||
}
|
||||
|
||||
req := ComputeRequest{
|
||||
Profile: Profile{Name: "Test", Timezone: "UTC"},
|
||||
Forecasts: makeForecastsWithUVI(base, temps, uvis),
|
||||
Toggles: map[string]bool{},
|
||||
Date: "2025-07-15",
|
||||
}
|
||||
|
||||
data, err := BuildDashboard(req)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(data.Timeline) != 24 {
|
||||
t.Fatalf("got %d timeline slots, want 24", len(data.Timeline))
|
||||
}
|
||||
// Hour 12 should have UVI 8.5
|
||||
if data.Timeline[12].UVIndex != 8.5 {
|
||||
t.Errorf("Timeline[12].UVIndex = %v, want 8.5", data.Timeline[12].UVIndex)
|
||||
}
|
||||
// Hour 0 should have UVI 0
|
||||
if data.Timeline[0].UVIndex != 0 {
|
||||
t.Errorf("Timeline[0].UVIndex = %v, want 0", data.Timeline[0].UVIndex)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@ type Forecast struct {
|
||||
SunshineMin *float64 `json:"sunshineMin"`
|
||||
ApparentTempC *float64 `json:"apparentTempC"`
|
||||
PressureHpa *float64 `json:"pressureHpa"`
|
||||
UVIndex *float64 `json:"uvIndex"`
|
||||
}
|
||||
|
||||
// Warning holds a weather warning sent from the client.
|
||||
@@ -172,6 +173,7 @@ type TimelineSlotData struct {
|
||||
TempC float64 `json:"tempC"`
|
||||
HumidityPct float64 `json:"humidityPct"`
|
||||
PressureHpa float64 `json:"pressureHpa"`
|
||||
UVIndex float64 `json:"uvIndex"`
|
||||
RiskLevel string `json:"riskLevel"`
|
||||
BudgetStatus string `json:"budgetStatus"`
|
||||
IndoorTempC float64 `json:"indoorTempC"`
|
||||
|
||||
Reference in New Issue
Block a user