prediction charts
This commit is contained in:
95
src/app/api/predictions/route.js
Normal file
95
src/app/api/predictions/route.js
Normal file
@@ -0,0 +1,95 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { connectToDatabase } from '@/lib/mongodb';
|
||||
|
||||
const CACHE_DURATION = 2 * 60 * 60 * 1000; // 2 hours in milliseconds
|
||||
|
||||
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`
|
||||
);
|
||||
if (!response.ok) throw new Error('Failed to fetch weather data');
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function calculatePrediction(weatherData, historicalData) {
|
||||
// Simple prediction model based on weather conditions and historical data
|
||||
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;
|
||||
|
||||
// Base prediction on historical average
|
||||
const historicalAvg = historicalData.reduce((sum, data) => sum + data.power, 0) / historicalData.length;
|
||||
|
||||
// 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
|
||||
|
||||
const predictedPower = historicalAvg * weatherFactor;
|
||||
const confidence = 0.7 + (weatherFactor * 0.3); // Higher confidence for better weather conditions
|
||||
|
||||
return {
|
||||
date: new Date(day.date),
|
||||
predictedPower: Math.round(predictedPower * 100) / 100,
|
||||
weatherData: day,
|
||||
confidence: Math.round(confidence * 100) / 100
|
||||
};
|
||||
});
|
||||
|
||||
return predictions;
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const { db } = await connectToDatabase();
|
||||
const now = new Date();
|
||||
|
||||
// Check for cached predictions
|
||||
const cachedPredictions = await db.collection('predictions')
|
||||
.find({
|
||||
date: { $gte: now },
|
||||
lastUpdated: { $gte: new Date(now.getTime() - CACHE_DURATION) }
|
||||
})
|
||||
.sort({ date: 1 })
|
||||
.toArray();
|
||||
|
||||
if (cachedPredictions.length === 7) {
|
||||
return NextResponse.json(cachedPredictions);
|
||||
}
|
||||
|
||||
// Fetch fresh weather data
|
||||
const weatherData = await fetchWeatherData();
|
||||
|
||||
// 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')
|
||||
.find({ timestamp: { $gte: thirtyDaysAgo } })
|
||||
.toArray();
|
||||
|
||||
// Calculate new predictions
|
||||
const predictions = await calculatePrediction(weatherData, historicalData);
|
||||
|
||||
// Store predictions in database
|
||||
const bulkOps = predictions.map(prediction => ({
|
||||
updateOne: {
|
||||
filter: { date: prediction.date },
|
||||
update: { $set: prediction },
|
||||
upsert: true
|
||||
}
|
||||
}));
|
||||
|
||||
if (bulkOps.length > 0) {
|
||||
await db.collection('predictions').bulkWrite(bulkOps);
|
||||
}
|
||||
|
||||
return NextResponse.json(predictions);
|
||||
} catch (error) {
|
||||
console.error('Prediction error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to generate predictions' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import { Navbar } from '@/components/Navbar'
|
||||
import WeatherForecast from '@/components/WeatherForecast'
|
||||
import PowerPredictionGraph from '@/components/PowerPredictionGraph'
|
||||
|
||||
export default function Dashboard() {
|
||||
const [data, setData] = useState([])
|
||||
@@ -180,6 +181,15 @@ export default function Dashboard() {
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="w-full"
|
||||
>
|
||||
<PowerPredictionGraph />
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
|
||||
118
src/components/PowerPredictionGraph.js
Normal file
118
src/components/PowerPredictionGraph.js
Normal file
@@ -0,0 +1,118 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import {
|
||||
LineChart,
|
||||
Line,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
Legend
|
||||
} from 'recharts';
|
||||
|
||||
export default function PowerPredictionGraph() {
|
||||
const [predictions, setPredictions] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPredictions = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/predictions');
|
||||
if (!response.ok) throw new Error('Failed to fetch predictions');
|
||||
const data = await response.json();
|
||||
setPredictions(data);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPredictions();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="w-full h-64 bg-gray-100 rounded-lg animate-pulse flex items-center justify-center">
|
||||
<div className="text-gray-400">Loading predictions...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="w-full h-64 bg-red-50 rounded-lg flex items-center justify-center">
|
||||
<div className="text-red-500">Error loading predictions</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!predictions?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const chartData = predictions.map(pred => ({
|
||||
date: new Date(pred.date).toLocaleDateString('en-US', { weekday: 'short' }),
|
||||
predicted: pred.predictedPower,
|
||||
confidence: pred.confidence * 100
|
||||
}));
|
||||
|
||||
return (
|
||||
<Card className="w-full p-4 bg-gray-800/50 backdrop-blur-sm border-gray-700 hover:bg-gray-800/70 transition-all duration-300">
|
||||
<h2 className="text-lg font-semibold text-white mb-4">7-Day Power Generation Forecast</h2>
|
||||
<div className="h-64">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={chartData} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#374151" />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
stroke="#9CA3AF"
|
||||
tick={{ fill: '#9CA3AF' }}
|
||||
/>
|
||||
<YAxis
|
||||
stroke="#9CA3AF"
|
||||
tick={{ fill: '#9CA3AF' }}
|
||||
label={{
|
||||
value: 'Power (kW)',
|
||||
angle: -90,
|
||||
position: 'insideLeft',
|
||||
fill: '#9CA3AF'
|
||||
}}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: '#1F2937',
|
||||
border: '1px solid #374151',
|
||||
borderRadius: '0.375rem',
|
||||
color: '#F3F4F6'
|
||||
}}
|
||||
/>
|
||||
<Legend />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="predicted"
|
||||
name="Predicted Power"
|
||||
stroke="#60A5FA"
|
||||
strokeWidth={2}
|
||||
dot={{ fill: '#60A5FA', strokeWidth: 2 }}
|
||||
activeDot={{ r: 8 }}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="confidence"
|
||||
name="Confidence %"
|
||||
stroke="#34D399"
|
||||
strokeWidth={2}
|
||||
dot={{ fill: '#34D399', strokeWidth: 2 }}
|
||||
activeDot={{ r: 8 }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
30
src/lib/models/prediction.js
Normal file
30
src/lib/models/prediction.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
const predictionSchema = new mongoose.Schema({
|
||||
date: {
|
||||
type: Date,
|
||||
required: true,
|
||||
index: true
|
||||
},
|
||||
predictedPower: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
weatherData: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
confidence: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
lastUpdated: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
});
|
||||
|
||||
// Create compound index for efficient querying
|
||||
predictionSchema.index({ date: 1, lastUpdated: 1 });
|
||||
|
||||
export const Prediction = mongoose.models.Prediction || mongoose.model('Prediction', predictionSchema);
|
||||
Reference in New Issue
Block a user