Forecast fixes

This commit is contained in:
2026-06-24 19:18:35 -04:00
parent fdb752073f
commit 82c10d9278
8 changed files with 34 additions and 113 deletions

View File

@@ -1,9 +1,10 @@
<script setup lang="ts">
import Loading from '@/components/Loading.vue';
import {formatDate} from '@ztimson/utils';
import {BASE, type DataRow} from '../services/api';
import {onMounted, ref} from 'vue';
import {api, BASE} from '../services/api';
const props = withDefaults(defineProps<{ days: DataRow[] }>(), {});
const days = ref<any[]>()
const filler = [1, 2, 3, 4, 5];
function dir(deg: number) {
@@ -16,6 +17,13 @@ function dir(deg: number) {
if(deg > 292.5 && deg <= 337.5) return 'NW';
return 'N';
}
onMounted(async () => {
const start = new Date(), end = new Date();
start.setDate(start.getDate() + 1);
end.setDate(end.getDate() + 5);
days.value = await api.daily(start, end);
})
</script>
<style scoped lang="scss">

View File

@@ -13,9 +13,9 @@ import Point from 'ol/geom/Point'
import CircleGeom from 'ol/geom/Circle'
import { fromLonLat, toLonLat } from 'ol/proj'
import { Style, Fill, Stroke, Circle as CircleStyle } from 'ol/style'
import { current } from '../services/weather'
import 'ol/ol.css'
const current = ref([0, 0]);
const props = defineProps<{ dark: boolean }>()
const mapEl = ref<HTMLDivElement>()
const showOverlays = ref(false)

View File

@@ -16,5 +16,9 @@ export const api = {
position: (fields?: string) => get<{latitude: number, longitude: number, altitude: number}>('/api/position', fields ? { fields } : {}),
current: (fields?: string) => get<DataRow>('/api/current', fields ? { fields } : {}),
hourly: (start?: string, end?: string, fields?: string) => get<DataRow[]>('/api/hourly', { ...(start ? { start } : {}), ...(end ? { end } : {}), ...(fields ? { fields } : {}) }),
daily: (start?: string, end?: string, fields?: string) => get<DataRow[]>('/api/daily', { ...(start ? { start } : {}), ...(end ? { end } : {}), ...(fields ? { fields } : {}) }),
daily: (start?: Date, end?: Date, fields?: string) => get<DataRow[]>('/api/daily', {
...(start ? { start: start.toISOString().slice(0, 10) } : {}),
...(end ? { end: end.toISOString().slice(0, 10) } : {}),
...(fields ? { fields } : {})
}),
}

View File

@@ -1,33 +0,0 @@
import { ref, computed } from 'vue'
import { api, type DataRow } from './api'
export const current = ref<DataRow>({})
export const hourly = ref<DataRow[]>([])
export const daily = ref<DataRow[]>([])
export const loading = ref(false)
export const lastFetch = ref<Date | null>(null)
export async function fetchAll() {
const date = (d: Date = new Date()) => `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`
loading.value = true
const start = new Date(), end = new Date();
start.setDate(start.getDate() + 1);
end.setDate(end.getDate() + 5);
const [c, h, d] = await Promise.allSettled([api.current(), api.hourly(), api.daily(date(start), date(end))])
if (c.status === 'fulfilled') current.value = c.value
if (h.status === 'fulfilled') hourly.value = h.value
if (d.status === 'fulfilled') daily.value = d.value
lastFetch.value = new Date()
loading.value = false
}
export async function fetchHistoricHourly(start: string, end: string): Promise<DataRow[]> {
return api.hourly(start, end)
}
export async function fetchHistoricDaily(start: string, end: string): Promise<DataRow[]> {
return api.daily(start, end)
}
export const isDaytime = computed(() => current.value.sun_is_day === 1)

View File

@@ -1,20 +1,14 @@
`<script setup lang="ts">
import {formatDate} from '@ztimson/utils';
import {onMounted, onUnmounted, ref, computed} from 'vue';
import {current, daily, fetchAll, loading} from '../services/weather';
import {METRICS, GROUPS} from '../services/units';
import {onMounted, onUnmounted, ref} from 'vue';
import MapView from '../components/MapView.vue';
import CurrentWeather from '../components/CurrentWeather.vue';
import ForecastStrip from '../components/ForecastStrip.vue';
import MetricCard from '../components/MetricCard.vue';
import GraphModal from '../components/GraphModal.vue';
const day = ref(formatDate('dddd'));
const date = ref(formatDate('MMMM D'));
const time = ref(formatDate('h:mm A'));
const props = defineProps<{dark: boolean}>();
const selectedMetric = ref<string | null>(null);
let interval: ReturnType<typeof setInterval>;
let clock: ReturnType<typeof setInterval>;
onMounted(async () => {
@@ -23,22 +17,10 @@ onMounted(async () => {
date.value = formatDate('MMMM D');
time.value = formatDate('h:mm A');
}, 1000);
await fetchAll();
interval = setInterval(fetchAll, 60 * 1000);
});
onUnmounted(() => {
clearInterval(clock);
clearInterval(interval);
});
const groupedMetrics = computed(() => {
return GROUPS.map(group => ({
group,
keys: Object.entries(METRICS)
.filter(([key, meta]) => meta.group === group && current.value[key] != null)
.map(([key]) => key)
})).filter(g => g.keys.length > 0);
});
</script>
@@ -161,8 +143,6 @@ const groupedMetrics = computed(() => {
<template>
<div class="dashboard">
<div v-if="loading" class="loading-bar"/>
<div class="map-col">
<MapView :dark="props.dark"/>
</div>
@@ -178,27 +158,14 @@ const groupedMetrics = computed(() => {
</div>
</div>
<CurrentWeather />
<ForecastStrip :days="daily"/>
<template v-for="g in groupedMetrics" :key="g.group">
<div class="group-label">{{ g.group }}</div>
<div class="metric-grid">
<MetricCard
v-for="key in g.keys"
:key="key"
:metric-key="key"
:data="current"
@click="selectedMetric = key"
/>
</div>
</template>
<ForecastStrip />
</div>
<GraphModal
:metric-key="selectedMetric"
:current-data="current"
@close="selectedMetric = null"
/>
<!-- <GraphModal-->
<!-- :metric-key="selectedMetric"-->
<!-- :current-data="current"-->
<!-- @close="selectedMetric = null"-->
<!-- />-->
</div>
</template>
`

View File

@@ -186,16 +186,6 @@ function moonriseMoonset(lat, lon, date = new Date()) {
return {moonrise: moonrise, moonset: moonset};
}
function nextSolsticeEquinox(date = new Date()) {
const y = date.getFullYear();
return {
summer_solstice: new Date(Date.UTC(y, 5, 21)),
winter_solstice: new Date(Date.UTC(y, 11, 21)),
vernal_equinox: new Date(Date.UTC(y, 2, 20)),
autumnal_equinox: new Date(Date.UTC(y + 1, 2, 20)),
};
}
export function getCelestialCurrent(lat, lon, date = new Date()) {
const data = {
...sunPosition(lat, lon, date),

View File

@@ -1,6 +1,6 @@
// forecast.mjs
import {queryHourly, getCoords, queryCurrent} from './influx.mjs';
import {getCelestialCurrent, getCelestialForecast} from './celestial.mjs';
import {getCelestialCurrent} from './celestial.mjs';
import {getWeatherCondition} from './openweather.mjs';
export let lastForecast = { ts: 0, slots: [], summary: null }

View File

@@ -102,40 +102,25 @@ app.get('/api/hourly', async (req, res) => {
app.get('/api/daily', async (req, res) => {
const { fields } = req.query
const now = new Date()
const now = new Date()
now.setHours(0, 0, 0, 0)
const next = new Date(now); next.setDate(now.getDate() + 1)
const next = new Date(now); next.setDate(now.getDate() + 1)
const start = req.query.start ? new Date(req.query.start) : now
const end = req.query.end ? new Date(req.query.end) : next
const coords = await getCoords()
// Does the requested window overlap with today (the 24h forecast window)?
const todayStr = now
const usePhysics = lastForecast.summary && start <= next && end >= now
// Past influx history (days before today)
const [sensorResult] = await Promise.allSettled([
start < now ? queryDaily(start, now) : Promise.resolve([])
])
const todayStr = now.toISOString().slice(0, 10) // ✅ string for comparison
const usePhysics = lastForecast.summary && start <= now && end > now // ✅ only when today is in window
const [sensorResult] = await Promise.allSettled([start < now ? queryDaily(start, now) : Promise.resolve([])])
const history = sensorResult.status === 'fulfilled' ? sensorResult.value : []
// Future meteo (days after today)
const meteoStart = end > next ? next : null
const [meteoResult] = await Promise.allSettled([
meteoStart && end > next ? dailyWeather(coords.latitude, coords.longitude, next, end) : Promise.resolve([])
])
// meteo starts from whichever is later: next or start
const meteoFrom = new Date(Math.max(next.getTime(), start.getTime()))
const [meteoResult] = await Promise.allSettled([end > next ? dailyWeather(coords.latitude, coords.longitude, meteoFrom, end) : Promise.resolve([])])
const meteo = meteoResult.status === 'fulfilled' ? meteoResult.value : []
// Splice in physics summary for today, skip any meteo row that duplicates it
const todaySlot = usePhysics ? [lastForecast.summary] : []
const meteoClean = meteo.filter(r => r.time !== todayStr)
const daily = getCelestialForecast(
coords.latitude, coords.longitude,
[...history, ...todaySlot, ...meteoClean]
)
const meteoClean = meteo.filter(r => String(r.time).slice(0, 10) !== todayStr) // ✅ safe string compare
const daily = getCelestialForecast(coords.latitude, coords.longitude, [...history, ...todaySlot, ...meteoClean])
res.json(filterArr(daily, fields))
})