Skip to content

Commit

Permalink
Merge pull request #267 from ORNL-AMO/issue-180
Browse files Browse the repository at this point in the history
Issue 180, 206 - Add projects to EOG
  • Loading branch information
nbintertech authored Mar 14, 2024
2 parents 73e7b74 + 6779dbc commit 1c85a61
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 38 deletions.
9 changes: 6 additions & 3 deletions GAMEPLAY.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,14 @@
- Mid-Sized Solar, Community Wind, Utility-PPPA Wind

###### One-time Payment Renewables:
Are paid for once, regardless of 1 or 2 year gameplay, and are renewed each year
Can be paid for once (or financed), regardless of 1 or 2 year gameplay, and are renewed each year
- Solar Panels Carport, Rooftop mid-sized Solar

###### Power Purchase Agreements (PPA):
Are paid for annually over a 10-year term. This is a special payment type and should not be included in most of the gameplay mechanisms and logic related to financing.

###### Always Carryover cost Savings:
Regardless of game settings, these projects carryover cost savings every year
**!!!!** Regardless of game settings, these projects carryover cost savings every year
- Small solar carport, Rooftop mid-sized Solar, Community Wind


Expand All @@ -82,7 +85,7 @@ Shows completed projects after their implementation year, as well non-renewable
Are only applied in the first year that the project is implemented.

### Capital Funding Rewards
Players will be awarded Capital Funding rewards (one free project) for reaching savings milestones of 5% and 35% throughout the game. Each Capital Funding reward must be used in the following new year. VPPA projects are ineligible.
Players will be awarded Capital Funding rewards (one free project) for reaching savings milestones of 5% and 35% throughout the game. Each Capital Funding reward must be used in the following new year. PPA projects are ineligible.

---

