From 992b7f02ea9363b0f3cd9b9e4b555bbfecbf957b Mon Sep 17 00:00:00 2001 From: douwe Date: Mon, 13 Apr 2026 16:14:54 +0200 Subject: [PATCH] Changed from daily records to quarter hour records --- Models/EnergyData.cs | 11 ++-- Pages/Home.razor | 30 +++++----- Pages/Test.razor | 96 ------------------------------ Program.cs | 3 +- Services/BatterySimulator.cs | 40 ++++++------- Services/DataLoader.cs | 109 +++++++++++++++++++++-------------- Services/ReportGenerator.cs | 86 --------------------------- _Imports.razor | 1 + 8 files changed, 105 insertions(+), 271 deletions(-) delete mode 100644 Pages/Test.razor delete mode 100644 Services/ReportGenerator.cs diff --git a/Models/EnergyData.cs b/Models/EnergyData.cs index 901ff8b..6a3b602 100644 --- a/Models/EnergyData.cs +++ b/Models/EnergyData.cs @@ -4,12 +4,9 @@ namespace BattSim.Models { public class EnergyData { - public DateOnly Date { get; set; } - public double DayConsumption { get; set; } - public double NightConsumption { get; set; } - public double TotalConsumption => DayConsumption + NightConsumption; - public double DayProduction { get; set; } - public double NightProduction { get; set; } - public double TotalProduction => DayProduction + NightProduction; + public DateTime Time { get; set; } + public bool DayTarif { get; set; } + public double Consumption { get; set; } + public double Production { get; set; } } } \ No newline at end of file diff --git a/Pages/Home.razor b/Pages/Home.razor index d288cfc..323a1a9 100644 --- a/Pages/Home.razor +++ b/Pages/Home.razor @@ -14,10 +14,10 @@ @if (_isLoadingFile){

Loading...

} @if (EnergyData.Length != 0){ - + - + @@ -30,16 +30,7 @@ } - -

Simulate Battery

-

Set the battery capacity

- - - -

Calculate Cost

- - -@code { +@code{ EnergyData[] EnergyData = []; bool _isLoadingFile = false; @@ -72,24 +63,29 @@ } } - bool _showProduction = true; - bool _showConsumption = true; - private void OnSeriesClick(){} private string FormatObject(object value) { if(value is double d) return $"{value:0.##} kWh"; if(value is DateOnly date) return (date.Day == 1) ? date.ToString("MM/yyyy") : string.Empty; else return string.Empty; } +} +

Simulate Battery

+

Set the battery capacity

+ + + +

Calculate Cost

+ + +@code { double BatteryCapacity = 0.0; BatteryDayResult[] SimulationData = []; - private async Task SimulateBattery(){ Console.WriteLine("Simulating..."); SimulationData = BatterySimulator.SimulateBattery(EnergyData, BatteryCapacity).ToArray(); Console.WriteLine("Done simulating!"); } - } \ No newline at end of file diff --git a/Pages/Test.razor b/Pages/Test.razor deleted file mode 100644 index b0eca29..0000000 --- a/Pages/Test.razor +++ /dev/null @@ -1,96 +0,0 @@ -@page "/Test" -@using Radzen -@using Radzen.Blazor -@using System.Globalization - - -Home - -

Hello, world!

- -Welcome to your new app. - -

A Radzen chart:

