import { WeatherData, ActivityResult, RuleResult, ActivityConfig } from '@/types'; import { getWeatherDescription } from './weather'; import { daysSinceLastActivity } from './storage'; export function evaluateMotorcycle( weather: WeatherData, config: ActivityConfig, ): ActivityResult { const rules: RuleResult[] = []; const reasoning: string[] = []; // Temperature const temp = weather.current.temperature_2m; const minTemp = config.min_temp_c ?? 10; const maxTemp = config.max_temp_c ?? 35; if (temp < minTemp) { rules.push({ name: 'Temperature', passed: false, message: `${temp}°C is below minimum ${minTemp}°C` }); reasoning.push(`Too cold: ${temp}°C < ${minTemp}°C minimum`); } else if (temp > maxTemp) { rules.push({ name: 'Temperature', passed: false, message: `${temp}°C exceeds maximum ${maxTemp}°C` }); reasoning.push(`Too hot: ${temp}°C > ${maxTemp}°C maximum`); } else { rules.push({ name: 'Temperature', passed: true, message: `${temp}°C is comfortable` }); } // Precipitation const precip = weather.daily.precipitation_sum[0] ?? 0; const maxPrecip = config.max_precipitation_mm ?? 0; if (precip > maxPrecip) { rules.push({ name: 'Precipitation', passed: false, message: `${precip}mm expected today` }); reasoning.push(`Rain expected: ${precip}mm`); } else { rules.push({ name: 'Precipitation', passed: true, message: 'No rain expected' }); } // Wind const wind = weather.current.wind_speed_10m; const maxWind = config.max_wind_speed_kmh ?? 30; if (wind > maxWind) { rules.push({ name: 'Wind', passed: false, message: `${wind} km/h exceeds safe limit` }); reasoning.push(`Wind too strong: ${wind} km/h`); } else { rules.push({ name: 'Wind', passed: true, message: `${wind} km/h is safe` }); } // Humidity const humidity = weather.current.relative_humidity_2m; const maxHum = config.max_humidity_pct ?? 90; if (humidity > maxHum) { rules.push({ name: 'Humidity', passed: false, message: `${humidity}% is uncomfortable` }); reasoning.push(`High humidity: ${humidity}%`); } else { rules.push({ name: 'Humidity', passed: true, message: `${humidity}% is comfortable` }); } // Weather code (snow, ice, thunderstorm) const code = weather.current.weather_code; const dangerousCodes = [45, 48, 66, 67, 71, 73, 75, 77, 85, 86, 95, 96, 99]; if (dangerousCodes.includes(code)) { rules.push({ name: 'Weather', passed: false, message: getWeatherDescription(code) }); reasoning.push(getWeatherDescription(code)); } else { rules.push({ name: 'Weather', passed: true, message: getWeatherDescription(code) }); } const verdict = determineVerdict(rules); return { name: 'Motorcycle Ride', verdict, rules, reasoning, icon: '🏍️', description: 'Check conditions for motorcycle commuting', }; } export function evaluateLawn( weather: WeatherData, config: ActivityConfig, ): ActivityResult { const rules: RuleResult[] = []; const reasoning: string[] = []; // Cooldown check const cooldownDays = config.cooldown_days ?? 3; const daysSince = daysSinceLastActivity('mow_lawn'); if (daysSince < cooldownDays) { rules.push({ name: 'Cooldown', passed: false, message: `Last mowed ${daysSince} day(s) ago (wait ${cooldownDays - daysSince} more)`, }); reasoning.push(`Cooldown not met: ${daysSince} days since last mow (need ${cooldownDays})`); } else { rules.push({ name: 'Cooldown', passed: true, message: `Good — ${daysSince} days since last mow (minimum: ${cooldownDays})`, }); } // Today's precipitation const precipToday = weather.daily.precipitation_sum[0] ?? 0; const maxPrecip = config.max_precipitation_mm ?? 0; if (precipToday > maxPrecip) { rules.push({ name: 'Rain today', passed: false, message: `${precipToday}mm expected` }); reasoning.push(`Rain expected today: ${precipToday}mm`); } else { rules.push({ name: 'Rain today', passed: true, message: 'No rain expected' }); } // Past 24h precipitation (ground dryness) const precip24h = calculatePast24hPrecip(weather); const max24h = config.max_precipitation_past_24h_mm ?? 5; if (precip24h > max24h) { rules.push({ name: 'Ground moisture', passed: false, message: `${precip24h}mm in past 24h` }); reasoning.push(`Ground too wet: ${precip24h}mm in past 24h`); } else { rules.push({ name: 'Ground moisture', passed: true, message: `Ground is dry (${precip24h}mm)` }); } // Wind const wind = weather.current.wind_speed_10m; const maxWind = config.max_wind_speed_kmh ?? 25; if (wind > maxWind) { rules.push({ name: 'Wind', passed: false, message: `${wind} km/h exceeds limit` }); reasoning.push(`Wind too strong: ${wind} km/h`); } else { rules.push({ name: 'Wind', passed: true, message: `${wind} km/h is safe` }); } // Temperature const temp = weather.current.temperature_2m; const minTemp = config.min_temp_c ?? 15; if (temp < minTemp) { rules.push({ name: 'Temperature', passed: false, message: `${temp}°C is below ${minTemp}°C` }); reasoning.push(`Too cold: ${temp}°C`); } else { rules.push({ name: 'Temperature', passed: true, message: `${temp}°C is comfortable` }); } // Weather code const code = weather.current.weather_code; if (code <= 3) { rules.push({ name: 'Weather', passed: true, message: getWeatherDescription(code) }); } else { rules.push({ name: 'Weather', passed: false, message: getWeatherDescription(code) }); reasoning.push(getWeatherDescription(code)); } const verdict = determineVerdict(rules); return { name: 'Mow Lawn', verdict, rules, reasoning, icon: '🌱', description: 'Check if conditions are good for mowing', }; } function calculatePast24hPrecip(weather: WeatherData): number { const now = new Date(); let total = 0; const hourly = weather.hourly; for (let i = 0; i < hourly.time.length; i++) { const time = new Date(hourly.time[i]); const diffHours = (now.getTime() - time.getTime()) / (1000 * 60 * 60); if (diffHours >= 0 && diffHours <= 24) { total += hourly.precipitation[i] ?? 0; } } return Math.round(total * 10) / 10; } function determineVerdict(rules: RuleResult[]): 'GO' | 'NO-GO' | 'CAUTION' { const failed = rules.filter(r => !r.passed); if (failed.length === 0) return 'GO'; // Critical failures = rain/precipitation const critical = failed.some(r => r.name.toLowerCase().includes('rain') || r.name.toLowerCase().includes('precip') || r.name.toLowerCase().includes('cooldown') ); return critical ? 'NO-GO' : 'CAUTION'; }