55 lines
1.8 KiB
TypeScript
55 lines
1.8 KiB
TypeScript
'use client';
|
|
|
|
import { fetchLatestPrices } from '@/actions/prices.js';
|
|
import type { getLatestPrices } from '@/generated/prisma/sql.js';
|
|
import { deserialize } from '@/lib/superjson.js';
|
|
import { useEffect, useRef } from 'react';
|
|
import { toast } from 'sonner';
|
|
|
|
const PRICE_SPIKE_THRESHOLD_MWH = 100;
|
|
const CHECK_INTERVAL_MS = 60_000;
|
|
|
|
export function PriceAlertMonitor() {
|
|
const previousPricesRef = useRef<Map<string, number>>(new Map());
|
|
const initialLoadRef = useRef(true);
|
|
|
|
useEffect(() => {
|
|
async function checkForSpikes() {
|
|
const result = await fetchLatestPrices();
|
|
if (!result.ok) return;
|
|
|
|
const prices = deserialize<getLatestPrices.Result[]>(result.data);
|
|
const prevPrices = previousPricesRef.current;
|
|
|
|
for (const p of prices) {
|
|
const prevPrice = prevPrices.get(p.region_code);
|
|
|
|
if (!initialLoadRef.current) {
|
|
if (p.price_mwh >= PRICE_SPIKE_THRESHOLD_MWH) {
|
|
toast.error(`Price Spike: ${p.region_code}`, {
|
|
description: `${p.region_name} hit $${p.price_mwh.toFixed(2)}/MWh — above $${PRICE_SPIKE_THRESHOLD_MWH} threshold`,
|
|
duration: 8000,
|
|
});
|
|
} else if (prevPrice !== undefined && p.price_mwh > prevPrice * 1.15) {
|
|
toast.warning(`Price Jump: ${p.region_code}`, {
|
|
description: `${p.region_name} jumped to $${p.price_mwh.toFixed(2)}/MWh (+${(((p.price_mwh - prevPrice) / prevPrice) * 100).toFixed(1)}%)`,
|
|
duration: 6000,
|
|
});
|
|
}
|
|
}
|
|
|
|
prevPrices.set(p.region_code, p.price_mwh);
|
|
}
|
|
|
|
initialLoadRef.current = false;
|
|
}
|
|
|
|
void checkForSpikes();
|
|
|
|
const interval = setInterval(() => void checkForSpikes(), CHECK_INTERVAL_MS);
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
return null;
|
|
}
|