Files

188 lines
5.7 KiB
Go

package weather
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"trankilou.fr/lassistanoque/backend/internal/domain"
)
type WeatherTool struct {
}
type WeatherConfig struct {
}
func (t *WeatherTool) Name() string {
return "weather"
}
func (t *WeatherTool) Description() string {
return "Get the current weather for a given location"
}
func (t *WeatherTool) Params() []domain.ToolParam {
return []domain.ToolParam{}
}
func (t *WeatherTool) Definition(ctx context.Context) *domain.ToolDefinition {
return &domain.ToolDefinition{
Type: "function",
Function: &domain.ToolFunction{
Name: t.Name(),
Description: t.Description(),
Parameters: map[string]any{
"type": "object",
"properties": map[string]any{
"location": map[string]any{
"type": "string",
"description": "The city name, e.g., 'Paris' or 'New York'",
},
},
"required": []string{"location"},
},
},
}
}
func (t *WeatherTool) Execute(ctx context.Context, input []byte, configuration string) ([]byte, error) {
var params struct {
Location string `json:"location"`
}
err := json.Unmarshal(input, &params)
if err != nil {
return nil, err
}
resp, err := http.Get("https://geocoding-api.open-meteo.com/v1/search?name=" + url.QueryEscape(params.Location))
if err != nil {
return nil, err
}
defer resp.Body.Close()
var location struct {
Results []struct {
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
} `json:"results"`
}
err = json.NewDecoder(resp.Body).Decode(&location)
if err != nil {
return nil, err
}
if len(location.Results) == 0 {
return nil, fmt.Errorf("location not found : %s", input)
}
lon := location.Results[0].Longitude
lat := location.Results[0].Latitude
weatherResp, err := http.Get(fmt.Sprintf("https://api.open-meteo.com/v1/forecast?latitude=%f&longitude=%f&current_weather=true&daily=weather_code,temperature_2m_max,temperature_2m_min,rain_sum,wind_speed_10m_max&hourly=temperature_2m&forecast_days=15", lat, lon))
if err != nil {
return nil, err
}
defer weatherResp.Body.Close()
var weatherData struct {
CurrentWeatherUnits struct {
Temperature string `json:"temperature"`
WindSpeed string `json:"windspeed"`
} `json:"current_weather_units"`
CurrentWeather struct {
Time string `json:"time"`
Temperature float64 `json:"temperature"`
WeatherCode int `json:"weathercode"`
} `json:"current_weather"`
DailyUnits struct {
TemperatureMax string `json:"temperature_2m_max"`
TemperatureMin string `json:"temperature_2m_min"`
Rain string `json:"rain_sum"`
WindSpeed string `json:"wind_speed_10m_max"`
} `json:"daily_units"`
Daily struct {
Time []string `json:"time"`
TemperatureMax []float64 `json:"temperature_2m_max"`
TemperatureMin []float64 `json:"temperature_2m_min"`
Rain []float64 `json:"rain_sum"`
WindSpeed []float64 `json:"wind_speed_10m_max"`
} `json:"daily"`
}
err = json.NewDecoder(weatherResp.Body).Decode(&weatherData)
if err != nil {
return nil, err
}
var builder strings.Builder
fmt.Fprintf(&builder, "Current weather in %s\n", params.Location)
fmt.Fprintf(&builder, "- Time: %s\n", weatherData.CurrentWeather.Time)
fmt.Fprintf(&builder, "- Temperature: %.2f %s\n", weatherData.CurrentWeather.Temperature, weatherData.CurrentWeatherUnits.Temperature)
fmt.Fprintf(&builder, "- Weather code: %d - %s\n", weatherData.CurrentWeather.WeatherCode, wmoCodeToDescription(weatherData.CurrentWeather.WeatherCode))
if len(weatherData.Daily.WindSpeed) > 0 {
fmt.Fprintf(&builder, "- Wind speed: %.2f %s\n", weatherData.Daily.WindSpeed[0], weatherData.DailyUnits.WindSpeed)
}
if len(weatherData.Daily.TemperatureMax) > 0 {
fmt.Fprintf(&builder, "- Max temperature: %.2f %s\n", weatherData.Daily.TemperatureMax[0], weatherData.DailyUnits.TemperatureMax)
}
if len(weatherData.Daily.TemperatureMin) > 0 {
fmt.Fprintf(&builder, "- Min temperature: %.2f %s\n", weatherData.Daily.TemperatureMin[0], weatherData.DailyUnits.TemperatureMin)
}
if len(weatherData.Daily.Rain) > 0 {
fmt.Fprintf(&builder, "- Rainfall: %.2f %s\n", weatherData.Daily.Rain[0], weatherData.DailyUnits.Rain)
}
fmt.Fprintf(&builder, "\nForecast for the next days:\n")
// Iterate over the daily data and append to the repor
for i := 0; i < len(weatherData.Daily.Time); i++ {
fmt.Fprintf(&builder, "%s: Max Temp: %.2f %s, Min Temp: %.2f %s, Rain: %.2f %s, Wind Speed: %.2f %s\n",
weatherData.Daily.Time[i],
weatherData.Daily.TemperatureMax[i], weatherData.DailyUnits.TemperatureMax,
weatherData.Daily.TemperatureMin[i], weatherData.DailyUnits.TemperatureMin,
weatherData.Daily.Rain[i], weatherData.DailyUnits.Rain,
weatherData.Daily.WindSpeed[i], weatherData.DailyUnits.WindSpeed)
}
return []byte(builder.String()), nil
}
func wmoCodeToDescription(code int) string {
switch code {
case 0:
return "Clear sky"
case 1, 2, 3:
return "Mainly clear, partly cloudy, and overcast"
case 45, 48:
return "Fog and depositing rime fog"
case 51, 53, 55:
return "Drizzle: Light, moderate, and dense intensity"
case 56, 57:
return "Freezing Drizzle: Light and dense intensity"
case 61, 63, 65:
return "Rain: Slight, moderate and heavy intensity"
case 66, 67:
return "Freezing Rain: Light and heavy intensity"
case 71, 73, 75:
return "Snowfall: Slight, moderate, and heavy intensity"
case 77:
return "Snow grains"
case 80, 81, 82:
return "Rain showers: Slight, moderate, and violent"
case 85, 86:
return "Snow showers slight and heavy"
case 95:
return "Thunderstorm: Slight and heavy intensity"
case 96, 99:
return "Thunderstorm with slight and heavy hail"
default:
return "N/A"
}
}