Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/nowcasting-app/components/API-Routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const API_BASE_URL = process.env.NEXT_PUBLIC_API_PREFIX || "";
const IS_FAKE = process.env.NEXT_PUBLIC_IS_FAKE === "1"; // Placeholder for fake data env variable. Please advise on how to implement this?

const paths = {
allForecasts: `${API_BASE_URL}/v0/solar/GB/gsp/forecast/all/?isFake=${IS_FAKE}`,
allPVLive: `${API_BASE_URL}/v0/solar/GB/gsp/pvlive/all/?isFake=${IS_FAKE}`,
forecastByGSPId: (gspId: number) => `${API_BASE_URL}/v0/solar/GB/gsp/${gspId}/forecast/?isFake=${IS_FAKE}`,
PVLiveByGSPId: (gspId: number) => `${API_BASE_URL}/v0/solar/GB/gsp/${gspId}/pvlive/?isFake=${IS_FAKE}`,
nationalPVLive: `${API_BASE_URL}/v0/solar/GB/national/pvlive/?isFake=${IS_FAKE}`
};

export { paths, IS_FAKE };
49 changes: 49 additions & 0 deletions apps/nowcasting-app/components/fake-API-calls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { paths, IS_FAKE } from "./API-Routes"

const FakeResponsehandler = async (response: Response) => {
if (!response.ok) {
if (IS_FAKE) {
return { fake : true, data: [] }; // Return empty array if no fake data returned
}
throw new Error(`HTTP Error status code: ${response.status}`);
}
return response.json();
};

// Fetch all forecasts
const fetchAllForecasts = async () => {
const response = await fetch(paths.allForecasts);
return FakeResponsehandler(response);
};

// Fetch forecasts by GSP ID
const fetchforecastByGSPID = async (gspId: number) => {
const response = await fetch(paths.forecastByGSPId(gspId));
return FakeResponsehandler(response);
};

// Fetch all PVLive forecasts
const fetchallPVLive = async () => {
const response = await fetch(paths.allPVLive);
return FakeResponsehandler(response);
};

// Fetch PVLive by GSP ID
const fetchPVLive = async (gspId: number) => {
const response = await fetch(paths.PVLiveByGSPId(gspId));
return FakeResponsehandler(response);
};

// Fetch fake PVLive National forecasts
const nationalPVLive = async () => {
const response = await fetch(paths.nationalPVLive);
return FakeResponsehandler(response);
};

export {
fetchAllForecasts,
fetchforecastByGSPID,
fetchallPVLive,
fetchPVLive,
nationalPVLive
};
129 changes: 129 additions & 0 deletions apps/nowcasting-app/components/fake-forecast-generation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React, { useEffect, useState } from "react";
import { IS_FAKE } from "./API-Routes";
import { fetchAllForecasts,
fetchforecastByGSPID,
fetchallPVLive,
fetchPVLive,
nationalPVLive
} from "./fake-API-calls";
import Tooltip from "./tooltip";
import Toggle from "./Toggle";
import ButtonGroup from "./button-group";

// Define available route keys
type RouteKey =
| "allForecasts"
| "forecastByGspId"
| "allPVLive"
| "PVLiveByGspId"
| "nationalPVLive";

interface GSPIds {
itemId?: number;
}

const ForecastsComponent: React.FC<GSPIds> = ({ itemId }) => {
const [selectedRoutes, setSelectedRoutes] = useState<RouteKey[]>(["allForecasts"]); // Set default route to 'allForecasts'
const [results, setResults] = useState<Array<{ route: RouteKey; data: any }>>([]);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);

