fixed chart names and added extra battery properties.
This commit is contained in:
@@ -5,7 +5,7 @@ namespace BattSim.Models
|
|||||||
public class EnergyData
|
public class EnergyData
|
||||||
{
|
{
|
||||||
public DateTime Time { get; set; }
|
public DateTime Time { get; set; }
|
||||||
public bool DayTarif { get; set; }
|
public bool DayTariff { get; set; }
|
||||||
public double Consumption { get; set; }
|
public double Consumption { get; set; }
|
||||||
public double Production { get; set; }
|
public double Production { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,18 +9,31 @@
|
|||||||
<h1>BattSim</h1>
|
<h1>BattSim</h1>
|
||||||
|
|
||||||
<h2>Input Data</h2>
|
<h2>Input Data</h2>
|
||||||
<p>Upload your fluvius daily csv file here.</p>
|
<div>
|
||||||
<InputFile OnChange="LoadCsvFile" accept=".csv"/>
|
<p>Upload your Fluvius quarterly csv file here. The longer the timeframe the longer it takes to process.</p>
|
||||||
@if (_isLoadingFile){ <p>Loading...</p> }
|
<InputFile OnChange="OnFileUploaded" accept=".csv"/>
|
||||||
@if (EnergyData.Length != 0){
|
@if (_isLoadingFile)
|
||||||
|
{
|
||||||
|
<p>Loading...</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (FluviusDataRaw.Count != 0)
|
||||||
|
{
|
||||||
|
<p>@(FluviusDataRaw.Count) entries read.</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (FluviusDataDaily.Count != 0)
|
||||||
|
{
|
||||||
<RadzenChart>
|
<RadzenChart>
|
||||||
<RadzenAreaSeries Smooth=true Data="@EnergyData" CategoryProperty="Time" Title="Consumption" ValueProperty="Consumption">
|
<RadzenAreaSeries Smooth=true Data="@FluviusDataDaily.Values" CategoryProperty="Time" Title="Consumption" ValueProperty="Consumption">
|
||||||
<RadzenChartTooltipOptions Visible="true"/>
|
<RadzenChartTooltipOptions Visible="true"/>
|
||||||
</RadzenAreaSeries>
|
</RadzenAreaSeries>
|
||||||
<RadzenAreaSeries Smooth=true Data="@EnergyData" CategoryProperty="Time" Title="Production" ValueProperty="Production">
|
<RadzenAreaSeries Smooth=true Data="@FluviusDataDaily.Values" CategoryProperty="Time" Title="Production" ValueProperty="Production">
|
||||||
<RadzenChartTooltipOptions Visible="true"/>
|
<RadzenChartTooltipOptions Visible="true"/>
|
||||||
</RadzenAreaSeries>
|
</RadzenAreaSeries>
|
||||||
<RadzenCategoryAxis Formatter="@FormatObject" Padding="20" LabelAutoRotation="-45">
|
<RadzenCategoryAxis Formatter="@FormatObject" Padding="20" LabelAutoRotation="-45">
|
||||||
|
<RadzenGridLines Visible="true"/>
|
||||||
<RadzenAxisTitle Text="Time"/>
|
<RadzenAxisTitle Text="Time"/>
|
||||||
</RadzenCategoryAxis>
|
</RadzenCategoryAxis>
|
||||||
<RadzenValueAxis Formatter="@FormatObject">
|
<RadzenValueAxis Formatter="@FormatObject">
|
||||||
@@ -29,12 +42,15 @@
|
|||||||
</RadzenValueAxis>
|
</RadzenValueAxis>
|
||||||
</RadzenChart>
|
</RadzenChart>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
@code{
|
@code{
|
||||||
EnergyData[] EnergyData = [];
|
Dictionary<DateTime, EnergyData> FluviusDataRaw = [];
|
||||||
|
Dictionary<DateOnly, EnergyData> FluviusDataDaily = [];
|
||||||
bool _isLoadingFile = false;
|
bool _isLoadingFile = false;
|
||||||
|
|
||||||
private async Task LoadCsvFile(InputFileChangeEventArgs e)
|
private async Task OnFileUploaded(InputFileChangeEventArgs e)
|
||||||
{
|
{
|
||||||
var file = e.File;
|
var file = e.File;
|
||||||
if (file.ContentType != "text/csv")
|
if (file.ContentType != "text/csv")
|
||||||
@@ -48,11 +64,8 @@
|
|||||||
Console.WriteLine("Reading csv file...");
|
Console.WriteLine("Reading csv file...");
|
||||||
_isLoadingFile = true;
|
_isLoadingFile = true;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
FluviusDataRaw = await FluviusDataHandler.LoadAndProcessFile(file);
|
||||||
var loadingTask = DataLoader.LoadAndProcessData(file);
|
FluviusDataDaily = FluviusDataHandler.GenerateDailyData(FluviusDataRaw);
|
||||||
var energyData = await loadingTask;
|
|
||||||
EnergyData = energyData.ToArray();
|
|
||||||
|
|
||||||
_isLoadingFile = false;
|
_isLoadingFile = false;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
Console.WriteLine("Done reading csv file!");
|
Console.WriteLine("Done reading csv file!");
|
||||||
@@ -65,27 +78,44 @@
|
|||||||
|
|
||||||
private void OnSeriesClick(){}
|
private void OnSeriesClick(){}
|
||||||
private string FormatObject(object value) {
|
private string FormatObject(object value) {
|
||||||
if(value is double d) return $"{value:0.##} kWh";
|
if(value is double d) return $"{d:0.##} kWh";
|
||||||
if(value is DateOnly date) return (date.Day == 1) ? date.ToString("MM/yyyy") : string.Empty;
|
if(value is DateTime date) return date.ToString("dd/MM/yyyy");
|
||||||
else return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<h2>Simulate Battery</h2>
|
<h2>Simulate Battery</h2>
|
||||||
<p>Set the battery capacity</p>
|
<div>
|
||||||
<InputNumber @bind-value="BatteryCapacity"/>
|
<p>Generate adjusted energy data simulating the effect of a battery with properties as configured here:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>Capacity: <InputNumber @bind-value="BatteryCapacity"/> kWh</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Discharge Rate: <InputNumber @bind-value="DischargeRate"/> kW</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Charge Rate: <InputNumber @bind-value="ChargeRate"/> kW</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Round-trip Efficiency: <InputNumber @bind-value="Efficiency"/> %</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
<Button @onclick="SimulateBattery">Simulate</Button>
|
<Button @onclick="SimulateBattery">Simulate</Button>
|
||||||
|
</div>
|
||||||
<h2>Calculate Cost</h2>
|
|
||||||
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
double BatteryCapacity = 0.0;
|
double BatteryCapacity = 7.5;
|
||||||
|
double DischargeRate = 3;
|
||||||
|
double ChargeRate = 3;
|
||||||
|
double Efficiency = 90;
|
||||||
BatteryDayResult[] SimulationData = [];
|
BatteryDayResult[] SimulationData = [];
|
||||||
|
|
||||||
private async Task SimulateBattery(){
|
private async Task SimulateBattery(){
|
||||||
Console.WriteLine("Simulating...");
|
Console.WriteLine("Simulating...");
|
||||||
SimulationData = BatterySimulator.SimulateBattery(EnergyData, BatteryCapacity).ToArray();
|
// SimulationData = BatterySimulator.SimulateBattery(EnergyData, BatteryCapacity).ToArray();
|
||||||
Console.WriteLine("Done simulating!");
|
Console.WriteLine("Done simulating!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<h2>Calculate Cost</h2>
|
||||||
|
|||||||
@@ -12,5 +12,4 @@ builder.RootComponents.Add<HeadOutlet>("head::after");
|
|||||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||||
builder.Services.AddRadzenComponents();
|
builder.Services.AddRadzenComponents();
|
||||||
|
|
||||||
|
|
||||||
await builder.Build().RunAsync();
|
await builder.Build().RunAsync();
|
||||||
@@ -4,44 +4,31 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection.PortableExecutable;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BattSim.Models;
|
using BattSim.Models;
|
||||||
using Microsoft.AspNetCore.Components.Forms;
|
using Microsoft.AspNetCore.Components.Forms;
|
||||||
|
|
||||||
namespace BattSim.Services
|
namespace BattSim.Services
|
||||||
{
|
{
|
||||||
public static class DataLoader
|
public static class FluviusDataHandler
|
||||||
{
|
{
|
||||||
public static async Task<List<EnergyData>> LoadAndProcessData(IBrowserFile file)
|
public static async Task<Dictionary<DateTime, EnergyData>> LoadAndProcessFile(IBrowserFile file)
|
||||||
{
|
{
|
||||||
var energyData = new ConcurrentBag<EnergyData>(); // Thread-safe collection
|
var lines = await ReadCsvFile(file);
|
||||||
await using var stream = file.OpenReadStream(maxAllowedSize: (int)1.0e9);
|
var energyData = new ConcurrentDictionary<DateTime,EnergyData>(); // Thread-safe collection
|
||||||
using var reader = new StreamReader(stream, bufferSize: (int)1.0e6);
|
|
||||||
|
|
||||||
// Skip header
|
|
||||||
await reader.ReadLineAsync();
|
|
||||||
|
|
||||||
// Read all lines into memory
|
|
||||||
var lines = new List<string>();
|
|
||||||
string line;
|
|
||||||
while ((line = await reader.ReadLineAsync()) is not null)
|
|
||||||
{
|
|
||||||
lines.Add(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process lines in parallel
|
// Process lines in parallel
|
||||||
Parallel.ForEach(lines, line =>
|
Parallel.ForEach(lines, line =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var parts = SplitLine(line);
|
var parts = SplitLine(line);
|
||||||
DateTime time = ParseDateTime(parts[0..2]);
|
DateTime time = ParseDateTime(parts[..2]);
|
||||||
double volume = ParseVolume(parts[8]);
|
double volume = ParseVolume(parts[8]);
|
||||||
string register = parts[7].Trim();
|
string register = parts[7].Trim();
|
||||||
bool dayTarif = register.Contains("Dag");
|
bool dayTarif = register.Contains("Dag");
|
||||||
|
|
||||||
// Use ConcurrentBag for thread-safe additions
|
var entry = new EnergyData { Time = time, DayTariff = dayTarif };
|
||||||
var entry = new EnergyData { Time = time, DayTarif = dayTarif };
|
|
||||||
if (register.Contains("Afname"))
|
if (register.Contains("Afname"))
|
||||||
entry.Consumption = volume;
|
entry.Consumption = volume;
|
||||||
else if (register.Contains("Injectie"))
|
else if (register.Contains("Injectie"))
|
||||||
@@ -49,28 +36,60 @@ namespace BattSim.Services
|
|||||||
else
|
else
|
||||||
throw new Exception("Unknown volume register");
|
throw new Exception("Unknown volume register");
|
||||||
|
|
||||||
energyData.Add(entry);
|
energyData.AddOrUpdate(entry.Time, entry, (_, existing) =>
|
||||||
|
{
|
||||||
|
existing.Consumption += entry.Consumption;
|
||||||
|
existing.Production += entry.Production;
|
||||||
|
return existing;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Error parsing line: {line}. Skipping to next line. Exception: {e}");
|
Console.WriteLine($"Error parsing line: {line}. Skipping to next line. Exception: {e}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return energyData.ToDictionary();
|
||||||
|
}
|
||||||
|
|
||||||
// Group by Time and merge entries
|
public static Dictionary<DateOnly, EnergyData> GenerateDailyData(Dictionary<DateTime, EnergyData> energyData)
|
||||||
var groupedData = energyData
|
|
||||||
.GroupBy(e => e.Time)
|
|
||||||
.Select(g =>
|
|
||||||
{
|
{
|
||||||
var first = g.First();
|
var dailyEnergyData = new ConcurrentDictionary<DateOnly, EnergyData>();
|
||||||
first.Consumption = g.Sum(e => e.Consumption);
|
Parallel.ForEach(energyData, entry =>
|
||||||
first.Production = g.Sum(e => e.Production);
|
{
|
||||||
return first;
|
var date = DateOnly.FromDateTime(entry.Key);
|
||||||
})
|
var energy = entry.Value;
|
||||||
.OrderBy(e => e.Time)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return groupedData;
|
// Use AddOrUpdate to avoid double lookup
|
||||||
|
dailyEnergyData.AddOrUpdate(
|
||||||
|
date,
|
||||||
|
energy, // If key doesn't exist, add this value
|
||||||
|
(_, existing) =>
|
||||||
|
{
|
||||||
|
// If key exists, aggregate the values
|
||||||
|
existing.Consumption += energy.Consumption;
|
||||||
|
existing.Production += energy.Production;
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return dailyEnergyData.ToDictionary();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<List<string>> ReadCsvFile(IBrowserFile file)
|
||||||
|
{
|
||||||
|
await using var stream = file.OpenReadStream(maxAllowedSize: (int)1.0e9);
|
||||||
|
using var reader = new StreamReader(stream, bufferSize: (int)1.0e6);
|
||||||
|
|
||||||
|
// Skip header
|
||||||
|
await reader.ReadLineAsync();
|
||||||
|
// Read all lines into memory
|
||||||
|
var lines = new List<string>();
|
||||||
|
string line;
|
||||||
|
while ((line = await reader.ReadLineAsync()) is not null)
|
||||||
|
{
|
||||||
|
lines.Add(line);
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string[] SplitLine(string line)
|
private static string[] SplitLine(string line)
|
||||||
28025
data/Verbruikshistoriek_kwartiertotalen_all.csv
Normal file
28025
data/Verbruikshistoriek_kwartiertotalen_all.csv
Normal file
File diff suppressed because it is too large
Load Diff
5569
data/Verbruikshistoriek_kwartiertotalen_month.csv
Normal file
5569
data/Verbruikshistoriek_kwartiertotalen_month.csv
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user