prediction charts

This commit is contained in:
lumijiez
2025-05-22 22:17:37 +03:00
parent 03d5f54b61
commit e2db9ed618
4 changed files with 253 additions and 0 deletions

View 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 }
);
}
}

View File

@@ -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"

View 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>
);
}

View 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);