useEffect(() => {
const loadForecasts = async () => {
setIsLoading(true);
try {
// Map selected routes to their corresponding FastAPI calls
const promises = selectedRoutes.map(async (route) => {
try {
let data;
switch (route) {
case "allForecasts":
data = await fetchAllForecasts();
break;
case "forecastByGspId":
if (!itemId && !IS_FAKE) throw new Error("Please enter GSP ID for this route");
data = await fetchforecastByGSPID(itemId || 1);
break;
case "allPVLive":
data = await fetchallPVLive();
break;
case "PVLiveByGspId":
if (!itemId && !IS_FAKE) throw new Error("Please enter GSP ID for this route");
data = await fetchPVLive(itemId || 1);
break;
case "nationalPVLive":
data = await nationalPVLive();
break;
default:
throw new Error(`Unknown route: ${route}`);
}
return { route, data };
} catch (error) {
if (IS_FAKE) {
return { route, data: { fake: true, data: [] } };
}
throw error;
}
});

// Wait for all requests to complete
const results = await Promise.all(promises);
setResults(results);
setError(null);
} catch (error) {
setError(error instanceof Error ? error.message : "Unable to fetch data");
} finally {
setIsLoading(false);
}
};

loadForecasts();
}, [setSelectedRoutes, setResults, setError, setIsLoading, itemId]);

const toggleRoute = (route: RouteKey) => {
setSelectedRoutes(prev =>
prev.includes(route)
? prev.filter(r => r !== route)
: [...prev, route]
);
};

// Define Route Configuration
const routeConfig: Array<{ key: RouteKey; label: string; tip: string }> = [
{ key: "allForecasts", label: "All available forecasts", tip: "Fetch all available forecasts" },
{ key: "forecastByGspId", label: "Forecast by GSP ID", tip: "Fetch forecast by GSP ID" },
{ key: "allPVLive", label: "All PV Live", tip: "Fetch all PV live data" },
{ key: "PVLiveByGspId", label: "PV Live by GSP ID", tip: "Fetch PV live data by GSP ID" },
{ key: "nationalPVLive", label: "National PV Live", tip: "Fetch national PV live data" }
];

return (
<div>
{IS_FAKE && (
<div className="bg-yellow-100 p-2 mb-4 rounded">
Running in Fake Data Mode
</div>
)}

<div className="mb-4">
{routeConfig.map(({ key, label, tip }) => (
<Tooltip key={key} tip={tip} position="top">
<Toggle
onClick={() => toggleRoute(key)}
visible={selectedRoutes.includes(key)}
/>
<span>{label}</span>
</Tooltip>
))}
</div>

{isLoading && <div>Loading Fake Forecasts...</div>}
{error && <div className="text-red-500">{error}</div>}
<pre>{JSON.stringify(results, null, 2)}</pre>

<ButtonGroup rightString="Forecast Data" />
</div>
);
};

export default ForecastsComponent;
69 changes: 69 additions & 0 deletions apps/nowcasting-app/pages/fake-forecasts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, { useEffect, useState } from "react";
import ForecastsComponent from "../components/fake-forecast-generation";
import Layout from "../components/layout/layout";
import useGlobalState, { get30MinNow } from '../components/helpers/globalState';
import { AGGREGATION_LEVELS } from "../constant";

export default function ForecastsPage() {
const [gspId, setGspId] = useState<number | 1>(1);

// Use global state for necessary views and states
const [, setView] = useGlobalState("aggregationLevel");
const [clickedGspId, setClickedGspId] = useGlobalState("clickedGspId");
const [, setSelectedISOTime] = useGlobalState("selectedISOTime");

useEffect(() => {
setView(AGGREGATION_LEVELS.GSP);

setSelectedISOTime(get30MinNow());

if (clickedGspId !== undefined && typeof clickedGspId === "number") {
setClickedGspId(clickedGspId);
}

return () => {
setView(AGGREGATION_LEVELS.GSP);

// Handle National view
if (clickedGspId == 0) {
setView(AGGREGATION_LEVELS.NATIONAL);
}
};

}, [setView, setSelectedISOTime, clickedGspId]);

const handleGspIdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
const newGspId = value === '' ? 1 : parseInt(value, 10)
setGspId(newGspId);

setClickedGspId(newGspId);
};

return (
<Layout>
<div className="container mx-auto px-4 py-8">
<h1 className="text-2xl font-bold mb-6">Fake Forecasts</h1>

<div className="mb-6">
<label className="block text-sm font-medium mb-2">
GSP ID
<input
type="number"
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2"
value={gspId}
onChange={handleGspIdChange}
placeholder="Enter GSP ID (default: 1)"
min="1"
/>
</label>
<p className="text-sm text-gray-500 mt-1">
Grid Supply Point (GSP) ID for targeted forecast data
</p>
</div>

<ForecastsComponent itemId={gspId} />
</div>
</Layout>
);
};