import {cfg} from './config.mjs'; import { frostRisk } from './forecast.mjs'; async function query(c, q, start, end) { const url = new URL('/api/v1/query_range', c.DB_HOST); url.searchParams.set('query', q); url.searchParams.set('step', '60s'); if (start) url.searchParams.set('start', start); if (end) url.searchParams.set('end', end); const res = await fetch(url); const json = await res.json(); return json.data?.result || []; } async function queryInstant(c, q) { const url = new URL('/api/v1/query', c.DB_HOST); url.searchParams.set('query', q); const res = await fetch(url); const json = await res.json(); return json.data?.result || []; } function metricToFields(results) { const out = {}; for (const r of results) { if(r.metric.__name__.startsWith('system_')) continue; const val = r.value?.[1] ?? r.values?.at(-1)?.[1]; if (val !== undefined) out[r.metric.__name__.replace('weather_', '')] = parseFloat(val); } return out; } export async function queryCurrent() { const c = cfg() const results = await queryInstant(c, `{__name__!=""}`) const fields = metricToFields(results) const dp = fields.dew_point ?? null return { time: new Date(), ...fields, frost_risk: frostRisk(fields.temperature ?? 99, dp ?? 99, fields.humidity ?? 0), } } export async function queryHourly(start, end) { const c = cfg(); const s = Math.floor(start.getTime() / 1000); const e = Math.floor(end.getTime() / 1000); const byTime = {}; const results = await query(c, `avg_over_time({__name__!=""}[1h])`, s, e); results.forEach(r => { r.values.forEach(([ts, val]) => { if(r.metric.__name__.startsWith('system_')) return; const time = new Date(ts * 1000).toISOString().slice(0, 13) + ':00'; if (!byTime[time]) byTime[time] = {time}; byTime[time][r.metric.__name__.replace('weather_', '')] = parseFloat(val); }); }); return Object.values(byTime) .sort((a, b) => a.time.localeCompare(b.time)) .map(r => ({ ...r, time: new Date(r.time) })); } export async function queryDaily(start, end) { const c = cfg(); const s = Math.floor(start.getTime() / 1000); const e = Math.floor(end.getTime() / 1000); const byTime = {}; const [means, mins, maxs] = await Promise.all([ query(c, `avg_over_time({__name__!=""}[1d])`, s, e), query(c, `min_over_time({__name__!=""}[1d])`, s, e), query(c, `max_over_time({__name__!=""}[1d])`, s, e), ]); means.forEach(r => { r.values.forEach(([ts, val]) => { if(r.metric.__name__.startsWith('system_')) return; const time = new Date(ts * 1000).toISOString().slice(0, 10); if (!byTime[time]) byTime[time] = {time}; byTime[time][r.metric.__name__.replace('weather_', '')] = parseFloat(val); }); }); mins.forEach(r => { if (r.metric.__name__ !== 'temperature') return; r.values.forEach(([ts, val]) => { const time = new Date(ts * 1000).toISOString().slice(0, 10); if (!byTime[time]) byTime[time] = {time}; byTime[time].env_temp_min_c = parseFloat(val); }); }); maxs.forEach(r => { if (r.metric.__name__ !== 'temperature') return; r.values.forEach(([ts, val]) => { const time = new Date(ts * 1000).toISOString().slice(0, 10); if (!byTime[time]) byTime[time] = {time}; byTime[time].env_temp_max_c = parseFloat(val); }); }); return Object.values(byTime) .sort((a, b) => a.time.localeCompare(b.time)) .map(r => ({ ...r, time: new Date(r.time) })); } export async function getCoords() { const c = cfg(); const results = await queryInstant(c, `{__name__=~"latitude|longitude|altitude"}`); const fields = metricToFields(results); if (fields.latitude && fields.longitude) { return {latitude: fields.latitude, longitude: fields.longitude, altitude: fields.altitude || c.ALTITUDE}; } return {latitude: c.LATITUDE, longitude: c.LONGITUDE, altitude: c.ALTITUDE}; }