feat: añadir agente meteorologo con tool get_weather
Nuevo agente Matrix especializado en consultas meteorológicas. Usa la API pública Open-Meteo (sin API key) para obtener condiciones actuales y previsión de 3 días para cualquier ciudad. Incluye: - agents/meteorologo/ — reglas puras, config.yaml, system prompt - tools/weather.go — tool get_weather (geocoding + forecast) - Registro en runtime.go (tool registry) y launcher (rulesRegistry) El agente responde a DMs y menciones delegando al LLM con tool_use habilitado. No tiene comandos directos (!xxx). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
// Package meteorologo defines the pure rules for the meteorologo bot.
|
||||||
|
// This agent uses tool_use (get_weather) to provide weather information.
|
||||||
|
package meteorologo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/enmanuel/agents/pkg/decision"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rules returns the decision rules for the meteorologo bot.
|
||||||
|
func Rules() []decision.Rule {
|
||||||
|
return []decision.Rule{
|
||||||
|
// Any DM or mention → LLM (with tool-use enabled)
|
||||||
|
{
|
||||||
|
Name: "llm-all",
|
||||||
|
Match: func(ctx decision.MessageContext) bool {
|
||||||
|
return ctx.IsDirectMsg || ctx.IsMention
|
||||||
|
},
|
||||||
|
Actions: []decision.Action{{
|
||||||
|
Kind: decision.ActionKindLLM,
|
||||||
|
LLM: &decision.LLMAction{},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
# ============================================
|
||||||
|
# IDENTIDAD
|
||||||
|
# ============================================
|
||||||
|
agent:
|
||||||
|
id: meteorologo
|
||||||
|
name: "Meteorologo"
|
||||||
|
version: "1.0.0"
|
||||||
|
enabled: true
|
||||||
|
description: "Meteorologo experto. Consulta el tiempo actual y prevision para cualquier ciudad del mundo."
|
||||||
|
tags: [weather, llm, tools]
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# PERSONALIDAD Y COMPORTAMIENTO
|
||||||
|
# ============================================
|
||||||
|
personality:
|
||||||
|
tone: friendly
|
||||||
|
verbosity: concise
|
||||||
|
language: es
|
||||||
|
languages_supported: [es, en]
|
||||||
|
emoji_style: minimal
|
||||||
|
prefix: ""
|
||||||
|
error_style: helpful
|
||||||
|
|
||||||
|
templates:
|
||||||
|
greeting: "Hola, soy el Meteorologo. Preguntame por el tiempo en cualquier ciudad."
|
||||||
|
unknown_command: "No entiendo ese comando. Preguntame directamente por el tiempo de una ciudad."
|
||||||
|
permission_denied: "No tengo permiso para hacer eso."
|
||||||
|
error: "Algo salio mal: {{.Error}}"
|
||||||
|
success: "{{.Summary}}"
|
||||||
|
busy: "Consultando datos meteorologicos, un momento..."
|
||||||
|
|
||||||
|
behavior:
|
||||||
|
proactive: false
|
||||||
|
ask_confirmation: false
|
||||||
|
show_reasoning: false
|
||||||
|
thread_replies: true
|
||||||
|
typing_indicator: true
|
||||||
|
acknowledge_receipt: false
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# LLM — CONEXION Y RAZONAMIENTO
|
||||||
|
# ============================================
|
||||||
|
llm:
|
||||||
|
primary:
|
||||||
|
provider: openai
|
||||||
|
model: gpt-4o
|
||||||
|
api_key_env: OPENAI_API_KEY
|
||||||
|
base_url: ""
|
||||||
|
max_tokens: 4096
|
||||||
|
temperature: 0.7
|
||||||
|
|
||||||
|
fallback:
|
||||||
|
provider: ""
|
||||||
|
model: ""
|
||||||
|
api_key_env: ""
|
||||||
|
base_url: ""
|
||||||
|
max_tokens: 0
|
||||||
|
temperature: 0
|
||||||
|
|
||||||
|
reasoning:
|
||||||
|
system_prompt_file: "prompts/system.md"
|
||||||
|
context_window: 16384
|
||||||
|
memory_messages: 30
|
||||||
|
|
||||||
|
tool_use:
|
||||||
|
enabled: true
|
||||||
|
max_iterations: 5
|
||||||
|
parallel_calls: false
|
||||||
|
|
||||||
|
rate_limit:
|
||||||
|
requests_per_minute: 60
|
||||||
|
tokens_per_minute: 200000
|
||||||
|
concurrent_requests: 5
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# TOOLS — get_weather habilitada
|
||||||
|
# ============================================
|
||||||
|
tools:
|
||||||
|
ssh:
|
||||||
|
enabled: false
|
||||||
|
allowed_targets: []
|
||||||
|
forbidden_commands: []
|
||||||
|
timeout: 0s
|
||||||
|
max_concurrent: 0
|
||||||
|
require_confirmation: []
|
||||||
|
|
||||||
|
http:
|
||||||
|
enabled: false
|
||||||
|
allowed_domains: []
|
||||||
|
timeout: 0s
|
||||||
|
max_retries: 0
|
||||||
|
|
||||||
|
scripts:
|
||||||
|
enabled: false
|
||||||
|
scripts_dir: ""
|
||||||
|
allowed: []
|
||||||
|
timeout: 0s
|
||||||
|
sandbox: false
|
||||||
|
|
||||||
|
file_ops:
|
||||||
|
enabled: false
|
||||||
|
allowed_paths: []
|
||||||
|
read_only: true
|
||||||
|
|
||||||
|
mcp:
|
||||||
|
enabled: false
|
||||||
|
servers: []
|
||||||
|
expose:
|
||||||
|
port: 0
|
||||||
|
tools: []
|
||||||
|
|
||||||
|
memory:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
knowledge:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# MEMORIA
|
||||||
|
# ============================================
|
||||||
|
memory:
|
||||||
|
enabled: true
|
||||||
|
window_size: 20
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# MATRIX — CONEXION Y ROOMS
|
||||||
|
# ============================================
|
||||||
|
matrix:
|
||||||
|
homeserver: "https://matrix-af2f3d.organic-machine.com"
|
||||||
|
user_id: "@meteorologo:matrix-af2f3d.organic-machine.com"
|
||||||
|
access_token_env: MATRIX_TOKEN_METEOROLOGO
|
||||||
|
device_id: "YNXDWAVGMW"
|
||||||
|
|
||||||
|
encryption:
|
||||||
|
enabled: true
|
||||||
|
store_path: "./agents/meteorologo/data/crypto/"
|
||||||
|
pickle_key_env: PICKLE_KEY_METEOROLOGO
|
||||||
|
trust_mode: tofu
|
||||||
|
recovery_key_env: SSSS_RECOVERY_KEY_METEOROLOGO
|
||||||
|
|
||||||
|
rooms:
|
||||||
|
listen: []
|
||||||
|
respond: []
|
||||||
|
admin: []
|
||||||
|
|
||||||
|
filters:
|
||||||
|
command_prefix: "!"
|
||||||
|
mention_respond: true
|
||||||
|
dm_respond: true
|
||||||
|
ignore_bots: true
|
||||||
|
ignore_users: []
|
||||||
|
min_power_level: 0
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# COMUNICACION INTER-AGENTES
|
||||||
|
# ============================================
|
||||||
|
agents:
|
||||||
|
peers:
|
||||||
|
- id: assistant-bot
|
||||||
|
capabilities: [general, llm]
|
||||||
|
room: ""
|
||||||
|
|
||||||
|
delegation:
|
||||||
|
enabled: false
|
||||||
|
can_delegate_to: []
|
||||||
|
can_receive_from: [assistant-bot]
|
||||||
|
max_delegation_depth: 1
|
||||||
|
timeout: 30s
|
||||||
|
|
||||||
|
protocol:
|
||||||
|
format: json
|
||||||
|
channel: matrix
|
||||||
|
heartbeat_interval: 60s
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# SSH — no aplica
|
||||||
|
# ============================================
|
||||||
|
ssh:
|
||||||
|
defaults:
|
||||||
|
user: ""
|
||||||
|
port: 22
|
||||||
|
key_file_env: ""
|
||||||
|
known_hosts: ""
|
||||||
|
keepalive_interval: 0s
|
||||||
|
timeout: 0s
|
||||||
|
targets: {}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# PERMISOS Y SEGURIDAD
|
||||||
|
# ============================================
|
||||||
|
security:
|
||||||
|
roles:
|
||||||
|
admin:
|
||||||
|
users: ["@admin:matrix-af2f3d.organic-machine.com"]
|
||||||
|
actions: ["*"]
|
||||||
|
user:
|
||||||
|
users: ["*"]
|
||||||
|
actions: ["ask", "help", "weather"]
|
||||||
|
|
||||||
|
audit:
|
||||||
|
enabled: false
|
||||||
|
log_file: "./agents/meteorologo/data/audit.log"
|
||||||
|
log_to_room: ""
|
||||||
|
include: []
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
provider: env
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# SCHEDULING
|
||||||
|
# ============================================
|
||||||
|
schedules: []
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# OBSERVABILIDAD
|
||||||
|
# ============================================
|
||||||
|
observability:
|
||||||
|
logging:
|
||||||
|
level: info
|
||||||
|
format: json
|
||||||
|
output: stdout
|
||||||
|
file: "./agents/meteorologo/data/meteorologo.log"
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
enabled: false
|
||||||
|
port: 9093
|
||||||
|
path: /metrics
|
||||||
|
export: prometheus
|
||||||
|
|
||||||
|
health:
|
||||||
|
enabled: true
|
||||||
|
port: 8083
|
||||||
|
path: /healthz
|
||||||
|
|
||||||
|
tracing:
|
||||||
|
enabled: false
|
||||||
|
provider: ""
|
||||||
|
endpoint: ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# RESILIENCIA
|
||||||
|
# ============================================
|
||||||
|
resilience:
|
||||||
|
circuit_breaker:
|
||||||
|
failure_threshold: 5
|
||||||
|
timeout: 30s
|
||||||
|
half_open_max: 2
|
||||||
|
|
||||||
|
retry:
|
||||||
|
max_attempts: 2
|
||||||
|
backoff: exponential
|
||||||
|
initial_delay: 1s
|
||||||
|
max_delay: 10s
|
||||||
|
|
||||||
|
shutdown:
|
||||||
|
timeout: 10s
|
||||||
|
drain_messages: true
|
||||||
|
save_state: false
|
||||||
|
state_file: ""
|
||||||
|
|
||||||
|
queue:
|
||||||
|
enabled: true
|
||||||
|
max_size: 100
|
||||||
|
priority_users: ["@admin:matrix-af2f3d.organic-machine.com"]
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# ALMACENAMIENTO Y ESTADO
|
||||||
|
# ============================================
|
||||||
|
storage:
|
||||||
|
state:
|
||||||
|
backend: sqlite
|
||||||
|
path: "./agents/meteorologo/data/meteorologo.db"
|
||||||
|
|
||||||
|
cache:
|
||||||
|
enabled: true
|
||||||
|
backend: memory
|
||||||
|
ttl: 5m
|
||||||
|
max_entries: 200
|
||||||
|
|
||||||
|
history:
|
||||||
|
backend: sqlite
|
||||||
|
path: "./agents/meteorologo/data/history.db"
|
||||||
|
retention: 168h
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Meteorologo — System Prompt
|
||||||
|
|
||||||
|
Eres un meteorologo experto que opera como bot en Matrix. Tu especialidad es proporcionar informacion meteorologica precisa y util.
|
||||||
|
|
||||||
|
## Identidad
|
||||||
|
- Nombre: Meteorologo
|
||||||
|
- Rol: Experto en meteorologia y clima
|
||||||
|
- Personalidad: Profesional pero cercano, con pasion por el tiempo atmosferico
|
||||||
|
|
||||||
|
## Capacidades
|
||||||
|
- Consultar el tiempo actual de cualquier ciudad del mundo usando la herramienta `get_weather`
|
||||||
|
- Proporcionar previsiones de hasta 3 dias
|
||||||
|
- Explicar fenomenos meteorologicos
|
||||||
|
- Dar recomendaciones basadas en el tiempo (ropa, actividades, precauciones)
|
||||||
|
|
||||||
|
## Herramientas disponibles
|
||||||
|
- `get_weather`: Obtiene el tiempo actual y prevision de 3 dias para una ciudad. Parametro: `city` (nombre de la ciudad). Usala SIEMPRE que te pregunten por el tiempo de una ciudad.
|
||||||
|
|
||||||
|
## Estilo de respuesta
|
||||||
|
- Responde siempre en el idioma del usuario
|
||||||
|
- Usa formato claro con temperaturas, humedad, viento y condiciones
|
||||||
|
- Anade recomendaciones practicas cuando sea relevante (ej: "Lleva paraguas", "Buen dia para pasear")
|
||||||
|
- Si te preguntan por el tiempo sin especificar ciudad, pregunta que ciudad quieren consultar
|
||||||
|
- Puedes explicar conceptos meteorologicos si te lo piden
|
||||||
|
- Usa markdown para formatear (listas, negritas) cuando mejore la legibilidad
|
||||||
|
|
||||||
|
## Restricciones
|
||||||
|
- No inventes datos meteorologicos: siempre usa la herramienta `get_weather`
|
||||||
|
- Si la herramienta falla o no encuentra la ciudad, informalo al usuario
|
||||||
|
- No respondas sobre temas que no tengan relacion con el tiempo o la meteorologia. Redirige amablemente al tema
|
||||||
@@ -742,6 +742,10 @@ func buildToolRegistry(
|
|||||||
reg.Register(tools.NewCurrentTime())
|
reg.Register(tools.NewCurrentTime())
|
||||||
logger.Debug("registered current_time tool")
|
logger.Debug("registered current_time tool")
|
||||||
|
|
||||||
|
// weather tool is always available
|
||||||
|
reg.Register(tools.NewWeather())
|
||||||
|
logger.Debug("registered weather tool")
|
||||||
|
|
||||||
// matrix_send is always available
|
// matrix_send is always available
|
||||||
reg.Register(tools.NewMatrixSend(matrixClient))
|
reg.Register(tools.NewMatrixSend(matrixClient))
|
||||||
logger.Debug("registered matrix tool")
|
logger.Debug("registered matrix tool")
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/enmanuel/agents/agents"
|
"github.com/enmanuel/agents/agents"
|
||||||
assistantagent "github.com/enmanuel/agents/agents/assistant-bot"
|
assistantagent "github.com/enmanuel/agents/agents/assistant-bot"
|
||||||
asistente2agent "github.com/enmanuel/agents/agents/asistente-2"
|
asistente2agent "github.com/enmanuel/agents/agents/asistente-2"
|
||||||
|
meteorologoagent "github.com/enmanuel/agents/agents/meteorologo"
|
||||||
"github.com/enmanuel/agents/internal/config"
|
"github.com/enmanuel/agents/internal/config"
|
||||||
"github.com/enmanuel/agents/pkg/decision"
|
"github.com/enmanuel/agents/pkg/decision"
|
||||||
"github.com/enmanuel/agents/pkg/orchestration"
|
"github.com/enmanuel/agents/pkg/orchestration"
|
||||||
@@ -36,6 +37,7 @@ import (
|
|||||||
var rulesRegistry = map[string]func() []decision.Rule{
|
var rulesRegistry = map[string]func() []decision.Rule{
|
||||||
"assistant-bot": assistantagent.Rules,
|
"assistant-bot": assistantagent.Rules,
|
||||||
"asistente-2": asistente2agent.Rules,
|
"asistente-2": asistente2agent.Rules,
|
||||||
|
"meteorologo": meteorologoagent.Rules,
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 182 KiB |
@@ -0,0 +1,205 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewWeather creates a get_weather tool that fetches current weather and forecast
|
||||||
|
// for a city using the Open-Meteo API (free, no API key required).
|
||||||
|
func NewWeather() Tool {
|
||||||
|
client := &http.Client{Timeout: 15 * time.Second}
|
||||||
|
|
||||||
|
return Tool{
|
||||||
|
Def: Def{
|
||||||
|
Name: "get_weather",
|
||||||
|
Description: "Get current weather conditions and 3-day forecast for a city. Returns temperature, humidity, wind speed, and weather description.",
|
||||||
|
Parameters: []Param{
|
||||||
|
{Name: "city", Type: "string", Description: "City name to look up (e.g. 'Madrid', 'New York', 'Tokyo')", Required: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Exec: func(ctx context.Context, args map[string]any) Result {
|
||||||
|
city := getString(args, "city")
|
||||||
|
if city == "" {
|
||||||
|
return Result{Err: fmt.Errorf("get_weather: city is required")}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Geocode city name to coordinates
|
||||||
|
lat, lon, resolvedName, country, err := geocodeCity(ctx, client, city)
|
||||||
|
if err != nil {
|
||||||
|
return Result{Err: fmt.Errorf("get_weather: geocoding failed: %w", err)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Fetch weather data
|
||||||
|
weather, err := fetchWeather(ctx, client, lat, lon)
|
||||||
|
if err != nil {
|
||||||
|
return Result{Err: fmt.Errorf("get_weather: forecast failed: %w", err)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Format output
|
||||||
|
output := formatWeather(resolvedName, country, weather)
|
||||||
|
return Result{Output: output}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// geocodeCity resolves a city name to coordinates using Open-Meteo Geocoding API.
|
||||||
|
func geocodeCity(ctx context.Context, client *http.Client, city string) (lat, lon float64, name, country string, err error) {
|
||||||
|
u := fmt.Sprintf("https://geocoding-api.open-meteo.com/v1/search?name=%s&count=1&language=es&format=json",
|
||||||
|
url.QueryEscape(city))
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, "", "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(io.LimitReader(resp.Body, 32*1024))
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result struct {
|
||||||
|
Results []struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
Latitude float64 `json:"latitude"`
|
||||||
|
Longitude float64 `json:"longitude"`
|
||||||
|
} `json:"results"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body, &result); err != nil {
|
||||||
|
return 0, 0, "", "", fmt.Errorf("invalid geocoding response: %w", err)
|
||||||
|
}
|
||||||
|
if len(result.Results) == 0 {
|
||||||
|
return 0, 0, "", "", fmt.Errorf("city %q not found", city)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := result.Results[0]
|
||||||
|
return r.Latitude, r.Longitude, r.Name, r.Country, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type weatherData struct {
|
||||||
|
Current struct {
|
||||||
|
Temperature float64 `json:"temperature_2m"`
|
||||||
|
Humidity int `json:"relative_humidity_2m"`
|
||||||
|
WindSpeed float64 `json:"wind_speed_10m"`
|
||||||
|
WeatherCode int `json:"weather_code"`
|
||||||
|
FeelsLike float64 `json:"apparent_temperature"`
|
||||||
|
} `json:"current"`
|
||||||
|
Daily struct {
|
||||||
|
Time []string `json:"time"`
|
||||||
|
TempMax []float64 `json:"temperature_2m_max"`
|
||||||
|
TempMin []float64 `json:"temperature_2m_min"`
|
||||||
|
WeatherCode []int `json:"weather_code"`
|
||||||
|
PrecipProb []int `json:"precipitation_probability_max"`
|
||||||
|
} `json:"daily"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchWeather gets current conditions and 3-day forecast from Open-Meteo.
|
||||||
|
func fetchWeather(ctx context.Context, client *http.Client, lat, lon float64) (*weatherData, error) {
|
||||||
|
u := fmt.Sprintf(
|
||||||
|
"https://api.open-meteo.com/v1/forecast?latitude=%.4f&longitude=%.4f"+
|
||||||
|
"¤t=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code,wind_speed_10m"+
|
||||||
|
"&daily=temperature_2m_max,temperature_2m_min,weather_code,precipitation_probability_max"+
|
||||||
|
"&timezone=auto&forecast_days=3",
|
||||||
|
lat, lon,
|
||||||
|
)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(io.LimitReader(resp.Body, 64*1024))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("API returned %d: %s", resp.StatusCode, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var data weatherData
|
||||||
|
if err := json.Unmarshal(body, &data); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid forecast response: %w", err)
|
||||||
|
}
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatWeather produces a human-readable weather summary.
|
||||||
|
func formatWeather(city, country string, w *weatherData) string {
|
||||||
|
var b strings.Builder
|
||||||
|
|
||||||
|
fmt.Fprintf(&b, "Tiempo en %s, %s\n\n", city, country)
|
||||||
|
fmt.Fprintf(&b, "AHORA:\n")
|
||||||
|
fmt.Fprintf(&b, " Temperatura: %.1f C (sensacion termica: %.1f C)\n", w.Current.Temperature, w.Current.FeelsLike)
|
||||||
|
fmt.Fprintf(&b, " Humedad: %d%%\n", w.Current.Humidity)
|
||||||
|
fmt.Fprintf(&b, " Viento: %.1f km/h\n", w.Current.WindSpeed)
|
||||||
|
fmt.Fprintf(&b, " Condicion: %s\n", weatherCodeToText(w.Current.WeatherCode))
|
||||||
|
|
||||||
|
if len(w.Daily.Time) > 0 {
|
||||||
|
fmt.Fprintf(&b, "\nPREVISION:\n")
|
||||||
|
for i, date := range w.Daily.Time {
|
||||||
|
fmt.Fprintf(&b, " %s: %.0f/%.0f C, %s",
|
||||||
|
date, w.Daily.TempMin[i], w.Daily.TempMax[i],
|
||||||
|
weatherCodeToText(w.Daily.WeatherCode[i]))
|
||||||
|
if i < len(w.Daily.PrecipProb) {
|
||||||
|
fmt.Fprintf(&b, ", prob. lluvia: %d%%", w.Daily.PrecipProb[i])
|
||||||
|
}
|
||||||
|
fmt.Fprintln(&b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// weatherCodeToText converts WMO weather codes to Spanish descriptions.
|
||||||
|
func weatherCodeToText(code int) string {
|
||||||
|
switch {
|
||||||
|
case code == 0:
|
||||||
|
return "Despejado"
|
||||||
|
case code == 1:
|
||||||
|
return "Mayormente despejado"
|
||||||
|
case code == 2:
|
||||||
|
return "Parcialmente nublado"
|
||||||
|
case code == 3:
|
||||||
|
return "Nublado"
|
||||||
|
case code >= 45 && code <= 48:
|
||||||
|
return "Niebla"
|
||||||
|
case code >= 51 && code <= 55:
|
||||||
|
return "Llovizna"
|
||||||
|
case code >= 56 && code <= 57:
|
||||||
|
return "Llovizna helada"
|
||||||
|
case code >= 61 && code <= 65:
|
||||||
|
return "Lluvia"
|
||||||
|
case code >= 66 && code <= 67:
|
||||||
|
return "Lluvia helada"
|
||||||
|
case code >= 71 && code <= 77:
|
||||||
|
return "Nieve"
|
||||||
|
case code >= 80 && code <= 82:
|
||||||
|
return "Chubascos"
|
||||||
|
case code >= 85 && code <= 86:
|
||||||
|
return "Chubascos de nieve"
|
||||||
|
case code >= 95 && code <= 99:
|
||||||
|
return "Tormenta"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("Codigo %d", code)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user