Weather Station / ECOWITT / DNT
0

Configure Feed

Select the types of activity you want to include in your feed.

forcast analysis debug api

+135 -6
+5 -4
README.md
··· 299 299 - Uses `FORECAST_STATION_ID` as default station if no station selected 300 300 - Last selected station is saved in localStorage 301 301 - **Analyse (Analysis)**: Forecast accuracy analysis comparing predictions with actual weather data. 302 - - **Automatic daily analysis at midnight (00:00)**: Compares yesterday's forecasts with actual weather data 302 + - **Automatic daily analysis at 20:00 (8 PM)**: Compares yesterday's forecasts with actual weather data 303 303 - **Persistent storage**: Analysis results stored in DuckDB `forecast_analysis` table 304 304 - Shows Mean Absolute Error (MAE) and Root Mean Square Error (RMSE) for temperature, precipitation, and wind 305 305 - Daily comparison details with error highlighting for all 4 forecast sources 306 306 - Configurable time range (7-60 days) and station selection 307 - - **Demo data shown if no real analysis available yet** (first analysis runs after midnight) 308 - - Automatically stores forecasts daily via server background poller (runs at midnight, not every 24h) 307 + - **Demo data shown if no real analysis available yet** (first analysis runs at 20:00) 308 + - Automatically stores forecasts daily via server background poller (runs at 20:00, not every 24h) 309 + - **Retry logic**: Each station gets 3 attempts with 30-second delays; failed stations are skipped 309 310 - Station ID configured via `FORECAST_STATION_ID` environment variable (default: 11035 - Wien) 310 311 - Same station ID is used for all forecast sources (Geosphere, Meteoblue, Open-Meteo, OpenWeatherMap) 311 312 - Color-coded display: red for max temperature, blue for min temperature and precipitation ··· 502 503 503 504 ### Forecast Storage & Analysis 504 505 505 - Forecasts are automatically stored **daily at midnight (00:00)** by the server background poller (configured via `FORECAST_STATION_ID` in `.env`). Analysis is calculated automatically after storage. 506 + Forecasts are automatically stored **daily at 20:00 (8 PM)** by the server background poller (configured via `FORECAST_STATION_ID` in `.env`). Analysis is calculated automatically after storage. Each station gets up to 3 retry attempts with 30-second delays if errors occur. 506 507 507 508 - Get stored analysis results (GET) **[Recommended]** 508 509
+128
src/app/api/dforecast/route.ts
··· 1 + import { NextResponse } from "next/server"; 2 + import { getDuckConn } from "@/lib/db/duckdb"; 3 + 4 + export const runtime = "nodejs"; 5 + 6 + /** 7 + * Debug API to view stored forecast data directly from DuckDB 8 + * GET /api/dforecast 9 + * GET /api/dforecast?stationId=11035 10 + * GET /api/dforecast?stationId=11035&limit=50 11 + * GET /api/dforecast?table=analysis (to view forecast_analysis instead) 12 + */ 13 + export async function GET(req: Request) { 14 + try { 15 + const { searchParams } = new URL(req.url); 16 + const stationId = searchParams.get("stationId"); 17 + const limit = parseInt(searchParams.get("limit") || "100"); 18 + const table = searchParams.get("table") || "forecasts"; // 'forecasts' or 'analysis' 19 + 20 + const conn = await getDuckConn(); 21 + 22 + let query = ""; 23 + let params: any[] = []; 24 + 25 + if (table === "analysis") { 26 + // Query forecast_analysis table 27 + query = ` 28 + SELECT 29 + analysis_date, 30 + station_id, 31 + forecast_date, 32 + source, 33 + temp_min_error, 34 + temp_max_error, 35 + precipitation_error, 36 + wind_speed_error, 37 + actual_temp_min, 38 + actual_temp_max, 39 + actual_precipitation, 40 + actual_wind_speed, 41 + forecast_temp_min, 42 + forecast_temp_max, 43 + forecast_precipitation, 44 + forecast_wind_speed, 45 + created_at 46 + FROM forecast_analysis 47 + ${stationId ? "WHERE station_id = ?" : ""} 48 + ORDER BY analysis_date DESC, forecast_date DESC, source 49 + LIMIT ? 50 + `; 51 + params = stationId ? [stationId, limit] : [limit]; 52 + } else { 53 + // Query forecasts table 54 + query = ` 55 + SELECT 56 + storage_date, 57 + station_id, 58 + forecast_date, 59 + source, 60 + temp_min, 61 + temp_max, 62 + precipitation, 63 + wind_speed, 64 + wind_gust, 65 + created_at 66 + FROM forecasts 67 + ${stationId ? "WHERE station_id = ?" : ""} 68 + ORDER BY storage_date DESC, forecast_date DESC, source 69 + LIMIT ? 70 + `; 71 + params = stationId ? [stationId, limit] : [limit]; 72 + } 73 + 74 + const reader = await conn.runAndReadAll(query, params); 75 + const rows = reader.getRowObjects(); 76 + 77 + // Convert DuckDB values to plain JSON 78 + const data = rows.map((row: any) => { 79 + const converted: any = {}; 80 + for (const [key, value] of Object.entries(row)) { 81 + if (value === null || value === undefined) { 82 + converted[key] = value; 83 + } else if (typeof value === 'bigint') { 84 + converted[key] = Number(value); 85 + } else if (typeof value === 'object' && value.valueOf) { 86 + const primitive = value.valueOf(); 87 + converted[key] = typeof primitive === 'bigint' ? Number(primitive) : String(primitive); 88 + } else { 89 + converted[key] = value; 90 + } 91 + } 92 + return converted; 93 + }); 94 + 95 + // Get row counts 96 + const forecastCountQuery = `SELECT COUNT(*) as count FROM forecasts ${stationId ? "WHERE station_id = ?" : ""}`; 97 + const forecastCountReader = await conn.runAndReadAll(forecastCountQuery, stationId ? [stationId] : []); 98 + const forecastCount = Number(forecastCountReader.getRowObjects()[0]?.count || 0); 99 + 100 + const analysisCountQuery = `SELECT COUNT(*) as count FROM forecast_analysis ${stationId ? "WHERE station_id = ?" : ""}`; 101 + const analysisCountReader = await conn.runAndReadAll(analysisCountQuery, stationId ? [stationId] : []); 102 + const analysisCount = Number(analysisCountReader.getRowObjects()[0]?.count || 0); 103 + 104 + return NextResponse.json({ 105 + table, 106 + stationId: stationId || "all", 107 + limit, 108 + totalRows: table === "analysis" ? analysisCount : forecastCount, 109 + returnedRows: data.length, 110 + counts: { 111 + forecasts: forecastCount, 112 + analysis: analysisCount 113 + }, 114 + data 115 + }, { 116 + headers: { 117 + 'Content-Type': 'application/json; charset=utf-8' 118 + } 119 + }); 120 + 121 + } catch (error: any) { 122 + console.error("DForecast API error:", error); 123 + return NextResponse.json({ 124 + error: error.message, 125 + stack: process.env.NODE_ENV === 'development' ? error.stack : undefined 126 + }, { status: 500 }); 127 + } 128 + }
+2 -2
src/instrumentation.ts
··· 163 163 console.log(`[forecast] DAILY POLLER COMPLETE`); 164 164 console.log(`[forecast] ========================================`); 165 165 } catch (e: any) { 166 - console.error("[forecast] Midnight storage failed:", e?.message || e); 166 + console.error("[forecast] Daily storage failed:", e?.message || e); 167 167 } 168 168 } 169 169 }, 600000); // Check every 10 minutes (600000 ms) 170 170 171 - console.log(`[forecast] Poller active: checking every 10 minutes for midnight window (00:00-00:30)`); 171 + console.log(`[forecast] Poller active: checking every 10 minutes for 20:00 window (20:00-20:30)`); 172 172 } 173 173 } 174 174