fixed chart names and added extra battery properties.

This commit is contained in:
douwe
2026-04-14 19:38:13 +02:00
parent 992b7f02ea
commit b89ab1062c
6 changed files with 33718 additions and 76 deletions

View File

@@ -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; }
} }

View File

@@ -9,32 +9,48 @@
<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)
<RadzenChart> {
<RadzenAreaSeries Smooth=true Data="@EnergyData" CategoryProperty="Time" Title="Consumption" ValueProperty="Consumption"> <p>Loading...</p>
<RadzenChartTooltipOptions Visible="true" /> }
</RadzenAreaSeries> else
<RadzenAreaSeries Smooth=true Data="@EnergyData" CategoryProperty="Time" Title="Production" ValueProperty="Production"> {
<RadzenChartTooltipOptions Visible="true" /> @if (FluviusDataRaw.Count != 0)
</RadzenAreaSeries> {
<RadzenCategoryAxis Formatter="@FormatObject" Padding="20" LabelAutoRotation="-45"> <p>@(FluviusDataRaw.Count) entries read.</p>
<RadzenAxisTitle Text="Time" /> }
</RadzenCategoryAxis>
<RadzenValueAxis Formatter="@FormatObject"> @if (FluviusDataDaily.Count != 0)
<RadzenGridLines Visible="true" /> {
<RadzenAxisTitle Text="Energy" /> <RadzenChart>
</RadzenValueAxis> <RadzenAreaSeries Smooth=true Data="@FluviusDataDaily.Values" CategoryProperty="Time" Title="Consumption" ValueProperty="Consumption">
</RadzenChart> <RadzenChartTooltipOptions Visible="true"/>
} </RadzenAreaSeries>
<RadzenAreaSeries Smooth=true Data="@FluviusDataDaily.Values" CategoryProperty="Time" Title="Production" ValueProperty="Production">
<RadzenChartTooltipOptions Visible="true"/>
</RadzenAreaSeries>
<RadzenCategoryAxis Formatter="@FormatObject" Padding="20" LabelAutoRotation="-45">
<RadzenGridLines Visible="true"/>
<RadzenAxisTitle Text="Time"/>
</RadzenCategoryAxis>
<RadzenValueAxis Formatter="@FormatObject">
<RadzenGridLines Visible="true"/>
<RadzenAxisTitle Text="Energy"/>
</RadzenValueAxis>
</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!");
@@ -64,28 +77,45 @@
} }
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>
<Button @onclick="SimulateBattery">Simulate</Button> <ul>
<li>
<h2>Calculate Cost</h2> <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>
</div>
@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>

View File

@@ -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();

View File

@@ -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) var dailyEnergyData = new ConcurrentDictionary<DateOnly, EnergyData>();
.Select(g => Parallel.ForEach(energyData, entry =>
{ {
var first = g.First(); var date = DateOnly.FromDateTime(entry.Key);
first.Consumption = g.Sum(e => e.Consumption); var energy = entry.Value;
first.Production = g.Sum(e => e.Production);
return first;
})
.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)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff