Changed from daily records to quarter hour records

This commit is contained in:
douwe
2026-04-13 16:14:54 +02:00
parent 9f1fbcdd5a
commit 992b7f02ea
8 changed files with 105 additions and 271 deletions

View File

@@ -4,12 +4,9 @@ namespace BattSim.Models
{ {
public class EnergyData public class EnergyData
{ {
public DateOnly Date { get; set; } public DateTime Time { get; set; }
public double DayConsumption { get; set; } public bool DayTarif { get; set; }
public double NightConsumption { get; set; } public double Consumption { get; set; }
public double TotalConsumption => DayConsumption + NightConsumption; public double Production { get; set; }
public double DayProduction { get; set; }
public double NightProduction { get; set; }
public double TotalProduction => DayProduction + NightProduction;
} }
} }

View File

@@ -14,10 +14,10 @@
@if (_isLoadingFile){ <p>Loading...</p> } @if (_isLoadingFile){ <p>Loading...</p> }
@if (EnergyData.Length != 0){ @if (EnergyData.Length != 0){
<RadzenChart> <RadzenChart>
<RadzenAreaSeries Smooth=true Data="@EnergyData" CategoryProperty="Date" Title="Consumption" ValueProperty="TotalConsumption"> <RadzenAreaSeries Smooth=true Data="@EnergyData" CategoryProperty="Time" Title="Consumption" ValueProperty="Consumption">
<RadzenChartTooltipOptions Visible="true" /> <RadzenChartTooltipOptions Visible="true" />
</RadzenAreaSeries> </RadzenAreaSeries>
<RadzenAreaSeries Smooth=true Data="@EnergyData" CategoryProperty="Date" Title="Production" ValueProperty="TotalProduction"> <RadzenAreaSeries Smooth=true Data="@EnergyData" 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">
@@ -30,16 +30,7 @@
</RadzenChart> </RadzenChart>
} }
@code{
<h2>Simulate Battery</h2>
<p>Set the battery capacity</p>
<InputNumber @bind-value="BatteryCapacity"/>
<Button @onclick="SimulateBattery">Simulate</Button>
<h2>Calculate Cost</h2>
@code {
EnergyData[] EnergyData = []; EnergyData[] EnergyData = [];
bool _isLoadingFile = false; bool _isLoadingFile = false;
@@ -72,24 +63,29 @@
} }
} }
bool _showProduction = true;
bool _showConsumption = true;
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 $"{value:0.##} kWh";
if(value is DateOnly date) return (date.Day == 1) ? date.ToString("MM/yyyy") : string.Empty; if(value is DateOnly date) return (date.Day == 1) ? date.ToString("MM/yyyy") : string.Empty;
else return string.Empty; else return string.Empty;
} }
}
<h2>Simulate Battery</h2>
<p>Set the battery capacity</p>
<InputNumber @bind-value="BatteryCapacity"/>
<Button @onclick="SimulateBattery">Simulate</Button>
<h2>Calculate Cost</h2>
@code {
double BatteryCapacity = 0.0; double BatteryCapacity = 0.0;
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!");
} }
} }

View File

@@ -1,96 +0,0 @@
@page "/Test"
@using Radzen
@using Radzen.Blazor
@using System.Globalization
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<h2>A Radzen chart:</h2>
<RadzenChart SeriesClick=@OnSeriesClick style="height: 400px">
<RadzenBarSeries Data="@revenue2024" CategoryProperty="Quarter" Title="2024" LineType="LineType.Dashed" ValueProperty="Revenue">
<RadzenSeriesDataLabels Visible="@showDataLabels" />
</RadzenBarSeries>
<RadzenBarSeries Data="@revenue2023" CategoryProperty="Quarter" Title="2023" ValueProperty="Revenue">
<RadzenSeriesDataLabels Visible="@showDataLabels" />
</RadzenBarSeries>
<RadzenValueAxis Formatter="@FormatAsUSD">
<RadzenGridLines Visible="true" />
<RadzenAxisTitle Text="Revenue in USD" />
</RadzenValueAxis>
<RadzenBarOptions Radius="5" />
</RadzenChart>
@code {
bool showDataLabels = false;
void OnSeriesClick(SeriesClickEventArgs args)
{
}
class DataItem
{
public string Quarter { get; set; }
public double Revenue { get; set; }
}
string FormatAsUSD(object value)
{
return ((double)value).ToString("C0", CultureInfo.CreateSpecificCulture("en-US"));
}
DataItem[] revenue2023 = new DataItem[]
{
new DataItem
{
Quarter = "Q1",
Revenue = 234000
},
new DataItem
{
Quarter = "Q2",
Revenue = 284000
},
new DataItem
{
Quarter = "Q3",
Revenue = 274000
},
new DataItem
{
Quarter = "Q4",
Revenue = 294000
},
};
DataItem[] revenue2024 = new DataItem[] {
new DataItem
{
Quarter = "Q1",
Revenue = 254000
},
new DataItem
{
Quarter = "Q2",
Revenue = 324000
},
new DataItem
{
Quarter = "Q3",
Revenue = 354000
},
new DataItem
{
Quarter = "Q4",
Revenue = 394000
},
};
}

