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 { motion, AnimatePresence } from 'framer-motion'
|
||||||
import { Navbar } from '@/components/Navbar'
|
import { Navbar } from '@/components/Navbar'
|
||||||
import WeatherForecast from '@/components/WeatherForecast'
|
import WeatherForecast from '@/components/WeatherForecast'
|
||||||
|
import PowerPredictionGraph from '@/components/PowerPredictionGraph'
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const [data, setData] = useState([])
|
const [data, setData] = useState([])
|
||||||
@@ -180,6 +181,15 @@ export default function Dashboard() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
variants={containerVariants}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<PowerPredictionGraph />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
variants={containerVariants}
|
variants={containerVariants}
|
||||||
initial="hidden"
|
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