Skip to content

Commit

Permalink
feat: configure simulation duration
Browse files Browse the repository at this point in the history
  • Loading branch information
ReidyT committed Dec 4, 2024
1 parent b1236f6 commit 2771bd4
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 219,172 deletions.
219,145 changes: 0 additions & 219,145 deletions public/temperatures/past_25_years.csv

This file was deleted.

File renamed without changes.
14 changes: 10 additions & 4 deletions src/config/simulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ import {

export const SIMULATION_FRAME_MS = 150;
export const SIMULATION_SLIDING_WINDOW = { window: 2, unit: TimeUnit.Days };
export const SIMULATION_CSV_FILE = {
path: 'temperatures/predictions_1_year.csv',
measurementFrequency: TimeUnit.Days,
};
export const SIMULATION_CSV_FILES = {
1: {
path: 'temperatures/predictions_1_year.csv',
measurementFrequency: TimeUnit.Days,
},
25: {
path: 'temperatures/predictions_25_year.csv',
measurementFrequency: TimeUnit.Days,
},
} as const;

export const SIMULATION_INDOOR_TEMPERATURE_CELCIUS = {
DEFAULT: 22,
Expand Down
40 changes: 27 additions & 13 deletions src/context/SimulationContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from 'react';

import {
SIMULATION_CSV_FILES,
SIMULATION_INDOOR_TEMPERATURE_CELCIUS,
SIMULATION_OUTDOOR_TEMPERATURE_CELCIUS,
SIMULATION_PRICE_KWH,
Expand All @@ -20,7 +21,7 @@ import { FormattedHeatLoss } from '@/types/heatLoss';
import { HeatLossPerComponent } from '@/types/houseComponent';
import { SimulationProgression, SimulationStatus } from '@/types/simulation';
import { SlidingWindow } from '@/types/temperatures';
import { FormattedTime, TimeUnit, TimeUnitType } from '@/types/time';
import { FormattedTime, TimeUnit } from '@/types/time';
import { undefinedContextErrorFactory } from '@/utils/context';
import { electricityCost } from '@/utils/electricity';
import { formatHeatLossRate, powerConversionFactors } from '@/utils/heatLoss';
Expand All @@ -45,42 +46,39 @@ type SimulationContextType = {
updateIndoorTemperature: (newTemperature: number) => void;
outdoorTemperature: OutdoorTemperature;
updateOutdoorTemperature: (props: OutdoorTemperature) => void;

progression: SimulationProgression;
period: SlidingWindow['period'];
duration: FormattedTime;
updateSimulationDuration: (
duration: Pick<FormattedTime, 'value'> & { unit: typeof TimeUnit.Years },
) => void;
startSimulation: () => void;
};

const SimulationContext = createContext<SimulationContextType | null>(null);

type Props = {
children: ReactNode;
/**
*
* @param path The file path of the CSV containing the temperatures.
* @param measurementFrequency - The time unit corresponding to the frequency of the temperature measurements.
* Indicates whether the temperatures are recorded once per hour, once per day, etc.
*/
csv: { path: string; measurementFrequency: TimeUnitType };
simulationFrameMS: number;
};

export const SimulationProvider = ({
children,
csv,
simulationFrameMS,
}: Props): ReactNode => {
const temperatureIterator = useRef<TemperatureIterator>();

const [simulationStatus, setSimulationStatus] = useState<SimulationStatus>(
SimulationStatus.IDLE,
);
const temperatureIterator = useRef<TemperatureIterator>();

const [currentWindow, setCurrentWindow] = useState<SlidingWindow>(
initSlidingWindow(0),
);

const [simulationDuration, setSimulationDuration] = useState<FormattedTime>({
value: 0,
unit: TimeUnit.Hours,
value: 1,
unit: TimeUnit.Years,
});

const [indoorTemperature, setIndoorTemperature] = useState(
Expand All @@ -94,6 +92,17 @@ export const SimulationProvider = ({

const [pricekWh, setPricekWh] = useState(SIMULATION_PRICE_KWH);

const csv =
SIMULATION_CSV_FILES[
simulationDuration.value as keyof typeof SIMULATION_CSV_FILES
];

if (!csv) {
throw new Error(
`The CSV was not found for the duration of ${simulationDuration.value}`,
);
}

const { houseComponentsConfigurator } = useHouseComponents();

const { heatLosses, totalHeatLoss } = useHeatLoss({
Expand Down Expand Up @@ -174,6 +183,11 @@ export const SimulationProvider = ({
period: currentWindow.period,
progression,
duration: simulationDuration,
updateSimulationDuration: (
duration: Pick<FormattedTime, 'value'> & {
unit: typeof TimeUnit.Years;
},
) => setSimulationDuration(duration),
status: simulationStatus,
heatLosses,
totalHeatLoss: formatHeatLossRate(totalHeatLoss),
Expand Down
4 changes: 4 additions & 0 deletions src/langs/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
}
},
"SIMULATION_CONTROL_PANEL": {
"DURATION_CONTROL_PANEL": {
"TITLE": "Simulation",
"LABEL": "Duration of Simulation"
},
"HOUSE_CONTROL_PANEL": {
"TITLE": "House",
"WALL_INSULATION_SELECT_LABEL": "Wall Insulation",
Expand Down
19 changes: 10 additions & 9 deletions src/modules/scenes/FirstScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Fab, Stack, useMediaQuery, useTheme } from '@mui/material';
import { OrbitControls } from '@react-three/drei';
import { Canvas } from '@react-three/fiber';

import { SIMULATION_CSV_FILE, SIMULATION_FRAME_MS } from '@/config/simulation';
import { SIMULATION_FRAME_MS } from '@/config/simulation';
import {
HouseComponentsProvider,
useHouseComponents,
Expand All @@ -27,7 +27,7 @@ const FirstSceneComponent = (): JSX.Element => {
const { numberOfFloors } = useHouseComponents();

const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.up('sm'));
const md = useMediaQuery(theme.breakpoints.up('sm'));

return (
<Stack
Expand All @@ -41,8 +41,8 @@ const FirstSceneComponent = (): JSX.Element => {

<Canvas
style={{
height: matches ? '500px' : '375px',
width: matches ? '500px' : '375px',
height: md ? '500px' : '375px',
width: md ? '500px' : '375px',
}}
camera={{ position: [0, 0, -35.5], fov: 30 }}
>
Expand Down Expand Up @@ -73,9 +73,13 @@ const FirstSceneComponent = (): JSX.Element => {

<Stack mt={2}>
<Fab
data-testid="simulation-control-button"
color="primary"
onClick={startSimulation}
disabled={status === SimulationStatus.RUNNING}
disabled={
status === SimulationStatus.RUNNING ||
status === SimulationStatus.LOADING
}
>
{status === SimulationStatus.RUNNING ? (
<PauseIcon />
Expand All @@ -95,10 +99,7 @@ const FirstSceneComponent = (): JSX.Element => {

const FirstScene = (): JSX.Element => (
<HouseComponentsProvider>
<SimulationProvider
csv={SIMULATION_CSV_FILE}
simulationFrameMS={SIMULATION_FRAME_MS}
>
<SimulationProvider simulationFrameMS={SIMULATION_FRAME_MS}>
<SeasonProvider>
<WindowSizeProvider>
<FirstSceneComponent />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,46 @@
import { useTranslation } from 'react-i18next';

import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { useMediaQuery, useTheme } from '@mui/material';
import Accordion from '@mui/material/Accordion';
import AccordionDetails from '@mui/material/AccordionDetails';
import AccordionSummary from '@mui/material/AccordionSummary';

import { ElectricityCostControl } from './ElectricityCostControl';
import { HouseControl } from './HouseControl';
import { SimulationDurationControl } from './SimulationDurationControl';
import { TemperatureControl } from './TemperatureControl/TemperatureControl';

export const SimulationControlPanel = (): JSX.Element => {
const { t } = useTranslation('SIMULATION_CONTROL_PANEL');
const theme = useTheme();
const sm = useMediaQuery(theme.breakpoints.down('md'));

return (
<div>
<div
style={
sm
? {}
: {
maxHeight: '95vh',
overflowY: 'auto',
padding: 3,
}
}
>
<Accordion defaultExpanded>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel-duration-content"
id="panel-duration-header"
>
{t('DURATION_CONTROL_PANEL.TITLE')}
</AccordionSummary>
<AccordionDetails>
<SimulationDurationControl />
</AccordionDetails>
</Accordion>

<Accordion defaultExpanded>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useTranslation } from 'react-i18next';

import { FormControl, InputLabel, MenuItem, Select } from '@mui/material';

import { SIMULATION_CSV_FILES } from '@/config/simulation';
import { useSimulation } from '@/context/SimulationContext';
import { SimulationStatus } from '@/types/simulation';
import { TimeUnit } from '@/types/time';

const OPTIONS = Object.keys(SIMULATION_CSV_FILES);

export const SimulationDurationControl = (): JSX.Element => {
const { t } = useTranslation('SIMULATION_CONTROL_PANEL', {
keyPrefix: 'DURATION_CONTROL_PANEL',
});

const { t: tDates } = useTranslation('DATES');

const { duration, updateSimulationDuration, status } = useSimulation();

const selectIsDisabled =
status === SimulationStatus.LOADING || status === SimulationStatus.RUNNING;

const handleChange = (newDuration: string | number): void => {
if (selectIsDisabled) {
return;
}

const value =
typeof newDuration === 'number'
? newDuration
: Number.parseInt(newDuration, 10);

if (!Number.isNaN(value)) {
updateSimulationDuration({ value, unit: TimeUnit.Years });
} else {
console.error(`The duration ${newDuration} is not a valid number!`);
}
};

return (
<FormControl fullWidth>
<InputLabel id="house-duration-select-label">{t('LABEL')}</InputLabel>
<Select
labelId="house-duration-select-label"
id="house-duration-select"
label={t('LABEL')}
value={duration.value}
onChange={(e) => handleChange(e.target.value)}
type="number"
disabled={selectIsDisabled}
>
{OPTIONS.map((years) => (
<MenuItem key={years} value={years}>
{tDates('YEARS', {
count: Number.parseInt(years, 10),
})}
</MenuItem>
))}
</Select>
</FormControl>
);
};
15 changes: 15 additions & 0 deletions tests/player/HousePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export class HousePage {

readonly outdoorTemperatureInfo: Locator;

readonly simulationDuration: Locator;

readonly simulationControlButton: Locator;

constructor(page: Page) {
this.page = page;
this.electricityCost = this.page.getByRole('spinbutton', {
Expand All @@ -25,6 +29,10 @@ export class HousePage {
this.outdoorTemperatureInfo = this.page.getByTestId(
'simulation-info-outdoor-temperature',
);
this.simulationDuration = this.page.getByLabel('Duration of Simulation');
this.simulationControlButton = this.page.getByTestId(
'simulation-control-button',
);
}

async goto(): Promise<void> {
Expand Down Expand Up @@ -110,6 +118,13 @@ export class HousePage {
await this.page.getByLabel('Override Temperature').setChecked(checked);
}

async setSimulationDuration(duration: number): Promise<void> {
await this.updateSelect(
'Duration of Simulation',
`${duration} year${duration > 1 ? 's' : ''}`,
);
}

async checkErrorIsVisible(
label: string,
type: 'Required' | 'Min' | 'Max',
Expand Down
13 changes: 13 additions & 0 deletions tests/player/simulation-duration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import test, { expect } from '@playwright/test';

import { HousePage } from './HousePage';

test('simulation duration should be disabled when simulation is running', async ({
page,
}) => {
const housePage = new HousePage(page);
await housePage.goto();
await housePage.setSimulationDuration(25);
await housePage.simulationControlButton.click();
await expect(housePage.simulationDuration).toBeDisabled();
});

0 comments on commit 2771bd4

Please sign in to comment.