Expand Down
1 change: 1 addition & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,7 @@ export class App extends React.PureComponent<unknown, AppState> {
let newYearRangeInitialStats = [...this.state.yearRangeInitialStats, { ...newYearTrackedStats }];
console.log('==============================');
console.log('Total new year budget (financing/renewable costs applied)', newYearTrackedStats.financesAvailable);
console.log('Game Total Spending', newYearTrackedStats.gameTotalSpending);
console.log('======== END =================');

const completedYears = this.state.completedYears < this.state.trackedStats.currentGameYear? this.state.completedYears + 1 : this.state.completedYears;
Expand Down
34 changes: 33 additions & 1 deletion src/Financing.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ImplementedProject, ProjectControl, RecapSurprise } from './ProjectControl';
import type { ImplementedProject, ProjectControl, RecapSurprise, RenewableProject } from './ProjectControl';
import Projects from './Projects';
import type { TrackedStats } from './trackedStats';
import AttachMoneyIcon from '@mui/icons-material/AttachMoney';
Expand Down Expand Up @@ -144,21 +144,53 @@ export function getRoundIsExpired(round: FundingRound, stats: TrackedStats) {
export function getDefaultFinancingOption(projectControl: ProjectControl, hasFinancingOptions: boolean, baseCost: number): FinancingOption {
let name = 'Pay with Existing Budget';
let description = hasFinancingOptions ? 'Reduce energy use with a one-time payment' : 'Pay for project with funds from current budget';
let term;
if (projectControl.customBudgetType) {
name = projectControl.customBudgetType.name;
description = projectControl.customBudgetType.description;
term = projectControl.customBudgetType.loanTerm;
}
return {
financingType: {
name: name,
description: description,
id: 'budget',
loanTerm: term
},
financedTotalCost: baseCost,
financedAnnualCost: undefined,
}
}

export function getProjectedFinancedSpending(implementedFinancedProjects: ImplementedProject[], renewableProjects: RenewableProject[], mutableStats: TrackedStats) {
let futureFinancedSpending: number = 0;
futureFinancedSpending = implementedFinancedProjects.reduce((totalFutureSpending: number, project: ImplementedProject) => {
return getRemainingProjectCosts(project, mutableStats, totalFutureSpending);
}, 0);
futureFinancedSpending += renewableProjects.reduce((totalFutureSpending: number, project: ImplementedProject) => {
return getRemainingProjectCosts(project, mutableStats, totalFutureSpending);
}, 0);
return futureFinancedSpending;
}


export function getRemainingProjectCosts(project: ImplementedProject, mutableStats: TrackedStats, totalFutureSpending: number) {
let finishedGameYear = mutableStats.currentGameYear + 1;
let yearsPaid = finishedGameYear - project.yearStarted;
yearsPaid = yearsPaid * mutableStats.gameYearInterval
let yearsRemaining = project.financingOption.financingType.loanTerm - yearsPaid;
let projectControl = Projects[project.page];
let isAnnuallyFinanced = getIsAnnuallyFinanced(project.financingOption.financingType.id);
if ((isAnnuallyFinanced || projectControl.isPPA) && yearsRemaining) {
// we always want this cost to be expressed in single year
let yearInterval = 1
let annualCost = projectControl.getImplementationCost(project.financingOption.financingType.id, yearInterval);
let futureCosts = annualCost * yearsRemaining;
totalFutureSpending += futureCosts;
}
return totalFutureSpending;
}


/**
* Set milestone round is earned
Expand Down
15 changes: 10 additions & 5 deletions src/Projects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,8 @@ Projects[Pages.smallVPPA] = new ProjectControl({
customBudgetType: {
name: "Power Purchase Agreement",
description: "Pay Annually",
id: 'budget'
id: 'budget',
loanTerm: 10
},
title: 'Invest in wind VPPA',
shortTitle: 'Invest in wind VPPA to offset {10%} of your electricity emissions. {THIS PROJECT WILL BE RENEWED ANNUALLY}.',
Expand Down Expand Up @@ -1136,7 +1137,8 @@ Projects[Pages.midVPPA] = new ProjectControl({
customBudgetType: {
name: "Power Purchase Agreement",
description: "Pay Annually",
id: 'budget'
id: 'budget',
loanTerm: 10
},
title: 'Invest in wind VPPA',
shortTitle: 'Invest in wind VPPA to offset {20%} of your electricity emissions. {THIS PROJECT WILL BE RENEWED ANNUALLY}.',
Expand Down Expand Up @@ -1169,7 +1171,8 @@ Projects[Pages.largeVPPA] = new ProjectControl({
customBudgetType: {
name: "Power Purchase Agreement",
description: "Pay Annually",
id: 'budget'
id: 'budget',
loanTerm: 10
},
title: 'Invest in wind VPPA',
shortTitle: 'Invest in wind VPPA to offset {30%} of your electricity emissions. {THIS PROJECT WILL BE RENEWED ANNUALLY}.',
Expand Down Expand Up @@ -1199,7 +1202,8 @@ Projects[Pages.midSolar] = new ProjectControl({
customBudgetType: {
name: "Power Purchase Agreement",
description: "Pay Annually",
id: 'budget'
id: 'budget',
loanTerm: 10
},
statsInfoAppliers: {
absoluteCarbonSavings: absolute(-1_717_000)
Expand Down Expand Up @@ -1243,7 +1247,8 @@ Projects[Pages.largeWind] = new ProjectControl({
customBudgetType: {
name: "Power Purchase Agreement",
description: "Pay Annually",
id: 'budget'
id: 'budget',
loanTerm: 10
},
statsInfoAppliers: {
absoluteCarbonSavings: absolute(-4_292_000)
Expand Down
9 changes: 4 additions & 5 deletions src/components/CurrentPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { StartPageProps } from './StartPage';
import { YearRecap } from './YearRecap';
import type { PageControlProps, ControlCallbacks } from './controls';
import { CapitalFundingState } from '../Financing';
import EndGameReport from './EndGameReport/EndGameReportPage';
import EndGameReportPage from './EndGameReport/EndGameReportPage';


interface CurrentPageProps extends ControlCallbacks, PageControlProps {
Expand Down Expand Up @@ -85,20 +85,19 @@ export class CurrentPage extends PureComponentIgnoreFuncs<CurrentPageProps> {
yearRangeInitialStats={this.props.yearRangeInitialStats}
handleNewYearSetup={this.props.handleNewYearSetupOnProceed}
/>;
case EndGameReport:
case EndGameReportPage:
return (
<Fragment>
<EndGameReport
<EndGameReportPage
{...controlCallbacks}
{...this.props.gameSettings}
trackedStats={this.props.trackedStats}
defaultTrackedStats={this.props.defaultTrackedStats}
capitalFundingState={this.props.capitalFundingState}
implementedRenewableProjects={this.props.implementedRenewableProjects}
implementedFinancedProjects={this.props.implementedFinancedProjects}
completedProjects={this.props.completedProjects}
yearRangeInitialStats={this.props.yearRangeInitialStats}
/>
</Fragment>
);
default:
return <></>;
Expand Down
100 changes: 95 additions & 5 deletions src/components/EndGameReport/EndGameReportPage.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { Fragment } from 'react';
import { TrackedStats } from '../../trackedStats';
import { TrackedStats, setCarbonEmissionsAndSavings, setCostPerCarbonSavings } from '../../trackedStats';
import { GameSettings } from '../SelectGameSettings';
import { CapitalFundingState, FinancingOption, isProjectFullyFunded } from '../../Financing';
import { CapitalFundingState, FinancingOption, getIsAnnuallyFinanced, getProjectedFinancedSpending, isProjectFullyFunded } from '../../Financing';
import { ControlCallbacks, Emphasis, PageControl } from '../controls';
import { Box, Button, Card, CardContent, CardHeader, Grid, Link, List, ListItem, ListItemText, Tooltip, TooltipProps, Typography, styled, tooltipClasses } from '@mui/material';
import { Box, Button, Card, CardContent, CardHeader, Grid, Link, List, ListItem, ListItemIcon, ListItemText, Tooltip, TooltipProps, Typography, styled, tooltipClasses } from '@mui/material';
import { ParentSize } from '@visx/responsive';
import { CompletedProject, ImplementedProject, ProjectControl, RenewableProject } from '../../ProjectControl';
import { ImplementedFinancingData } from '../YearRecap';
Expand All @@ -12,6 +12,7 @@ import { DialogFinancingOptionCard } from '../Dialogs/ProjectDialog';
import { parseSpecialText, truncate } from '../../functions-and-types';
import EnergyUseLineChart from '../EnergyUseLineChart';
import DownloadPDF, { ReportPDFProps } from './DownloadPDF';
import InfoIcon from '@mui/icons-material/Info';


export default class EndGameReportPage extends React.Component<EndGameReportPageProps> {
Expand All @@ -29,13 +30,15 @@ export default class EndGameReportPage extends React.Component<EndGameReportPage
});

let completedProjects = this.props.completedProjects.concat(completedRenewables);

return (
<Box m={2}>
<EndGameReport
yearRangeInitialStats={yearRangeInitialStats}
implementedFinancedProjects={this.props.implementedFinancedProjects}
renewableProjects={this.props.implementedRenewableProjects}
completedProjects={completedProjects}
mutableStats={mutableStats}
defaultTrackedStats={this.props.defaultTrackedStats}
/>
</Box>
);
Expand Down Expand Up @@ -69,6 +72,31 @@ function EndGameReport(props: ReportProps) {

});

let projectedFinancedSpending = getProjectedFinancedSpending(props.implementedFinancedProjects, props.renewableProjects, props.mutableStats);
let gameCurrentAndProjectedSpending = props.mutableStats.gameTotalSpending + projectedFinancedSpending;
setCarbonEmissionsAndSavings(props.mutableStats, props.defaultTrackedStats);

setCostPerCarbonSavings(props.mutableStats, gameCurrentAndProjectedSpending);

let endOfGameResults = {
carbonSavingsPercent: props.mutableStats.carbonSavingsPercent,
gameTotalSpending: props.mutableStats.gameTotalSpending,
projectedFinancedSpending: projectedFinancedSpending,
gameCurrentAndProjectedSpending: gameCurrentAndProjectedSpending,
costPerCarbonSavings: props.mutableStats.costPerCarbonSavings
}
const noDecimalsFormatter = Intl.NumberFormat('en-US', {
minimumFractionDigits: 0,
maximumFractionDigits: 0,
});
const carbonSavingsPercentFormatted: string = (endOfGameResults.carbonSavingsPercent * 100).toFixed(2);
const gameTotalNetCostFormatted: string = noDecimalsFormatter.format(endOfGameResults.gameTotalSpending);
const projectedFinancedSpendingFormatted: string = noDecimalsFormatter.format(endOfGameResults.projectedFinancedSpending);
const costPerCarbonSavingsFormatted: string = endOfGameResults.costPerCarbonSavings !== undefined ? Intl.NumberFormat('en-US', {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
}).format(endOfGameResults.costPerCarbonSavings) : '0';

return (
<Fragment>
<ParentSize>
Expand All @@ -78,6 +106,64 @@ function EndGameReport(props: ReportProps) {
parentElement={parent}/>
)}
</ParentSize>
<Box m={2}>
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<List dense={true}>
<ListItem >
<ListItemIcon>
<InfoIcon />
</ListItemIcon>
<ListItemText
primary={
<Typography variant='h5'>
Your company has reduced CO<sub>2</sub>e Emissions by{' '}
<Emphasis>{carbonSavingsPercentFormatted}%</Emphasis>{' '}
</Typography>
}
/>
</ListItem>
<ListItem>
<ListItemIcon>
<InfoIcon />
</ListItemIcon>
<ListItemText
primary={
<Typography variant={'h5'}>
You have spent{' '}<Emphasis>${gameTotalNetCostFormatted}</Emphasis>{' '} throughout the game.
</Typography>
}
/>
</ListItem>
<ListItem>
<ListItemIcon>
<InfoIcon />
</ListItemIcon>
<ListItemText
primary={
<Typography variant={'h5'}>
Your cost per kg reduced was{' '}<Emphasis>${costPerCarbonSavingsFormatted}/kg CO<sub>2</sub>e</Emphasis>{' '}
</Typography>
}
/>
</ListItem>
{endOfGameResults.projectedFinancedSpending > 0 &&
<ListItem >
<ListItemIcon>
<InfoIcon />
</ListItemIcon>
<ListItemText
primary={
<Typography variant={'h5'}>
You are projected to spend {' '}<Emphasis>${projectedFinancedSpendingFormatted}</Emphasis>{' '} on financed and renewed projects in future years
</Typography>
}
/>
</ListItem>
}
</List>
</Box>
</Box>

<Box sx={{ display: 'flex', justifyContent: 'end', marginY: '1rem' }}>
<DownloadPDF yearRangeInitialStats={props.yearRangeInitialStats}
completedProjects={props.completedProjects}
Expand Down Expand Up @@ -106,7 +192,10 @@ function EndGameReport(props: ReportProps) {
}

interface ReportProps extends ReportPDFProps {
mutableStats: TrackedStats
mutableStats: TrackedStats,
defaultTrackedStats: TrackedStats,
implementedFinancedProjects: ImplementedProject[],
renewableProjects: RenewableProject[]
}


Expand Down Expand Up @@ -334,6 +423,7 @@ export interface EndGameReportPageProps extends
GameSettings {
capitalFundingState: CapitalFundingState,
trackedStats: TrackedStats,
defaultTrackedStats: TrackedStats,
completedProjects: CompletedProject[];
implementedRenewableProjects: RenewableProject[];
implementedFinancedProjects: ImplementedProject[];
Expand Down
2 changes: 0 additions & 2 deletions src/components/EnergyUseLineChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ export default function EnergyUseLineChart(props: EnergyUseLineChartProps) {
landfillGases: [],
}
props.yearRangeInitialStats.forEach(statYear => {
// 1 MMBTU = 293.07107kw
let conversionFactorKwH = 293.07107;
let electricityEmissions = statYear.electricityUseKWh * getElectricityEmissionsFactor(statYear.currentGameYear, statYear.gameYearInterval, statYear.gameYearDisplayOffset);
let natGasEmissions = statYear.naturalGasMMBTU * statYear.naturalGasEmissionsPerMMBTU;
let landfillGasEmissions = statYear.hydrogenMMBTU * statYear.hydrogenEmissionsPerMMBTU;
Expand Down
Loading

0 comments on commit 1c85a61

Please sign in to comment.