View File

@@ -4,8 +4,6 @@ using Radzen;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
// Setup Frontend // Setup Frontend
var builder = WebAssemblyHostBuilder.CreateDefault(args); var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app"); builder.RootComponents.Add<App>("#app");
@@ -14,4 +12,5 @@ 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

@@ -12,31 +12,29 @@ namespace BattSim.Services
foreach (var day in data) foreach (var day in data)
{ {
// Charge battery from production // // Charge battery from production
var totalProduction = day.DayProduction + day.NightProduction; // var chargedEnergy = System.Math.Min(day.TotalProduction, batteryCapacity);
var chargedEnergy = System.Math.Min(totalProduction, batteryCapacity); // var excessProduction = day.TotalProduction - chargedEnergy;
var excessProduction = totalProduction - chargedEnergy;
// Use battery for consumption // // Use battery for consumption
var totalConsumption = day.DayConsumption + day.NightConsumption; // var usedEnergy = System.Math.Min(chargedEnergy + remainingEnergy, day.TotalConsumption);
var usedEnergy = System.Math.Min(chargedEnergy + remainingEnergy, totalConsumption); // var remainingAfterUse = chargedEnergy + remainingEnergy - usedEnergy;
var remainingAfterUse = chargedEnergy + remainingEnergy - usedEnergy;
// Calculate reduced values // // Calculate reduced values
var reducedConsumption = System.Math.Min(usedEnergy, totalConsumption); // var reducedConsumption = System.Math.Min(usedEnergy, day.TotalConsumption);
var reducedProduction = totalProduction - chargedEnergy; // var reducedProduction = day.TotalProduction - chargedEnergy;
results.Add(new BatteryDayResult // results.Add(new BatteryDayResult
{ // {
Date = day.Date, // Date = day.Date,
ChargedEnergy = chargedEnergy, // ChargedEnergy = chargedEnergy,
UsedEnergy = usedEnergy, // UsedEnergy = usedEnergy,
RemainingEnergy = remainingAfterUse, // RemainingEnergy = remainingAfterUse,
ReducedConsumption = reducedConsumption, // ReducedConsumption = reducedConsumption,
ReducedProduction = reducedProduction // ReducedProduction = reducedProduction
}); // });
remainingEnergy = remainingAfterUse; // remainingEnergy = remainingAfterUse;
} }
return results; return results;

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@@ -13,61 +14,85 @@ namespace BattSim.Services
{ {
public static async Task<List<EnergyData>> LoadAndProcessData(IBrowserFile file) public static async Task<List<EnergyData>> LoadAndProcessData(IBrowserFile file)
{ {
var energyData = new List<EnergyData>(); var energyData = new ConcurrentBag<EnergyData>(); // Thread-safe collection
await using var stream = file.OpenReadStream(); await using var stream = file.OpenReadStream(maxAllowedSize: (int)1.0e9);
using var reader = new StreamReader(stream); using var reader = new StreamReader(stream, bufferSize: (int)1.0e6);
// Skip header // Skip header
await reader.ReadLineAsync(); await reader.ReadLineAsync();
// Read all lines into memory
var lines = new List<string>();
string line; string line;
while ((line = await reader.ReadLineAsync()) != null) while ((line = await reader.ReadLineAsync()) is not null)
{
lines.Add(line);
}
// Process lines in parallel
Parallel.ForEach(lines, line =>
{
try
{
var parts = SplitLine(line);
DateTime time = ParseDateTime(parts[0..2]);
double volume = ParseVolume(parts[8]);
string register = parts[7].Trim();
bool dayTarif = register.Contains("Dag");
// Use ConcurrentBag for thread-safe additions
var entry = new EnergyData { Time = time, DayTarif = dayTarif };
if (register.Contains("Afname"))
entry.Consumption = volume;
else if (register.Contains("Injectie"))
entry.Production = volume;
else
throw new Exception("Unknown volume register");
energyData.Add(entry);
}
catch (Exception e)
{
Console.WriteLine($"Error parsing line: {line}. Skipping to next line. Exception: {e}");
}
});
// Group by Time and merge entries
var groupedData = energyData
.GroupBy(e => e.Time)
.Select(g =>
{
var first = g.First();
first.Consumption = g.Sum(e => e.Consumption);
first.Production = g.Sum(e => e.Production);
return first;
})
.OrderBy(e => e.Time)
.ToList();
return groupedData;
}
private static string[] SplitLine(string line)
{ {
var parts = line.Split(';'); var parts = line.Split(';');
if (parts.Length < 9) if (parts.Length < 9)
{ throw new Exception($"Malformed line (too many parts): {line}");
Console.WriteLine($"Skipping malformed line: {line}"); return parts;
continue;
} }
try private static DateTime ParseDateTime(string[] dateTimeStrings)
{ {
var date = DateOnly.ParseExact(parts[0].Length == 10 ? parts[0] : "0"+parts[0], "dd/MM/yyyy", CultureInfo.InvariantCulture); string dateTimeString = dateTimeStrings[0] + " " + dateTimeStrings[1];
var register = parts[7].Trim(); return DateTime.ParseExact(dateTimeString, "dd-MM-yyyy HH:mm:ss", CultureInfo.GetCultureInfo("nl-BE"));
var volumeStr = parts[8].Trim(); }
var volume = string.IsNullOrEmpty(volumeStr)
private static double ParseVolume(string volumeString)
{
var volumeStr = volumeString.Trim();
return string.IsNullOrEmpty(volumeStr)
? 0 ? 0
: double.Parse(volumeStr.Replace(",", "."), CultureInfo.InvariantCulture); : double.Parse(volumeStr.Replace(",", "."), CultureInfo.InvariantCulture);
var existing = energyData.FirstOrDefault(e => e.Date == date);
if (existing == null)
{
existing = new EnergyData { Date = date };
energyData.Add(existing);
} }
switch (register)
{
case "Afname Dag":
existing.DayConsumption = volume;
break;
case "Afname Nacht":
existing.NightConsumption = volume;
break;
case "Injectie Dag":
existing.DayProduction = volume;
break;
case "Injectie Nacht":
existing.NightProduction = volume;
break;
}
}
catch (Exception ex)
{
Console.WriteLine($"Error parsing line: {line}. Exception: {ex.Message}");
}
}
return energyData;
} }
} }
}

