agentic-os/agents/k8s/weather-cronjob.yaml

208 lines
10 KiB
YAML

apiVersion: batch/v1
kind: CronJob
metadata:
name: weather-report
namespace: ai-agents
labels:
app: weather-report
spec:
schedule: "0 11 * * *"
timeZone: "America/New_York" # K8s 1.27+ supports timeZone natively
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: weather-reporter
image: node:18-alpine
env:
- name: GROUP_URL
valueFrom:
configMapKeyRef:
name: whatsapp-config
key: default-group-url
- name: PERSONAL_NUMBER
valueFrom:
configMapKeyRef:
name: whatsapp-config
key: personal-number
command:
- "node"
- "-e"
- |
const https = require('https');
const http = require('http');
const ZIP = '16046';
const GATEWAY = 'http://whatsapp-gateway.ai-agents.svc.cluster.local:5001/send-message';
const CONFIG_API = 'http://hq-dashboard.ai-agents.svc.cluster.local/api/config/whatsapp';
const PERSONAL = process.env.PERSONAL_NUMBER;
function fetchConfig() {
return new Promise((resolve) => {
http.get(CONFIG_API, (res) => {
let d = '';
res.on('data', chunk => d += chunk);
res.on('end', () => {
try {
const parsed = JSON.parse(d);
if (parsed.success && parsed.data && parsed.data['default-group-url']) {
resolve(parsed.data['default-group-url']);
return;
}
} catch (e) {}
resolve(process.env.GROUP_URL);
});
}).on('error', () => resolve(process.env.GROUP_URL));
});
}
function weatherEmoji(desc) {
const d = desc.toLowerCase();
if (d.includes('sunny') || d.includes('clear')) return '☀️';
if (d.includes('partly cloudy')) return '⛅';
if (d.includes('cloud') || d.includes('overcast')) return '☁️';
if (d.includes('thunder') || d.includes('storm')) return '⛈️';
if (d.includes('rain') || d.includes('drizzle')) return '🌧️';
if (d.includes('snow') || d.includes('blizzard')) return '❄️';
if (d.includes('fog') || d.includes('mist')) return '🌫️';
if (d.includes('sleet') || d.includes('ice')) return '🌨️';
return '🌤️';
}
function uvLabel(uv) {
const u = parseInt(uv);
if (u <= 2) return 'Low';
if (u <= 5) return 'Moderate';
if (u <= 7) return 'High';
if (u <= 10) return 'Very High';
return 'Extreme';
}
function windLabel(mph) {
const m = parseInt(mph);
if (m < 5) return 'Calm';
if (m < 15) return 'Breezy';
if (m < 25) return 'Windy';
if (m < 40) return 'Very Windy';
return 'Dangerous';
}
const url = `https://wttr.in/${ZIP}?format=j1`;
https.get(url, { headers: { 'User-Agent': 'WeatherBot/1.0' } }, (res) => {
let raw = '';
res.on('data', d => raw += d);
res.on('end', () => {
try {
const data = JSON.parse(raw);
const cur = data.current_condition[0];
const area = data.nearest_area[0];
const today = data.weather[0];
const tmrw = data.weather[1];
const city = area.areaName[0].value;
const state = area.region[0].value;
const desc = cur.weatherDesc[0].value;
const emoji = weatherEmoji(desc);
const now = new Date();
const etTime = now.toLocaleString('en-US', { timeZone: 'America/New_York', hour: 'numeric', minute: '2-digit', hour12: true });
const etDate = now.toLocaleDateString('en-US', { timeZone: 'America/New_York', weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' });
// Hourly snapshots (morning 9am, afternoon 3pm, evening 9pm)
const hours = today.hourly || [];
const morn = hours[2]; // index 2 = 600 (6am slot) or pick closest
const aftn = hours[4]; // 1200
const even = hours[6]; // 1800
const tmrwDesc = tmrw ? tmrw.hourly[4]?.weatherDesc[0]?.value : '';
const tmrwEmoji = tmrw ? weatherEmoji(tmrwDesc) : '';
const msg = [
`${emoji} *Good Morning — Daily Weather Briefing*`,
`📍 *${city}, ${state}* (ZIP: ${ZIP})`,
`📅 ${etDate} • ${etTime} ET`,
``,
`━━━━━━ 🌡️ *Current Conditions* ━━━━━━`,
`${emoji} ${desc}`,
`🌡️ *Temp:* ${cur.temp_F}°F (feels like ${cur.FeelsLikeF}°F)`,
`💧 *Humidity:* ${cur.humidity}%`,
`💨 *Wind:* ${cur.windspeedMiles} mph ${cur.winddir16Point} (${windLabel(cur.windspeedMiles)})`,
`👁️ *Visibility:* ${cur.visibility} mi`,
`☀️ *UV Index:* ${cur.uvIndex} — ${uvLabel(cur.uvIndex)}`,
``,
`━━━━━━ 📊 *Today's Forecast* ━━━━━━`,
`🔺 High: ${today.maxtempF}°F 🔻 Low: ${today.mintempF}°F`,
`🌅 Sunrise: ${today.astronomy[0].sunrise} 🌇 Sunset: ${today.astronomy[0].sunset}`,
morn ? `🕗 Morning (9am): ${morn.tempF}°F — ${morn.weatherDesc[0].value} 🌧️ ${morn.chanceofrain}%` : '',
aftn ? `🕒 Afternoon (3pm): ${aftn.tempF}°F — ${aftn.weatherDesc[0].value} 🌧️ ${aftn.chanceofrain}%` : '',
even ? `🕘 Evening (9pm): ${even.tempF}°F — ${even.weatherDesc[0].value} 🌧️ ${even.chanceofrain}%` : '',
``,
tmrw ? `━━━━━━ 🔮 *Tomorrow* ━━━━━━` : '',
tmrw ? `${tmrwEmoji} ${tmrwDesc}` : '',
tmrw ? `🔺 High: ${tmrw.maxtempF}°F 🔻 Low: ${tmrw.mintempF}°F 🌧️ Rain: ${tmrw.hourly[4]?.chanceofrain || 0}%` : '',
``,
`_Powered by Agentic OS • hq.applaude.net_ 🏠`,
].filter(l => l !== null && l !== undefined).join('\n');
// Helper: send msg to one recipient
function sendTo(to, label) {
return new Promise((resolve) => {
const body = JSON.stringify({ to, message: msg });
const gwUrl = new URL(GATEWAY);
const opts = {
hostname: gwUrl.hostname,
port: parseInt(gwUrl.port) || 80,
path: gwUrl.pathname,
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
};
const req = http.request(opts, (r) => {
let resp = '';
r.on('data', d => resp += d);
r.on('end', () => {
try {
const parsed = JSON.parse(resp);
if (parsed.success) {
console.log(`[WeatherBot] ✅ Sent to ${label}. MsgID: ${parsed.messageId}`);
} else {
console.error(`[WeatherBot] ❌ ${label} error: ${parsed.error}`);
}
} catch(e) { console.error(`[WeatherBot] ${label} parse error:`, e.message); }
resolve();
});
});
req.on('error', e => { console.error(`[WeatherBot] ${label} request failed:`, e.message); resolve(); });
req.write(body);
req.end();
});
}
// Send to group then personal number
fetchConfig().then(GROUP => {
sendTo(GROUP, 'WhatsApp Group')
.then(() => sendTo(PERSONAL, 'Personal (' + PERSONAL + ')'))
.catch(e => { console.error('[WeatherBot] Fatal:', e.message); process.exit(1); });
});
} catch (e) {
console.error('[WeatherBot] Parse error:', e.message);
process.exit(1);
}
});
}).on('error', e => {
console.error('[WeatherBot] Fetch error:', e.message);
process.exit(1);
});
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi