From fe5fe3da667f203066e7d01fb6dbb02c4761a81d Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Thu, 22 May 2025 22:48:51 +0300 Subject: [PATCH] working prediction --- src/app/api/predictions/route.js | 122 +++++++++++++++++++++---- src/app/page.js | 19 ++-- src/components/PowerPredictionGraph.js | 29 ++++-- src/components/WeatherForecast.js | 26 ++++-- 4 files changed, 156 insertions(+), 40 deletions(-) diff --git a/src/app/api/predictions/route.js b/src/app/api/predictions/route.js index 21cf644..0e7649d 100644 --- a/src/app/api/predictions/route.js +++ b/src/app/api/predictions/route.js @@ -3,6 +3,30 @@ import { connectToDatabase } from '@/lib/mongodb'; const CACHE_DURATION = 2 * 60 * 60 * 1000; // 2 hours in milliseconds +// Solar panel efficiency factors based on real-world data +const WEATHER_IMPACT = { + // Cloud coverage impact on solar efficiency (based on NREL data) + CLOUD_IMPACT: { + CLEAR: 1.0, // 0-10% clouds + PARTLY_CLOUDY: 0.7, // 11-50% clouds + MOSTLY_CLOUDY: 0.4, // 51-90% clouds + OVERCAST: 0.2, // 91-100% clouds + }, + // Temperature impact (based on solar panel temperature coefficient) + TEMP_IMPACT: { + OPTIMAL: 1.0, // 25°C (optimal temperature) + COLD: 0.95, // < 10°C + HOT: 0.85, // > 35°C + }, + // Rain impact + RAIN_IMPACT: { + NONE: 1.0, // 0% chance + LIGHT: 0.8, // 1-30% chance + MODERATE: 0.6, // 31-60% chance + HEAVY: 0.4, // 61-100% chance + } +}; + async function fetchWeatherData() { const response = await fetch( `http://api.weatherapi.com/v1/forecast.json?key=${process.env.WEATHER_API_KEY}&q=47.06149235737582,28.86683069635403&days=7&aqi=no` @@ -11,30 +35,94 @@ async function fetchWeatherData() { return response.json(); } +function getCloudImpact(cloudCover) { + if (cloudCover <= 10) return WEATHER_IMPACT.CLOUD_IMPACT.CLEAR; + if (cloudCover <= 50) return WEATHER_IMPACT.CLOUD_IMPACT.PARTLY_CLOUDY; + if (cloudCover <= 90) return WEATHER_IMPACT.CLOUD_IMPACT.MOSTLY_CLOUDY; + return WEATHER_IMPACT.CLOUD_IMPACT.OVERCAST; +} + +function getTempImpact(temp) { + if (temp >= 10 && temp <= 35) { + // Linear interpolation for temperatures between optimal ranges + if (temp <= 25) { + return 1.0 - ((25 - temp) * 0.003); // 0.3% loss per degree below optimal + } else { + return 1.0 - ((temp - 25) * 0.01); // 1% loss per degree above optimal + } + } + return temp < 10 ? WEATHER_IMPACT.TEMP_IMPACT.COLD : WEATHER_IMPACT.TEMP_IMPACT.HOT; +} + +function getRainImpact(rainChance) { + if (rainChance <= 0) return WEATHER_IMPACT.RAIN_IMPACT.NONE; + if (rainChance <= 30) return WEATHER_IMPACT.RAIN_IMPACT.LIGHT; + if (rainChance <= 60) return WEATHER_IMPACT.RAIN_IMPACT.MODERATE; + return WEATHER_IMPACT.RAIN_IMPACT.HEAVY; +} + async function calculatePrediction(weatherData, historicalData) { - // Simple prediction model based on weather conditions and historical data + // Validate historical data + if (!historicalData || historicalData.length === 0) { + throw new Error('No historical data available for predictions'); + } + + // Filter out invalid data points and calculate average + const validHistoricalData = historicalData.filter(data => + data && + data.data && + typeof data.data.pv_input_power === 'number' && + !isNaN(data.data.pv_input_power) + ); + + if (validHistoricalData.length === 0) { + throw new Error('No valid historical power data available'); + } + + // Calculate base power from historical data + const historicalAvg = validHistoricalData.reduce((sum, data) => sum + data.data.pv_input_power, 0) / validHistoricalData.length; + const basePower = Math.max(200, historicalAvg); // Minimum 200W baseline + + // Calculate predictions for each day const predictions = weatherData.forecast.forecastday.map(day => { - const cloudCover = day.day.cloud; - const rainChance = day.day.daily_chance_of_rain; - const avgTemp = (day.day.maxtemp_c + day.day.mintemp_c) / 2; + // Ensure we have the required data + if (!day || !day.day) { + throw new Error('Invalid weather data structure'); + } + + const cloudCover = day.day.cloud || 0; + const rainChance = day.day.daily_chance_of_rain || 0; + const avgTemp = ((day.day.maxtemp_c || 0) + (day.day.mintemp_c || 0)) / 2; - // Base prediction on historical average - const historicalAvg = historicalData.reduce((sum, data) => sum + data.power, 0) / historicalData.length; + // Validate weather data + if (typeof cloudCover !== 'number' || typeof rainChance !== 'number' || typeof avgTemp !== 'number') { + console.error('Weather data validation failed:', { cloudCover, rainChance, avgTemp }); + throw new Error('Invalid weather data received'); + } - // Adjust based on weather conditions - let weatherFactor = 1.0; - weatherFactor *= (100 - cloudCover) / 100; // Less clouds = more power - weatherFactor *= (100 - rainChance) / 100; // Less rain = more power - weatherFactor *= Math.min(1, 1 - Math.abs(avgTemp - 25) / 50); // Optimal temperature around 25°C + // Calculate weather impact factors + const cloudImpact = getCloudImpact(cloudCover); + const tempImpact = getTempImpact(avgTemp); + const rainImpact = getRainImpact(rainChance); - const predictedPower = historicalAvg * weatherFactor; - const confidence = 0.7 + (weatherFactor * 0.3); // Higher confidence for better weather conditions + // Calculate final prediction with all factors + const weatherFactor = cloudImpact * tempImpact * rainImpact; + const predictedPower = Math.round(basePower * weatherFactor); + + // Calculate confidence based on weather stability + const weatherStability = 1 - (Math.abs(cloudImpact - 1) + Math.abs(tempImpact - 1) + Math.abs(rainImpact - 1)) / 3; + const confidence = 0.7 + (weatherStability * 0.3); return { date: new Date(day.date), - predictedPower: Math.round(predictedPower * 100) / 100, + predictedPower, weatherData: day, - confidence: Math.round(confidence * 100) / 100 + confidence: Math.round(confidence * 100) / 100, + factors: { + cloudImpact, + tempImpact, + rainImpact + } }; }); @@ -64,7 +152,7 @@ export async function GET() { // Get historical power data for the last 30 days const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); - const historicalData = await db.collection('power') + const historicalData = await db.collection('solar_data') .find({ timestamp: { $gte: thirtyDaysAgo } }) .toArray(); @@ -88,7 +176,7 @@ export async function GET() { } catch (error) { console.error('Prediction error:', error); return NextResponse.json( - { error: 'Failed to generate predictions' }, + { error: error.message || 'Failed to generate predictions' }, { status: 500 } ); } diff --git a/src/app/page.js b/src/app/page.js index e02e665..9ebd5f6 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -16,7 +16,7 @@ import PowerPredictionGraph from '@/components/PowerPredictionGraph' export default function Dashboard() { const [data, setData] = useState([]) const [currentData, setCurrentData] = useState([]) - const [timeRange, setTimeRange] = useState('today') + const [chartTimeRange, setChartTimeRange] = useState('today') const [loading, setLoading] = useState(true) const [minLoadingComplete, setMinLoadingComplete] = useState(false) const [lastUpdateTime, setLastUpdateTime] = useState(null) @@ -52,8 +52,8 @@ export default function Dashboard() { const latestTimestamp = newData[newData.length - 1].timestamp.$date || newData[newData.length - 1].timestamp setLastUpdateTime(latestTimestamp) - // Update historical data if viewing today - if (timeRange === 'today') { + // Update chart data if viewing today's data + if (chartTimeRange === 'today') { setData(prevData => { const mergedData = [...prevData] newData.forEach(newItem => { @@ -79,10 +79,9 @@ export default function Dashboard() { const fetchHistoricalData = async () => { try { setLastUpdateTime(null) // Reset last update time when changing time range - const response = await fetch(`/api/solar-data?timeRange=${timeRange}`) + const response = await fetch(`/api/solar-data?timeRange=${chartTimeRange}`) const newData = await response.json() setData(newData) - setCurrentData(newData) } catch (error) { console.error('Error fetching historical data:', error) } finally { @@ -107,7 +106,7 @@ export default function Dashboard() { useEffect(() => { fetchHistoricalData() - }, [timeRange]) + }, [chartTimeRange]) if (loading || !minLoadingComplete) { return ( @@ -198,22 +197,22 @@ export default function Dashboard() { > - + - + - + - + diff --git a/src/components/PowerPredictionGraph.js b/src/components/PowerPredictionGraph.js index ada4553..f148889 100644 --- a/src/components/PowerPredictionGraph.js +++ b/src/components/PowerPredictionGraph.js @@ -12,6 +12,7 @@ import { ResponsiveContainer, Legend } from 'recharts'; +import { motion } from 'framer-motion'; export default function PowerPredictionGraph() { const [predictions, setPredictions] = useState(null); @@ -37,17 +38,31 @@ export default function PowerPredictionGraph() { if (loading) { return ( -
-
Loading predictions...
-
+ +
+ +
+
+
+
+
+
+
); } if (error) { return ( -
-
Error loading predictions
-
+ +
+
Error loading predictions
+
+
); } @@ -77,7 +92,7 @@ export default function PowerPredictionGraph() { stroke="#9CA3AF" tick={{ fill: '#9CA3AF' }} label={{ - value: 'Power (kW)', + value: 'Power (W)', angle: -90, position: 'insideLeft', fill: '#9CA3AF' diff --git a/src/components/WeatherForecast.js b/src/components/WeatherForecast.js index f617c09..f618eaa 100644 --- a/src/components/WeatherForecast.js +++ b/src/components/WeatherForecast.js @@ -50,17 +50,31 @@ export default function WeatherForecast() { if (loading) { return ( -
-
Loading weather data...
-
+ +
+ +
+
+
+
+
+
+
); } if (error) { return ( -
-
Error loading weather data
-
+ +
+
Error loading weather data
+
+
); }