View File

@@ -1,86 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using BattSim.Models;
namespace BattSim.Services
{
public class ReportGenerator
{
public static void GenerateReport(List<EnergyData> originalData, Dictionary<int, List<BatteryDayResult>> scenarios)
{
using (var writer = new StreamWriter("../../../BatteryAnalysisReport.md"))
{
writer.WriteLine("# Battery Cost Analysis Report\n");
// Original data summary
writer.WriteLine("## Original Data Summary");
var totalConsumption = originalData.Sum(d => d.DayConsumption + d.NightConsumption);
var totalProduction = originalData.Sum(d => d.DayProduction + d.NightProduction);
writer.WriteLine($"Total Consumption: {totalConsumption:F2} kWh");
writer.WriteLine($"Total Production: {totalProduction:F2} kWh\n");
// Generate graph data
writer.WriteLine("### Energy Consumption and Production\n");
writer.WriteLine("```");
writer.WriteLine("Date,Total Production,Total Consumption");
foreach (var day in originalData)
{
var prod = day.DayProduction + day.NightProduction;
var cons = day.DayConsumption + day.NightConsumption;
writer.WriteLine($"{day.Date:yyyy-MM-dd},{prod:F2},{cons:F2}");
}
writer.WriteLine("```\n");
writer.WriteLine("![Energy Graph](energy_graph.png)");
writer.WriteLine("(Graph showing total production in green and total consumption in red)\n");
// Scenario analysis
foreach (var scenario in scenarios)
{
var batteryCount = scenario.Key;
var results = scenario.Value;
writer.WriteLine($"## Scenario with {batteryCount} Battery{(batteryCount > 1 ? "ies" : "")}");
writer.WriteLine($"Battery Capacity: {batteryCount * 2.6} kWh\n");
var totalReducedConsumption = results.Sum(r => r.ReducedConsumption);
var totalReducedProduction = results.Sum(r => r.ReducedProduction);
var savings = totalReducedConsumption * 0.30; // Assuming €0.30 per kWh
writer.WriteLine($"Total Reduced Consumption: {totalReducedConsumption:F2} kWh");
writer.WriteLine($"Total Reduced Production: {totalReducedProduction:F2} kWh");
writer.WriteLine($"Estimated Annual Savings: €{savings:F2}\n");
// Generate scenario graph data
writer.WriteLine($"### Battery Performance for {batteryCount} Battery{(batteryCount > 1 ? "ies" : "")}\n");
writer.WriteLine("```");
writer.WriteLine("Date,Charged Energy,Used Energy,Remaining Energy");
foreach (var result in results)
{
writer.WriteLine($"{result.Date:yyyy-MM-dd},{result.ChargedEnergy:F2},{result.UsedEnergy:F2},{result.RemainingEnergy:F2}");
}
writer.WriteLine("```\n");
writer.WriteLine($"![Battery Performance Graph](battery_{batteryCount}_graph.png)");
writer.WriteLine("(Graph showing charged energy in blue, used energy in orange, and remaining energy in gray)\n");
// Detailed daily results
writer.WriteLine("### Daily Results");
writer.WriteLine("| Date | Charged (kWh) | Used (kWh) | Remaining (kWh) |");
writer.WriteLine("|------|---------------|------------|-----------------|");
foreach (var result in results.Take(10)) // Show first 10 days
{
writer.WriteLine($"| {result.Date:yyyy-MM-dd} | {result.ChargedEnergy:F2} | {result.UsedEnergy:F2} | {result.RemainingEnergy:F2} |");
}
writer.WriteLine("\n---\n");
}
}
Console.WriteLine("Report generated: BatteryAnalysisReport.md");
}
}
}

View File

@@ -8,3 +8,4 @@
@using Microsoft.JSInterop @using Microsoft.JSInterop
@using BattSim @using BattSim
@using BattSim.Layout @using BattSim.Layout
@using Radzen.Blazor