24h forecasting
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
// forecast.mjs
|
||||
import { queryHourly, getCoords } from './influx.mjs';
|
||||
import {queryHourly, getCoords, queryCurrent} from './influx.mjs';
|
||||
import { getCelestialForecast } from './celestial.mjs';
|
||||
|
||||
let cache = { ts: 0, slots: [], summary: null }
|
||||
const TTL = 10 * 60 * 1000
|
||||
|
||||
// ── Physics Helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
function linearTrend(values) {
|
||||
@@ -181,3 +184,52 @@ export async function get24HourForecast(currentSensors) {
|
||||
|
||||
return enriched;
|
||||
}
|
||||
|
||||
function summarise(slots) {
|
||||
if (!slots.length) return null
|
||||
const temps = slots.map(s => s.temperature).filter(Number.isFinite)
|
||||
const precips = slots.map(s => s.precip_chance).filter(Number.isFinite)
|
||||
const winds = slots.map(s => s.wind_speed).filter(Number.isFinite)
|
||||
const gusts = slots.map(s => s.wind_gusts).filter(Number.isFinite)
|
||||
const uvs = slots.map(s => s.uv_index).filter(Number.isFinite)
|
||||
const solar = slots.map(s => s.solar_wm2).filter(Number.isFinite)
|
||||
|
||||
// Most frequent label/code by occurrence
|
||||
const labelCount = {}
|
||||
for (const s of slots) labelCount[s.label] = (labelCount[s.label] || 0) + 1
|
||||
const label = Object.entries(labelCount).sort((a, b) => b[1] - a[1])[0][0]
|
||||
const dominant = slots.find(s => s.label === label)
|
||||
|
||||
return {
|
||||
time: slots[0].time.slice(0, 10),
|
||||
label,
|
||||
code: dominant.code,
|
||||
icon: dominant.icon ?? null,
|
||||
temperature: Math.max(...temps),
|
||||
temperature_max: Math.max(...temps),
|
||||
temperature_min: Math.min(...temps),
|
||||
precip_chance: Math.round(Math.max(...precips)),
|
||||
wind_speed: Math.round(Math.max(...winds) * 10) / 10,
|
||||
wind_gusts: gusts.length ? Math.round(Math.max(...gusts) * 10) / 10 : null,
|
||||
uv_index: uvs.length ? Math.max(...uvs) : null,
|
||||
uv_index_label: dominant.uv_index_label ?? null,
|
||||
solar_wm2: solar.length ? Math.round(solar.reduce((a, b) => a + b, 0) / solar.length * 10) / 10 : null,
|
||||
sunrise: slots.find(s => s.sunrise)?.sunrise ?? null,
|
||||
sunset: slots.find(s => s.sunset)?.sunset ?? null,
|
||||
// useful extras for /api/current
|
||||
humidity: Math.round(slots.map(s => s.humidity).filter(Number.isFinite).reduce((a, b) => a + b, 0) / slots.length * 10) / 10,
|
||||
pressure_hpa: Math.round(slots.map(s => s.pressure_hpa).filter(Number.isFinite).reduce((a, b) => a + b, 0) / slots.length * 10) / 10,
|
||||
}
|
||||
}
|
||||
|
||||
export async function refreshForecast() {
|
||||
const sensors = await queryCurrent()
|
||||
const slots = await get24HourForecast(sensors)
|
||||
cache = { ts: Date.now(), slots, summary: summarise(slots) }
|
||||
return cache
|
||||
}
|
||||
|
||||
export async function ensureForecast() {
|
||||
if (Date.now() - cache.ts > TTL) await refreshForecast()
|
||||
return cache
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user