diff --git a/backend/lambda/gb_snapshot/bm/boalf.py b/backend/lambda/gb_snapshot/bm/boalf.py index 21c0c59..7df74ac 100644 --- a/backend/lambda/gb_snapshot/bm/boalf.py +++ b/backend/lambda/gb_snapshot/bm/boalf.py @@ -58,8 +58,13 @@ def _parse_raw_data(self, raw: RawBoalfResponse) -> pd.DataFrame: logging.info( f"parsing {len(raw.data)} pn records for {self.params.model_dump_json()}..." ) + data = [r.model_dump() for r in raw.data] + if len(data) == 0: + logging.info(f"no boalf data for {self.params}") + output = {} + return pd.DataFrame(columns=["level", "delta"], index=output.keys()) - df = pd.DataFrame([r.model_dump() for r in raw.data]) + df = pd.DataFrame(data) df = df.dropna(subset=["bmUnit"]) output = {} for bmUnit, group in df.groupby("bmUnit"): diff --git a/backend/lambda/gb_snapshot/bm/mels.py b/backend/lambda/gb_snapshot/bm/mels.py index a891080..d101ec2 100644 --- a/backend/lambda/gb_snapshot/bm/mels.py +++ b/backend/lambda/gb_snapshot/bm/mels.py @@ -43,8 +43,14 @@ def _parse_raw_data(self, raw: RawMelsResponse) -> pd.Series: logging.info( f"parsing {len(raw.data)} mels records for {self.params.model_dump_json()}..." ) + data = [r.model_dump() for r in raw.data] + if len(data) == 0: + raise Exception( + "no data returned from mels request. Likely a problem with the API." + ) + + df = pd.DataFrame(data) - df = pd.DataFrame([r.model_dump() for r in raw.data]) df = df.dropna(subset=["bmUnit"]) output = {} for bmUnit, group in df.groupby("bmUnit"): diff --git a/backend/lambda/gb_snapshot/bm/pn.py b/backend/lambda/gb_snapshot/bm/pn.py index 2aab63e..cdf7e7e 100644 --- a/backend/lambda/gb_snapshot/bm/pn.py +++ b/backend/lambda/gb_snapshot/bm/pn.py @@ -43,7 +43,13 @@ def _parse_raw_data(self, raw: RawPnResponse) -> pd.DataFrame: logging.info( f"parsing {len(raw.data)} pn records for {self.params.model_dump_json()}..." ) - df = pd.DataFrame([r.model_dump() for r in raw.data]) + + data = [r.model_dump() for r in raw.data] + if len(data) == 0: + raise Exception( + "no data returned from mels request. Likely a problem with the API." + ) + df = pd.DataFrame(data) df = df.dropna(subset=["bmUnit"]) output = {} for bmUnit, group in df.groupby("bmUnit"): diff --git a/backend/lib/kilowatts-grid-stack.ts b/backend/lib/kilowatts-grid-stack.ts index b7a8cb2..57eca24 100644 --- a/backend/lib/kilowatts-grid-stack.ts +++ b/backend/lib/kilowatts-grid-stack.ts @@ -88,7 +88,7 @@ export class KilowattsGridStack extends cdk.Stack { functionName: "gb-snapshot", code: lambda.Code.fromAsset("lambda"), handler: "gb_snapshot.handler.handler", - timeout: cdk.Duration.seconds(60), + timeout: cdk.Duration.seconds(90), memorySize: 512, environment: { BUCKET_NAME: bucket.bucketName, diff --git a/components/gb-live/bottom-sheet-tabs/totals-list/balancing-totals/balancing-totals.tsx b/components/gb-live/bottom-sheet-tabs/totals-list/balancing-totals/balancing-totals.tsx index ea82b70..977c4d3 100644 --- a/components/gb-live/bottom-sheet-tabs/totals-list/balancing-totals/balancing-totals.tsx +++ b/components/gb-live/bottom-sheet-tabs/totals-list/balancing-totals/balancing-totals.tsx @@ -8,7 +8,7 @@ import { GbLiveListItemBalancingTotal } from "../../live-list-item/live-list-ite export const GbBalancingTotals = () => { const { data } = useGbSummaryOutputQuery(undefined, { - pollingInterval: 1000 * 15 + pollingInterval: 1000 * 60 }); return ( diff --git a/components/gb-live/bottom-sheet-tabs/totals-list/totals-list.tsx b/components/gb-live/bottom-sheet-tabs/totals-list/totals-list.tsx index 0356fc4..8370367 100644 --- a/components/gb-live/bottom-sheet-tabs/totals-list/totals-list.tsx +++ b/components/gb-live/bottom-sheet-tabs/totals-list/totals-list.tsx @@ -1,6 +1,5 @@ import React from "react"; -import { FlatList, StyleSheet, View } from "react-native"; -import { Button, Card, Icon, Text } from "@rneui/themed"; +import { FlatList, Platform } from "react-native"; import { useGbSummaryOutputQuery } from "../../../../state/apis/cloudfront/api"; import { @@ -9,6 +8,7 @@ import { calculateCycleSeconds } from "../../../../state/utils"; import { ErrorDataRetryCard } from "../../error-data-retry-card"; +import StaleDataCard from "../../stale-data-card"; import { GbLiveListItem } from "../live-list-item/live-list-item"; import { GbBalancingTotals } from "./balancing-totals/balancing-totals"; @@ -19,7 +19,7 @@ export const GbTotalsList: React.FC = () => { const { data, isLoading, refetch, isError } = useGbSummaryOutputQuery( undefined, { - pollingInterval: 1000 * 15, + pollingInterval: 1000 * 60, refetchOnReconnect: true } ); @@ -33,6 +33,7 @@ export const GbTotalsList: React.FC = () => { data={data && data.totals} refreshing={isLoading} onRefresh={() => refetch()} + ListHeaderComponent={Platform.OS !== "web" && StaleDataCard} renderItem={({ item }) => ( { const { data, isLoading, refetch, isError } = useGbSummaryOutputQuery( undefined, { - pollingInterval: 1000 * 15, + pollingInterval: 1000 * 60, refetchOnReconnect: true } ); @@ -72,6 +73,7 @@ export const GbUnitGroupsList: React.FC = () => { code={item.code} /> )} + ListHeaderComponent={Platform.OS !== "web" && StaleDataCard} ListFooterComponent={ Platform.OS === "web" ? ( { return ( @@ -42,14 +43,17 @@ const Live: React.FC = () => { List App - {currentTab === 0 && } - {currentTab === 1 && } - {currentTab === 2 && ( -
- -
- )} - {currentTab === 3 && } + <> + + {currentTab === 0 && } + {currentTab === 1 && } + {currentTab === 2 && ( +
+ +
+ )} + {currentTab === 3 && } + ); }; diff --git a/components/gb-live/stale-data-card.tsx b/components/gb-live/stale-data-card.tsx new file mode 100644 index 0000000..b8be683 --- /dev/null +++ b/components/gb-live/stale-data-card.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import { Linking, View } from "react-native"; +import { Button, Card, Text } from "@rneui/themed"; + +import { useGbSummaryOutputQuery } from "../../state/apis/cloudfront/api"; +import { GbSummaryOutputResponse } from "../../state/apis/cloudfront/types"; + +interface StaleDataCardProps {} + +const THRESHOLD_MINUTES = 10; + +const determineIfDataIsStale = (data: GbSummaryOutputResponse) => { + if (!data) return false; + const lastUpdated = new Date(data.dt); + const now = new Date(); + const diff = now.getTime() - lastUpdated.getTime(); + return diff > THRESHOLD_MINUTES * 60 * 1000; +}; + +const StaleDataCard: React.FC = () => { + const data = useGbSummaryOutputQuery(); + if (!data.currentData) return undefined; + const isStale = determineIfDataIsStale(data.data); + if (!isStale) return undefined; + return ( + + + Data Error + + {`Data shown is from ${new Date(Date.parse(data.data.dt)).toLocaleString()} and is stale/out-of-date. There may be an outage affecting Elexon/BMRS data, our data pipeline, or your internet connection. `} + + + + + + + + + + + ); +}; +export default StaleDataCard;