import { Box, Button, Card, CardHeader, CircularProgress, Grid2 as Grid, LinearProgress, Typography } from "@mui/material"; import { teal } from "@mui/material/colors"; import { makeStyles } from "@mui/styles"; import SingleSetAreaChart, { SSAreaDataPoint } from "charts/SingleSetAreaChart"; import { Gate } from "models/Gate"; import moment, { Moment } from "moment"; import { pond } from "protobuf-ts/pond"; import { useGateAPI, useGlobalState } from "providers"; import React, { useEffect, useState } from "react"; interface Props { gate: Gate; device: string | number; start: Moment; end: Moment; ambient?: string; newXDomain?: number[] | string[]; pressureComponent?: string; setPCAState: (state: boolean) => void; multiGraphZoom?: (domain: number[] | string[]) => void; } const useStyles = makeStyles(() => ({ calcCard: { height: 100, display: "flex", alignItems: "center" }, eventCard: { height: 100, paddingX: 10, display: "flex", alignItems: "center" }, runtimeGrid: { marginBottom: 2 }, eventGrid: { marginTop: 2 } })); export default function GateFlowGraph(props: Props) { const { gate, device, ambient, pressureComponent, setPCAState, start, end, newXDomain, multiGraphZoom } = props; const [{as}] = useGlobalState(); const gateAPI = useGateAPI(); const [flowData, setFlowData] = useState([]); const [loadingChartData, setLoadingChartData] = useState(false); const [recent, setRecent] = useState(); const [runtime, setRuntime] = useState(); const classes = useStyles(); const [flowEvents, setFlowEvents] = useState([]); const [eventsLoading, setEventsLoading] = useState(false); const eventThreshold = 5; const idleFlow = 2.44; useEffect(() => { if (loadingChartData) return; if (ambient && pressureComponent) { let recent: SSAreaDataPoint | undefined; setLoadingChartData(true); gateAPI .listGateAirflow( gate.key, device, ambient, pressureComponent, start.toISOString(), end.toISOString(), undefined, undefined, as ) .then(resp => { let data: SSAreaDataPoint[] = []; if (resp.data.values) { let start: Moment | undefined; let stop: Moment | undefined; let runtime = 0; resp.data.values.forEach((val, i) => { let time = moment(val.time); let newPoint: SSAreaDataPoint = { timestamp: time.valueOf(), value: val.airFlow ?? 0 }; data.push(newPoint); if (!recent || recent.timestamp < newPoint.timestamp) { recent = newPoint; } /** determine runtime */ // set the start time if the values is greater than the idleFlow and start is not already set if (val.airFlow >= idleFlow && !start) { start = time; } // set the stop time when off or at the last measurements and the start time is set if ((val.airFlow < idleFlow || i === resp.data.values.length - idleFlow) && start) { stop = time; } // if both start and stop are set calculate add the timeframe to the total runtime if (start && stop) { runtime = runtime + stop.diff(start); start = undefined; stop = undefined; } }); setRuntime(moment.duration(runtime)); let state = false; if ( data.length > 1 && data[data.length - 1].value > gate.lowerFlow() && data[data.length - 1].value < gate.upperFlow() ) { state = true; } setPCAState(state); } setFlowData(data); setLoadingChartData(false); setRecent(recent); }); } }, [gateAPI, gate, ambient, pressureComponent, start, end, device, setPCAState, as]); // eslint-disable-line react-hooks/exhaustive-deps const loadFlowEvents = () => { if (ambient && pressureComponent) { setEventsLoading(true); gateAPI .listGateFlowEvents( gate.key, device, ambient, pressureComponent, start.toISOString(), end.toISOString(), idleFlow, eventThreshold, undefined, undefined, as ) .then(resp => { console.log(resp); setFlowEvents(resp.data.events.map(e => pond.AirFlowEvent.fromObject(e))); }) .catch(err => {}) .finally(() => { setEventsLoading(false); }); } }; const flowChart = () => { return ( Mass Air Flow} subheader={ recent ? ( {"Mass Flow Rate: "} {recent.value.toFixed(2)} kg/s
{moment(recent.timestamp).fromNow()}
) : ( No Data ) } /> {flowData.length !== 0 ? ( ) : (
A component may be missing or have no measurements, this data needs the ambient and pressure components to be set and measuring
)}
); }; const eventCards = () => { let totalEvents = 0; let eventsInside = 0; let eventsOutside = 0; let totalTimeS = 0; let timeSInside = 0; let timeSOutside = 0; flowEvents.forEach(event => { totalEvents++; totalTimeS = totalTimeS + moment.duration(moment(event.start).diff(moment(event.end))).asSeconds(); let avg = 0; let count = 0; event.readings.forEach(reading => { avg = avg + reading.airFlow; count++; }); avg = avg / count; //if the average of the readings for an event are within the range if (avg < gate.upperFlow() && avg > gate.lowerFlow()) { eventsInside++; timeSInside = timeSInside + moment.duration(moment(event.start).diff(moment(event.end))).asSeconds(); } else { eventsOutside++; timeSOutside = timeSOutside + moment.duration(moment(event.start).diff(moment(event.end))).asSeconds(); } }); return ( {flowEvents.length > 0 ? ( Total Events: {totalEvents} Total Time: {moment.duration(totalTimeS, "s").humanize()} Events Inside: {eventsInside} Event Time: {moment.duration(timeSInside, "s").humanize()} Events Outside: {eventsOutside} Event Time: {moment.duration(timeSOutside, "s").humanize()} Performance: {(eventsInside / totalEvents) * 100}% ) : ( {eventsLoading ? ( ) : ( )} )} ); }; return ( {runtime && ( Approximate Runtime: {runtime.asHours().toFixed(2)} hr Cost to run PCA: $ {parseFloat( (runtime.asHours() * gate.settings.hourlyPcaCost).toFixed(2) ).toLocaleString()} Cost to run APU: $ {parseFloat( (runtime.asHours() * gate.settings.hourlyApuCost).toFixed(2) ).toLocaleString()} Total Cost: $ {parseFloat( ( runtime.asHours() * gate.settings.hourlyApuCost + runtime.asHours() * gate.settings.hourlyPcaCost ).toFixed(2) ).toLocaleString()} Estimated Savings: $ {parseFloat( (runtime.asHours() * gate.settings.hourlyApuCost).toFixed(2) ).toLocaleString()} )} {loadingChartData ? : flowChart()} {eventCards()} ); }