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() {
>