- - - - - - - - - - - - - - - - -@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 - }, - - }; -} \ No newline at end of file diff --git a/Program.cs b/Program.cs index 4a31976..13fc4e3 100644 --- a/Program.cs +++ b/Program.cs @@ -4,8 +4,6 @@ using Radzen; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; - - // Setup Frontend var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); @@ -14,4 +12,5 @@ builder.RootComponents.Add("head::after"); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddRadzenComponents(); + await builder.Build().RunAsync(); \ No newline at end of file diff --git a/Services/BatterySimulator.cs b/Services/BatterySimulator.cs index 6867963..7bc1811 100644 --- a/Services/BatterySimulator.cs +++ b/Services/BatterySimulator.cs @@ -12,31 +12,29 @@ namespace BattSim.Services foreach (var day in data) { - // Charge battery from production - var totalProduction = day.DayProduction + day.NightProduction; - var chargedEnergy = System.Math.Min(totalProduction, batteryCapacity); - var excessProduction = totalProduction - chargedEnergy; + // // Charge battery from production + // var chargedEnergy = System.Math.Min(day.TotalProduction, batteryCapacity); + // var excessProduction = day.TotalProduction - chargedEnergy; - // Use battery for consumption - var totalConsumption = day.DayConsumption + day.NightConsumption; - var usedEnergy = System.Math.Min(chargedEnergy + remainingEnergy, totalConsumption); - var remainingAfterUse = chargedEnergy + remainingEnergy - usedEnergy; + // // Use battery for consumption + // var usedEnergy = System.Math.Min(chargedEnergy + remainingEnergy, day.TotalConsumption); + // var remainingAfterUse = chargedEnergy + remainingEnergy - usedEnergy; - // Calculate reduced values - var reducedConsumption = System.Math.Min(usedEnergy, totalConsumption); - var reducedProduction = totalProduction - chargedEnergy; + // // Calculate reduced values + // var reducedConsumption = System.Math.Min(usedEnergy, day.TotalConsumption); + // var reducedProduction = day.TotalProduction - chargedEnergy; - results.Add(new BatteryDayResult - { - Date = day.Date, - ChargedEnergy = chargedEnergy, - UsedEnergy = usedEnergy, - RemainingEnergy = remainingAfterUse, - ReducedConsumption = reducedConsumption, - ReducedProduction = reducedProduction - }); + // results.Add(new BatteryDayResult + // { + // Date = day.Date, + // ChargedEnergy = chargedEnergy, + // UsedEnergy = usedEnergy, + // RemainingEnergy = remainingAfterUse, + // ReducedConsumption = reducedConsumption, + // ReducedProduction = reducedProduction + // }); - remainingEnergy = remainingAfterUse; + // remainingEnergy = remainingAfterUse; } return results; diff --git a/Services/DataLoader.cs b/Services/DataLoader.cs index 41c9f2b..3b0fc18 100644 --- a/Services/DataLoader.cs +++ b/Services/DataLoader.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -13,61 +14,85 @@ namespace BattSim.Services { public static async Task> LoadAndProcessData(IBrowserFile file) { - var energyData = new List(); - await using var stream = file.OpenReadStream(); - using var reader = new StreamReader(stream); + var energyData = new ConcurrentBag(); // Thread-safe collection + 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 line; - while ((line = await reader.ReadLineAsync()) != null) + while ((line = await reader.ReadLineAsync()) is not null) { - var parts = line.Split(';'); - if (parts.Length < 9) - { - Console.WriteLine($"Skipping malformed line: {line}"); - continue; - } + lines.Add(line); + } + // Process lines in parallel + Parallel.ForEach(lines, line => + { try { - var date = DateOnly.ParseExact(parts[0].Length == 10 ? parts[0] : "0"+parts[0], "dd/MM/yyyy", CultureInfo.InvariantCulture); - var register = parts[7].Trim(); - var volumeStr = parts[8].Trim(); - var volume = string.IsNullOrEmpty(volumeStr) - ? 0 - : double.Parse(volumeStr.Replace(",", "."), CultureInfo.InvariantCulture); + 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"); - var existing = energyData.FirstOrDefault(e => e.Date == date); - if (existing == null) - { - existing = new EnergyData { Date = date }; - energyData.Add(existing); - } + // 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"); - 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; - } + energyData.Add(entry); } - catch (Exception ex) + catch (Exception e) { - Console.WriteLine($"Error parsing line: {line}. Exception: {ex.Message}"); + Console.WriteLine($"Error parsing line: {line}. Skipping to next line. Exception: {e}"); } - } - return energyData; + }); + + // 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(';'); + if (parts.Length < 9) + throw new Exception($"Malformed line (too many parts): {line}"); + return parts; + } + + private static DateTime ParseDateTime(string[] dateTimeStrings) + { + string dateTimeString = dateTimeStrings[0] + " " + dateTimeStrings[1]; + return DateTime.ParseExact(dateTimeString, "dd-MM-yyyy HH:mm:ss", CultureInfo.GetCultureInfo("nl-BE")); + } + + private static double ParseVolume(string volumeString) + { + var volumeStr = volumeString.Trim(); + return string.IsNullOrEmpty(volumeStr) + ? 0 + : double.Parse(volumeStr.Replace(",", "."), CultureInfo.InvariantCulture); + } + } } \ No newline at end of file diff --git a/Services/ReportGenerator.cs b/Services/ReportGenerator.cs deleted file mode 100644 index 2e8d174..0000000 --- a/Services/ReportGenerator.cs +++ /dev/null @@ -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 originalData, Dictionary> 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"); - } - } -} \ No newline at end of file diff --git a/_Imports.razor b/_Imports.razor index b9514d2..f240922 100644 --- a/_Imports.razor +++ b/_Imports.razor @@ -8,3 +8,4 @@ @using Microsoft.JSInterop @using BattSim @using BattSim.Layout +@using Radzen.Blazor