Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quartz Energy Forecast Horizon Select [Development -> Staging] #521

Merged
merged 18 commits into from
Jun 19, 2024
Merged
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
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
# Solar Electricity Nowcasting
# Quartz Energy
### A set of forecasting products driven by the exciting modelling work in Open Climate Fix and the community

The code is as open source as we can possibly make it (safely) and is powered by various forecast APIs, which are also available as services under the same Quartz umbrella.

<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->

This is a "meta-repository" for [Open Climate Fix](https://openclimatefix.org/)'s solar electricity nowcasting project. See [this great Wired article about OCF's solar electricity forecasting work](https://www.wired.co.uk/article/solar-weather-forecasting) for a good intro to solar electricity nowcasting.
Head to [quartz.solar](https://quartz.solar) to find out more or to get in touch about using our Production services.



## Solar Electricity Nowcasting UI

The `nowcasting-app` is the repository for [Open Climate Fix](https://openclimatefix.org/)'s solar electricity nowcasting project. See [this great Wired article about OCF's solar electricity forecasting work](https://www.wired.co.uk/article/solar-weather-forecasting) for a good intro to solar electricity nowcasting.

The plan is to enable the community to build the world's best near-term forecasting system for solar electricity generation, and then let anyone use it! :) We'll do this by using state-of-the-art machine learning and 5-minutely satellite imagery to predict the movement of clouds over the next few hours, and then use this to predict solar electricity generation.

Expand Down
3 changes: 2 additions & 1 deletion apps/nowcasting-app/components/map/deltaMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import mapboxgl, { Expression } from "mapbox-gl";
import { FailedStateMap, LoadStateMap, Map, MeasuringUnit } from "./";
import { ActiveUnit, SelectedData } from "./types";
import { DELTA_BUCKET, VIEWS } from "../../constant";
import ButtonGroup from "../../components/button-group";
import gspShapeData from "../../data/gsp_regions_20220314.json";
import useGlobalState from "../helpers/globalState";
import { formatISODateString, formatISODateStringHuman } from "../helpers/utils";
Expand All @@ -28,7 +27,9 @@ import DeltaColorGuideBar from "./delta-color-guide-bar";
import { safelyUpdateMapData } from "../helpers/mapUtils";
import { generateGeoJsonForecastData } from "../helpers/data";
import { components } from "../../types/quartz-api";
import dynamic from "next/dynamic";
const yellow = theme.extend.colors["ocf-yellow"].DEFAULT;
const ButtonGroup = dynamic(() => import("../../components/button-group"), { ssr: false });

const getRoundedPv = (pv: number, round: boolean = true) => {
if (!round) return Math.round(pv);
Expand Down
4 changes: 3 additions & 1 deletion apps/nowcasting-app/components/map/pvLatestMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import mapboxgl, { Expression } from "mapbox-gl";
import { FailedStateMap, LoadStateMap, Map, MeasuringUnit } from "./";
import { ActiveUnit, SelectedData } from "./types";
import { MAX_POWER_GENERATED, VIEWS } from "../../constant";
import ButtonGroup from "../../components/button-group";
import gspShapeData from "../../data/gsp_regions_20220314.json";
import useGlobalState from "../helpers/globalState";
import { formatISODateString, formatISODateStringHuman } from "../helpers/utils";
Expand All @@ -17,8 +16,11 @@ import { FeatureCollection } from "geojson";
import { safelyUpdateMapData } from "../helpers/mapUtils";
import { components } from "../../types/quartz-api";
import { generateGeoJsonForecastData } from "../helpers/data";
import dynamic from "next/dynamic";
const yellow = theme.extend.colors["ocf-yellow"].DEFAULT;

const ButtonGroup = dynamic(() => import("../../components/button-group"), { ssr: false });

type PvLatestMapProps = {
className?: string;
combinedData: CombinedData;
Expand Down
3 changes: 2 additions & 1 deletion apps/nowcasting-app/components/map/sitesMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
MAX_POWER_GENERATED,
VIEWS
} from "../../constant";
import ButtonGroup from "../../components/button-group";
import gspShapeData from "../../data/gsp_regions_20220314.json";
import dnoShapeData from "../../data/dno_regions_lat_long_converted.json";
import useGlobalState from "../helpers/globalState";
Expand All @@ -26,8 +25,10 @@ import { Feature, FeatureCollection } from "geojson";
import Slider from "./sitesMapFeatures/sitesZoomSlider";
import SitesLegend from "./sitesMapFeatures/sitesLegend";
import { safelyUpdateMapData } from "../helpers/mapUtils";
import dynamic from "next/dynamic";

const yellow = theme.extend.colors["ocf-yellow"].DEFAULT;
const ButtonGroup = dynamic(() => import("../../components/button-group"), { ssr: false });

const getRoundedPv = (pv: number, round: boolean = true) => {
if (!round) return Math.round(pv);
Expand Down
1 change: 1 addition & 0 deletions apps/quartz-app/.env.preview
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
AUTH0_BASE_URL=https://$VERCEL_URL
1 change: 1 addition & 0 deletions apps/quartz-app/.env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
AUTH0_BASE_URL=https://$VERCEL_URL
14 changes: 14 additions & 0 deletions apps/quartz-app/app/api/auth/[auth0]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { handleAuth, handleLogout, HandleAuth } from "@auth0/nextjs-auth0";

/**
* This is a GET endpoint that automatically handles authentication using Auth0.
* We're using a logout option override to redirect to "/logout" after logout.
*
* @function GET
* @returns {HandleAuth} A function that handles authentication.
*/
export const GET = handleAuth({
logout: handleLogout({
returnTo: "/logout",
}),
});
19 changes: 19 additions & 0 deletions apps/quartz-app/app/api/token/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NextRequest, NextResponse } from "next/server";
import { getAccessToken, withApiAuthRequired } from "@auth0/nextjs-auth0";

/**
* This is a secured GET endpoint that requires API authentication.
* It retrieves the access token for the authenticated user.
*
* @async
* @function GET
* @param {NextRequest} req - The Next.js API request object.
* @returns {NextResponse} A JSON response containing the access token.
*/
const GET = withApiAuthRequired(async function GET(req: NextRequest) {
const res = new NextResponse();
const { accessToken } = await getAccessToken(req, res);
return NextResponse.json({ accessToken }, res);
});

export { GET };
20 changes: 14 additions & 6 deletions apps/quartz-app/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,24 @@

body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}

@layer utilities {
.text-balance {
text-wrap: balance;
}
}

.fade-out {
opacity: 1;
animation: fade-out 2s ease-in-out forwards;
}

@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
15 changes: 7 additions & 8 deletions apps/quartz-app/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import Header from "../src/components/layout/Header";
import Providers from "@/app/providers";
import { ReactNode } from "react";
import { UserProvider } from "@auth0/nextjs-auth0/client";
import LayoutWrapper from "@/src/components/layout/LayoutWrapper";

const inter = Inter({ subsets: ["latin"] });

Expand All @@ -19,12 +19,11 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body className={inter.className}>
<Providers>
<Header />
{children}
</Providers>
</body>
<UserProvider>
<body className={inter.className}>
<LayoutWrapper>{children}</LayoutWrapper>
</body>
</UserProvider>
</html>
);
}
93 changes: 93 additions & 0 deletions apps/quartz-app/app/logout/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"use client";

import { useRouter } from "next/navigation";
import { ChevronRightIcon } from "@heroicons/react/solid";
import { SupportIcon, ViewListIcon } from "@heroicons/react/outline";

const MyPage = () => {
const router = useRouter();
const links = [
// {
// title: "Documentation",
// description: "Learn how to integrate our tools with your app",
// icon: BookOpenIcon,
// url: "https://openclimatefix.notion.site/Quartz-Solar-Documentation-0d718915650e4f098470d695aa3494bf",
// },
{
title: "API Reference",
description: "A complete API reference for our library",
icon: ViewListIcon,
url:
process.env.NEXT_PUBLIC_API_URL + "docs" ||
"https://api.quartz.energy/docs",
},
{
title: "Support",
description: "Get help with any problems you encounter",
icon: SupportIcon,
url: "mailto:[email protected]?subject=Quartz%20Energy%20Support%20Request",
},
];
return (
<div className="container flex-1 flex flex-col gap-6 py-24 items-center">
<div className="flex-1 flex flex-col gap-6 justify-center text-center md:text-left items-center">
<h1 className="text-4xl text-white">See you next time.</h1>
<h2 className="text-xl text-white px-3">
You have been successfully logged out.
</h2>
<button
className="bg-ocf-yellow py-2 px-3 rounded-md"
onClick={() => router.push("/api/auth/login")}
>
Log back in &rarr;
</button>
</div>
<div className="max-w-3xl w-full flex flex-1 flex-col gap-6 justify-center items-center">
<div className="mt-16">
<h2 className="text-sm font-semibold tracking-wide text-gray-300 uppercase">
More from Quartz
</h2>
<ul
role="list"
className="mt-4 border-t border-b border-gray-200 divide-y divide-gray-200"
>
{links.map((link, linkIdx) => (
<li
key={`LogoutLink${linkIdx}`}
className="relative flex items-start py-6 space-x-4"
>
<div className="flex-shrink-0">
<span className="flex items-center justify-center w-12 h-12 rounded-lg">
<link.icon
className="w-6 h-6 text-gray-300"
aria-hidden="true"
/>
</span>
</div>
<div className="flex-1 min-w-0">
<h3 className="text-base font-medium text-gray-100">
<span className="rounded-sm focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-gray-500">
<a href={link.url} className="focus:outline-none">
<span className="absolute inset-0" aria-hidden="true" />
{link.title}
</a>
</span>
</h3>
<p className="text-base text-gray-400">{link.description}</p>
</div>
<div className="self-center flex-shrink-0">
<ChevronRightIcon
className="w-5 h-5 text-gray-400"
aria-hidden="true"
/>
</div>
</li>
))}
</ul>
</div>
</div>
</div>
);
};

export default MyPage;
Loading
Loading