Created
July 30, 2022 07:06
-
-
Save tathamoddie/5df503ce176a4b024dc545da2b065f70 to your computer and use it in GitHub Desktop.
Run a solar and battery simulation using real historical data
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
Run a solar and battery simulation using real historical data | |
.DESCRIPTION | |
Combines real usage data from Powershop's export format, in 30-minute buckets, | |
with real regional solar performance data from https://pv-map.apvi.org.au/live, | |
in 15-minute buckets, to simulate the effectiveness of a solar and battery system. | |
.EXAMPLE | |
.\Run-Simulation.ps1 ` | |
-PeakGeneration 5.46 ` | |
-BatteryCapacity 9.59 ` | |
-FirstTwoDigitsOfPostcode '30' ` | |
-PowershopUsageExportPath .\meter_usage_data.csv ` | |
| ConvertTo-Csv ` | |
| Out-File result.csv | |
#> | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory = $true, HelpMessage = "Peak solar generation in kW")] | |
[double] | |
$PeakGeneration, | |
[Parameter(Mandatory = $true, HelpMessage = "Battery capacity in kWh")] | |
[double] | |
$BatteryCapacity, | |
[Parameter(Mandatory = $true, HelpMessage = "First two digits of site's postcode, aligned to https://pv-map.apvi.org.au/live")] | |
[string] | |
$FirstTwoDigitsOfPostcode, | |
[Parameter(Mandatory = $true, HelpMessage = "Path to Powershop usage export CSV")] | |
[ValidateNotNullOrEmpty()] | |
[string] | |
$PowershopUsageExportPath | |
) | |
$ErrorActionPreference = 'Stop' | |
$powershopUsageData = Import-Csv -Path $PowershopUsageExportPath | |
$currentBatteryStorage = 0 | |
$daysProcessed = 0; | |
foreach ($usageDay in $powershopUsageData) { | |
Write-Progress -Activity "Running simulation" -Status $usageDay.DATE -PercentComplete ($daysProcessed / $powershopUsageData.Length * 100) | |
$daysProcessed++; | |
$batteryStorageAtStartOfDay = $currentBatteryStorage | |
$gridImportToday = 0 | |
$gridExportToday = 0 | |
$totalProducedToday = 0 | |
$totalConsumedToday = 0 | |
$totalConsumedFromSolarToday = 0 | |
$totalConsumedFromBatteryToday = 0 | |
$totalConsumedFromGridToday = 0 | |
$date = [DateTime]::Parse($usageDay.DATE) | |
# Load generation data for that day | |
$generationData = Invoke-WebRequest -Method POST -Uri https://pv-map.apvi.org.au/data -Body @{ day = $date.ToString("yyyy-MM-dd") } | | |
ConvertFrom-Json | | |
Select-Object -ExpandProperty postcode | | |
Select-Object -Property ` | |
@{ label='LocalTime'; expression = { $_.ts.ToLocalTime() } }, | |
@{ label='ProductionPercentage'; expression = { $_.$FirstTwoDigitsOfPostcode } } | |
# Simulate each 30 minute interval of the day | |
for ($hour = 0; $hour -lt 24 ; $hour++) { | |
for ($minute = 0; $minute -lt 60; $minute += 30) { | |
$startTime = [TimeSpan]::new($hour, $minute, 0) | |
$endTime = $startTime + [TimeSpan]::FromMinutes(30) | |
$usagePeriod = '{0:hh}:{0:mm} - {1:hh}:{1:mm}' -f $startTime, $endTime | |
# Combine the two 15-minute generation intervals that correlate to this 30-minute usage interval | |
$averageProductionPercentage = $generationData | | |
Where-Object { $_.LocalTime.TimeOfDay -ge $startTime -and $_.LocalTime.TimeOfDay -lt $endTime } | | |
Measure-Object -Property ProductionPercentage -Average | | |
Select-Object -ExpandProperty Average | |
if ($null -eq $averageProductionPercentage) { | |
$averageProductionPercentage = 0 | |
} | |
$producedInPeriod = $PeakGeneration * $averageProductionPercentage / 100 * ($endTime - $startTime).TotalHours | |
$consumedInPeriod = [Double]::Parse($usageDay.$usagePeriod) | |
$netInPeriod = $producedInPeriod - $consumedInPeriod | |
Write-Debug ('{0} {1}: produced {2:n2} kWh from {3,6:p2}, consumed {4:n2} kWh, net {5:n2} kWh' -f ` | |
$date, | |
$usagePeriod, | |
$producedInPeriod, | |
($averageProductionPercentage / 100), | |
$consumedInPeriod, | |
$netInPeriod | |
) | |
$totalConsumedToday += $consumedInPeriod | |
$totalProducedToday += $producedInPeriod | |
$spaceInBattery = $BatteryCapacity - $currentBatteryStorage | |
if ($producedInPeriod -gt $consumedInPeriod) { | |
# We made power | |
$totalConsumedFromSolarToday += $consumedInPeriod | |
$excessProductionInPeriod = $producedInPeriod - $consumedInPeriod | |
$amountThatCanGoInToBattery = [Math]::Min($spaceInBattery, $excessProductionInPeriod) | |
$amountThatDoesNotFitInBattery = $excessProductionInPeriod - $amountThatCanGoInToBattery | |
$currentBatteryStorage += $amountThatCanGoInToBattery | |
$gridExportToday += $amountThatDoesNotFitInBattery | |
} | |
elseif ($consumedInPeriod -gt $producedInPeriod) { | |
# We used power | |
$totalConsumedFromSolarToday += $producedInPeriod | |
$excessConsumptionInPeriod = $consumedInPeriod - $producedInPeriod | |
$amountThatCouldComeFromBattery = [Math]::Min($currentBatteryStorage, $excessConsumptionInPeriod) | |
$amountThatCouldNotComeFromBattery = $excessConsumptionInPeriod - $amountThatCouldComeFromBattery | |
$currentBatteryStorage -= $amountThatCouldComeFromBattery | |
$totalConsumedFromBatteryToday += $amountThatCouldComeFromBattery | |
$gridImportToday += $amountThatCouldNotComeFromBattery | |
$totalConsumedFromGridToday += $amountThatCouldNotComeFromBattery | |
} | |
else { | |
# Balanced | |
$totalConsumedFromSolarToday += $producedInPeriod | |
} | |
$consumptionTrackingError = $totalConsumedToday - $totalConsumedFromSolarToday - $totalConsumedFromBatteryToday - $totalConsumedFromGridToday | |
if ($consumptionTrackingError -gt 0.1) { | |
throw "Something didn't balance" | |
} | |
} | |
} | |
$batteryStorageAtEndOfDay = $currentBatteryStorage | |
[PSCustomObject]@{ | |
Date = $date.ToString("yyyy-MM-dd"); | |
BatteryStorageAtStartOfDay = $batteryStorageAtStartOfDay; | |
GridImportToday = $gridImportToday; | |
GridExportToday = $gridExportToday; | |
TotalProducedToday = $totalProducedToday; | |
TotalConsumedToday = $totalConsumedToday; | |
TotalConsumedFromSolarToday = $totalConsumedFromSolarToday; | |
TotalConsumedFromBatteryToday = $totalConsumedFromBatteryToday; | |
TotalConsumedFromGridToday = $totalConsumedFromGridToday; | |
BatteryStorageAtEndOfDay = $batteryStorageAtEndOfDay; | |
BatteryStorageAtEndOfDayPercentage = '{0:p}' -f ($batteryStorageAtEndOfDay / $BatteryCapacity); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment